A dsh (DeepSeek Harness) Cordis plugin that keeps llm-pi-ai provider routes' model catalog in sync with the pi.dev gateway — written through the official settings seam, zero patches to dsh internals.
在 DeepSeek Harness 终端运行:
dsh plugin --profile web add fan56/dsh-model-sync简体中文 | English
· GitHub
A dsh (DeepSeek Harness) Cordis plugin that keeps the model catalog of your llm-pi-ai provider routes in step with the pi.dev gateway's model listing — written into dsh settings.yaml through the official settings seam (settings.mutate), with zero patches to dsh internals.
Requires dsh >= 0.1.2-rc.1 — this plugin targets the dsh RC/stable line only (CI and releases resolve the newest of the latest/next dist-tags at runtime). The alpha line is no longer supported.
https://github.com/user-attachments/assets/c3f9c8b1-ea5e-470c-b8a8-60a81fc5c20a
A live recording of a dsh session running /model-sync (MP4, 1.5× speed) — drop reasons, per-route sync statuses and the change report in action. More demos in the Demos issue.
Model lists drift: providers ship new models, retire old ones, and adjust capabilities (contextWindow, input modalities, thinkingFormat, reasoning efforts). Keeping them in sync by hand is error-prone busywork. dsh-model-sync does it for you:
writer.ts, profilesEqual, reason: 'no-change').settings.yaml simply reflects it.https://pi.dev/api/models/providers/<route> with ETag/304 revalidation and a persistent per-provider cache under ~/.dsh/models-store.json (remote-catalog.ts). Transient failures and aborts keep the last-good cache; a 404/501 treats the route as absent for the round.managedRoutes is empty, these pi.dev routes are synced: opencode-go, zai-coding-cn, minimax-cn, xiaomi-token-plan-cn (DEFAULT_ROUTES in src/index.ts).writeMode):settings (default) — the zero-patch pipeline: fetch → translate → settings.mutate. Self-contained; never touches settings.yaml directly, only via the official settings API.overlay (legacy) — delegates to the patched dsh-llm-pi-ai adapter's piAiCatalog.refresh() and merges pi.dev entries in memory (requires the optional patch).intervalMinutes auto rounds (default 240 / 4h) plus a startupDelaySeconds initial delay (default 5); each round logs the same report a manual refresh produces. 0 disarms the interval (startup-only). The interval re-arms live when the config changes (src/index.ts).diffModelIds), and in settings mode added/removed/changed entries against the current raw settings (diffEntries, diff.ts). Dropped and degraded entries are reported with their reasons.modelSync service. Exposes a modelSync service (syncNow()) that a UI can call to force one refresh round and read the report./model-sync command. The plugin registers a /model-sync slash command itself through the shared dsh command registry (@deepseek-ai/dsh-commands), so every interactive UI lists it automatically — no UI-side wiring. Running it forces one sync round on the spot and prints the same report the scheduled rounds log; the sync scope is decided by managedRoutes (arguments are ignored). The registry is an optional peer: hosts without a command registry still get the scheduled rounds and the modelSync service.translate.ts): base-matching vs base-less classification, reasoningEfforts derivation (S2 gate), compat gating to openai-completions (S5 gate), maxTokens handling, and drop logic for mixed-protocol routes. Capacity values get a sanity gate: a contextWindow that is not a positive integer, or a maxTokens that is not a positive integer strictly below the context window (listings sometimes echo the context window into maxTokens), is skipped with a degrade warning instead of written.modelOverrides is your own per-model channel (think levels, narrowed context windows). dsh refuses a models list beside non-empty overrides, so the sync folds your fields into the written models, clears the key in the same write, and re-applies the values from its store (~/.dsh/models-store.json) on every round — they keep winning over the synced values for as long as the route is managed.keepBuiltinOnly: true — keep built-in catalog models that are not (yet) on pi.dev, so adopting the sync doesn't delete models you already use.dropUnserviceable: true — drop unserviceable entries and continue; set to false to abort the whole route instead of writing a partial list.forceMaxReasoningEffort — force models with a non-empty thinkingFormat to max reasoning effort (ensures reasoningEfforts contains max and forces compat.supportsReasoningEffort = true on openai-completions).SETTINGS_CONFLICT (writer.ts).Requires Node ≥ 22.19 and a dsh profile. Install as a dsh plugin:
npm i @aiwayds/dsh-model-sync
dsh plugin add @aiwayds/dsh-model-sync
The package ships cordis.patch.yml (wired as dsh.bundle.patch), which mounts the plugin into the profile's assembly tree under the stable plugin id dsh-model-sync and registers the model-sync settings namespace.
This plugin ships standalone — install it explicitly with dsh plugin add @aiwayds/dsh-model-sync when you want it.
dsh plugin remove @aiwayds/dsh-model-sync
The host auto-cleans: the bundles entry is spliced out of the profile and the plugin's patch layer drops with the package, so the sync rounds and the /model-sync command simply stop. Three things intentionally stay on disk:
~/.dsh/models-store.json — back this file up before purging it. It holds the catalog cache plus your modelOverrides: under the store-first invariant the plugin folds overrides into the written models and unsets the settings key, so for a managed route the store can be the only copy of your override values. Deleting the file deletes them.~/.dsh/settings.yaml. The plugin wrote them into the host-owned llm-pi-ai namespace (providers.<route>.models) through the official settings seam. They persist after removal and remain valid host config — dsh consumes them exactly as if you had written them by hand. Remove those entries by hand if you don't want them.~/.dsh/models-store.json.<pid>.tmp file can remain. It is safe to delete.Configure the plugin under the model-sync namespace in settings.yaml — every key is optional:
| Key | Default | Description |
|---|---|---|
writeMode |
'settings' |
Zero-patch pipeline; 'overlay' for the legacy patched-adapter mode |
intervalMinutes |
240 (4h) |
Auto-refresh interval in minutes; 0 = startup-only |
startupDelaySeconds |
5 |
Delay before the first auto refresh, so the llm adapter is ready |
refreshTimeoutMs |
120000 |
Abort budget for one refresh round's network request (min 1000) |
managedRoutes |
[] |
Routes to sync; empty = the default pi.dev routes |
keepBuiltinOnly |
true |
Keep built-in-only models not present on pi.dev (smooth migration) |
dropUnserviceable |
true |
Drop unserviceable entries; false aborts the route instead |
syncNotify |
false |
Notify on changes (logger + /model-sync report) |
forceMaxReasoningEffort |
false |
Force max reasoning effort on models with a non-empty thinkingFormat |
Example:
model-sync:
writeMode: settings
intervalMinutes: 30
managedRoutes:
- opencode-go
- zai-coding-cn
The plugin writes to the llm-pi-ai namespace (providers.<route>.models) — the same document the adapter consumes — and only for the routes it manages. During migration, keepBuiltinOnly preserves models that exist in your installed built-in catalog but aren't on pi.dev yet.
Synced contextWindow / maxTokens describe what the model accepts at most, as advertised by the gateway listing — not what your deployment is configured for. dsh resolves the settings-written value over the installed catalog, and a written maxTokens becomes the request-level default. Pointing a route at a local or proxied endpoint that serves a smaller context (vLLM / Ollama and friends) while carrying catalog-sized capacities is a known recipe for the "output token limit reached" family of failures.
If you need a model to run under a smaller budget, set it in modelOverrides under the same route — the sync folds the fields into the synced list and re-applies them from its store every round:
providers:
zai-coding-cn:
modelOverrides:
glm-5.3:
contextWindow: 32768
Note that listing data itself can be noisy: values are sanity-gated (positive integers; maxTokens strictly below contextWindow), and stripped values show up in the sync report as DEGRADED lines with reasons.
/model-sync commandType /model-sync in any interactive UI to force one sync round on the spot. The plugin registers the command in the shared command registry (@deepseek-ai/dsh-commands), and UIs discover it automatically. It returns the same report the scheduled rounds log. The sync scope is decided by managedRoutes; any arguments typed after the command are ignored. Hosts without a command registry degrade gracefully — the scheduled rounds and the modelSync service keep working.
npm run build # tsc → lib/
npm run check # tsc --noEmit typecheck
npm test # node --test (pretest builds): diff / translate / writer / remote-catalog / serviceability / command
Tests use per-route pi.dev fixtures under test/fixtures/ and temp directories for the models store — they never touch the real ~/.dsh.
Utility scripts under scripts/:
generate-builtin-snapshot.mjs — regenerate src/builtin-catalog-snapshot.ts from the installed @deepseek-ai/dsh-llm-pi-ai catalog (--generate for dev, --check for CI).verify-no-patch.mjs — exits non-zero if any installed dsh-llm-pi-ai still carries the overlay patch signatures (withRemoteCatalog / piAiCatalog).backup/backup-patched.mjs — back up a patched dsh-llm-pi-ai/lib/index.js to backups/.backup/restore-official.mjs — restore the official unpatched dsh-llm-pi-ai/lib/index.js from npm, validated against the patch (--dry-run supported).The repo also carries the reference patches that document the legacy overlay behavior: docs-dsh-llm-pi-ai.patch (pi.dev remote-catalog overlay for dsh-llm-pi-ai) and docs-dsh-llm-pi-ai-compat.patch (supportsDeveloperRole compat passthrough).
MIT.
登录后即可为该插件评分和评价。
还没有人评价这个插件,来抢个沙发吧!