@mars-sea/dsh-commandcode-provider 0.11.4 → 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/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.54.0";
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 with 429 (`rate-limit`) or 401 (`invalid-credential`) before
116
- * any response body streamed. The host marks that key and returns the next
117
- * account's key to retry with, or `undefined` to surface the failure.
118
- * Only pre-stream rejections rotate — a mid-stream failure never replays a
119
- * partially consumed generation against another account.
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
- rotateApiKey?: (rejectedKey: string, rejection: 'rate-limit' | 'invalid-credential', connection: C, model?: string) => Promise<string | undefined>;
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
- * The billing facts behind the picker's plan filter, cached for
306
- * {@link BILLING_ACCESS_TTL_MS} and shared across concurrent callers.
307
- * `undefined` means "unknown — show everything" (fail-open).
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 loadBillingAccess;
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 five-hour window from `/alpha/billing/credits`. The
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 window no longer reports `exceeded` is revived, and the
360
- * `resetAt` values feed the "earliest reset" error message. Returns
361
- * `undefined` when the probe itself failed (transport, non-200, or a
362
- * payload without window limits) — a failed probe never changes pool state.
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
- probeFiveHourWindow(apiKey: string): Promise<{
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
- /** Five-hour window facts probed from `/alpha/billing/credits`. */
418
- interface FiveHourWindowProbe {
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
- /** Probe one key's five-hour window; undefined when the probe itself failed. */
431
- probeWindow(apiKey: string): Promise<FiveHourWindowProbe | undefined>;
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.exclude` skips one key during the probe-revival pass: the
523
- * rotation hook excludes the just-rejected key so a probe that clears its
524
- * window cannot re-offer the same key within the same request (the adapter
525
- * refuses already-tried keys; the next request picks the revived key up).
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
- exclude?: string;
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` (429) marks the key
536
- * exhausted with an unknown reset (probed lazily at the next resolution
537
- * once every account is marked); `invalid-credential` (401) disables the
538
- * key until the stored credential changes.
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 `meituan/LongCat-2.0:free`
619
- * (reasoning:!0, no efforts). command-code@1.45.0 gave the Muse Spark family
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
- * `meituan/LongCat-2.0:free` (Go, free promo); command-code@1.43.0 added
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