@mars-sea/dsh-commandcode-provider 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,22 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.2.4] - 2026-08-16
8
+
9
+ ### Fixed
10
+
11
+ - **`requestTimeoutMs` no longer aborts a healthy generate body mid-stream.** `AbortSignal.timeout(requestTimeoutMs)` was passed straight into `fetch()`, so Fetch cancelled the SSE body after the connection budget even when events were still flowing — long reasoning/generation then failed as `TRANSPORT` (`failed while reading: aborted due to timeout`) and triggered harness retries. The adapter now clears the connect deadline once response headers arrive; after that only the caller `AbortSignal` and `streamIdleTimeoutMs` may abort the stream.
12
+
13
+ ## [0.2.3] - 2026-08-16
14
+
15
+ ### Changed
16
+
17
+ - **`streamIdleTimeoutMs` default raised from 120s to 300s.** The stream idle watchdog used to kill a generation that produced no events for 120s, but frontier reasoning models (xhigh/max effort) can legitimately stay silent for minutes while thinking — the official CLI sets no idle cap at all. An aggressive cap turned long thinking into a spurious `TIMEOUT`, which dsh-llm-retry then retried, surfacing to users as "stuck, then reconnecting". The new 300s default keeps the dead-connection protection (a truly stalled socket still fails instead of hanging) without cutting off legitimate long thinking. Tune `streamIdleTimeoutMs` in the `llm-commandcode` settings section or on the settings page for your workload.
18
+
19
+ ### Fixed
20
+
21
+ - **In-band stream `error` events are now classified like the official CLI, so transient server-side drops get retried instead of failing the turn.** The adapter previously threw every stream `error` event as `PROVIDER_STREAM_ERROR`, which is outside the harness default retryable set — a server blip that the official CLI recovers from (e.g. "Upstream stream ended before terminal chunk") failed the whole turn. Now the adapter mirrors command-code's `readStreamErrorEvent`/`isStreamErrorRetryable`: an error that is explicitly non-retryable, carries a terminal marker (`premium_credits_exhausted`, `model_not_in_plan`, `insufficient credits`), or reports a non-retryable HTTP status stays `PROVIDER_STREAM_ERROR`; everything else is thrown as `SERVER`, which the default retry policy retries.
22
+
7
23
  ## [0.2.2] - 2026-08-16
8
24
 
9
25
  ### Added
package/LICENSE CHANGED
@@ -1,12 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 dsh-commandcode-provider contributors
4
-
5
- This plugin is a port of pi-commandcode-provider (Copyright (c) 2025 patlux,
6
- MIT), which reverse-engineered the Command Code Provider API. Command Code is
7
- a product of Command Code, Inc.; this project is not affiliated with or
8
- endorsed by it, and your use of the Command Code service is governed by
9
- Command Code's own terms.
3
+ Copyright (c) 2026 Mars-Sea
10
4
 
11
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
12
6
  of this software and associated documentation files (the "Software"), to deal
package/NOTICE ADDED
@@ -0,0 +1,10 @@
1
+ dsh-commandcode-provider
2
+ Copyright (c) 2026 Mars-Sea
3
+
4
+ This software incorporates portions ported from pi-commandcode-provider
5
+ (Copyright (c) 2025 Pat Woz), licensed under the MIT License, which
6
+ reverse-engineered the Command Code Provider API.
7
+
8
+ Command Code is a product of Command Code, Inc.; this project is not
9
+ affiliated with or endorsed by it, and your use of the Command Code service
10
+ is governed by Command Code's own terms.
package/README.md CHANGED
@@ -2,26 +2,28 @@
2
2
 
3
3
  **English** | [简体中文](./README.zh-CN.md)
4
4
 
