A DeepSeek Harness (dsh) LLM
adapter that routes model calls through Command Code,
so a Command Code Go plan can serve DeepSeek — and Claude, GPT, Kimi, GLM,
Qwen, MiniMax — to the harness, including image input.
pi ──────────► pi-commandcode-provider ──┐
dsh ─────────► dsh-llm-commandcode ──────┼──► api.commandcode.ai ──► model
cmd ─────────► (native) ─────────────────┘
Command Code's plan tiers gate which API surface an account may reach:
| Plan | POST /provider/v1/chat/completions |
POST /alpha/generate |
GET /provider/v1/models |
|---|---|---|---|
| Go | 403 upgrade_required |
allowed | allowed |
| Provider and above | allowed | allowed | allowed |
/alpha/generate is the endpoint the cmd CLI itself drives, and it is the one
a Go plan can reach. This adapter speaks it: the harness gets a normal
LlmAdapter route, and the requests underneath are the CLI's own transport.
The pi extension pi-commandcode-provider does the same thing with a transport
router; this plugin is the dsh equivalent, written against the harness adapter
seam rather than a compat shim.
If your account is on Provider plan or higher, you do not need this plugin —
dsh-llm-pi-ai can reach the documented Provider API with plain configuration.
From a checkout of this directory:
dsh plugin --profile web add link:/absolute/path/to/dsh-llm-commandcodeThen add two rows to that profile's cordis.patch.yml
($DSH_HOME/profiles/<profile>/cordis.patch.yml):
# Aim the harness at the new route.
- id: agent-default-model
config:
provider: commandcode
model: deepseek/deepseek-v4.1-flash
reasoningEffort: high
# Mount the adapter.
- insert:
- id: llm-commandcode
name: 'dsh-llm-commandcode'
config:
baseURL: https://api.commandcode.ai
apiKeyEnv: COMMAND_CODE_API_KEY
modelCatalog: auto
defaultReasoningEffort: highRestart dsh web. The model picker then shows a Command Code group beside
the built-in DeepSeek one.
The key resolves per request, first match wins:
- the harness credentials service (
~/.dsh/.credentials.yaml, written by the Models page in the Web UI) - the environment variable named by
apiKeyEnv ~/.commandcode/auth.json— whatcmd loginwrites — then pi's and OMP's auth files
Nothing in the configuration stores the secret, and a request without any key
fails with MISSING_CREDENTIAL rather than at plugin load, so the rest of the
harness keeps working.
| Field | Default | Meaning |
|---|---|---|
apiKeyEnv |
COMMAND_CODE_API_KEY |
Credential reference resolved per request |
baseURL |
https://api.commandcode.ai |
API root; /alpha/generate is appended |
workingDir |
process working directory | Reported to the endpoint as request context |
cliVersion |
installed command-code version, else 1.53.0 |
Sent as x-command-code-version, which the endpoint gates on |
maxTokens |
64000 |
Default output cap; a catalog model's own cap and an explicit request value win |
defaultContextWindow |
1000000 |
Capacity for a model the catalog does not size |
defaultReasoningEffort |
high |
Effort used while a session has picked none |
streamIdleTimeoutMs |
300000 |
Maximum silence between two stream events |
models |
the five DeepSeek models | Static advisory catalog, used when discovery is off or unreachable |
modelCatalog |
auto |
auto refreshes from the endpoint (cached an hour); static never asks |
zeroDataRetention |
false |
Send x-cmd-zdr: 1, asking the gateway for zero retention |
requestImagePixelBudget |
640000 |
Route default pixel budget per request image; a catalog model's own budget wins |
requestImageMaxBytes |
1048576 |
Route default encoded-byte target per request image; a catalog model's own target wins |
maxRequestImageBytes |
20971520 |
Bound on accumulated base64 image payload in one request |
maxImagesPerRequest |
600 |
Maximum represented images in one request |
imageOffloadByteQuantum |
10485760 |
Raw-byte removal step once the payload bound is exceeded |
imageOffloadCountQuantum |
20 |
Image-count removal step once the count bound is exceeded |
retryPolicy |
normal, five retries | Provider-owned retry policy |
A per-model override goes on the catalog entry itself:
- id: llm-commandcode
name: 'dsh-llm-commandcode'
config:
models:
- id: deepseek/deepseek-v4.1-flash
inputModalities: [text, image]
imagePixelBudget: 1048576 # a larger per-image budget than the route default
imageMaxBytes: 2097152An image is transported as an inline base64 data URL part. Each image is preceded by a short identity line the model can refer to, which also carries a read-only path when the harness can map the attachment to one, so the model's file tools can open the normalized copy.
Whether a model accepts images comes from MODEL_INPUT_MODALITIES in
src/models.js, which mirrors the cmd CLI's own catalog.
A model the table does not describe is treated as text-only — the harness
then substitutes deterministic text for each image instead of sending one the
endpoint may reject on every later turn of the session. That is why an
uncatalogued model still works with an image attached rather than failing.
Images from a tool result cannot ride in its text-only role: 'tool' message,
so they follow it in their own user turn — the same shape Command Code's own CLI
sends.
Working, and verified against the live endpoint:
- streaming text and reasoning (
reasoning_contentequivalents) - tool calls, including multi-turn call/result round trips
- image input on models the catalog declares image-capable
- cache-aware token accounting reported as disjoint counts
- the reasoning-effort menu (
off,low,high,max) - per-request credential and endpoint re-resolution
Deliberate limits, declared rather than hidden:
- Stop sequences are not sent. The generate route has no verified field for them, and inventing one risks a refused request.
- Images are sent inline, not uploaded. One request is bounded by
maxRequestImageBytes; a history that exceeds it has its oldest image occurrences replaced by deterministic placeholder text rather than failing. - The model catalog is advisory. Discovery reads the plan-gated
GET /provider/v1/modelslisting and falls back to the static catalog; any model id can still be requested directly, as the seam requires.
/alpha/generate is Command Code's internal CLI endpoint, not its documented
Provider API, and the plan gating above is a business decision that can change.
This adapter is the same workaround the pi extension uses; treat it as such. If
you need a supported contract, Provider plan plus dsh-llm-pi-ai against
/provider/v1 is the intended path.
Not affiliated with or endorsed by Command Code or DeepSeek.
dsh is a 0.x prerelease whose own README promises breaking changes, so an
adapter has to re-earn its correctness on each release rather than assume it.
check:upgrade does that in one command:
npm run check:upgrade # upgrade to the `next` tag, verify, roll back on failure
npm run check:upgrade -- --dry-run # print the plan, change nothing
npm run check:upgrade -- --to 0.1.6-rc.1 # a specific version or tag
npm run check:upgrade -- --skip-live # offline checks onlyIt runs four steps:
- Baseline — verify the plugin against the installed version first. A machine that is already broken must not have its failure blamed on the upgrade, and a rollback target is only worth returning to if it worked.
- Install the target with
npm install -g. - Verify — the offline suite, then a real agent turn through a dsh profile (skipped, with a reason, when no profile or credential is available).
- Roll back automatically when the target fails, and verify the restored version too.
Exit codes, which are what make it usable from automation:
| Code | Meaning |
|---|---|
0 |
the target is installed and verified |
1 |
the install failed; nothing was changed |
2 |
the target failed verification; the previous version was restored and verified |
3 |
the target failed verification and the rollback could not be verified |
A rollback exits 2 rather than 0 on purpose: the machine is healthy again,
but the upgrade did not happen, and a caller must be able to tell those apart.
It also refuses to move backwards unless told to. dsh's dist-tags are not
monotonic — latest currently resolves to an older build than next — so
the default target is next, and a plain npm install -g @deepseek-ai/dsh
would silently downgrade you. --allow-downgrade overrides the refusal.
npm test # 38 offline checks, no network
npm run test:live # drives the real endpoint, needs a Command Code key
node tests/live-adapter.mjs deepseek/deepseek-v4-proThe offline suite covers request serialization, the image path, stream translation, configuration resolution, and the version ordering the upgrade check depends on. The live script drives the real endpoint through this plugin's own serialize and translate path with no harness runtime, and prints each turn's blocks, usage, and finish reason.
The pretest hook runs scripts/link-dsh-deps.mjs,
which links the @deepseek-ai/* packages this plugin imports out of your dsh
installation. That is what makes the suite runnable from a bare clone; a plugin
installed into a profile through dsh plugin add needs none of it, because the
profile's loader supplies the resolution.