@mars-sea/dsh-commandcode-provider 0.11.17 → 0.12.1
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 +117 -1
- package/README.md +16 -10
- package/README.zh-CN.md +13 -7
- package/lib/client.js +197 -123
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +267 -92
- package/lib/index.js +2502 -717
- package/lib/index.js.map +1 -1
- package/package.json +40 -37
package/CHANGELOG.md
CHANGED
|
@@ -4,7 +4,123 @@ 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
|
-
## [
|
|
7
|
+
## [0.12.1] - 2026-09-30
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Upstream skill regression coverage for the three reformats: rendered plan-page rows, a page with no model rows, the extended availability ladder (including a model served only through a `-v1` variant), and changelog parsing — 27 Python tests and 12 Node tests pass.
|
|
12
|
+
|
|
13
|
+
- Optional numeric request timing through `DSH_COMMANDCODE_TIMING`, including request phases, attempts, and first content without request text or credentials.
|
|
14
|
+
|
|
15
|
+
- **Claude models now answer on `/provider/v1/messages` — a real third transport, not the CLI's private protocol.** The Provider API serves the whole Claude family only through that endpoint: posted to `/provider/v1/chat/completions` every catalog Claude id answers `400 Model "<id>" must be called via /provider/v1/messages`, and a non-Claude model posted the other way answers `400 Model "<id>" is not supported on this endpoint. Use /provider/v1/chat/completions for OpenAI and OSS models.` Until now the adapter answered that refusal by routing Claude to `/alpha/generate`, a private protocol with its own body wrapper, its own event vocabulary and its own cache semantics. Re-measured 2026-09-29 against the 84-model catalog: the ten `claude-*` ids are the only ones carrying `supported_endpoints: ["/messages"]`, and `supported_endpoints` is now the **routing authority** — `MESSAGES_ONLY_MODELS` in `./capabilities.ts` dropped to a fallback for a catalog entry that carries no route list, which is every pre-1.55 cache file and any hand-built one, so a Claude model shipping tomorrow still routes correctly through the `claude-*` prefix. The decision is still never written to `protocolCache`, which is keyed by gateway and API key and would otherwise pin the whole ACCOUNT to one transport and drag DeepSeek, GLM and Qwen off the Provider API with it (issue #46); the same rule now governs the `upgrade_required` downgrade, where a Messages refusal is not cached but a Chat Completions one is. `/alpha/generate` remains for Go-plan keys — the one plan without Provider API access, which is the only reason it still exists — and as the fallback.
|
|
16
|
+
|
|
17
|
+
The contract was measured live rather than read off the Anthropic docs, and it departs from `dsh-llm-deepseek`'s Messages API in ways that make copying that adapter fail. **`thinking` accepts only `{ type: 'adaptive' }`**: `enabled` and `disabled` are refused outright (`"thinking.type.enabled" is not supported for this model. Use "thinking.type.adaptive" and "output_config.effort" to control thinking behavior.`), `between_tools` fails validation, and strength travels in `output_config.effort` over `low | medium | high | xhigh | max` — no `none`, and `xhigh` is absent from DeepSeek's own type, so `reasoningEffort: 'off'` now sends no `output_config` rather than an `off` the endpoint would reject. **`max_tokens` is capped per model** and the endpoint names the ceiling in its own rejection (`max_tokens: 200000 > 128000, which is the maximum allowed number of output tokens for claude-sonnet-5-5`); `/provider/v1/models` publishes no `max_output_tokens`, so the ceiling comes from a new `MODEL_OUTPUT_TOKEN_LIMITS` snapshot, an unlisted Messages-route model falls back to a conservative 64 000, and `modelOutputTokenLimit()` applies it even when the catalog has not warmed — otherwise every first request of a fresh process would have been refused. **Rejections come in three shapes** and all three are now read: the Anthropic envelope, the same envelope with a whole second JSON document inside `message` (unwrapped once for the sentence the user sees, while the raw text is kept for pattern matching, because the embedded `"type":"provider_error"` is part of how the overflow classifier tells an over-reserved request from an ordinary one), and **bare prose** (`Invalid input: expected number, received undefined at max_tokens`), which used to parse to nothing at all. A plan refusal carries no `code` member — `MODEL_NOT_IN_PLAN` is a message PREFIX — and the existing prose classifier already reads it, so the account rotates instead of the turn dying.
|
|
18
|
+
|
|
19
|
+
Two things the endpoint does that the other two transports cannot. **`tool_result.content` accepts image blocks**, so a tool-result image stays inside its tool result instead of being split into a following user message — issue #30's workaround is now scoped to the two wires that actually need it. And **a replayed `thinking` block requires its `signature`** (`thinking.signature: Field required`), which rides the per-block replay envelope and is dropped entirely when no signature was recorded: the OPPOSITE of issue #34, where the DeepSeek route refuses a tool loop whose reasoning was not sent back, but the Messages endpoint accepts the block omitted (verified 200 either way). This is the only transport that writes `replayState.blocks`.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- **Updated the exact DeepSeek Harness pairing from `0.2.0-rc.1` to `0.2.0-rc.2`.** Every Harness peer and development range, `dsh.compatibility.dsh`, `engines.dsh`, and the single `dshReleases` record now name 0.2.0-rc.2 together, and no business code changed: a release audit over the full public source delta (1025 changed paths, 321 package manifests) found every package this plugin imports with **no breaking change** — `dsh-llm` (including the `GenerateOptions`/`RequestMessage`/`ContentBlock`/`LlmError` wire types), `dsh-settings`, `dsh-credentials`, `dsh-commands`, `dsh-web`, `dsh-typert-protocol`, `dsh-timeout`, `dsh-invariants` and `dsh-launch-environment` carry zero source changes, and the published `.d.ts` of the packages this plugin links against are byte-identical. The rc.2 deltas that do exist are additive and were checked one by one: `dsh-client-ui-primitives` gains `MenuGroup`/`observeStickyMenuGroups` and turns `Input` into a `forwardRef` (this plugin renders its own `InlineInput`, so it is unaffected), `dsh-api-remotes` mounts one more `userQuestionsRemote`, and `dsh-llm`/`dsh-commands` add a `'user-question-reply'` message source. All five slot ids this plugin registers (`settings.section`, `settings.models.provider-card`, `settings.models.footer`, `sidebar.footer.action`, `conversation.composer.dock`) survive in the new slot catalog, the Web shell's `staticModules` seed table (`seed.ts`, `platform.ts`) is byte-identical, and the `data-composer-stats` outlet the cost readout attaches to is still present at its original site. `npm run test:engine` passes against a real `@deepseek-ai/dsh@0.2.0-rc.2`. Deliberately not adopted: the new `user-question-reply` chat node, the `userQuestionPanels` service, the `sidebar.right.tab.files.actions` slot and `ui-commands`' optional `group`/`searchMode` — none of them is required by a surface this plugin owns.
|
|
24
|
+
|
|
25
|
+
- **Upstream sync: command-code@1.68.0 → 1.72.1.** Re-verified against the npm `latest` tarball (sha512-checked before extraction), `/provider/v1/models`, the pricing page's embedded availability, the CLI registry's effort/ZDR/plan tables and the rendered changelog. Everything below is what actually moved; efforts, Vision, automatic reasoning, peak membership and the ZDR exception list were re-checked and are unchanged.
|
|
26
|
+
|
|
27
|
+
**Two models joined the catalog and none left it** (86 rows, up from 84). `gpt-6.1-sol` (command-code@1.71.0, GPT-6.1 Sol, 1 050 000 context) is the **first Max-minimum model this catalog has had**: the page's availability sets every lower tier false and only `individual-max` / `individual-ultra` / `teams-pro` true. `KNOWN_PLANS` therefore records `max`, which needs no new `PLAN_ORDER` entry — `plan-tiers.ts` already ranks `max` above `provider` — so the picker sorts it last and a Pro account's plan filter hides it without further work. It is also Vision-capable and takes the same five-level effort set as `gpt-6-astra`. `inclusionai/ling-3.1-flash:free` (command-code@1.70.0, Ling 3.1 Flash) is free "while it lasts" with the same 100-requests-a-day cap as its predecessor, available on every tier, **text-only**, and — unlike that predecessor — carries selectable `low | medium | high` efforts, so it belongs in `KNOWN_EFFORTS` and NOT in `KNOWN_THINKING_MODELS`.
|
|
28
|
+
|
|
29
|
+
**Grok 4.7's 40%-off launch deal ended and left the page.** Its window ran to 2026-09-27T23:59:59.999Z; the pricing data no longer carries a `deal` member at all and publishes the model at its list price directly. The `KNOWN_DEALS` entry is removed rather than left to lapse, and its price row drops `listRates` for plain `rates` ($2 / $6 / $0.5) — no longer switching with any clock. `Qwen/Qwen3.7-Max` appears in the page's embedded `deal` field with an `expires` of 2026-06-22, three months past, and is **not** recorded: an already-elapsed promotion is evidence of a stale record, not of a live discount. Its row already carries the discounted figures either way.
|
|
30
|
+
|
|
31
|
+
**command-code@1.69.0 moved Go to per-model credits**, adding a second Go row to the CLI's plan maps: `individual-go-v1` (Go, 10 credits/month), recorded alongside `individual-go` at the same `tierWeight` 0 — exactly how the pre-existing `individual-pro-v1` already sits at Pro's weight. Longest-prefix resolution keeps the explicit suffix winning over the base key. That release also introduced `individual-ultra` (Ultra, 300 credits/month) into the availability ladder.
|
|
32
|
+
|
|
33
|
+
**Output ceilings for both arrivals.** `scripts/sync-output-limits.mjs` now covers 76 of the 86 catalog ids. `gpt-6.1-sol` is 128 000 from OpenAI's own row, corroborated by every other provider carrying it. `inclusionai/ling-3.1-flash:free` has **no models.dev row at all** yet, so it is the first entry in the script's new `MANUAL_CEILINGS` map: it takes the Ling family's uniform 32 768 rather than starting at `DEFAULT_MESSAGES_MAX_TOKENS`, and the map exists so a genuine hand-recorded inference survives the rewrite instead of being silently dropped by the "learn it at runtime" fallback. The map is keyed by id and each entry names its evidence; wrong values still cost exactly one refused request before `streamRequest()` remembers the truth (issue #71).
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
|
|
37
|
+
- **Three Harness packages the cost-projection tests import are now declared directly.** `tests/cost-projection.test.ts` imports `@deepseek-ai/dsh-session` and `@deepseek-ai/dsh-session-projection` by name, but both arrived only as transitive peers of other Harness packages — and the lock file for 0.2.0-rc.2 initially dropped them entirely, which broke `npm ci` outright (`Missing: @deepseek-ai/dsh-session@0.2.0-rc.2 from lock file`, alongside 24 other packages). `dsh-session`, `dsh-scope` and `dsh-session-projection` are now explicit `devDependencies`, so the test tree no longer depends on another package happening to hoist them.
|
|
38
|
+
- **`npm run test:install` no longer depends on one `npm pack --json` output shape.** The script read `packed[0].filename`, which assumes the array form. `npm pack --json` reports an array in every published npm (`logTar` keys each entry by index and the CLI merges numeric keys back into one), but that shape is not contractual, so a keyed object is now accepted too instead of failing with a confusing "did not report a tarball filename".
|
|
39
|
+
- **The upstream compatibility skill's audit script was failing on every run**, independent of any plugin change. Three official pages reformatted around 2026-09-30 and the parser read all three as errors. The per-plan pages (`/docs/plans/go|goat|pro`) replaced each model row's embedded `availability` map with a rendered `minPlanName` + `slug`/`vendor` row, which `page_models()` did not recognise — so every plan looked like it served no models, indistinguishable from a genuinely empty plan. `/docs/plans/max` dropped its table and `/docs/plans/provider` became the Provider API reference, so both now carry no rows at all; that silence is now an expected, recorded shape (`NO_MODEL_ROWS`) which shouts if either starts carrying rows again, rather than an exception that aborted the whole audit before any other check ran. The availability ladder itself grew twice — `individual-ultra` above max, and `-v1` variants beside their base — and `minimum_plan()` was still pinned to the old five keys, so a new tier would have been read as *false*; it now requires every key it knows and treats "absent" as unknown rather than unavailable.
|
|
40
|
+
|
|
41
|
+
Separately, **the changelog was never actually being read.** Its content is server-rendered, so `flight_chunks()` yielded an 832-character empty shell while the plain markup carried every release — a parser waiting on a Markdown or JSON endpoint would report no entries and no error. `changelog_entries()` now strips tags and parses the `[date] // v<version> <product> FEAT…` structure, so the check reads **what** shipped independently of **whether** the version moved: the catalog and plan pages legitimately move without a CLI release. What's New carries a different, version-less shape and is kept as raw prose for review rather than forced through that parser.
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
- Reuse serialized bodies and prepared images during pre-stream account rotation; bound ordinary transient failures to three retries while retaining separate usage-window and transport budgets.
|
|
45
|
+
- Scope learned and catalog-published output limits, billing access, and protocol choices to `apiBase`. Disk catalog cache version 3 records its gateway and rejects unscoped or mismatched files.
|
|
46
|
+
- **The consecutive-header-timeout streak was shared across every concurrent session on the process, not scoped to one.** `CommandCodeAdapter` is registered once and reused for every agent, but `headerTimeoutStreak` lived as a single instance field rather than the `WeakMap<agent, …>` pattern the new transient/transport retry budgets use. Two failure modes followed from that: a success on ANY session cleared every other session's in-progress streak (the escalation to a terminal diagnosis after `MAX_CONSECUTIVE_HEADER_TIMEOUTS` almost never fired under concurrency), and, since the streak also compares a body fingerprint, two sessions repeatedly timing out on different request shapes kept resetting each other's count back to 1 (same effect, opposite mechanism). The field is now `headerTimeoutStreaks`, a `Map` keyed by `GenerateOptions.sessionId` (not `agent`: `GenerateOptions` carries no agent reference) with FIFO eviction past 200 entries; a session-less call keeps the old single-slot behavior via a fixed fallback key. `tests/adapter.test.ts` pins independent per-session accumulation and the session-less fallback.
|
|
47
|
+
|
|
48
|
+
- **Claude sessions were billed at the full input rate on every turn — the Messages route never asked for a cache.** Anthropic's prompt caching is opt-in per content block: a prefix is stored only when a `cache_control` marker names it, unlike the automatic prefix caching the OpenAI-shaped routes get. The adapter set exactly one marker on the whole plugin, on the CLI route's system section. The Messages route — the only route the entire Claude family answers on — sent **no marker at all**, so every turn re-prefilled the whole replayed history at the undiscounted input price. A 10-turn `claude-sonnet-5-5` session measured 2026-09-29 billed **$0.5648**, and each of its ten requests matched the undiscounted formula to the cent (`37898×$2/M + 1312×$10/M = $0.088916` against a logged `$0.0889`), which is what establishes the zero rather than any display bug — one cached token anywhere in the session would have moved the total. `mapMessagesUsage()` was already reading `cache_read_input_tokens` and `cache_creation_input_tokens` correctly, so the counters were faithfully reporting a zero the request had asked for.
|
|
49
|
+
|
|
50
|
+
The body now carries **three ephemeral breakpoints** — the `system` block, the LAST tool declaration, and the last block of the last `user` message — for **$0.1869 on that same session, a 66.9% reduction**, with no other field changed. The last user turn is the rolling boundary: DSH resends the full history each turn, so a marker there grows with the conversation, while a `system`-only marker would leave every turn's history uncached. `messagesMessageCodec` renders tool results as `role: 'user'`, so a tool loop's trailing turn is marked too; `thinking` and `tool_use` blocks never are, since a breakpoint there caches a prefix the next turn invalidates. `system` becomes a one-block array on this route only — the same shape the CLI route has always sent, and the only way a string field could carry a marker. The CLI and OpenAI routes are untouched, and `tests/adapter.test.ts` pins both.
|
|
51
|
+
|
|
52
|
+
**The placements are copied from the official client, not invented.** Command Code ships
|
|
53
|
+
[`pi-commandcode-provider`](https://github.com/CommandCodeAI/pi-commandcode-provider), which calls this
|
|
54
|
+
same `/provider/v1/messages` endpoint through `@earendil-works/pi-ai` and hand-writes no marker at all:
|
|
55
|
+
that library's `anthropic-messages` transport marks precisely these three positions, and reads
|
|
56
|
+
`pricing.cache_read` / `pricing.cache_write` from the model list to price the result. Both artifacts were
|
|
57
|
+
checked against their registry-published sha512 digests (`pi-ai` 0.87.1, `command-code` 1.68.0, both
|
|
58
|
+
matching) rather than taken on trust, and `command-code` 1.68.0 separately shows the CLI marks only its own
|
|
59
|
+
system section and never touches this endpoint — which is why the CLI-route cache regressions in
|
|
60
|
+
[docs/issue-64-cache-review.md](../docs/issue-64-cache-review.md) say nothing about this one. **This could
|
|
61
|
+
not be confirmed against a live Command Code response** — the account had no remaining quota — so the
|
|
62
|
+
claim that the gateway honours the field rests on the official provider depending on it for its own cost
|
|
63
|
+
display, not on a measured `cache_read_input_tokens`. The downside is bounded either way: a gateway that
|
|
64
|
+
ignored the field would leave price and shape exactly as they are, and one that honoured it without a hit
|
|
65
|
+
would bill the first turn's write at $2.5/M against $2/M, with every later turn hitting inside the
|
|
66
|
+
5-minute window.
|
|
67
|
+
|
|
68
|
+
- **A lone UTF-16 surrogate half in any outgoing string is now removed before it reaches the wire.** A surrogate half is not a character — it comes from a truncated slice, a lossy decode, or a stray `\uD83D` escape — and `JSON.stringify` does **not** reject it: it serializes to the escape `"\ud83d"` and leaves the process as a string no UTF-8 consumer can round-trip. Whether that is fatal depends entirely on the receiving decoder, and the official provider for pi strips these from every outgoing string on this same endpoint, which is the evidence this change rests on. The strip is applied through the `JSON.stringify` replacer at the **single point a request body is serialized**, so all three transports and every field — system text, message content, tool schemas, tool results — are covered at once, rather than enumerating the fields that can carry text and missing one. **This is defensive, not a reproduced crash**: measured locally, `JSON.stringify` accepts a lone surrogate without complaint and the bytes stay valid UTF-8, so the failure it guards against is a strict upstream decoder, which could not be exercised without quota.
|
|
69
|
+
|
|
70
|
+
Two details are load-bearing. **Paired surrogates must survive** — a well-formed astral character IS a surrogate pair, so a naive range filter would delete every emoji; `tests/adapter.test.ts` pins that both a lone half and an emoji are handled correctly. And the **exact** pattern needs lookaround, which V8 cannot turn into a fast scan: on a 412 KB request body it costs 1.40 ms against a bare `JSON.stringify` of 0.59 ms. A flagless range pre-test gates it, because real prompts are ASCII-plus-CJK with no surrogates at all, bringing the ordinary case to 0.77 ms. The pre-test deliberately uses a **non-global** regex: a global one is stateful, and testing with it would advance `lastIndex` so consecutive calls on the same string alternated between true and false.
|
|
71
|
+
|
|
72
|
+
- **A model that refuses `max_tokens` now costs one wasted request, not a failed turn — and the failure is no longer blamed on the context.** Issue #71: `claude-sonnet-5-5` sends `max_tokens: 131072` (the global default, which is 128 × 1024) against a real ceiling of 128 000, and the Messages endpoint refuses it by name. Two things went wrong, and the first one hid the second. The refusal text contains the word `max_tokens`, which `CLI_CONTEXT_OVERFLOW_PATTERN` matches, so it was classified `CONTEXT_WINDOW_EXCEEDED` — the harness then compacted a session that was not oversized, resent the **identical** request, was refused identically, and told the user to start a new conversation that could not possibly work. `OUTPUT_LIMIT_REJECTION` was already claiming those shapes for the HTTP path; the fix here extends the same ordering to `streamErrorToLlmError()`, which is the path issue #71 actually reported (`Command Code stream error: …`, a 200 body carrying an in-stream error frame, not a 400).
|
|
73
|
+
|
|
74
|
+
The refusal is now **retried once at the ceiling the endpoint states**: `streamRequest()` wraps `streamAttempt()` and, when a failure arrives before the first chunk reaches the host, parses Y out of `max_tokens: X > Y`, records it and re-sends. Both shapes are covered because the recovery is identical for each. What makes it pay off is the record — `learnedOutputLimits` is process-wide and only ever tightens, so the turn that *is* refused still leaves every later turn for that model built correctly, without spending a 400 to find out again. A refusal arriving after a delta has been delivered is not retried (the answer is already partly on screen; a second request would duplicate it), and a second refusal within one turn surfaces, so this is one downshift per turn and not a loop. `streamAttempt()` now takes the smaller of the catalog row and the learned value, so a warm catalog cannot mask a limit the gateway has already stated.
|
|
75
|
+
|
|
76
|
+
- **The Messages route no longer halves every Opus answer to avoid a refusal it can now predict.** With the ceiling known, the `claude-*` fallback of 64 000 that predates the table is no longer needed for the family it was written for. `MODEL_OUTPUT_TOKEN_LIMITS` is now **generated** by `scripts/sync-output-limits.mjs` from models.dev rather than hand-maintained from whatever 400 a user happened to hit, and it covers **74 of the 84 catalog ids** — not only the Claude and GPT families, but the open-weight models too, because Command Code sells those through their **vendor's own API** and the vendor's published ceiling is the one worth writing. The script resolves it in order: the vendor's own catalog entry (matched on the normalized id tail, since Command Code namespaces `Qwen/Qwen3.8-Max` while the vendor files `qwen3.8-max`), the same entry with a `free` suffix dropped — both spellings occur, `inclusionai/ling-3.0-flash-sante:free` and `poolside/laguna-s-2.1-free` — and finally unanimity across every provider carrying the id.
|
|
77
|
+
|
|
78
|
+
An earlier pass at this took the opposite line: it wrote a value only where every provider agreed, and left all 48 open-weight models to runtime learning. That was over-cautious in a way that cost real capability. A cross-provider aggregation disagrees precisely because each host configures its own deployment — `moonshotai/Kimi-K3` carries nine `output` values from 8000 to 1048576 across 26 providers — so unanimity was never going to arrive for exactly the models that needed an answer, and "take the minimum" would have cut Kimi to 8000 tokens. The vendor figure is the correct one to start from, and when the gateway is stricter than the vendor the runtime correction above handles it: a ceiling that is too high costs one round trip, while a conservative guess costs half of every answer for as long as the table stands. The 10 models no vendor publishes yet (`Qwen/Qwen3.8-27B`, `deepseek/deepseek-v4.1-flash`, `moonshotai/Kimi-K2.5`, `zai-org/GLM-5.2-Fast`, `thinkingmachines/inkling-small`, two `tencent/hy*`, three id-only rows) stay unlisted and are learned the same way.
|
|
79
|
+
|
|
80
|
+
- **Messages asked a non-reasoning model to reason.** `thinking: { type: 'adaptive' }` was sent unconditionally, but that switch exists to be driven by `output_config.effort` — the endpoint's own wording is "Use `thinking.type.adaptive` AND `output_config.effort` to control thinking behavior". `claude-haiku-4-5-20251001` has neither: no effort levels, and the official CLI's model table marks it neither `reasoning_effort` nor `reasoning:!0`, so it does not reason. It was being handed half of a pair that controls nothing. The switch now travels only when `KNOWN_EFFORTS` publishes levels for the model (reusing the lookup `stream()` already performs), leaving nine of the ten Claude models untouched. **This one could not be confirmed against a live response**: every request for that model stops at 403 `MODEL_NOT_IN_PLAN` on a GOAT-plan tier, before generation, so it rests on the official model table plus the endpoint's field validation — the switch is accepted there, it is simply meaningless. Related, and measured: the switch does **not** suppress thinking (six live requests across `adaptive` present/absent × `temperature` present/absent all produced a thinking block), so an apparent difference between two probe runs was the `max` vs `xhigh` effort rather than the switch.
|
|
81
|
+
|
|
82
|
+
- **Messages never sends `temperature` — adaptive thinking refuses any other value.** The body builder copied the other two transports' `temperature: options.temperature ?? 0.3`, and the endpoint answers `` `temperature` may only be set to 1 when thinking is enabled or in adaptive mode ``. **Every real request was refused with HTTP 400 while all 722 unit tests passed**: the failure only exists on the wire, which is what `scripts/verify-messages-live.mjs` now exists to catch. Omission is the only field state that satisfies both the thinking switch and a caller that asked for a temperature, so Claude loses sampling control on this route because the endpoint removed it. `tests/adapter.test.ts` pins the absence across `temperature` values 0 / 0.3 / 1 / 2.
|
|
83
|
+
|
|
84
|
+
- **A `max_tokens` rejection no longer triggers an expensive, useless compaction.** The context-overflow pattern matches a bare `max_tokens`, so both a ceiling refusal and a plain validation error on that field were reported as `CONTEXT_WINDOW_EXCEEDED` — which hands the turn to `dsh-compaction-basic`, and the harness then compacts a session that was never oversized, at full price on a long context, and is refused identically on the retry. `OUTPUT_LIMIT_REJECTION` now claims the request-side shapes first and returns a bilingual `PROVIDER_HTTP_ERROR` quoting the endpoint's own numbers, so the user learns which setting to lower instead of watching a compaction that cannot help.
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
## [0.12.0] - 2026-09-29
|
|
88
|
+
|
|
89
|
+
### Added
|
|
90
|
+
|
|
91
|
+
- **The model list now shows how far each model stretches your plan: its monthly allowance.** The pricing page publishes a **per-model allowance** (its own term) alongside every model's rates — "$47 of monthly usage on GOAT, $57 on Pro", to quote its MiniMax M3 example, which is the exact pair the new snapshot carries. The settings page's model dropdowns (Visible models, and the per-account routing pickers, which share one component) now render it beside the model name as `$20/mo`, with a tooltip spelling out what it is. **It is not extra credit**: every allowance draws on the same monthly pool, so `$20/mo` means "this model may consume $20 of that pool", not "$20 added to it". Two facts shape the implementation, both from the page rather than from guesswork: the figure exists for **GOAT and Pro only** ("the boost is a per-model allowance, so it lives on the plans that have them"), and deals are **already baked in** — MiniMax M3's `$47 / $57` IS its 2× promotion, folded in upstream. So `allowanceTierForWeight()` maps a subscription tier weight onto those two brackets and answers `undefined` for Go / Provider / Max / Ultra, and the Host resolves the bracket from the POOL's highest plan (`adapter.allowanceTier()`), matching the question `modelVisibleForAnyAccount` already answers for a pool. A Go or Max account therefore sees no allowance at all rather than a GOAT number — the honest outcome, and the reason the badge is absent rather than zero. The data rides the price rows (`allowance: { goat, pro }`, same page record, same slug resolution as the rates) and reaches the browser through the existing `commandcode/models` Remote as a single Host-resolved number; the wire stays a plain `allowance?: number` so the browser never re-derives the bracket. `tests/model-prices.test.ts` checks the snapshot against the page's own prose (MiniMax M3 at `$47 / $57`), asserts every priced catalog model carries both brackets, and pins that only weights 1 and 2 map; `tests/adapter.test.ts` covers the pool's highest-plan rule and its fail-open shape when billing is unreadable.
|
|
92
|
+
|
|
93
|
+
- **One accessibility fix on the way past: badges are no longer below the 12 px floor.** The shared `.cc-badge` / `.cc-badgeMuted` rule rendered at `font-size: 11px`. It is now 12px, the minimum the repository's visual rules allow for labels and auxiliary text. Only that one rule changed — the remaining sub-12px declarations in `panel-styles.ts` and the usage-plan pill are untouched and still need a decision.
|
|
94
|
+
|
|
95
|
+
### Fixed
|
|
96
|
+
|
|
97
|
+
- **A header timeout now names its real cause, and stops being retried forever (#67).** The single message "通常是网络或代理问题,请检查后重试" was wrong for both transports and sent the reporter of #67 to their proxy while their gateway was simply not answering. Measured 2026-09-28, keeping **headers** separate from **totals** (a total includes the model producing every token and says nothing about the header wait). Headers, with `max_tokens: 16` to collapse the generation, 26 250-token prompt: `/alpha/generate` 2.12 / 2.56 / 2.75 s, `/provider/v1/chat/completions` 5.52 / 2.98 / 3.70 s — a ~1.1 s median gap, with the Provider API route swinging 2.5 s where the CLI route moved 0.6 s. Totals at `max_tokens: 1024` were 11.1 s / 17.9 s for the CLI route and 116.7 s / 30.5 s for the Provider API route at 200 k and 1.2 M chars — the 200 k prompt finishing 10× slower than the 1.2 M one is why a size-based rule was wrong, and it is also why the 116.7 s figure is not header evidence. So the message branches on the transport: the CLI route names provider queueing, Go-plan capacity or a network/proxy path and says a longer budget will not help; the Provider API route names the upstream first token and suggests a concrete `requestTimeoutMs` (doubled, floored at 5 minutes, capped at the retry backoff's own 15). Both say outright that prompt size is not the cause. Separately, three consecutive header timeouts on one unchanged payload now leave the retryable whitelist as `PROVIDER_HTTP_ERROR` with an explanation, because dsh-llm exposes a single scalar `maxRetries` and no per-code limit, and `maxRetries: 1000` with a 500 ms → 15 min backoff meant the identical multi-megabyte body was re-sent for hours instead of producing a diagnosis. `RATE_LIMIT` / `SERVER` / `TRANSPORT` keep retrying — each carries a real "come back later" signal — and any answer from the gateway ends the streak. The "Request timeout" hint in the Advanced card now says when the Provider API route needs a bigger budget, and **the default moved from 60 s to 300 s to match the official CLI**. That number is the CLI's, not a round choice: decompiling `command-code@1.66.0` shows its `createNodeTransport` fetch passes only the caller's `signal` and sets no timeout at all, so a model request inherits Node/undici's own default — measured at 301.0 s against a local server that accepts the connection and never sends headers. Being stricter than the CLI only ever costs legitimate requests, since the Provider API route charges its upstream's time-to-first-token to the header wait. The trade is that a genuinely dead gateway now takes longer to name itself, which is what the streak is for.
|
|
98
|
+
|
|
99
|
+
- **The output budget was raised from 64 000 to 131 072.** It was never a provider requirement: probing `/alpha/generate` directly, 64 000 → 200, 131 072 → 200, 196 608 → 200, **200 000 → 200, 200 704 → 400**, 1 000 000 → 400. The server caps `max_tokens` at 200 000, and identically so for a 262 144-window model and a 1 000 000-window one — the cap is global, not per model — so 64 000 was using 32 % of what the provider allows, and the 384 000 figure quoted elsewhere would be refused. 131 072 keeps a 35 % margin under the measured ceiling while covering the real output limits of the models that declare one (Claude 64 K, Gemini 64 K, several at 128 K). It cost nothing in latency to leave at 64 000 (a 64 000 request answered headers in 1.28 s against 1.57 s for 131 072) but it did cap what a model could say in one turn. `DEFAULT_MAX_OUTPUT_TOKENS` moved with it: `/provider/v1/models` publishes only `context_length` and no output ceiling, so the catalog derives a stand-in from the window, and leaving that fallback lower would have re-capped every request before the ceiling was ever consulted. Per request, `requestContextBudget` still lowers the budget from the prompt actually being sent, so raising the ceiling cannot push a large session over its window.
|
|
100
|
+
|
|
101
|
+
- **Both timeout fields are edited in seconds, not milliseconds.** The settings page asked users to do arithmetic to answer "how long is a reasonable wait" — the defaults were `300000` and the ceiling `2147483647`. The stored unit is unchanged (milliseconds, the Host schema, and switching it would break every existing profile); this is presentation only, formatted and parsed through a new `secondsField` spec. Inputs are whole seconds from 1 to 3600 — past an hour a header wait is a hung process, not a budget, and the input is the place to refuse it. Fractions are refused rather than rounded, so the box and the saved profile cannot disagree, and a profile hand-edited beyond the range still displays its true number instead of being silently clamped. The hints now state the default in seconds, and a test reads `DEFAULT_REQUEST_TIMEOUT_MS` to keep the copy from drifting away from the constant.
|
|
102
|
+
|
|
103
|
+
- **CLI image requests now match the official final wire shape (#64).** `/alpha/generate` sends image data URLs with `mimeType`, and both chat transports label each request image with the media type returned by `readImageRequest()` after any JPEG/WebP transcode. Tests cover tool-result images, stored-PNG/request-JPEG variants and stable historical image messages when another image is appended. Live calls showed that this correction alone does not prevent the provider's cache retreat when another image arrives.
|
|
104
|
+
|
|
105
|
+
### Changed
|
|
106
|
+
|
|
107
|
+
- **Updated the exact DeepSeek Harness pairing from `0.1.7-rc.2` to `0.2.0-rc.1`.** Every Harness peer and development range, `dsh.compatibility.dsh`, `engines.dsh`, and the single `dshReleases` record now name 0.2.0-rc.1 together, and no business code changed: the release audit over the full public source delta (1111 changed paths, 322 package manifests) found 13 of the 16 Harness packages with **zero source changes** — `dsh-llm`, `dsh-settings`, `dsh-typert-protocol`, `dsh-credentials`, `dsh-commands`, `dsh-web`, `dsh-timeout`, `dsh-agent`, `dsh-attachment`, `dsh-launch-environment`, `dsh-invariants`, `cordis` and the two client-only rows — so `GenerateOptions`/`RequestMessage`/`ContentBlock`/`LlmError` and the settings `volatile` contract are byte-identical. The three that moved are additive: `dsh-api-remotes/client` adds one `productAnalyticsRemote` contribution to its assembly list, and `dsh-client-ui-primitives` adds one `pointerModality` export. All five slot ids this plugin registers (`settings.section`, `settings.models.provider-card`, `settings.models.footer`, `sidebar.footer.action`, `conversation.composer.dock`) survive in the new slot catalog with an unchanged `slot-contract.ts`, `apps/web` has no source change so the platform seed table is untouched, and the nine `staticModules` seeds were re-read out of the 0.2.0-rc.1 frontend and are identical to 0.1.7-rc.2's — the argument for keeping `react`, `dsh-client-ui-primitives` and `dsh-client-ui-slots` optional still holds. The `data-composer-stats` outlet the cost readout attaches to is still present at its original site. `npm run test:engine` passes against a real `@deepseek-ai/dsh@0.2.0-rc.1` (ESM link, every static named import, the client `require()` seed audit, image pricing, the volatile Config, tool history on both transports, and the durable offload contract). Deliberately not adopted: the optional `dsh-experimental-schedule-bundle` that automation tasks moved into, the desktop-only product-analytics telemetry, and `resolveAccountToken` in the official `web-search-deepseek` — this plugin ships its own `web_search` provider over the same credential chain and gains nothing from it.
|
|
108
|
+
|
|
109
|
+
- **Opt-in cache-aware image history for the CLI transport (#64).** `offloadSeenImagesForCache` asks dsh's durable image-offload surface to replace older images only after the same model has answered with them. A four-request live A/B showed that the next image then retained the growing text prefix instead of reverting to the first image boundary. It is off by default because old pixels are unavailable until reread or reattached; this is a mitigation, not a provider-side cache fix.
|
|
110
|
+
|
|
111
|
+
- **Internal cleanup with no behavior change: dead-code removal, comment trimming, and small refactors.** Removed the unreferenced `isPeakHour()` helper (the readout prices through `cost-facts.ts` and `capabilities.ts`; its test is replaced by one that pins billing-vs-picker peak agreement across every weekday boundary), inlined the single-use `StatTile` wrapper, dropped the never-used `AccountReport summaryOnly` mode, narrowed the mirror's internal status union to the states it actually writes, and registered the login endpoints through `LOGIN_HOST_CONTRIBUTION` instead of spreading the raw descriptors. Trimmed duplicated design narratives in `transport-retry.ts`, `accounts.ts`, and the slot-declaration files (one canonical copy each). Small performance refactors: `costGroupKey()` now resolves prices through a per-table index instead of scanning the table per call, and the cost projection builds its group lookup in one pass. Typecheck clean, full suite green (679 tests).
|
|
112
|
+
|
|
113
|
+
- **Synced with the official command-code@1.67.0 and 1.68.0 CLIs.** `COMMAND_CODE_CLI_VERSION` and the `x-command-code-version` header now report `1.68.0`. Both releases landed within hours of each other (npm `1.67.0` at 2026-09-28T21:39Z, `1.68.0` at 2026-09-28T23:13Z) and **the official changelog page still documents neither** — its newest entry is 1.66.0 (2026-09-26) — so the integrity-verified tarballs are the evidence, and the two releases were separated by diffing the pinned 1.66.0 artifact, the 1.67.0 artifact and `latest` side by side. 1.67.0 adds **`deepseek/deepseek-v4.1-flash-fast`** (DeepSeek V4.1 Flash Fast): `/chat/completions` + `/responses`, 1M context, text+image, `low`/`high`/`max` efforts, **Go**-tier, hourly peak/off-peak pricing at its own $0.16/$0.58/$0.016 off-peak and $0.32/$1.16/$0.032 peak — deliberately **not** 2× its `deepseek-v4.1-flash` sibling's rates, so only that row's own `timeOfDay` block is the evidence — and it is **not** ZDR-covered. 1.68.0 adds **`claude-sonnet-5-5`** (Claude Sonnet 5.5): `/messages`, 1M context, text+image, the five-level effort set, **GOAT and above**, at $2.00/$10.00/$0.20/$2.50 — one tier *above* its `claude-sonnet-5` predecessor, which keeps its Pro slot. 1.68.0 also adds one non-model feature, the TUI setting **"Show Taste learning in feed"** (`showTasteLearningInFeed`, boolean, user scope, default on), which hides Taste notices from the feed without disabling learning; it lives in the CLI's own config and has no counterpart in this plugin. Everything else is unchanged and was re-verified rather than assumed: the subscription plan maps, the ZDR anchor sets beyond the new row, the API route table, the `x-command-code-version` / `x-cmd-zdr` header table, the account-rejection vocabulary and the stream event names are byte-identical across 1.66.0 → 1.68.0. The remaining artifact diff is minified identifier rotation (e.g. `kR` → `CR`), which carries no behavior.
|
|
114
|
+
|
|
115
|
+
- **The capability snapshot grew by two models, and the vendored price table with it.** `KNOWN_EFFORTS` 53 → 55, `KNOWN_IMAGE_MODELS` 62 → 64, `KNOWN_PLANS` 82 → 84 (Go 52 → 53, GOAT 8 → 9 more; the cumulative chain is now **53/62/76/84** against a catalog that serves 84 models at 66 × chat/completions + responses, 10 Claude ids × messages, 8 × chat/completions), `KNOWN_PEAK_PRICING` 4 → 5, and `KNOWN_NON_ZDR_MODELS` 22 → 23. `scripts/sync-model-prices.mjs` rewrote `MODEL_PRICE_ROWS` with exactly two new rows and no other change, which is also what keeps `tests/model-prices.test.ts`'s "every catalog model is either priced or explicitly free" invariant green. `tests/zdr.test.ts` pins the new membership deliberately, `tests/adapter.test.ts` gained the two models' effort/plan/Vision/peak assertions and its version constant moved with the header.
|
|
116
|
+
|
|
117
|
+
- **One provenance note was corrected, not carried forward.** The `stealth/pixel-canary` comment claimed it entered the ZDR exclusion set "through `modelSupportsZdr` alone", and the `KNOWN_NON_ZDR_MODELS` docstring repeated that both stealth previews were missing from the sibling route table. Re-reading all three artifacts (1.66.0 / 1.67.0 / 1.68.0) on 2026-09-29 shows the row is listed in **both** anchor sets in every one of them; `meituan/LongCat-2.0` is the only genuine divergence. Membership never depended on this — the snapshot is the union — but the explanation was wrong and is now the evidence it claims to be.
|
|
118
|
+
|
|
119
|
+
- **A lapsed discount no longer prices a session at the promotional rate.** The picker has always hidden an expired deal, but the vendored price table kept charging its discount: Grok 4.7's 40%-off launch promotion ended `2026-09-27T23:59:59.999Z`, yet the composer still quoted **$1.20 / $3.60 / $0.30** against a row the page reverts to **$2.00 / $6.00 / $0.50** — a 40% under-report on every session, with the >200K band off by the same factor. The page publishes both sets (the promotional figures as the row's own rates, the pre-discount `listRates` alongside), so `scripts/sync-model-prices.mjs` now copies both and `modelPriceTable(now)` picks between them on **`dealLabel()`'s own clock**. Badge and dollars therefore flip on the same instant, and both context bands flip together so a >200K request is never priced off a mixed pair. The table is rebuilt per request, so the switch needs no redeploy; a deal with no `expiresAt` (the two MiMo rows, MiniMax M3) never lapses and keeps its promotional rates. `tests/model-prices.test.ts` pins the revert, the shared boundary (inclusive on the expiry side) and the no-expiry case.
|
|
120
|
+
|
|
121
|
+
- **The page's `discountPercent` is not the revert factor, and the script no longer pretends it is.** Dividing the promotional rates by `1 - pct/100` looks obvious and is wrong: MiMo V2.5 is advertised at "98% off" while its published list is `0.8 / 4 / 0.16` against charged `0.14 / 0.28 / 0.0028` — ratios of 5.7×, 14.3× and 57×, not one percentage. The generator therefore copies the page's literal `listRates` verbatim, scales a context tier only by that row's own per-bucket ratios, and prints a warning instead of guessing when those disagree. A deal with **no** literal `listRates` is left alone deliberately: the page is saying the unit price does not change (Qwen 3.7 Max's `2x-usage` doubles the allowance, it does not discount a token). Rows whose list equals their charged rates (typesafe/jev) get no fallback data, so nothing dormant ships.
|
|
122
|
+
|
|
123
|
+
- **Fixed, same day: the price script's HTML cross-check had been blind since the page redesign.** `renderedRates()` split on `<tr>`/`<td>`, but the model table is now a `<div>` grid — a live run extracted **6 rows out of 88 models**, and those six were the plan cards (Go/GOAT/Pro/Max 10×/Max 20×/Team Pro). Every model row had therefore been rewritten from the embedded JSON alone with no second opinion, which is the exact safety net that once caught `deepseek-v4-flash-vision-exp` shipping ~47% high. The parser now anchors on the row's name cell (`flex min-w-0 items-center gap-2 px-4 py-3`, whose display name is marked `text-[13px] font-medium` — a deal badge next to it uses `text-[10px]`, so they never collide) and harvests **88 of 88** rows. Re-running the generator on the fixed parser produced a **byte-identical table**, which is what makes the previous write trustworthy in hindsight rather than merely unchecked. Two guards came out of it: `main()` now **exits 2 instead of rewriting** when the harvest falls below half the record count, so the next layout change fails loudly rather than comparing nothing; and a discounted row's struck-through `<s>` figures are compared against the table JSON's `listRates`, giving the pre-discount copy an independent rendering to agree with. The `STRUCK_RATE` regex is deliberately `<s(?:\s[^>]*)?>`: the obvious `<s[^>]*>` also matches `<span …>`, which read the DeepSeek rows' tooltip spans as struck-through prices during development. Rows showing a word (`Free`) or an em-dash publish no figure and are skipped — an unparseable row is never counted as agreement.
|
|
8
124
|
|
|
9
125
|
## [0.11.17] - 2026-09-27
|
|
10
126
|
|
package/README.md
CHANGED
|
@@ -35,21 +35,22 @@ See [Screenshots](#screenshots) below for what the UI looks like.
|
|
|
35
35
|
|
|
36
36
|
## Install
|
|
37
37
|
|
|
38
|
-
This release supports **dsh 0.
|
|
38
|
+
This release supports **dsh 0.2.0-rc.2 and nothing else** — the plugin's peer
|
|
39
39
|
range is that one version, and its compatibility record names it alone:
|
|
40
40
|
|
|
41
41
|
```sh
|
|
42
42
|
dsh plugin --profile web add @mars-sea/dsh-commandcode-provider@latest
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
- **Older dsh releases.** The 0.1.2–0.1.
|
|
45
|
+
- **Older dsh releases.** The 0.1.2–0.1.7 line is no longer supported: those
|
|
46
46
|
engines predate the 0.1.7 settings rewrite, the `RequestMessage` envelope, and
|
|
47
47
|
the durable image-offload contract, and the compatibility code that bridged
|
|
48
|
-
the two was removed. The last plugin release covering
|
|
49
|
-
0.
|
|
50
|
-
version and
|
|
48
|
+
the two was removed. The last plugin release covering 0.1.7 is 0.11.17; the
|
|
49
|
+
0.1.2–0.1.6 line's last release is 0.11.11; the 0.5.0-era Harness line's last
|
|
50
|
+
release is 0.9.1. All are installed by exact version and none is maintained:
|
|
51
51
|
|
|
52
52
|
```sh
|
|
53
|
+
dsh plugin --profile web add @mars-sea/dsh-commandcode-provider@0.11.17 # dsh 0.1.7
|
|
53
54
|
dsh plugin --profile web add @mars-sea/dsh-commandcode-provider@0.11.11 # dsh 0.1.2–0.1.6
|
|
54
55
|
dsh plugin --profile web add @mars-sea/dsh-commandcode-provider@0.9.1 # dsh 0.5.0 line
|
|
55
56
|
```
|
|
@@ -69,7 +70,7 @@ Fresh pnpm 10 marketplace generations are supported directly. Do not add a separ
|
|
|
69
70
|
Update with the same tag you installed with:
|
|
70
71
|
|
|
71
72
|
```sh
|
|
72
|
-
dsh plugin --profile web update @mars-sea/dsh-commandcode-provider@latest # dsh 0.
|
|
73
|
+
dsh plugin --profile web update @mars-sea/dsh-commandcode-provider@latest # dsh 0.2.0-rc.2
|
|
73
74
|
dsh plugin --profile web update @mars-sea/dsh-commandcode-provider@0.9.1 # older dsh (0.5.0 line, unmaintained)
|
|
74
75
|
```
|
|
75
76
|
|
|
@@ -143,7 +144,7 @@ cmd login # writes ~/.commandcode/auth.json
|
|
|
143
144
|
cwd: !!js process.cwd()
|
|
144
145
|
```
|
|
145
146
|
|
|
146
|
-
**Engine version.** The plugin is maintained against exactly one engine: **dsh 0.
|
|
147
|
+
**Engine version.** The plugin is maintained against exactly one engine: **dsh 0.2.0-rc.2**. Its `@deepseek-ai/dsh-*` peer range is `^0.2.0-rc.2` (semver resolves that to 0.2.0-rc.2 alone), and `dsh.compatibility.dshReleases` records that single release. On an older engine the settings page, the message envelope, or the request-image budget will not line up — install the last release that supported your engine (see [Install](#install)) instead of forcing this one.
|
|
147
148
|
|
|
148
149
|
## Usage dashboard
|
|
149
150
|
|
|
@@ -191,7 +192,7 @@ llm-commandcode:
|
|
|
191
192
|
|
|
192
193
|
## Configure
|
|
193
194
|
|
|
194
|
-
**Settings → Command Code** is organized as **Accounts** (keys, sign-in, live quota, pinning, dedicated models), **Models** (hide out-of-plan models, visible models), **Privacy & security** (zero data retention, off by default, see below), **Integrations & display** (serve web search with Command Code, show the quota card in the sidebar, off by default), and a collapsed **Advanced** section (API base URL, request/stream timeouts, transport retries). The working directory is no longer on the page; `workingDir` in config still works.
|
|
195
|
+
**Settings → Command Code** is organized as **Accounts** (keys, sign-in, live quota, pinning, dedicated models), **Models** (hide out-of-plan models, visible models), **Privacy & security** (zero data retention, off by default, see below), **Integrations & display** (serve web search with Command Code, show the quota card in the sidebar, off by default), and a collapsed **Advanced** section (API base URL, request/stream timeouts, transport retries, cache-aware image offload). The working directory is no longer on the page; `workingDir` in config still works.
|
|
195
196
|
|
|
196
197
|
The same options live in `$DSH_HOME/settings.yaml` (changes apply immediately, no restart):
|
|
197
198
|
|
|
@@ -201,12 +202,17 @@ llm-commandcode:
|
|
|
201
202
|
apiBase: https://api.commandcode.ai
|
|
202
203
|
workingDir: /path/to/project # optional
|
|
203
204
|
modelsCachePath: ~/.commandcode/models-cache.json
|
|
204
|
-
requestTimeoutMs:
|
|
205
|
+
requestTimeoutMs: 300000 # default 300s (the official CLI's own budget)
|
|
205
206
|
streamIdleTimeoutMs: 300000 # default 300s
|
|
207
|
+
# offloadSeenImagesForCache: true # opt in to CLI cache mitigation; later requests omit old pixels
|
|
206
208
|
showSidebarQuota: true # optional: show the plans & quota card in the sidebar (default off)
|
|
207
209
|
zdr: true # optional: route requests only through zero-data-retention upstreams (default off)
|
|
208
210
|
```
|
|
209
211
|
|
|
212
|
+
### Repeated image cache misses
|
|
213
|
+
|
|
214
|
+
On the CLI transport (`/alpha/generate`), a new image can make Command Code's reported prompt cache retreat to the first historical image even when every previous request message is byte-identical. The opt-in **Stop replaying images already seen** setting (`offloadSeenImagesForCache`) asks dsh's durable `image/offload` surface to replace an older image with a stable text placeholder after this same model has answered with it. The next request keeps its new image; later text can be cached without replaying old pixels. The default is off because the model cannot inspect an offloaded image again unless it rereads the source file or the user reattaches it. This mitigates the observed cost spike; it does not repair Command Code's underlying multimodal cache behavior. [Live evidence and limits](docs/issue-64-cache-review.md).
|
|
215
|
+
|
|
210
216
|
## Web search
|
|
211
217
|
|
|
212
218
|
When your deployment's dsh shell mounts the web capability (`@deepseek-ai/dsh-web` + `@deepseek-ai/dsh-tool-web`), the model's `web_search` tool is served by this plugin's `commandcode` search provider — it calls the Command Code Provider API's `/alpha/web-search` endpoint with the **same API key and base URL** as chat. You do not configure a separate search key, endpoint, or model.
|
|
@@ -225,7 +231,7 @@ Command Code can serve a request only through upstreams that retain no prompts o
|
|
|
225
231
|
|
|
226
232
|
**Off by default.** Turn it on with the *"Zero data retention (ZDR)"* toggle (`zdr`) in the Privacy & security card, or in your profile config. How this plugin implements it:
|
|
227
233
|
|
|
228
|
-
- The header is sent on **every chat request** while ZDR is on. The plugin maintains an informational exception list (`KNOWN_NON_ZDR_MODELS`, synced from the official CLI —
|
|
234
|
+
- The header is sent on **every chat request** while ZDR is on. The plugin maintains an informational exception list (`KNOWN_NON_ZDR_MODELS`, synced from the official CLI — 23 models as of 2026-09-29, e.g. `xai/grok-4.5`, `stepfun/Step-3.7-Flash`, `meta/muse-spark-1.3`). A model without an available ZDR upstream fails with `422 cmd_zdr_no_providers`; the request is never retried without the header.
|
|
229
235
|
- If a refusal still happens (coverage churn, or no ZDR upstream with spare capacity at that moment), the error names the cause and how to turn ZDR off, instead of surfacing as a bare HTTP 422.
|
|
230
236
|
- **ZDR usually costs more**: capacity is limited and billed at each upstream's pass-through rates, and which upstream serves a request can change per request. The session-cost readout keeps quoting the ordinary catalog rates; the real per-request price shows in Command Code's Studio usage page.
|
|
231
237
|
- Works on every plan; plan credits meter ZDR requests at the plan's default allowance.
|
package/README.zh-CN.md
CHANGED
|
@@ -33,15 +33,16 @@
|
|
|
33
33
|
|
|
34
34
|
## 安装
|
|
35
35
|
|
|
36
|
-
本版本**只支持 dsh 0.
|
|
36
|
+
本版本**只支持 dsh 0.2.0-rc.2**:插件的 peer 范围就是这一个版本,兼容性记录里也只列它。
|
|
37
37
|
|
|
38
38
|
```sh
|
|
39
39
|
dsh plugin --profile web add @mars-sea/dsh-commandcode-provider@latest
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
- **更早的 dsh 版本。** 0.1.2–0.1.
|
|
42
|
+
- **更早的 dsh 版本。** 0.1.2–0.1.7 线已不再支持:那些引擎早于 0.1.7 的设置重写、`RequestMessage` 消息封装和持久化图片卸载契约,用来桥接两代引擎的兼容代码已经删除。覆盖 0.1.7 的最后一个插件版本是 0.11.17;0.1.2–0.1.6 线的最后一个版本是 0.11.11;0.5.0 时代 Harness 线的最后一个版本是 0.9.1。三者都按精确版本安装,且都不再维护:
|
|
43
43
|
|
|
44
44
|
```sh
|
|
45
|
+
dsh plugin --profile web add @mars-sea/dsh-commandcode-provider@0.11.17 # dsh 0.1.7
|
|
45
46
|
dsh plugin --profile web add @mars-sea/dsh-commandcode-provider@0.11.11 # dsh 0.1.2–0.1.6
|
|
46
47
|
dsh plugin --profile web add @mars-sea/dsh-commandcode-provider@0.9.1 # dsh 0.5.0 线
|
|
47
48
|
```
|
|
@@ -61,7 +62,7 @@ dsh plugin --profile web add @mars-sea/dsh-commandcode-provider@0.11.15
|
|
|
61
62
|
用与安装时相同的 tag 更新:
|
|
62
63
|
|
|
63
64
|
```sh
|
|
64
|
-
dsh plugin --profile web update @mars-sea/dsh-commandcode-provider@latest # dsh 0.
|
|
65
|
+
dsh plugin --profile web update @mars-sea/dsh-commandcode-provider@latest # dsh 0.2.0-rc.2
|
|
65
66
|
dsh plugin --profile web update @mars-sea/dsh-commandcode-provider@0.9.1 # 更早的 dsh(0.5.0 线,不再维护)
|
|
66
67
|
```
|
|
67
68
|
|
|
@@ -135,7 +136,7 @@ cmd login # 写入 ~/.commandcode/auth.json
|
|
|
135
136
|
cwd: !!js process.cwd()
|
|
136
137
|
```
|
|
137
138
|
|
|
138
|
-
**引擎版本要求。** 插件只针对一个引擎维护:**dsh 0.
|
|
139
|
+
**引擎版本要求。** 插件只针对一个引擎维护:**dsh 0.2.0-rc.2**。它的 `@deepseek-ai/dsh-*` peer 范围是 `^0.2.0-rc.2`(按 semver,这只会解析到 0.2.0-rc.2),`dsh.compatibility.dshReleases` 也只记录这一个版本。在更早的引擎上,设置页、消息封装或请求图片预算总有一处对不上——请改装支持你所用引擎的最后一个版本,而不是硬装这一个。
|
|
139
140
|
|
|
140
141
|
## 用量面板
|
|
141
142
|
|
|
@@ -183,7 +184,7 @@ llm-commandcode:
|
|
|
183
184
|
|
|
184
185
|
## 配置
|
|
185
186
|
|
|
186
|
-
**设置 → Command Code** 分为:**账户**(密钥、网页登录、实时额度、固定账户、专用模型)、**模型**(隐藏套餐外模型、可见模型)、**隐私与安全**(零数据保留 ZDR,默认关闭,见下)、**集成与显示**(用 Command Code 承载联网搜索、在侧边栏显示额度卡片,默认关闭),以及默认折叠的**高级设置**(API
|
|
187
|
+
**设置 → Command Code** 分为:**账户**(密钥、网页登录、实时额度、固定账户、专用模型)、**模型**(隐藏套餐外模型、可见模型)、**隐私与安全**(零数据保留 ZDR,默认关闭,见下)、**集成与显示**(用 Command Code 承载联网搜索、在侧边栏显示额度卡片,默认关闭),以及默认折叠的**高级设置**(API 地址、请求/流超时、传输重试次数、历史图片缓存优化)。工作目录已不在页面上显示,配置中的 `workingDir` 仍然有效。
|
|
187
188
|
|
|
188
189
|
同一组选项也位于 `$DSH_HOME/settings.yaml`(修改即刻生效,无需重启):
|
|
189
190
|
|
|
@@ -193,12 +194,17 @@ llm-commandcode:
|
|
|
193
194
|
apiBase: https://api.commandcode.ai
|
|
194
195
|
workingDir: /path/to/project # 可选
|
|
195
196
|
modelsCachePath: ~/.commandcode/models-cache.json
|
|
196
|
-
requestTimeoutMs:
|
|
197
|
+
requestTimeoutMs: 300000 # 默认 300s(与官方 CLI 一致)
|
|
197
198
|
streamIdleTimeoutMs: 300000 # 默认 300s
|
|
199
|
+
# offloadSeenImagesForCache: true # 可选:模型回复后不再重发旧图片,默认关闭
|
|
198
200
|
showSidebarQuota: true # 可选:在侧边栏显示套餐与配额卡片(默认关闭)
|
|
199
201
|
zdr: true # 可选:请求只经由零数据保留上游(默认关闭)
|
|
200
202
|
```
|
|
201
203
|
|
|
204
|
+
### 连续图片导致的缓存回退
|
|
205
|
+
|
|
206
|
+
在 CLI 传输(`/alpha/generate`)上,即使此前所有请求消息逐字节一致,新图片也可能让服务端报告的缓存命中量退回第一张历史图片处。可在「高级设置」打开「已看过的图片不再重复发送」(`offloadSeenImagesForCache`):同一模型看过图片并回复后,插件通过 dsh 持久的 `image/offload` 事件把旧图替换成稳定文字占位;当前新图仍会送给模型,后续文本更容易保持缓存。**默认关闭**,因为模型如需重新查看已 offload 的图片,必须重新读取原文件或由用户再次附加。这是费用缓解方案,未修复 Command Code 服务端的多模态缓存行为。[实测证据与限制](docs/issue-64-cache-review.md)。
|
|
207
|
+
|
|
202
208
|
## 联网搜索
|
|
203
209
|
|
|
204
210
|
当你的 dsh 部署加载了 web 能力(`@deepseek-ai/dsh-web` + `@deepseek-ai/dsh-tool-web`)时,模型所用的 `web_search` 工具会由本插件的 `commandcode` 搜索 provider 承载——它用**与聊天相同的 API key 与 base URL** 调用 Command Code Provider API 的 `/alpha/web-search` 端点。你无需另外配置搜索 key、端点或模型。
|
|
@@ -217,7 +223,7 @@ Command Code 可以让请求只经由「不留存提示词与回复、也不用
|
|
|
217
223
|
|
|
218
224
|
**默认关闭。** 在「隐私与安全」卡片里打开「零数据保留(ZDR)」(`zdr`),或写进 profile 配置。本插件的实现方式:
|
|
219
225
|
|
|
220
|
-
- 开启后,**每次聊天请求**都会带上 ZDR 请求头。插件仍维护官方 CLI 的例外名单(`KNOWN_NON_ZDR_MODELS
|
|
226
|
+
- 开启后,**每次聊天请求**都会带上 ZDR 请求头。插件仍维护官方 CLI 的例外名单(`KNOWN_NON_ZDR_MODELS`,截至 2026-09-29 共 23 个模型,例如 `xai/grok-4.5`、`stepfun/Step-3.7-Flash`、`meta/muse-spark-1.3`)供查询。没有可用 ZDR 上游时,服务端返回 `422 cmd_zdr_no_providers`;插件不会去掉请求头重试。
|
|
221
227
|
- 万一仍被拒绝(名单过期,或那一刻没有空闲的 ZDR 上游容量),错误信息会说明原因并给出关闭 ZDR 的办法,而不是抛出一个光秃秃的 HTTP 422。
|
|
222
228
|
- **ZDR 通常更贵**:容量有限,按各上游实价透传计费,且每次请求落在哪个上游可能不同。会话费用读数仍按常规目录价估算;真实单价见 Command Code Studio 的用量页。
|
|
223
229
|
- 各套餐均可使用;ZDR 请求按套餐的默认额度(而非提升额度)计量。
|