@mars-sea/dsh-commandcode-provider 0.11.5 → 0.11.6
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 +15 -0
- package/lib/client.js +1 -1
- package/lib/index.d.ts +171 -36
- package/lib/index.js +429 -73
- package/lib/index.js.map +1 -1
- package/package.json +13 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,21 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.11.6] - 2026-09-19
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- **An account the user explicitly selected comes back on its own** ([#51](https://github.com/Mars-Sea/dsh-commandcode-provider/issues/51)). With several accounts, pinning one in the **Active account** dropdown and then hitting a 429 on it moved every later request to `default` for the rest of the process, and re-selecting the account in the settings page could not bring it back — only restarting dsh did (the report's 被切换后需要重启 dsh 才能换回来). The mechanism: a 429 marks the key `unknown`, which only a `/alpha/billing/credits` window probe can clear, and the pool's only probe pass runs once EVERY account is marked — a state that never arrives while another account can still serve. The documented promise ("if it is exhausted, requests fall back to other accounts and return once its window resets") was therefore permanent in practice, and because that state is in-memory, a restart was the only way out. `resolveKey()` now re-checks the account the user explicitly asked for — the manual pin, or a model rule's routed slot — before serving the fallback: its window is probed at most once a minute, a window that is no longer exceeded drops the mark and restores the selection on that very request, and an exhausted one is stamped as a cooldown carrying the provider's own `resetAt`, after which the account returns by the clock with no further probe. Everything that keeps the steady state cheap is unchanged: a failed probe keeps the mark and is retried at most once per interval, a 401 stays out until the stored credential changes, the just-rejected key is still skipped during rotation, and an account nobody explicitly asked for is never probed while another one can serve. One visible side effect: a pinned 429 now shows a real cooldown end in the account usage card instead of an open-ended "rate limited". `tests/accounts.test.ts` pins the cleared-window revival, the clock-based return, the throttled failed probe, the `tried`/401 gates, the zero-probe steady state, and the routing case.
|
|
13
|
+
- **An account that cannot serve no longer traps the turn** ([#51](https://github.com/Mars-Sea/dsh-commandcode-provider/issues/51), follow-up report). The same reporter added that it is "not only `default`": a request would land on an unusable account and fail with a bare `400`, frequently, while the other three accounts were fine. Three verified gaps made that possible. (1) Only 429/401 rotated, but the provider's account-scoped rejections are wider — the official CLI's own classifier (command-code@1.56.0: `parseWindowLimitError`, `isInsufficientCreditsRequestError`, `parseSpendCapError` and its terminal-marker list `["premium_credits_exhausted", "model_not_in_plan", "insufficient credits"]`) accepts the code `RATE_LIMITED` on ANY status plus `400 Insufficient credits`, `USAGE_EXCEEDED`, `PREMIUM_CREDITS_EXHAUSTED` and `MODEL_NOT_IN_PLAN`, all of which this adapter reported as a permanent `PROVIDER_HTTP_ERROR` — and a rejection that is neither marked nor rotated keeps the account "usable", so the pool hands it out again on the next request and the same error returns. (2) Even once classified, rotation could not have moved: the pool was asked to exclude only the just-rejected key and answered with the first *usable* account, which is the same key whenever the rejection marked nothing, so the adapter's already-tried guard ended the loop and the accounts behind it were never reached. (3) The revival probe read only `windowLimits.fiveHour`, so an account whose WEEKLY quota was spent but whose five-hour window was open was revived and handed out on every all-marked pass. The adapter now classifies account-scoped rejections with the CLI's rules and rotates on them, the whole tried set travels with the rejection so one request can walk a four-account pool, `error.rateLimit.reset` (seconds) becomes the mark's own cooldown instead of waiting for a probe, and `probeWindowLimits()` reads every window the endpoint publishes. A rejection that is NOT account-scoped — an invalid tool schema, a context overflow — still fails fast on the first attempt. Two deliberate refusals: an `unavailable` rejection is rotated past but never marked (the fact is about the model or the balance rather than the key, either can change without the key changing, and a `:free` model is still served by a credits-empty account), and when the tried set covers every account the pool still answers with its own all-exhausted diagnosis (earliest reset + the retry wait) only if they are all marked — an unmarked rejection is surfaced as itself rather than mislabelled as an exhausted window. `tests/adapter.test.ts` pins the credits/plan rotations, the `RATE_LIMITED`-code-with-reset path and the no-rotation control; `tests/accounts.test.ts` pins the pool walk, the all-tried diagnosis and the marks that must stay probe-eligible.
|
|
14
|
+
|
|
15
|
+
- **The model picker no longer changes with the serving account** ([#51](https://github.com/Mars-Sea/dsh-commandcode-provider/issues/51), same follow-up report: 《卡片会切换》). The picker's **Hide out-of-plan models** filter asked the account that would serve *right now* — `loadBillingAccess()` resolved a single key through the pool — so with accounts on different plans the model list changed as rotation moved between them, and a model only ANOTHER account could run was hidden outright: no request could ever bring it back, because the filter had already removed it from the picker. The question is now asked of every account in the pool (`modelVisibleForAnyAccount()`, fed by a new optional `resolveAccountKeys` seam the plugin entry implements from `pool.resolvedAccounts()`), so a model is hidden only when NO account can run it — the honest question for a pool, and the one that composes with the entitlement rotation above: a model offered because some account includes it is served by that account after at most one rejected attempt. Nothing else about the filter changes: three requests per account per `BILLING_ACCESS_TTL_MS`, cached per key exactly like the single-account path; an account whose facts are unknown counts as "may include it" (the existing fail-open rule, so a transient billing failure still never hides a usable model); a host without the seam falls back to the serving key; and a tier no account reaches (Provider, in a Go+Pro pool) is still hidden. `tests/adapter.test.ts` pins the union through a per-account fetch stub, the still-hidden tier, the unknown-account fail-open and the per-account caching.
|
|
16
|
+
|
|
17
|
+
- **The plugin boots on DSH Desktop** ([#52](https://github.com/Mars-Sea/dsh-commandcode-provider/pull/52)). An older Desktop release validates the entire peer closure of every active plugin, and its runtime tree ships host packages only — so the three peers whose modules the Web frontend already hands to every client bundle through its `staticModules` seed table (`react`, `@deepseek-ai/dsh-client-ui-primitives`, `@deepseek-ai/dsh-client-ui-slots`) were a hard startup failure there (`desktop profile: … requires missing …`) even though no installed copy of them is ever needed: `lib/client.js` requires exactly those three, and the webview resolves them from the seed on every engine this plugin supports. They now carry `peerDependenciesMeta.optional: true`, and every other peer stays required, because a `dsh.client` row is resolved and served by the Host and a harness peer a fresh generation does not install is a bundle that cannot load. `react` also moved into `devDependencies`: npm and pnpm auto-install only NON-optional peers, and the test suite imports the React component tree at runtime, so without it the next `package-lock.json` refresh would have dropped `react` and turned `npm test` red for a reason that looks unrelated to this change. `tests/package.test.ts` pins the exact optional set, that each optional name is a declared peer, and that each stays a development package.
|
|
18
|
+
- **The issue #51 rotation work is hardened where review found it short.** Four paths could still misbehave, each now pinned by a test. (1) A pool whose untried accounts were all marked, while an account the request had ALREADY used sat unmarked — a credits/plan refusal marks nothing — threw a synthesized `INVALID_CREDENTIAL`/`RATE_LIMIT` that named the wrong cause, counted only the tried-filtered subset ("every configured Command Code account (1)…" out of two), and could hand dsh-llm-retry a ~15-minute wait for a permanent failure; `resolveKey()` now returns undefined in that case exactly as the all-tried branch already did, and builds its diagnosis from the pool's FULL account list (a second resolution that came back empty used to report "every configured account (0) was rejected with 401"). (2) A revival dropped the probe throttle's timestamp, so a pinned account whose window probe said "open" while the chat endpoint still answered 429 was probed once per request, forever — the stamp now survives the revival, and the interval's other half is reachable through a new `now()` clock seam instead of a real minute of waiting. (3) The account-scoped classifier read only spaced wordings, so a code-only `400 {"error":{"code":"INSUFFICIENT_CREDITS"}}` — the shape its own fixture implied — rotated nothing; the code list gained `INSUFFICIENT_CREDITS` and the wording check normalizes underscores, so both wire spellings classify. (4) An unroutable window limit on a non-429 status ended the turn as a dead `PROVIDER_HTTP_ERROR` even though the provider had named its reset; it is now a retryable `RATE_LIMIT` carrying that reset (a plain 429 keeps the `Retry-After` mapping it had). Also: an empty scalar `reasoning: ""` no longer shadows a populated `reasoning_details` array in the same chunk — the array layer already refused to let an empty `text` hide a `summary`, and the scalar layer now agrees, keeping the thinking issue #34's tool-loop continuity depends on. `tests/accounts.test.ts` and `tests/adapter.test.ts` pin all of it, including the 5xx negative control (a code-less 5xx rotates nobody) beside the deliberate exception (a 5xx carrying `RATE_LIMITED` is the window limit it says it is — the classifier reads the code before its status guard).
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- **Synced with the official command-code@1.57.0 CLI** (2026-09-19, npm `latest`; no changelog entry yet — the page stops at 1.56.1, and 1.56.2 shipped without one). `COMMAND_CODE_CLI_VERSION` is now `1.57.0`. The CLI's only registry change is the new `z-ai/glm-5.3-flashx` ("GLM-5.3 FlashX": chatComplete, text + image, 1M context, selectable `low`/`high`/`max` efforts, Go and every tier above it, $0.37 in / $1.25 out / $0.075 cache read with no cache-write rate, no deal and no hourly schedule) — the picker now annotates it like its `z-ai/glm-5.3-flash` sibling, through a capability-snapshot entry and a vendored price row. The backend catalog changed under this same check: Meituan's LongCat 2.0 promo ended on 2026-09-19, so the public catalog renamed `meituan/LongCat-2.0:free` to the paid `meituan/LongCat-2.0` ($0.30 in / $1.20 out / $0.006 cache read, same 1M context, text-only, automatic reasoning), the pricing page dropped both the free slug and the 100%-off deal (its deal count fell 6 → 5 and its free count 4 → 3), and the docs list the new id — while the command-code@1.57.0 CLI registry still carries the retired `:free` id, one release behind the backend. The snapshot follows the catalog this plugin actually serves: the new id takes the old one's Go tier, its `FREE` deal is removed rather than left to badge a now-paid model, the automatic-reasoning set is renamed with it, and `longcat-2.0` joins the vendored price table — `node scripts/sync-model-prices.mjs` reports exactly those two rows as real drift. The public catalog serves 71 models, one more than at 1.56.0, with `gpt-6-astra` still a CLI/pricing/docs-only model as before. Static inspection of both bundles finds no transport drift: the `/alpha/*` endpoint set, the `/alpha/generate` converters (`toWireMessages`/`toWireTools`/`toWireToolOutput`) and the stream event vocabulary are byte-identical to 1.56.0, and the version rides every request as `x-command-code-version` as usual. The one 1.56.1 changelog item — "Send session id to BYOK hosts that require it" — adds the `x-opencode-session` header on the BYOK transport, host-gated to `opencode.ai`, so neither of this adapter's transports is affected, and 1.57.0's other additions (a `command-code/<version>` BYOK user agent, a renamed internal warning kind, the removal of the CLI's own image-vision consent prompt) are CLI-side as well. Everything else is re-verified unchanged: the per-model effort map beyond the addition, the subscription plan maps, every plan tier, the five active deals, and the peak/off-peak membership and schedule (still 01:00–04:00 and 06:00–10:00 UTC, Monday–Friday only). One upstream leftover was deliberately NOT snapshotted: the pricing page still embeds a `qwen-3.7-max-2x-usage` deal record for Qwen 3.7 Max, but its own Deals section (five entries) does not list it, its `expires` stamp (2026-06-22) is already in the past, and the vendored rate row already carries the discounted figures.
|
|
23
|
+
|
|
9
24
|
## [0.11.5] - 2026-09-18
|
|
10
25
|
|
|
11
26
|
### Fixed
|
package/lib/client.js
CHANGED
|
@@ -2157,7 +2157,7 @@ window.__ModuleLoader__.load({
|
|
|
2157
2157
|
}
|
|
2158
2158
|
//#endregion
|
|
2159
2159
|
//#region package.json
|
|
2160
|
-
var version = "0.11.
|
|
2160
|
+
var version = "0.11.6";
|
|
2161
2161
|
var repository = {
|
|
2162
2162
|
"type": "git",
|
|
2163
2163
|
"url": "git+https://github.com/Mars-Sea/dsh-commandcode-provider.git"
|
package/lib/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { Context } from "@deepseek-ai/cordis";
|
|
|
7
7
|
import { AttachmentStore, ImageAttachmentRef } from "@deepseek-ai/dsh-attachment";
|
|
8
8
|
import { CommandDefinition } from "@deepseek-ai/dsh-commands";
|
|
9
9
|
//#region src/adapter.d.ts
|
|
10
|
-
declare const COMMAND_CODE_CLI_VERSION = "1.
|
|
10
|
+
declare const COMMAND_CODE_CLI_VERSION = "1.57.0";
|
|
11
11
|
declare const DEFAULT_API_BASE = "https://api.commandcode.ai";
|
|
12
12
|
declare const DEFAULT_GENERATE_MAX_TOKENS = 64000;
|
|
13
13
|
declare const DEFAULT_MAX_OUTPUT_TOKENS = 65536;
|
|
@@ -100,6 +100,22 @@ interface CommandCodeConnectionOptions {
|
|
|
100
100
|
* text-only request never depends on the attachment seam.
|
|
101
101
|
*/
|
|
102
102
|
type ResolveAttachments = () => AttachmentStore | undefined;
|
|
103
|
+
/**
|
|
104
|
+
* Why a pre-stream rejection rotates to another account. `rate-limit` and
|
|
105
|
+
* `invalid-credential` are the two the pool records as marks; `unavailable` is
|
|
106
|
+
* an account-scoped rejection that must NOT become a mark — the account's key
|
|
107
|
+
* is valid and its windows may be open, the ACCOUNT just cannot serve THIS
|
|
108
|
+
* request (no credits, a model outside its plan) — so the pool moves on
|
|
109
|
+
* without remembering anything.
|
|
110
|
+
*/
|
|
111
|
+
type AccountRotationReason = 'rate-limit' | 'invalid-credential' | 'unavailable';
|
|
112
|
+
/** What the rotation hook knows about the request it is rotating within. */
|
|
113
|
+
interface AccountRotationContext {
|
|
114
|
+
/** Every API key this request has already used, just-rejected key included. */
|
|
115
|
+
tried: readonly string[];
|
|
116
|
+
/** The provider's own reset time for a `rate-limit` rejection, in millis. */
|
|
117
|
+
resetAtMs?: number;
|
|
118
|
+
}
|
|
103
119
|
/** Everything the adapter needs beyond the request itself. */
|
|
104
120
|
interface CommandCodeAdapterDeps<C extends CommandCodeConnectionOptions = CommandCodeConnectionOptions> {
|
|
105
121
|
/** Resolve the current connection facts (fresh per request, settings-aware). */
|
|
@@ -112,13 +128,33 @@ interface CommandCodeAdapterDeps<C extends CommandCodeConnectionOptions = Comman
|
|
|
112
128
|
resolveApiKey: (connection: C, model?: string) => Promise<string>;
|
|
113
129
|
/**
|
|
114
130
|
* Multi-account rotation hook: the request sent with `rejectedKey` was
|
|
115
|
-
* refused
|
|
116
|
-
*
|
|
117
|
-
* account's key to retry with, or
|
|
118
|
-
* Only pre-stream rejections rotate — a
|
|
119
|
-
* partially consumed generation against
|
|
131
|
+
* refused before any response body streamed, for an account-scoped reason
|
|
132
|
+
* (see {@link AccountRotationReason}). The host marks that key when the
|
|
133
|
+
* reason warrants it and returns the next account's key to retry with, or
|
|
134
|
+
* `undefined` to surface the failure. Only pre-stream rejections rotate — a
|
|
135
|
+
* mid-stream failure never replays a partially consumed generation against
|
|
136
|
+
* another account.
|
|
137
|
+
*
|
|
138
|
+
* `rotation.tried` lists every key this request has already used (the
|
|
139
|
+
* just-rejected one included). The host must not offer one of them again:
|
|
140
|
+
* with several accounts a rejection that does not mark the key (a plan or
|
|
141
|
+
* balance rejection is model-specific, not account-fatal) would otherwise
|
|
142
|
+
* re-offer the same account on every attempt and the pool could never reach
|
|
143
|
+
* the accounts behind it.
|
|
144
|
+
*
|
|
145
|
+
* `rotation.resetAtMs` carries the provider's own reset time when the
|
|
146
|
+
* rejection body published one, so the host can hold the key out until then
|
|
147
|
+
* instead of waiting for a billing probe to learn the same fact.
|
|
148
|
+
*/
|
|
149
|
+
rotateApiKey?: (rejectedKey: string, rejection: AccountRotationReason, connection: C, model?: string, rotation?: AccountRotationContext) => Promise<string | undefined>;
|
|
150
|
+
/**
|
|
151
|
+
* Every API key the host can serve from, in rotation order and deduplicated
|
|
152
|
+
* — the multi-account pool's own list. The picker's plan filter asks all of
|
|
153
|
+
* them, because the pool (not any single account) is what serves a request;
|
|
154
|
+
* a host without a pool omits this seam and the filter falls back to the key
|
|
155
|
+
* that would serve the current request.
|
|
120
156
|
*/
|
|
121
|
-
|
|
157
|
+
resolveAccountKeys?: () => Promise<readonly string[]>;
|
|
122
158
|
/** HTTP transport override (tests); defaults to the global `fetch`. */
|
|
123
159
|
fetchImpl?: typeof fetch;
|
|
124
160
|
/** Resolve the optional durable attachment service for image input (tests); defaults to none. */
|
|
@@ -302,11 +338,30 @@ declare class CommandCodeAdapter<C extends CommandCodeConnectionOptions = Comman
|
|
|
302
338
|
*/
|
|
303
339
|
private fetchEndpointJson;
|
|
304
340
|
/**
|
|
305
|
-
*
|
|
306
|
-
*
|
|
307
|
-
*
|
|
341
|
+
* Every account's billing facts behind the picker's plan filter, in the
|
|
342
|
+
* pool's rotation order and cached per key for {@link BILLING_ACCESS_TTL_MS}
|
|
343
|
+
* (so the extra accounts cost their three requests once per TTL, not once
|
|
344
|
+
* per picker load). `undefined` means the filter cannot be evaluated at all
|
|
345
|
+
* — no key resolved — which {@link modelVisibleForAnyAccount} reads as
|
|
346
|
+
* "show everything".
|
|
347
|
+
*
|
|
348
|
+
* The serving account is only the first entry: with several accounts the
|
|
349
|
+
* model list must not depend on which of them rotation happens to be using.
|
|
350
|
+
*/
|
|
351
|
+
private loadPoolBillingAccess;
|
|
352
|
+
/**
|
|
353
|
+
* The API keys the picker's plan filter must consult: every account the host
|
|
354
|
+
* can serve from, in rotation order, when the host exposes one. A host
|
|
355
|
+
* without a pool (or without the seam) reports just the key that would serve
|
|
356
|
+
* this request — exactly the pre-pool behaviour.
|
|
357
|
+
*/
|
|
358
|
+
private poolAccountKeys;
|
|
359
|
+
/**
|
|
360
|
+
* One account's billing facts, cached for {@link BILLING_ACCESS_TTL_MS} and
|
|
361
|
+
* shared across concurrent callers. `undefined` means "unknown — show
|
|
362
|
+
* everything" (fail-open).
|
|
308
363
|
*/
|
|
309
|
-
private
|
|
364
|
+
private loadBillingAccessForKey;
|
|
310
365
|
/**
|
|
311
366
|
* The billing facts behind the picker's plan filter, mirroring the CLI's
|
|
312
367
|
* `createBilling` flow: whoami yields the org id, then the subscriptions
|
|
@@ -354,14 +409,29 @@ declare class CommandCodeAdapter<C extends CommandCodeConnectionOptions = Comman
|
|
|
354
409
|
*/
|
|
355
410
|
getUsage(apiKey?: string): Promise<CommandCodeUsageReport>;
|
|
356
411
|
/**
|
|
357
|
-
* Probe one account's
|
|
412
|
+
* Probe one account's real usage windows from `/alpha/billing/credits`. The
|
|
358
413
|
* multi-account pool calls this when every account is marked exhausted: an
|
|
359
|
-
* account whose
|
|
360
|
-
* `resetAt` values feed the "earliest reset" error message.
|
|
361
|
-
*
|
|
362
|
-
*
|
|
414
|
+
* account whose windows no longer report `exceeded` is revived, and the
|
|
415
|
+
* `resetAt` values feed the "earliest reset" error message.
|
|
416
|
+
*
|
|
417
|
+
* BOTH windows the endpoint publishes are read, not just `fiveHour`. An
|
|
418
|
+
* account can have an open five-hour window and an exhausted WEEKLY quota at
|
|
419
|
+
* the same time (the two are metered separately), and reading only the
|
|
420
|
+
* shorter one revived such an account as "usable" — the pool then handed it
|
|
421
|
+
* out, the provider rejected the request again, and the next all-marked pass
|
|
422
|
+
* revived it once more, so a used-up account kept coming back (issue #51's
|
|
423
|
+
* follow-up: "切到一个不可用账号"). `exceeded` is therefore true when ANY
|
|
424
|
+
* published window is exceeded, and `resetAt` is the LATEST reset among the
|
|
425
|
+
* exceeded ones — the binding constraint, because a clear five-hour window
|
|
426
|
+
* buys nothing while the weekly quota is spent.
|
|
427
|
+
*
|
|
428
|
+
* Returns `undefined` when the probe itself failed (transport, non-200, or a
|
|
429
|
+
* payload with no window limits at all) — a failed probe never changes pool
|
|
430
|
+
* state. The monthly/purchased/free credit balances are deliberately NOT part
|
|
431
|
+
* of this answer: they are balances rather than windows, they carry no reset
|
|
432
|
+
* time, and the server remains the final gate on them.
|
|
363
433
|
*/
|
|
364
|
-
|
|
434
|
+
probeWindowLimits(apiKey: string): Promise<{
|
|
365
435
|
exceeded: boolean;
|
|
366
436
|
resetAt: number;
|
|
367
437
|
} | undefined>;
|
|
@@ -414,8 +484,13 @@ interface ResolvedAccount {
|
|
|
414
484
|
/** The key's current rotation state; undefined means usable. */
|
|
415
485
|
state: CommandCodeAccountState | undefined;
|
|
416
486
|
}
|
|
417
|
-
/**
|
|
418
|
-
|
|
487
|
+
/**
|
|
488
|
+
* One account's real rate-limit state, probed from `/alpha/billing/credits`:
|
|
489
|
+
* `exceeded` is true when ANY of the account's usage windows is spent, and
|
|
490
|
+
* `resetAt` is the latest reset among them (the binding constraint — an open
|
|
491
|
+
* five-hour window buys nothing while the weekly quota is exhausted).
|
|
492
|
+
*/
|
|
493
|
+
interface AccountWindowProbe {
|
|
419
494
|
exceeded: boolean;
|
|
420
495
|
resetAt: number;
|
|
421
496
|
}
|
|
@@ -427,13 +502,19 @@ interface CommandCodeAccountPoolDeps {
|
|
|
427
502
|
resolveRef(ref: CredentialRef): Promise<string | undefined>;
|
|
428
503
|
/** The official CLI auth-file key (`~/.commandcode/auth.json`); default slot only. */
|
|
429
504
|
authFileKey(): string | undefined;
|
|
430
|
-
/**
|
|
431
|
-
|
|
505
|
+
/**
|
|
506
|
+
* Probe one key's usage windows — BOTH windows the endpoint publishes (the
|
|
507
|
+
* five-hour and the weekly one), not just the five-hour one; see
|
|
508
|
+
* {@link AccountWindowProbe}. Undefined when the probe itself failed.
|
|
509
|
+
*/
|
|
510
|
+
probeWindow(apiKey: string): Promise<AccountWindowProbe | undefined>;
|
|
432
511
|
/**
|
|
433
512
|
* The manually selected account (a slot id, e.g. `default` or an extra's
|
|
434
513
|
* credential reference), re-read per resolution. The preferred account
|
|
435
514
|
* serves whenever it is usable; an unknown id or an exhausted preferred
|
|
436
|
-
* account falls back to the first usable slot
|
|
515
|
+
* account falls back to the first usable slot — and an exhausted one is
|
|
516
|
+
* re-probed on the way (see {@link CommandCodeAccountPool.resolveKey}), so
|
|
517
|
+
* the fallback lasts only as long as the window really is exceeded.
|
|
437
518
|
*/
|
|
438
519
|
preferredId?(): string | undefined;
|
|
439
520
|
/**
|
|
@@ -445,6 +526,13 @@ interface CommandCodeAccountPoolDeps {
|
|
|
445
526
|
* back to the normal selection (the router is a hint, never a hard gate).
|
|
446
527
|
*/
|
|
447
528
|
modelAccountRules?(): readonly CommandCodeModelAccountRule[];
|
|
529
|
+
/**
|
|
530
|
+
* Clock seam for the explicit-account probe throttle. Tests drive it so the
|
|
531
|
+
* "the interval elapsed, probe again" half of
|
|
532
|
+
* {@link CommandCodeAccountPool.canProbeExplicit} is reachable without
|
|
533
|
+
* waiting a real minute; production reads `Date.now()`.
|
|
534
|
+
*/
|
|
535
|
+
now?(): number;
|
|
448
536
|
}
|
|
449
537
|
/**
|
|
450
538
|
* One "route these models to that account" rule. `models` lists catalog ids
|
|
@@ -493,6 +581,8 @@ declare class CommandCodeAccountPool {
|
|
|
493
581
|
private readonly deps;
|
|
494
582
|
/** Rotation state by API key. */
|
|
495
583
|
private readonly states;
|
|
584
|
+
/** Last explicit-revival probe attempt by API key (throttles a failing probe). */
|
|
585
|
+
private readonly explicitProbes;
|
|
496
586
|
constructor(deps: CommandCodeAccountPoolDeps);
|
|
497
587
|
/**
|
|
498
588
|
* Resolve every slot's key, deduplicated by key (first slot wins). Slots
|
|
@@ -519,25 +609,37 @@ declare class CommandCodeAccountPool {
|
|
|
519
609
|
* `options.model` is the request's model id; routing rules re-read per
|
|
520
610
|
* resolution, so a settings change applies live.
|
|
521
611
|
*
|
|
522
|
-
* `options.
|
|
523
|
-
*
|
|
524
|
-
*
|
|
525
|
-
*
|
|
612
|
+
* `options.tried` lists the keys this request has already used — the
|
|
613
|
+
* just-rejected one included. They are removed from the resolution entirely,
|
|
614
|
+
* which is what lets one request walk a four-account pool: an account-scoped
|
|
615
|
+
* rejection that does not mark the key (no credits, a model outside the
|
|
616
|
+
* account's plan) would otherwise be offered again on every attempt, and the
|
|
617
|
+
* accounts behind it would never be reached.
|
|
618
|
+
*
|
|
619
|
+
* An explicit selection (the pin or a model rule) that a rate-limit mark
|
|
620
|
+
* would demote is probed before the fallback serves, so "falls back while
|
|
621
|
+
* exhausted" never becomes "stays demoted until the process restarts".
|
|
526
622
|
*/
|
|
527
623
|
resolveKey(options?: {
|
|
528
|
-
|
|
624
|
+
tried?: readonly string[];
|
|
529
625
|
model?: string;
|
|
530
626
|
}): Promise<{
|
|
531
627
|
key: string;
|
|
532
628
|
slot: CommandCodeAccountSlot;
|
|
533
629
|
} | undefined>;
|
|
534
630
|
/**
|
|
535
|
-
* Record a rejection against one key. `rate-limit`
|
|
536
|
-
*
|
|
537
|
-
*
|
|
538
|
-
*
|
|
631
|
+
* Record a rejection against one key. `rate-limit` marks the key exhausted
|
|
632
|
+
* (`429`/`RATE_LIMITED`) — as a `cooldown` until `resetAtMs` when the
|
|
633
|
+
* rejection body published the provider's own reset time, otherwise as an
|
|
634
|
+
* `unknown` mark whose reset is probed lazily — and `invalid-credential`
|
|
635
|
+
* (401) disables the key until the stored credential changes.
|
|
636
|
+
*
|
|
637
|
+
* `resetAtMs` is seconds-to-millis converted by the adapter from the
|
|
638
|
+
* provider's `error.rateLimit.reset`: knowing the real reset immediately is
|
|
639
|
+
* what keeps an exhausted account out of rotation for exactly as long as the
|
|
640
|
+
* provider said, instead of until a probe happens to run.
|
|
539
641
|
*/
|
|
540
|
-
markRejected(apiKey: string, rejection: AccountRejection): void;
|
|
642
|
+
markRejected(apiKey: string, rejection: AccountRejection, resetAtMs?: number): void;
|
|
541
643
|
/**
|
|
542
644
|
* One account's key: literal → credential seam → auth file (default slot).
|
|
543
645
|
*
|
|
@@ -553,6 +655,35 @@ declare class CommandCodeAccountPool {
|
|
|
553
655
|
* agree on one string.
|
|
554
656
|
*/
|
|
555
657
|
private resolveSlotKey;
|
|
658
|
+
/**
|
|
659
|
+
* The account the user explicitly asked for: the slot a model rule routes
|
|
660
|
+
* the request to when that id exists among the resolved slots, else the
|
|
661
|
+
* manually pinned slot. Consulted only on the fallback path — a usable
|
|
662
|
+
* routed account already returned above — so unlike
|
|
663
|
+
* {@link selectAccountForModel} it does NOT require the account to be
|
|
664
|
+
* usable, which is exactly what {@link resolveKey} re-probes.
|
|
665
|
+
*/
|
|
666
|
+
private explicitAccount;
|
|
667
|
+
/**
|
|
668
|
+
* Whether an explicitly selected account's mark is due for a window probe.
|
|
669
|
+
* Only an `unknown` mark (a 429 whose reset was never learned) is worth
|
|
670
|
+
* re-probing: a `cooldown` already carries its reset time and expires by
|
|
671
|
+
* itself, and a `disabled` (401) key stays out until the stored credential
|
|
672
|
+
* changes. The interval bounds a probe endpoint that keeps failing.
|
|
673
|
+
*/
|
|
674
|
+
private canProbeExplicit;
|
|
675
|
+
/** The clock the probe throttle reads; injected so a test can travel in time. */
|
|
676
|
+
private now;
|
|
677
|
+
/**
|
|
678
|
+
* Probe one explicitly selected account's window and apply the answer. A
|
|
679
|
+
* window that is no longer exceeded drops the mark, so the user's own
|
|
680
|
+
* selection serves again on this very request. An exceeded one is stamped
|
|
681
|
+
* as a cooldown carrying the provider's reset time, after which
|
|
682
|
+
* {@link accountUsable} lets the account back in with no further probe. A
|
|
683
|
+
* probe that fails changes nothing: the mark stays `unknown` and the next
|
|
684
|
+
* attempt waits out the interval.
|
|
685
|
+
*/
|
|
686
|
+
private probeExplicit;
|
|
556
687
|
/** Hand out the chosen account's key. */
|
|
557
688
|
private pick;
|
|
558
689
|
}
|
|
@@ -615,8 +746,9 @@ declare const KNOWN_IMAGE_MODELS: ReadonlySet<string>;
|
|
|
615
746
|
* `['low', 'medium', 'high']` efforts in command-code@1.38.0 and moved to
|
|
616
747
|
* `KNOWN_EFFORTS`. `moonshotai/Kimi-K3` followed the same path in
|
|
617
748
|
* command-code@1.39.3 — it gained `['low', 'high', 'max']` efforts and moved
|
|
618
|
-
* to `KNOWN_EFFORTS`. command-code@1.42.0 added
|
|
619
|
-
*
|
|
749
|
+
* to `KNOWN_EFFORTS`. command-code@1.42.0 added LongCat 2.0 (then
|
|
750
|
+
* `meituan/LongCat-2.0:free`, now the paid `meituan/LongCat-2.0`; reasoning:!0,
|
|
751
|
+
* no efforts). command-code@1.45.0 gave the Muse Spark family
|
|
620
752
|
* (1.1, 1.2, 1.2-contributor, 1.3, 1.3-contributor) selectable
|
|
621
753
|
* `['low', 'medium', 'high', 'xhigh']` efforts — they moved to `KNOWN_EFFORTS`.
|
|
622
754
|
* command-code@1.51.3 gave `MiniMaxAI/MiniMax-M3` selectable
|
|
@@ -639,11 +771,14 @@ declare const KNOWN_THINKING_MODELS: ReadonlySet<string>;
|
|
|
639
771
|
* the official plan/pricing pages grants individual-provider/max/ultra and
|
|
640
772
|
* teams-pro only, and the CLI's plan-access map blocks it on Go/GOAT/Pro.
|
|
641
773
|
* command-code@1.41.0 added `Qwen/Qwen3.8-Max-0902` (Go) and 1.42.0 added
|
|
642
|
-
*
|
|
774
|
+
* LongCat 2.0 (Go — a free promo until 2026-09-19, when the backend renamed
|
|
775
|
+
* `meituan/LongCat-2.0:free` to the paid `meituan/LongCat-2.0`); 1.43.0 added
|
|
643
776
|
* `google/gemini-3.8-flash` (GOAT) and 1.44.0 added `meta/muse-spark-1.3`
|
|
644
777
|
* (GOAT) plus its Contributor sibling (Go); command-code@1.52.0 added the
|
|
645
778
|
* free `inclusionai/ling-3.0-flash-sante:free` (Go); command-code@1.53.0
|
|
646
|
-
* added `deepseek/deepseek-v4.1-flash` (Go).
|
|
779
|
+
* added `deepseek/deepseek-v4.1-flash` (Go); command-code@1.56.0 added
|
|
780
|
+
* `Qwen/Qwen3.8-Omni-Flash` (Go); command-code@1.57.0 added
|
|
781
|
+
* `z-ai/glm-5.3-flashx` (Go).
|
|
647
782
|
*
|
|
648
783
|
* The Provider API exposes no plan metadata, so this snapshot is the source of
|
|
649
784
|
* truth for the picker's plan annotation — it answers "which plan do I need to
|