5
+ [![Awesome](https://awesome.re/badge.svg)](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)
6
+ [![GitHub Repo stars](https://img.shields.io/github/stars/Mars-Sea/dsh-commandcode-provider?style=flat-square)](https://github.com/Mars-Sea/dsh-commandcode-provider/stargazers)
7
+ [![DeepSeek Harness](https://img.shields.io/badge/DeepSeek%20Harness-plugin-4D6BFE?style=flat-square)](https://github.com/deepseek-ai/deepseek-harness)
8
+ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://github.com/Mars-Sea/dsh-commandcode-provider/pulls)
5
9
  [![CI](https://github.com/Mars-Sea/dsh-commandcode-provider/actions/workflows/ci.yml/badge.svg)](https://github.com/Mars-Sea/dsh-commandcode-provider/actions/workflows/ci.yml)
6
10
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
11
  [![npm](https://img.shields.io/badge/npm-@mars--sea%2Fdsh--commandcode--provider-blue.svg)](https://www.npmjs.com/package/@mars-sea/dsh-commandcode-provider)
8
12
 
9
- Unofficial [DeepSeek Harness](https://deepseek-harness.github.io/deepseek-harness/) LLM provider plugin for **Command Code**, ported from [pi-commandcode-provider](https://github.com/patlux/pi-commandcode-provider) (MIT). It registers a `commandcode` model provider whose requests are translated to Command Code's Provider API (`POST /alpha/generate`, reverse-engineered by the pi plugin, `command-code@1.26.0`).
13
+ Unofficial [DeepSeek Harness](https://deepseek-harness.github.io/deepseek-harness/) LLM provider plugin for **Command Code**, ported from [pi-commandcode-provider](https://github.com/patlux/pi-commandcode-provider) (MIT). It registers a `commandcode` provider whose requests are translated to Command Code's Provider API (`POST /alpha/generate`, reverse-engineered by the pi plugin, `command-code@1.26.0`).
10
14
 
11
15
  > This is a community integration. You need your own Command Code account and API key or subscription, and Command Code's terms apply. This project is not affiliated with Command Code, Inc.
12
16
 
13
17
  ## What you get
14
18
 
15
- - A **plugin bundle** installable into any dsh profile with `dsh plugin add` (npm package with a `dsh.bundle` layer).
16
- - A **`commandcode` provider route** registered on the `llm` service, selectable in the model picker, with the **live model catalog** fetched from `GET {apiBase}/provider/v1/models` (cached at `~/.commandcode/models-cache.json`).
17
- - A **dedicated "Command Code" settings page** (Settings **Command Code**, a top-level nav entry at the same level as General / Models / Plugins) with an **API-key field** plus the connection knobs (API base, working directory, request/stream timeouts). The key is stored through the dsh credentials service — the same seam the Models page uses — and connection fields land in the `llm-commandcode` settings section, so a change reaches the very next request with no restart.
18
- - A **Models-page card** ("Command Code") that reflects the provider's presence (the Models page's generic card disables its editor for unknown adapter families, so configure the key on the dedicated page above).
19
- - **API key resolution** in this order: `config.apiKey` credential reference `apiKeyEnv` (the Command Code settings page writes it, default `COMMANDCODE_API_KEY`) the launching environment the official Command Code CLI auth file (`~/.commandcode/auth.json`, written by `command-code login`).
20
- - **Reasoning-effort support** for the models Command Code's catalog marks as such (e.g. `claude-opus-5`, `gpt-5.5`, `deepseek/deepseek-v4-pro`, `google/gemini-3.7-flash`, …) via `KNOWN_EFFORTS`, matching the official command-code@1.26.0 bundled model table. Reasoning models without selectable effort levels (e.g. `MiniMaxAI/MiniMax-M3`, `moonshotai/Kimi-K3`, `moonshotai/Kimi-K2.5`) still think Command Code drives their reasoning depth automatically, exactly like the official CLI.
21
- - **Plan-tier annotation in the model picker**: every Command Code model is tagged with the minimum plan that includes it (`KNOWN_PLANS`, synced from the [official plan pages](https://commandcode.ai/docs/plans/go)) — **Go** (33 models), **GOAT** (+3), **Pro** (+14), or **Provider/Max** (+5: Claude Opus/Fable, Fugu Ultra). The picker's `description` leads with the plan label, e.g. *"Go · 50% off · Image · 1M"*, *"Pro · Image · 1M"*, so you know which plan a model needs before switching — no more 403 `MODEL_NOT_IN_PLAN` surprises. **The list itself is sorted by plan tier** (`compareByPlan()`): Go models first, then GOAT, Pro, Provider/Max, alphabetical within each tier — the models your plan can actually use lead the picker.
22
- - **Deal and free-model annotations**: active discounts (`75% off`, `50% off`, `98% off`, `99% off`) and the `FREE` badge (Laguna S 2.1) show next to the plan tier (`KNOWN_DEALS`, synced from the [official pricing page](https://commandcode.ai/docs/resources/pricing-limits#deals)). **Expiry-aware**: each deal records its official end date and is hidden the moment it passes — an un-updated plugin never shows a lapsed discount as if it were live (only Gemini 3.7 Flash's 50% off is time-limited, through December 31, 2026; the rest are permanent).
23
- - **Image + context markers**: the picker shows `Image` for Vision-capable models and the context window in human form (`1M`, `256K`, `262K`); text-only models show neither — plan tier plus context is enough.
24
- - **Image input for Vision-capable models**: models the official registry lists with Vision (e.g. `claude-sonnet-5`, `gpt-5.4`, `google/gemini-3.5-flash`, …) accept attached images, resolved through the dsh attachment service and sent in the official Command Code wire format. Text-only models (e.g. `deepseek/deepseek-v4-flash`, `zai-org/GLM-5.3`) refuse images loudly rather than silently dropping them.
19
+ - **Plugin bundle** installable into any dsh profile with `dsh plugin add`, plus a **`commandcode` provider route** with a live catalog (`GET {apiBase}/provider/v1/models`, cached at `~/.commandcode/models-cache.json`).
20
+ - **Dedicated "Command Code" settings page** (Settings **Command Code**) with an **API-key field** and connection knobs (API base, working directory, request/stream timeouts). The key is stored through the dsh credentials service; connection fields land in the `llm-commandcode` section and apply to the very next request, no restart.
21
+ - **API key resolution order**: `config.apiKey`credential ref `apiKeyEnv` (default `COMMANDCODE_API_KEY`) launch environment the official CLI auth file (`~/.commandcode/auth.json`, from `command-code login`).
22
+ - **Model-picker annotations**: every model shows the **minimum plan** that includes it (`KNOWN_PLANS`), an **active deal** or `FREE` badge (`KNOWN_DEALS`, expiry-aware so lapsed discounts hide themselves), an **`Image`** marker for Vision models, and the **context window** (`1M` / `256K` / `262K`) e.g. *"Go · 50% off · Image · 1M"*. The list is **sorted by plan tier** (Go → GOAT → Pro → Provider/Max), so the models your plan can use lead the picker.
23
+ - **Reasoning-effort support** for models the official catalog marks as such (`KNOWN_EFFORTS`, matching `command-code@1.26.0`); reasoning models without effort levels still think automatically, exactly like the official CLI.
24
+ - **Image input for Vision-capable models** (sent in the official wire format via the dsh attachment service); text-only models refuse images loudly (`UNSUPPORTED_CONTENT`) rather than dropping them.
25
+
26
+ <img src="assets/screenshots/model-picker.png" alt="Model picker with plan, deal, image and context annotations" width="250">
25
27
 
26
28
  ## Getting an API key
27
29
 
@@ -32,13 +34,13 @@ npm i -g command-code@latest
32
34
  cmd login # macOS/Linux; native Windows: cmdc login
33
35
  ```
34
36
 
35
- `cmd login` opens a browser to authenticate; on success the key is written to `~/.commandcode/auth.json` — this plugin picks it up automatically (last-resort fallback). Alternatively create an API key in the browser ([Command Code Studio](https://commandcode.ai/studio/auth/cli)) and paste it into **Settings → Command Code** (the dedicated page's API-key field), or `export COMMANDCODE_API_KEY="user_..."`.
37
+ `cmd login` opens a browser to authenticate; the key is written to `~/.commandcode/auth.json` — picked up automatically as a last-resort fallback. Alternatively create a key in the browser ([Command Code Studio](https://commandcode.ai/studio/auth/cli)) and paste it into **Settings → Command Code**, or `export COMMANDCODE_API_KEY="user_..."`.
36
38
 
37
39
  ## Install
38
40
 
39
41
  ### From npm (recommended)
40
42
 
41
- The plugin is published to the npm registry as **`@mars-sea/dsh-commandcode-provider`** (the bare name `dsh-commandcode-provider` is taken by an unrelated package):
43
+ The bare name `dsh-commandcode-provider` is taken by an unrelated package, so this plugin is published as **`@mars-sea/dsh-commandcode-provider`**:
42
44
 
43
45
  ```sh
44
46
  dsh plugin --profile web add @mars-sea/dsh-commandcode-provider
@@ -48,12 +50,12 @@ dsh plugin --profile web add @mars-sea/dsh-commandcode-provider
48
50
 
49
51
  ```sh
50
52
  # Pin a release tag (recommended — readable and immutable)
51
- dsh plugin --profile web add github:Mars-Sea/dsh-commandcode-provider#v0.2.1
53
+ dsh plugin --profile web add github:Mars-Sea/dsh-commandcode-provider#v0.2.2
52
54
  # Or pin any exact commit by its SHA
53
55
  dsh plugin --profile web add github:Mars-Sea/dsh-commandcode-provider#<full-commit-sha>
54
56
  ```
55
57
 
56
- The `#<ref>` suffix pins the source to one exact revision (pnpm git-dependency syntax: a tag, branch, or commit SHA). Without it the install tracks the default branch, so a later push can silently change what you get — pin a tag or commit and audit the code you run.
58
+ The `#<ref>` suffix pins one exact revision (pnpm git-dependency syntax). Without it the install tracks the default branch, so a later push can silently change what you get.
57
59
 
58
60
  A git install fetches **sources**, so the package's `prepare` script builds `lib/` after install. pnpm ≥10 blocks that script by default — run the `add`, then copy the **exact package key pnpm prints** into `~/.dsh/profiles/web/pnpm-workspace.yaml`:
59
61
 
@@ -62,21 +64,21 @@ allowBuilds:
62
64
  '@mars-sea/dsh-commandcode-provider@github:Mars-Sea/dsh-commandcode-provider#<full-commit-sha>': true
63
65
  ```
64
66
 
65
- and re-run the `add`. Only allow packages whose source you trust (and pin a commit).
67
+ and re-run the `add`.
66
68
 
67
69
  ### From a local checkout
68
70
 
69
71
  ```sh
70
72
  npm install
71
- npm run build # git-installed/tarball installs do this via `prepare` automatically
73
+ npm run build # git/tarball installs do this via `prepare` automatically
72
74
  dsh plugin --profile web add /path/to/dsh-commandcode-provider
73
75
  ```
74
76
 
75
- A local path install links the checkout as-is, so after changing `src/` re-run `npm run build` and restart the app.
77
+ After changing `src/`, re-run `npm run build` and restart the app.
76
78
 
77
79
  ### What the install does
78
80
 
79
- `dsh plugin add` links the package into the profile (pnpm records dependencies and links `node_modules` by the **true package name**, i.e. `@mars-sea/dsh-commandcode-provider`), appends that same name to the profile's `dsh.profile.bundles`, and activates the `cordis.patch.yml` layer, which inserts:
81
+ `dsh plugin add` links the package into the profile (pnpm records it under the **true package name** `@mars-sea/dsh-commandcode-provider`), appends that name to `dsh.profile.bundles`, and activates the `cordis.patch.yml` layer:
80
82
 
81
83
  ```yaml
82
84
  - insert:
@@ -86,7 +88,7 @@ A local path install links the checkout as-is, so after changing `src/` re-run `
86
88
  apiKeyEnv: COMMANDCODE_API_KEY
87
89
  ```
88
90
 
89
- The `name` in the patch row must be the **full package specifier, quoted**: the loader imports it as a module and resolves it from the profile's `node_modules`, where pnpm only ever links the scoped name. A bare `dsh-commandcode-provider` fails with `ERR_MODULE_NOT_FOUND` and crashes the app on boot, and an unquoted `@mars-sea/...` fails YAML parsing (see [Troubleshooting](#troubleshooting)).
91
+ The patch `name` must be the **full package specifier, quoted** the loader imports it as a module from the profile's `node_modules`, where pnpm only links the scoped name. A bare name fails with `ERR_MODULE_NOT_FOUND` and crashes the app on boot; an unquoted `@mars-sea/...` fails YAML parsing (see [Troubleshooting](#troubleshooting)).
90
92
 
91
93
  Verify the composed layer, then (re)start the web app:
92
94
 
@@ -97,79 +99,50 @@ dsh web # or restart your running instance
97
99
 
98
100
  ## Updating
99
101
 
100
- The bundle's patch layer is read from the **installed package** at every boot, so updating the package brings in the fixed patch row automatically — you do not need to hand-edit `cordis.patch.yml` unless you copied its contents into your own profile layer.
101
-
102
- Update according to how you installed it:
102
+ The patch layer is read from the **installed package** at every boot, so updating the package brings the fixed row automatically — no need to hand-edit `cordis.patch.yml` unless you copied it into your own profile layer.
103
103
 
104
104
  ```sh
105
- # From npm (recommended): always the latest published release
105
+ # npm: always the latest published release
106
106
  dsh plugin --profile web update @mars-sea/dsh-commandcode-provider
107
107
 
108
- # From GitHub pinned to a tag: point at the new tag
109
- # (no need to uninstall first — pnpm swaps the pinned revision in place,
110
- # and the bundle layer is re-read from the installed package on next boot)
111
- dsh plugin --profile web add github:Mars-Sea/dsh-commandcode-provider#v0.2.1
108
+ # GitHub (pinned): point at the new tag — no uninstall needed, pnpm swaps it in place
109
+ dsh plugin --profile web add github:Mars-Sea/dsh-commandcode-provider#v0.2.2
112
110
 
113
- # From a local checkout: pull the new code, rebuild, restart
111
+ # local checkout: pull, rebuild, restart
114
112
  git -C /path/to/dsh-commandcode-provider pull
115
113
  npm run build --prefix /path/to/dsh-commandcode-provider
116
114
  dsh web
117
115
  ```
118
116
 
119
- Then restart the web app (`dsh web`, or restart the service). Verify the running version with `dsh --profile web --dump-config` — the layer should show `name: '@mars-sea/dsh-commandcode-provider'`.
117
+ Then restart the web app. Verify with `dsh --profile web --dump-config` — the layer should show `name: '@mars-sea/dsh-commandcode-provider'`.
120
118
 
121
- > **`update` says "Already up to date" but the version did not move (pnpm ≥ 11)?** pnpm 11's `minimumReleaseAge` supply-chain policy can refuse to update to a freshly published version and report "Already up to date" even though a newer release exists. Pin the exact version instead:
122
- >
123
- > ```sh
124
- > dsh plugin --profile web add @mars-sea/dsh-commandcode-provider@0.1.9
125
- > ```
126
- >
127
- > `add` with an explicit version installs it (and moves your spec to `^0.1.9`). If you trust your registry you can also disable the gate with `pnpm config set minimumReleaseAge 0 --location project` inside the profile directory (or delete the `minimumReleaseAgeExclude` entry pnpm wrote into `pnpm-workspace.yaml`).
119
+ > **`update` says "Already up to date" but the version did not move (pnpm ≥ 11)?** pnpm 11's `minimumReleaseAge` supply-chain policy can refuse a freshly published version. Pin the exact version instead: `dsh plugin --profile web add @mars-sea/dsh-commandcode-provider@0.2.2` (or `pnpm config set minimumReleaseAge 0 --location project` inside the profile directory).
128
120
 
129
- > **Upgrading from ≤0.1.6** (or a broken hand-edited profile): the installed package's patch layer now carries the corrected, quoted `name`. If you previously *copied* the old patch row into your profile's own `cordis.patch.yml`, that copy still wins over the bundle layer — fix it manually to `name: "@mars-sea/dsh-commandcode-provider"` (see [Troubleshooting](#troubleshooting)) or remove it and let the bundle layer apply.
121
+ > **Upgrading from ≤0.1.6** (or a broken hand-edited profile): if you *copied* the old patch row into your profile's own `cordis.patch.yml`, that copy still wins over the bundle layer — fix it to `name: "@mars-sea/dsh-commandcode-provider"` or remove it (see [Troubleshooting](#troubleshooting)).
130
122
 
131
- > **To uninstall instead of upgrading** (e.g. you are on a broken pre-0.1.7 tag and want to start clean): `dsh plugin --profile web remove @mars-sea/dsh-commandcode-provider` (the scoped name — pnpm records the dependency under its real package name, so the bare `dsh-commandcode-provider` form does not match). This removes the dependency and its layer; your API key in the dsh credential store and `~/.commandcode/auth.json` are left untouched. Then install the current version with the npm or GitHub command above.
123
+ > **To uninstall instead**: `dsh plugin --profile web remove @mars-sea/dsh-commandcode-provider` (the **scoped** name — pnpm records the dependency under its real name). Your API key in the dsh credential store and `~/.commandcode/auth.json` are left untouched.
132
124
 
133
125
  ## Verify it works
134
126
 
135
- After restart, in the web UI: **Settings → Command Code** shows the dedicated page — enter your API key there and click **Save** (the badge flips to 已配置/Configured once the Host holds it). **Settings → Models** shows a **Command Code** card; the model picker lists the live catalog under **commandcode** (54 models at the time of writing). Send a message with a model your plan includes — the default `deepseek/deepseek-v4-flash` works on entry-level plans; open-weight models (DeepSeek/Qwen/Kimi/MiniMax) generally do, while frontier models (Claude/GPT/Gemini/Grok) may require Pro/Max plans or on-demand usage (see FAQ).
127
+ After restart: **Settings → Command Code** shows the dedicated page — enter your API key and click **Save** (the badge flips to 已配置/Configured once the Host holds it). **Settings → Models** shows a **Command Code** card; the model picker lists the live catalog under **commandcode**. Send a message with a model your plan includes — `deepseek/deepseek-v4-flash` works on entry-level plans, and open-weight models (DeepSeek/Qwen/Kimi/MiniMax) generally do, while frontier models (Claude/GPT/Gemini/Grok) may require Pro/Max plans or on-demand usage.
136
128
 
137
129
  ## Usage dashboard
138
130
 
139
- The plugin registers a `/commandcode` slash command (requires the dsh `commands` service, present in the standard web profile) that shows your Command Code account state straight from the official account endpoints:
131
+ The plugin registers a `/commandcode` slash command (requires the dsh `commands` service, present in the standard web profile) showing your account state from the official account endpoints:
140
132
 
141
133
  ```text
142
134
  /commandcode (or /commandcode status)
143
135
  ```
144
136
 
145
- Example output (structured text with Unicode bar charts):
146
-
147
- ```text
148
- 📊 Command Code 用量 (mars-sea)
149
-
150
- ── 请求 ──────────────────────────────
151
- 💬 请求 992 次 / 失败 0 成功率 100%
152
- 💰 花费 $1.4446 ($1.44 credits)
153
- 🔤 Token 205.3M 入 / 808.8K 出
154
-
155
- ── 信用 ──────────────────────────────
156
- 💳 月额度 $8.54 (已购 $0.00 / 赠送 $0.00)
157
- └ ██████████ 100%
158
-
159
- ── 窗口用量 ──────────────────────────
160
- ⏱ 5 小时 $0.18 / $3.00
161
- └ █░░░░░░░░░ 重置 8/15/2026, 2:39:36 PM
162
- 📅 每周 $1.46 / $6.00
163
- └ ██░░░░░░░░ 重置 8/21/2026, 7:10:57 PM
164
- ```
137
+ ![Usage dashboard](assets/screenshots/usage-dashboard.png)
165
138
 
166
- Each endpoint degrades independently: a temporary failure of one (e.g. the credits endpoint) leaves the rest visible and notes the failure inline.
139
+ Each endpoint degrades independently a temporary failure of one leaves the rest visible and notes the failure inline.
167
140
 
168
141
  ## Configure
169
142
 
170
- **Settings → Command Code** is the primary surface: an **API-key** field (stored in `$DSH_HOME/.credentials.yaml` via the credentials service; the field is write-only and reports whether a key is configured), plus **API base URL**, **working directory**, and the **request/stream timeout** fields all written to the `llm-commandcode` section and applied to the next request. The model catalog is browsable without a key. The **working directory** is optional: leave it blank and the field's placeholder shows the process cwd it resolves to (it defaults to the dsh process's working directory, so there is nothing to configure unless you want to pin a specific path).
143
+ **Settings → Command Code** is the primary surface: an **API-key** field (stored in `$DSH_HOME/.credentials.yaml` via the credentials service; write-only, reports whether a key is set), plus **API base URL**, **working directory**, and **request/stream timeout** fields, all written to the `llm-commandcode` section. The catalog is browsable without a key. The **working directory** is optional leave it blank and the placeholder shows the process cwd it resolves to.
171
144
 
172
- The same knobs live in the `llm-commandcode` section of `$DSH_HOME/settings.yaml` (overrides the bundle defaults per request, no restart needed):
145
+ The same knobs live in `$DSH_HOME/settings.yaml` (per-request overrides, no restart):
173
146
 
174
147
  ```yaml
175
148
  llm-commandcode:
@@ -178,59 +151,45 @@ llm-commandcode:
178
151
  workingDir: /path/to/project # reported to the API (project slug, config block)
179
152
  modelsCachePath: ~/.commandcode/models-cache.json
180
153
  requestTimeoutMs: 60000 # max wait for the first response byte (default 60s)
181
- streamIdleTimeoutMs: 120000 # stream stall before treated as a dead connection (default 120s)
154
+ streamIdleTimeoutMs: 300000 # stream stall before treated as dead (default 300s — generous, so long-thinking models are not cut off)
182
155
  ```
183
156
 
184
- The composition-entry config (`cordis.patch.yml` / your profile `cordis.patch.yml`) accepts the same keys; a literal `apiKey` there takes precedence over the credential reference.
157
+ The composition-entry config (`cordis.patch.yml`) accepts the same keys; a literal `apiKey` there takes precedence over the credential reference.
185
158
 
186
159
  ## Troubleshooting
187
160
 
188
- - **`Command Code API request to .../alpha/generate failed` and the turn keeps retrying (`重试延迟` / "Retry delay")** — this is a **transport-layer failure**: `fetch()` never received an HTTP response (not a 401/403/429, which would say "API error"). Since dsh's retry policy retries `TRANSPORT` twice with backoff, you'll see retry rows in the UI before the turn finally fails. Since 0.1.8 the failure reason shows the **real root cause** (e.g. `fetch failed: connect ECONNREFUSED`, `ENOTFOUND`, `CERT_HAS_EXPIRED`, `The operation was aborted due to timeout`). Common causes:
189
- - **A proxy is required in your network.** Node's `fetch` (undici) does **not** read `HTTP_PROXY`/`HTTPS_PROXY` environment variables, so a browser/curl that goes through a system proxy works while dsh fails. Run dsh with the proxy configured for undici (e.g. `NODE_OPTIONS=--import undici` with a dispatcher, or a network-level route), or whitelist `api.commandcode.ai`.
190
- - **The connection is being reset/throttled mid-request** (firewall, GFW-style interference, unstable Wi-Fi). The error message will name it (`socket hang up`, `ECONNRESET`, `UND_ERR_SOCKET`).
191
- - **TLS interception** (corporate MITM) — `CERT_HAS_EXPIRED`/`DEPTH_ZERO_SELF_SIGNED_CERT` in the chain.
192
- - A transient blip that a retry recovers from; if it persists every turn, it's environmental, not the API (the models endpoint and generate endpoint respond normally from healthy networks).
193
- - **A long generation stops mid-stream** since 0.1.8 the adapter aborts a request that gets no response within `requestTimeoutMs` (60s default) and a stream that stalls past `streamIdleTimeoutMs` (120s default) instead of hanging forever. Both failures surface as `TIMEOUT` with the stall duration; tune the knobs in the `llm-commandcode` settings if your network is slow but stable.
194
- - **The web app crashes on boot with `ERR_MODULE_NOT_FOUND: Cannot find package 'dsh-commandcode-provider'`** — the patch row's `name` is the bare package name, but the loader imports it as a module from the profile's `node_modules`, where pnpm only links the scoped name `@mars-sea/dsh-commandcode-provider`. Pre-0.1.7 bundles shipped this wrong row, and the bug also bites when an old `cordis.patch.yml` example (or a cached profile layer) is copied by hand. Fix the row in your profile's `cordis.patch.yml` (or re-add the plugin) so it reads `name: "@mars-sea/dsh-commandcode-provider"` — note the **quotes**: an unquoted `@`-prefixed scalar fails YAML parsing (0.1.7 shipped that regression; 0.1.8 quotes it) — then restart.
195
- - **`MODEL_NOT_IN_PLAN` (403)** the selected model is not in your Command Code plan. Pick an open-weight model (e.g. `deepseek/deepseek-v4-flash`) or upgrade. The error names the model and links the official docs.
196
- - **`MISSING_CREDENTIAL`** no key anywhere. Store one via the Models page card, export `COMMANDCODE_API_KEY`, set `config.apiKey`, or run `command-code login`. The route stays registered and the catalog stays browsable without a key.
197
- - **The Models page card shows "not configured" but requests work** — the key came from `~/.commandcode/auth.json` (the `cmd login` fallback), not the dsh credential store. Paste it into the card once to make the card show as configured; both coexist fine.
198
- - **A reasoning model returns no visible text on short requests** — reasoning models (e.g. `deepseek/deepseek-v4-*`) consume output tokens on reasoning first; a small `maxTokens` can be exhausted before any visible text. This is normal.
199
- - **`allowBuilds` errors on `dsh plugin add` from git** — copy the exact package key pnpm printed (with the commit hash) into `pnpm-workspace.yaml` and re-run (see [Install](#from-github)).
161
+ - **`Command Code API request to .../alpha/generate failed` with retries** — a **transport-layer failure**: `fetch()` never got an HTTP response (a 401/403/429 would say "API error"). Since 0.1.8 the message names the real root cause (`ECONNREFUSED`, `ENOTFOUND`, `CERT_HAS_EXPIRED`, `socket hang up`, …). Common causes: **a required proxy** (Node's `fetch`/undici ignores `HTTP_PROXY`/`HTTPS_PROXY` configure a dispatcher or whitelist `api.commandcode.ai`), **connection reset/throttled** (firewall, GFW-style interference, unstable Wi-Fi), **TLS interception** (corporate MITM), or a transient blip retry recovers from.
162
+ - **A long generation stops mid-stream** since 0.1.8 the adapter aborts after `requestTimeoutMs` (60s) with no first byte, and treats a stream stalling past `streamIdleTimeoutMs` (300s by default) as dead. Both surface as `TIMEOUT` with the stall duration; tune the knobs for slow-but-stable networks.
163
+ - **"Reconnects" when a reasoning model thinks for a long time** — the stream idle watchdog used to default to 120s, which is shorter than a frontier reasoning model's silent thinking phase (xhigh/max effort can stay quiet for minutes, and the official CLI sets no idle cap at all). Since 0.2.3 the default is **300s**; if you still hit spurious timeouts on very long thinking, raise `streamIdleTimeoutMs` in the `llm-commandcode` section or on the settings page.
164
+ - **Boot crash: `ERR_MODULE_NOT_FOUND: Cannot find package 'dsh-commandcode-provider'`** the patch row's `name` is the bare name, but pnpm only links the scoped name. Fix the row to `name: "@mars-sea/dsh-commandcode-provider"` — note the **quotes** (an unquoted `@`-prefixed scalar fails YAML parsing) — then restart.
165
+ - **`MODEL_NOT_IN_PLAN` (403)** the model isn't in your plan. Pick an open-weight model or upgrade; the error names the model and links the docs.
166
+ - **`MISSING_CREDENTIAL`** no key anywhere. Store one via the settings page, `export COMMANDCODE_API_KEY`, set `config.apiKey`, or run `command-code login`. The route and catalog stay browsable without a key.
167
+ - **Models card shows "not configured" but requests work** — the key came from `~/.commandcode/auth.json` (the `cmd login` fallback), not the credential store. Paste it into the card once; both coexist fine.
168
+ - **A reasoning model returns no visible text on short requests** it consumes output tokens on reasoning first; a small `maxTokens` can be exhausted before visible text. Normal.
169
+ - **`allowBuilds` errors on `dsh plugin add` from git** — copy the exact package key pnpm printed into `pnpm-workspace.yaml` and re-run (see [Install](#from-github)).
200
170
 
201
171
  ## Notes & limitations
202
172
 
203
- - **Image input is model-gated**: only models the official Command Code registry lists with Vision accept images (see the `KNOWN_IMAGE_MODELS` snapshot in `src/adapter.ts`, synced from the [official model registry](https://commandcode.ai/docs/reference/cli/models)). The model picker marks Vision-capable models with *`Image`* (e.g. *"Go · 50% off · Image · 1M"*); text-only models carry no marker, so the capability is visible before you switch. Sending an image to a text-only model throws `UNSUPPORTED_CONTENT`. Command Code's own CLI falls back to a client-side *VISION* side-call for text-only models; this adapter does **not** reproduce that interactive feature — switch to a Vision-capable model instead. Image input also requires the dsh **attachment service** (`ctx.attachments`); without it, requests carrying images throw `UNSUPPORTED_CONTENT`.
204
- - **Switching to a text-only model in an image-bearing session is rejected by dsh itself** — a harness-level guard (`dsh-host-apiproxy`'s `selectModel` handler) refuses `model-unavailable` when the session history or the pending input already contains images and the target model does not declare `image` input. The rejection is intentional and cannot be relaxed from the plugin side (the picker rows this adapter provides are the input that makes the guard work — a text-only model correctly reports `inputModalities: ['text']`). What this bundle **does** do is make the message friendlier: its client half wraps `session.selectModel` and rewrites that rejection to `当前会话已包含图片,而模型 <model> 不支持图片输入;请选择支持图片的模型,或先移除会话中的图片。` (the error code and details pass through unchanged, so any caller switching on `error.code` keeps working). To keep using images, select a model the picker marks *`Image`*, or remove the images from the session first; alternatively an image-routing bundle (e.g. `@deepseek-ai/dsh-llm-image-routing`) can transparently route image turns to a vision fallback.
205
- - **No `stop` sequences**: the wire format has no stop field; requests carrying one throw `UNSUPPORTED_OPTION`.
206
- - Reasoning blocks are **not replayed** into later turns (matches the official CLI: prior private reasoning must not leak).
207
- - Only tool calls with a paired tool result are replayed into the conversation.
208
- - The model catalog endpoint is public; requests to `/alpha/generate` require the key above.
173
+ - **Image input is model-gated**: only models the official registry lists with Vision accept images (see `KNOWN_IMAGE_MODELS` in `src/adapter.ts`). Text-only models throw `UNSUPPORTED_CONTENT`; Command Code's own CLI's client-side *VISION* fallback is **not** reproduced here — switch to a Vision model instead. Image input also requires the dsh **attachment service**.
174
+ - **Switching to a text-only model in an image-bearing session is rejected by dsh itself** — a harness-level guard (`dsh-host-apiproxy`'s `selectModel`) refuses `model-unavailable` and cannot be relaxed from the plugin side. This bundle makes the message friendlier via its client half: it rewrites the rejection to *"当前会话已包含图片,而模型 \<model\> 不支持图片输入;请选择支持图片的模型,或先移除会话中的图片。"* (the error code and details pass through unchanged). Select a model the picker marks *`Image`*, remove the images first, or use an image-routing bundle (e.g. `@deepseek-ai/dsh-llm-image-routing`).
175
+ - **No `stop` sequences** (the wire format has none): requests carrying one throw `UNSUPPORTED_OPTION`.
176
+ - Reasoning blocks are **not replayed** into later turns (matches the official CLI); only tool calls with a paired tool result are replayed.
177
+ - The catalog endpoint is public; `/alpha/generate` requires your key.
209
178
 
210
179
  ## Permissions & privacy
211
180
 
212
- This plugin operates entirely within your dsh profile and your Command Code account. What it touches:
213
-
214
- - **Local files**
215
- - Reads `~/.commandcode/auth.json` (the official CLI login) **only** as a last-resort key fallback.
216
- - Reads/writes `~/.commandcode/models-cache.json` (model catalog cache).
217
- - Reads your API key from the dsh credential store (`$DSH_HOME/.credentials.yaml`) via the standard credential seam — the key is never logged or sent anywhere but the Command Code API.
218
- - **Network**
219
- - `GET {apiBase}/provider/v1/models` — public model catalog (no key required).
220
- - `POST {apiBase}/alpha/generate` — the model requests themselves, authenticated with your key.
221
- - The request body includes the `workingDir` (project path) you configure (defaults to the process cwd), sent as Command Code's `config.workingDir`.
222
- - **No telemetry**: no analytics, no tracking, no third-party endpoints. The only outbound hosts are the Command Code API (`api.commandcode.ai` by default, configurable via `apiBase`).
181
+ This plugin operates entirely within your dsh profile and your Command Code account. **Local files**: reads `~/.commandcode/auth.json` only as a last-resort key fallback; reads/writes `~/.commandcode/models-cache.json`; reads your key from `$DSH_HOME/.credentials.yaml` via the standard credential seam (never logged). **Network**: `GET {apiBase}/provider/v1/models` (public catalog) and `POST {apiBase}/alpha/generate` (your requests, authenticated), the body including your configured `workingDir`. **No telemetry** — the only outbound host is the Command Code API (`api.commandcode.ai` by default, configurable via `apiBase`).
223
182
 
224
183
  ## Disabling / uninstalling
225
184
 
226
- - **Disable** the provider without removing it: edit your profile's `cordis.patch.yml` and comment out (or remove) the `llm-commandcode` row, or set `disabled: true` on it, then restart the web app.
185
+ - **Disable** without removing: edit your profile's `cordis.patch.yml` and comment out (or remove) the `llm-commandcode` row, or set `disabled: true`, then restart.
227
186
  - **Uninstall** completely:
228
187
 
229
188
  ```sh
230
189
  dsh plugin --profile web remove @mars-sea/dsh-commandcode-provider
231
190
  ```
232
191
 
233
- This removes the bundle dependency and its layer. Your API key in the dsh credential store and `~/.commandcode/auth.json` are left untouched (you can remove them manually if you want to revoke access).
192
+ This removes the bundle dependency and its layer; your API key in the dsh credential store and `~/.commandcode/auth.json` are left untouched.
234
193
 
235
194
  ## Development
236
195