@mars-sea/dsh-commandcode-provider 0.11.8 → 0.11.9

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.js CHANGED
@@ -1,4 +1,4 @@
1
- import { homedir } from "node:os";
1
+ import { homedir, tmpdir } from "node:os";
2
2
  import { dirname, join } from "node:path";
3
3
  import z from "@deepseek-ai/schemastery";
4
4
  import { MAX_TIMER_DELAY_MS } from "@deepseek-ai/dsh-timeout";
@@ -6,7 +6,7 @@ import * as dshLlm from "@deepseek-ai/dsh-llm";
6
6
  import { CONTEXT_WINDOW_EXCEEDED_CODE, LlmAdapter, LlmError, ReasoningEffortId, ToolCallId, assertUsableApiKey, attributionHeaders, errorChain, isContextWindowExceededError, offloadedImageText, resolveRetryPolicy, textOnlyImageText } from "@deepseek-ai/dsh-llm";
7
7
  import { credentialRef } from "@deepseek-ai/dsh-credentials";
8
8
  import { launchEnvironmentOf } from "@deepseek-ai/dsh-launch-environment";
9
- import { existsSync, readFileSync } from "node:fs";
9
+ import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
10
10
  import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
11
11
  import { randomBytes, randomUUID } from "node:crypto";
12
12
  import { TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
@@ -2652,6 +2652,181 @@ function scaleBy(size, factor) {
2652
2652
  };
2653
2653
  }
2654
2654
  //#endregion
2655
+ //#region src/stream-trace.ts
2656
+ /**
2657
+ * Opt-in raw-stream trace for the Command Code transports.
2658
+ *
2659
+ * Why this exists: this route's failure mode of record is a response that ENDS
2660
+ * EARLY. The gateway, an intermediary proxy, or the network closes the socket
2661
+ * mid-generation, so the adapter's read loop sees `done` without ever seeing
2662
+ * the transport's terminal event. From inside the adapter those two endings are
2663
+ * indistinguishable — the provider cut the stream, a proxy timed out, or the
2664
+ * socket was dropped — and the only artifact that separates them is the raw
2665
+ * wire: which events actually arrived, when the last one landed, how long the
2666
+ * socket then sat silent, and how it ended. Without that, a truncated answer is
2667
+ * a report with no evidence attached.
2668
+ *
2669
+ * Enable it per process:
2670
+ *
2671
+ * ```sh
2672
+ * DSH_COMMANDCODE_TRACE=/tmp/cc-stream.jsonl dsh web
2673
+ * DSH_COMMANDCODE_TRACE=1 dsh web # -> $TMPDIR/dsh-commandcode-stream.jsonl
2674
+ * ```
2675
+ *
2676
+ * Any value that is not a flag is a path, and the explicit off-spellings
2677
+ * (`0`, `false`, `no`, `off`) are recognized as "off" rather than being taken
2678
+ * for a file name.
2679
+ *
2680
+ * Every model request appends one JSONL record per raw chunk plus lifecycle
2681
+ * records (`response`, `eof`, `idle-timeout`, `read-error`, `end`,
2682
+ * `stream-close`), each stamped with `at` — milliseconds since that request's
2683
+ * first byte. The chunk records carry the provider's own payload verbatim
2684
+ * (bounded per chunk), because a truncated answer is exactly what is being
2685
+ * diagnosed: treat the file as conversation content, not as a log safe to
2686
+ * share. It never carries the request body, an API key, or any request header.
2687
+ *
2688
+ * Deliberate trade-offs:
2689
+ * - OFF costs one `process.env` read per request.
2690
+ * - ON appends synchronously, one file open per chunk. The trace must survive
2691
+ * a host killed mid-diagnosis, and the microseconds it costs are irrelevant
2692
+ * beside the seconds-scale silences it is there to measure.
2693
+ * - A trace that cannot be written turns itself off instead of failing the
2694
+ * request it is observing: a diagnostic must never become the outage.
2695
+ *
2696
+ * @module
2697
+ */
2698
+ /** Environment variable that turns the trace on. */
2699
+ const STREAM_TRACE_ENV = "DSH_COMMANDCODE_TRACE";
2700
+ /** File used when the variable is set to a bare flag instead of a path. */
2701
+ const STREAM_TRACE_DEFAULT_FILE = "dsh-commandcode-stream.jsonl";
2702
+ /** One raw chunk is recorded verbatim up to this many characters. */
2703
+ const STREAM_TRACE_MAX_TEXT = 16384;
2704
+ /** Values of {@link STREAM_TRACE_ENV} that mean "trace to the default file". */
2705
+ const FLAG_VALUES = /* @__PURE__ */ new Set([
2706
+ "1",
2707
+ "true",
2708
+ "yes",
2709
+ "on"
2710
+ ]);
2711
+ /**
2712
+ * Values that explicitly mean OFF.
2713
+ *
2714
+ * A user turning the trace off writes `false` as readily as they write `1` to
2715
+ * turn it on, and without this set every one of these spellings fell through to
2716
+ * "…so it must be a path" — the trace switched ON and wrote the conversation to
2717
+ * a file NAMED `false` (or `0`, or `off`) in the process's working directory.
2718
+ */
2719
+ const OFF_VALUES = /* @__PURE__ */ new Set([
2720
+ "0",
2721
+ "false",
2722
+ "no",
2723
+ "off"
2724
+ ]);
2725
+ /** The inert trace used whenever the switch is off. */
2726
+ const OFF = {
2727
+ enabled: false,
2728
+ record: () => void 0,
2729
+ close: () => void 0
2730
+ };
2731
+ /**
2732
+ * Resolve the trace file for this process.
2733
+ *
2734
+ * @param raw - the variable's raw value; defaults to the live environment.
2735
+ * @returns the file to append to, or undefined when the trace is off.
2736
+ */
2737
+ function resolveStreamTracePath(raw = process.env[STREAM_TRACE_ENV]) {
2738
+ const value = raw?.trim();
2739
+ if (value === void 0 || value === "") return void 0;
2740
+ const lower = value.toLowerCase();
2741
+ if (OFF_VALUES.has(lower)) return void 0;
2742
+ if (FLAG_VALUES.has(lower)) return join(tmpdir(), STREAM_TRACE_DEFAULT_FILE);
2743
+ return value;
2744
+ }
2745
+ /**
2746
+ * Clamp one recorded payload so a single chunk cannot consume the whole cap.
2747
+ *
2748
+ * @param text - the text to record.
2749
+ * @param max - characters to keep, defaulting to {@link STREAM_TRACE_MAX_TEXT}.
2750
+ * @returns `text` unchanged, or its head plus a marker naming what was dropped.
2751
+ */
2752
+ function boundTraceText(text, max = STREAM_TRACE_MAX_TEXT) {
2753
+ if (text.length <= max) return text;
2754
+ return `${text.slice(0, max)}…[+${text.length - max} chars]`;
2755
+ }
2756
+ /**
2757
+ * Open one request's trace.
2758
+ *
2759
+ * @param scope - what the records describe (the `provider/model` route).
2760
+ * @param options - `path` overrides the environment (tests pass a temp file);
2761
+ * `now` overrides the clock the `at` stamps are measured with.
2762
+ * @returns a writer that never throws and never fails the request.
2763
+ */
2764
+ function openStreamTrace(scope, options = {}) {
2765
+ const path = options.path ?? resolveStreamTracePath();
2766
+ if (path === void 0) return OFF;
2767
+ const now = options.now ?? Date.now;
2768
+ const startedAt = now();
2769
+ let written = 0;
2770
+ let stopped = false;
2771
+ let capped = false;
2772
+ const terminals = /* @__PURE__ */ new Set();
2773
+ try {
2774
+ mkdirSync(dirname(path), { recursive: true });
2775
+ } catch {
2776
+ return OFF;
2777
+ }
2778
+ const write = (record) => {
2779
+ if (stopped) return;
2780
+ const terminal = record.event === "end" || record.event === "stream-close";
2781
+ if (capped && !terminal) return;
2782
+ if (terminal && terminals.has(String(record.event))) return;
2783
+ let line;
2784
+ try {
2785
+ line = `${JSON.stringify({
2786
+ at: now() - startedAt,
2787
+ scope,
2788
+ ...record
2789
+ })}\n`;
2790
+ } catch {
2791
+ return;
2792
+ }
2793
+ if (terminal) {
2794
+ if (Buffer.byteLength(line) > 16384) return;
2795
+ terminals.add(String(record.event));
2796
+ } else if (written + Buffer.byteLength(line) > 4194304) {
2797
+ capped = true;
2798
+ line = `${JSON.stringify({
2799
+ at: now() - startedAt,
2800
+ scope,
2801
+ event: "trace-capped",
2802
+ bytes: written
2803
+ })}\n`;
2804
+ }
2805
+ try {
2806
+ appendFileSync(path, line);
2807
+ written += Buffer.byteLength(line);
2808
+ } catch {
2809
+ stopped = true;
2810
+ }
2811
+ };
2812
+ write({ event: "stream-open" });
2813
+ return {
2814
+ enabled: true,
2815
+ record(event, fields) {
2816
+ write(fields === void 0 ? { event } : {
2817
+ event,
2818
+ ...fields
2819
+ });
2820
+ },
2821
+ close() {
2822
+ write({
2823
+ event: "stream-close",
2824
+ bytes: written
2825
+ });
2826
+ }
2827
+ };
2828
+ }
2829
+ //#endregion
2655
2830
  //#region src/adapter.ts
2656
2831
  /**
2657
2832
  * DeepSeek Harness LLM adapter for the Command Code Provider API.
@@ -2767,15 +2942,21 @@ function periodEndValue(value) {
2767
2942
  * Terminal stream-error markers from the official CLI (`Xw` in command-code's
2768
2943
  * cli.mjs): these always mean "retrying cannot succeed", so the adapter must
2769
2944
  * not classify them as transient server errors.
2945
+ *
2946
+ * Written with spaces rather than the CLI's underscores, because
2947
+ * {@link hasTerminalStreamMarker} normalizes the separator before matching:
2948
+ * a JSON body usually carries `premium credits exhausted` while the CLI's own
2949
+ * list is written `premium_credits_exhausted`, and both are the same refusal
2950
+ * (the pre-stream classifier normalizes identically).
2770
2951
  */
2771
2952
  const TERMINAL_STREAM_ERROR_MARKERS = [
2772
- "premium_credits_exhausted",
2773
- "model_not_in_plan",
2953
+ "premium credits exhausted",
2954
+ "model not in plan",
2774
2955
  "insufficient credits"
2775
2956
  ];
2776
2957
  function hasTerminalStreamMarker(message) {
2777
- const lower = message.toLowerCase();
2778
- return TERMINAL_STREAM_ERROR_MARKERS.some((marker) => lower.includes(marker));
2958
+ const normalized = message.toLowerCase().replaceAll("_", " ");
2959
+ return TERMINAL_STREAM_ERROR_MARKERS.some((marker) => normalized.includes(marker));
2779
2960
  }
2780
2961
  /**
2781
2962
  * Context-window wording the provider uses to reject a request that outgrew
@@ -2814,7 +2995,10 @@ function isContextOverflowDetail(detail) {
2814
2995
  * (issue #39).
2815
2996
  * - Credits/plan wording is terminal, so an exhausted balance or a model
2816
2997
  * outside the plan is not retried as if it were a transient rate limit.
2817
- * - Only then does the status/`isRetryable` pair decide, as before.
2998
+ * - Only then does the status/`isRetryable` pair decide — and an explicit
2999
+ * refusal (`isRetryable: false`) or a terminal marker OUTRANKS a status, so a
3000
+ * 5xx carrying "insufficient credits" stays terminal instead of entering the
3001
+ * retry cadence as `SERVER`.
2818
3002
  */
2819
3003
  function streamErrorToLlmError(value, fallbackMessage) {
2820
3004
  const record = isRecord(value) ? value : void 0;
@@ -2829,7 +3013,7 @@ function streamErrorToLlmError(value, fallbackMessage) {
2829
3013
  const statusOption = statusCode !== void 0 ? { status: statusCode } : void 0;
2830
3014
  if (isContextOverflowDetail(detail)) return new LlmError(`Command Code stream error: ${message};Command Code API 拒绝了这次请求:内容超出模型上下文窗口。正在压缩上下文后重试;如仍失败,请新建会话或减少上下文`, CONTEXT_WINDOW_EXCEEDED_CODE, statusOption);
2831
3015
  const terminal = hasTerminalStreamMarker(detail);
2832
- if (!(isRetryable === true || (statusCode !== void 0 ? statusCode !== void 0 && (statusCode === 429 || statusCode >= 500) : isRetryable !== false && !terminal))) return new LlmError(`Command Code stream error: ${message}`, "PROVIDER_STREAM_ERROR", statusOption);
3016
+ if (!(isRetryable === true || (isRetryable === false || terminal ? false : statusCode !== void 0 ? statusCode !== void 0 && (statusCode === 429 || statusCode >= 500) : true))) return new LlmError(`Command Code stream error: ${message}`, "PROVIDER_STREAM_ERROR", statusOption);
2833
3017
  return new LlmError(`Command Code stream error: ${message}`, "SERVER", statusOption);
2834
3018
  }
2835
3019
  function recordOrEmpty(value) {
@@ -3033,42 +3217,32 @@ async function writeModelsCache(cachePath, models) {
3033
3217
  }
3034
3218
  }
3035
3219
  /**
3036
- * Whether an inbound message is a tool RESULT — a `user`-role message the
3037
- * producer tagged with `source: { kind: 'tool' }`.
3038
- *
3039
- * The tag is read defensively because it is PRODUCER-supplied and nothing
3040
- * validates it: dsh-llm's `Message` type declares `source` required, but the
3041
- * runtime never checks it and every adapter shipped before this one ignored the
3042
- * field outright (`dsh-llm-deepseek` never reads it). A third-party plugin that
3043
- * assembles its own history can therefore omit it and run correctly everywhere
3044
- * else — dsh-mneme's memory pipeline did — which made this adapter, the first
3045
- * to actually read the field, the first to crash on such a message with
3046
- * `TypeError: Cannot read properties of undefined (reading 'kind')` in the
3047
- * serialization phase, 0–8 ms in, before any request left the machine and with
3048
- * nothing in the error naming the message or the producer (issue #47).
3220
+ * >=0.1.7 puts toolCallId/isError on a role:'tool' message and carries raw
3221
+ * content blocks. Older engines wrap those fields in the first tool-result
3222
+ * block of a role:'user' message. Read structurally so the bundle still builds
3223
+ * and loads against older peers. Pairing and emission MUST use this same view:
3224
+ * otherwise a dropped result either drops its call too (making the model
3225
+ * forget completed work), or leaves an unanswered call on the wire.
3049
3226
  *
3050
- * Two rules keep an untagged message from costing the turn:
3051
- *
3052
- * - A PRESENT tag is authoritative. `kind: 'user'`, `'model'`, `'plugin'` or
3053
- * any producer-added kind means "not a tool result", whatever the content
3054
- * looks like — the tag answers *who produced this*.
3055
- * - An ABSENT tag falls back to the content shape. A `user` message whose
3056
- * first block is a `tool-result` is a tool result by the harness's own
3057
- * definition (`ToolResultMessage` is exactly that shape with a
3058
- * `ToolMessageSource`), and the fallback is not cosmetic: `pairedToolCalls()`
3059
- * counts a `tool-result` block from ANY message, so classifying such a
3060
- * message as a plain user message would drop it at emission while its call
3061
- * still counted as paired — leaving the assistant's tool call unanswered on
3062
- * the wire, which the gateway rejects outright. FIRST block, not "any block":
3063
- * the tool-result branch reads `content[0]` and skips anything else, so
3064
- * claiming a mixed message as a tool result would discard its other blocks
3065
- * instead of sending them.
3066
- */
3067
- function isToolResultMessage(message) {
3068
- if (message.role !== "user") return false;
3227
+ * For the legacy envelope a present source tag remains authoritative; an
3228
+ * absent tag falls back to the first block (issue #47). The modern role is
3229
+ * itself the discriminator, as it is for the Harness's own serializers.
3230
+ */
3231
+ function toolResultOf(message) {
3232
+ const current = message;
3233
+ if (current.role === "tool") {
3234
+ if (typeof current.toolCallId !== "string" || current.toolCallId === "") return void 0;
3235
+ return {
3236
+ toolCallId: current.toolCallId,
3237
+ isError: current.isError ?? false,
3238
+ content: current.content
3239
+ };
3240
+ }
3241
+ if (message.role !== "user") return void 0;
3069
3242
  const kind = message.source?.kind;
3070
- if (kind !== void 0) return kind === "tool";
3071
- return message.content?.[0]?.type === "tool-result";
3243
+ if (kind !== void 0 && kind !== "tool") return void 0;
3244
+ const block = message.content[0];
3245
+ return block?.type === "tool-result" ? block : void 0;
3072
3246
  }
3073
3247
  /**
3074
3248
  * Collect the tool calls that have a paired tool result, plus each call's
@@ -3081,12 +3255,13 @@ function pairedToolCalls(messages) {
3081
3255
  const callIds = /* @__PURE__ */ new Set();
3082
3256
  const names = /* @__PURE__ */ new Map();
3083
3257
  const resultIds = /* @__PURE__ */ new Set();
3084
- for (const message of messages) for (const block of message.content) {
3085
- if (message.role === "assistant" && block.type === "tool-call") {
3258
+ for (const message of messages) {
3259
+ const result = toolResultOf(message);
3260
+ if (result) resultIds.add(result.toolCallId);
3261
+ for (const block of message.content) if (message.role === "assistant" && block.type === "tool-call") {
3086
3262
  callIds.add(block.id);
3087
3263
  names.set(block.id, block.name);
3088
3264
  }
3089
- if (block.type === "tool-result") resultIds.add(block.toolCallId);
3090
3265
  }
3091
3266
  return {
3092
3267
  ids: new Set([...callIds].filter((id) => resultIds.has(id))),
@@ -3394,7 +3569,8 @@ async function messagesToCC(messages, readImage) {
3394
3569
  };
3395
3570
  for (const message of messages) {
3396
3571
  if (message.role === "system") continue;
3397
- if (message.role === "user" && !isToolResultMessage(message)) {
3572
+ const result = toolResultOf(message);
3573
+ if (message.role === "user" && !result) {
3398
3574
  flushPendingImages();
3399
3575
  const parts = [];
3400
3576
  for (const block of message.content) {
@@ -3437,18 +3613,17 @@ async function messagesToCC(messages, readImage) {
3437
3613
  });
3438
3614
  continue;
3439
3615
  }
3440
- if (isToolResultMessage(message)) {
3441
- const block = message.content[0];
3442
- if (!block || block.type !== "tool-result" || !paired.has(block.toolCallId)) continue;
3443
- const media = toolResultMedia(block);
3444
- const wireToolCallId = wireIds.get(block.toolCallId) ?? block.toolCallId;
3616
+ if (result) {
3617
+ if (!paired.has(result.toolCallId)) continue;
3618
+ const media = toolResultMedia(result);
3619
+ const wireToolCallId = wireIds.get(result.toolCallId) ?? result.toolCallId;
3445
3620
  out.push({
3446
3621
  role: "tool",
3447
3622
  content: [{
3448
3623
  type: "tool-result",
3449
3624
  toolCallId: wireToolCallId,
3450
- toolName: toolNames.get(block.toolCallId) || "unknown",
3451
- output: block.isError ? {
3625
+ toolName: toolNames.get(result.toolCallId) || "unknown",
3626
+ output: result.isError ? {
3452
3627
  type: "error-text",
3453
3628
  value: toolResultTextForWire(media)
3454
3629
  } : {
@@ -3506,7 +3681,8 @@ async function messagesToOpenAI(messages, readImage) {
3506
3681
  };
3507
3682
  for (const message of messages) {
3508
3683
  if (message.role === "system") continue;
3509
- if (message.role === "user" && !isToolResultMessage(message)) {
3684
+ const result = toolResultOf(message);
3685
+ if (message.role === "user" && !result) {
3510
3686
  flushPendingImages();
3511
3687
  const parts = [];
3512
3688
  for (const block of message.content) {
@@ -3552,11 +3728,10 @@ async function messagesToOpenAI(messages, readImage) {
3552
3728
  out.push(assistant);
3553
3729
  continue;
3554
3730
  }
3555
- if (isToolResultMessage(message)) {
3556
- const block = message.content[0];
3557
- if (!block || block.type !== "tool-result" || !paired.has(block.toolCallId)) continue;
3558
- const media = toolResultMedia(block);
3559
- const wireToolCallId = wireIds.get(block.toolCallId) ?? block.toolCallId;
3731
+ if (result) {
3732
+ if (!paired.has(result.toolCallId)) continue;
3733
+ const media = toolResultMedia(result);
3734
+ const wireToolCallId = wireIds.get(result.toolCallId) ?? result.toolCallId;
3560
3735
  out.push({
3561
3736
  role: "tool",
3562
3737
  tool_call_id: wireToolCallId,
@@ -3817,6 +3992,7 @@ function createBlockAssembler() {
3817
3992
  reasoningIndex: -1,
3818
3993
  reasoningContent: "",
3819
3994
  sawContent: false,
3995
+ finishReason: void 0,
3820
3996
  openAiToolCalls: []
3821
3997
  };
3822
3998
  }
@@ -4036,8 +4212,10 @@ var CommandCodeAdapter = class extends LlmAdapter {
4036
4212
  /** The headers every authenticated account endpoint shares. */
4037
4213
  async accountHeaders(apiKey) {
4038
4214
  const connection = this.deps.options();
4215
+ const raw = apiKey ?? await this.deps.resolveApiKey(connection);
4216
+ const named = connection.apiKeyEnv;
4039
4217
  return {
4040
- Authorization: `Bearer ${apiKey ?? await this.deps.resolveApiKey(connection)}`,
4218
+ Authorization: `Bearer ${assertUsableApiKey(raw, "llm-commandcode", typeof named === "string" && named !== "" ? named : "the stored credential")}`,
4041
4219
  "x-command-code-version": COMMAND_CODE_CLI_VERSION,
4042
4220
  "x-cli-environment": "production",
4043
4221
  ...attributionHeaders()
@@ -4049,11 +4227,16 @@ var CommandCodeAdapter = class extends LlmAdapter {
4049
4227
  * failure accounting: the billing probe fails open silently, the usage
4050
4228
  * report books failures per endpoint. Non-2xx and non-record bodies come
4051
4229
  * back without a record; only a transport throw propagates to the caller.
4230
+ *
4231
+ * `timeoutMs` is the caller's budget for this one request. The default is the
4232
+ * CATALOG probe's short cap, which fits the picker's fail-open reads; the
4233
+ * usage report deliberately passes the connection's own request budget so an
4234
+ * account query is not held to a stricter (and invisible) limit than chat.
4052
4235
  */
4053
- async fetchEndpointJson(url, headers) {
4236
+ async fetchEndpointJson(url, headers, timeoutMs = MODELS_TIMEOUT_MS) {
4054
4237
  const response = await this.fetchImpl(url, {
4055
4238
  headers,
4056
- signal: AbortSignal.timeout(MODELS_TIMEOUT_MS)
4239
+ signal: AbortSignal.timeout(timeoutMs)
4057
4240
  });
4058
4241
  if (!response.ok) return { status: response.status };
4059
4242
  const parsed = await response.json();
@@ -4211,15 +4394,39 @@ var CommandCodeAdapter = class extends LlmAdapter {
4211
4394
  * view. Requires a usable API key (throws `MISSING_CREDENTIAL` otherwise).
4212
4395
  * Pass `apiKey` to report on a specific account of a multi-account pool;
4213
4396
  * the default resolves the currently active account.
4397
+ *
4398
+ * Two failure modes are NOT "the network is down", and the report must say
4399
+ * so rather than let {@link classifyTotalFailure} blame the connection:
4400
+ *
4401
+ * - A key that no HTTP header can carry (a stray newline from a paste, a
4402
+ * full-width character) makes `fetch` throw a `TypeError` **before any
4403
+ * I/O**, for every endpoint at once. The chat path refuses such a key
4404
+ * through `assertUsableApiKey`; this path must too, and it answers
4405
+ * `blocked: 'invalid-key'` instead of four phantom transport failures.
4406
+ * - The four endpoints get the SAME per-request budget as a chat call
4407
+ * (`connection.requestTimeoutMs`, default 60 s), not the catalog's 10 s
4408
+ * probe budget: on a slow link a 10 s cap made every account query time
4409
+ * out while chat kept working, which is exactly a "check your network"
4410
+ * banner that never clears.
4214
4411
  */
4215
4412
  async getUsage(apiKey) {
4216
- const base = this.deps.options().apiBase;
4217
- const headers = await this.accountHeaders(apiKey);
4413
+ const connection = this.deps.options();
4414
+ const base = connection.apiBase;
4415
+ let headers;
4416
+ try {
4417
+ headers = await this.accountHeaders(apiKey);
4418
+ } catch (error) {
4419
+ if (error instanceof LlmError && error.code === "INVALID_CREDENTIAL") return {
4420
+ failures: [error.message],
4421
+ blocked: "invalid-key"
4422
+ };
4423
+ throw error;
4424
+ }
4218
4425
  const failures = [];
4219
4426
  const failedStatuses = [];
4220
4427
  const getJson = async (path) => {
4221
4428
  try {
4222
- const { status, record } = await this.fetchEndpointJson(`${base}${path}`, headers);
4429
+ const { status, record } = await this.fetchEndpointJson(`${base}${path}`, headers, connection.requestTimeoutMs);
4223
4430
  if (record === void 0) {
4224
4431
  failures.push(`${path}: HTTP ${status}`);
4225
4432
  failedStatuses.push(status);
@@ -4305,7 +4512,7 @@ var CommandCodeAdapter = class extends LlmAdapter {
4305
4512
  reported = true;
4306
4513
  if (!window.exceeded) continue;
4307
4514
  exceeded = true;
4308
- resetAt = Math.max(resetAt, window.resetAt);
4515
+ if (window.resetAt > 0 && window.resetAt - Date.now() <= MAX_TRUSTED_RESET_MS) resetAt = Math.max(resetAt, window.resetAt);
4309
4516
  }
4310
4517
  if (!reported) return void 0;
4311
4518
  return {
@@ -4413,6 +4620,19 @@ var CommandCodeAdapter = class extends LlmAdapter {
4413
4620
  const reader = response.body.getReader();
4414
4621
  const decoder = new TextDecoder();
4415
4622
  let buffer = "";
4623
+ const trace = openStreamTrace(`${options.provider}/${options.model}`);
4624
+ const streamStartedAt = Date.now();
4625
+ let chunkCount = 0;
4626
+ let totalBytes = 0;
4627
+ let lastChunkAt = streamStartedAt;
4628
+ trace.record("response", {
4629
+ protocol,
4630
+ endpoint: protocol === "cli" ? `${connection.apiBase}/alpha/generate` : `${connection.apiBase}/provider/v1/chat/completions`,
4631
+ status: response.status,
4632
+ attempts: tried.size,
4633
+ maxTokens,
4634
+ ...reasoningEffort === void 0 ? {} : { reasoningEffort }
4635
+ });
4416
4636
  let idleTimer;
4417
4637
  let idleFired = false;
4418
4638
  const armIdle = () => {
@@ -4429,9 +4649,16 @@ var CommandCodeAdapter = class extends LlmAdapter {
4429
4649
  }
4430
4650
  };
4431
4651
  const asm = createBlockAssembler();
4432
- const handle = (event) => handleEvent(asm, protocol, event);
4652
+ let finish;
4653
+ let usage;
4654
+ let usageEmitted = false;
4655
+ let endRecorded = false;
4656
+ function* handle(event) {
4657
+ for (const chunk of handleEvent(asm, protocol, event)) if (chunk.type === "finish") finish = chunk;
4658
+ else if (chunk.type === "usage") usage = chunk.usage;
4659
+ else yield chunk;
4660
+ }
4433
4661
  try {
4434
- let finished = false;
4435
4662
  for (;;) {
4436
4663
  let read;
4437
4664
  armIdle();
@@ -4439,44 +4666,104 @@ var CommandCodeAdapter = class extends LlmAdapter {
4439
4666
  read = await reader.read();
4440
4667
  } catch (error) {
4441
4668
  if (options.signal?.aborted) throw error;
4669
+ trace.record("read-error", {
4670
+ chunks: chunkCount,
4671
+ bytes: totalBytes,
4672
+ silentMs: Date.now() - lastChunkAt,
4673
+ message: errorChain(error)
4674
+ });
4442
4675
  throw new LlmError(`Command Code API stream from ${connection.apiBase} failed while reading: ${errorChain(error)};Command Code API 流式响应中途断开——网络波动所致,重试通常可恢复`, "TRANSPORT", { cause: error });
4443
4676
  } finally {
4444
4677
  clearIdle();
4445
4678
  }
4446
4679
  const { done, value } = read;
4447
4680
  if (done) {
4681
+ trace.record("eof", {
4682
+ chunks: chunkCount,
4683
+ bytes: totalBytes,
4684
+ silentMs: Date.now() - lastChunkAt,
4685
+ totalMs: Date.now() - streamStartedAt,
4686
+ finishSeen: finish !== void 0,
4687
+ idleFired,
4688
+ buffered: buffer.length
4689
+ });
4448
4690
  if (idleFired) throw new LlmError(`Command Code API stream from ${connection.apiBase} was idle for ${connection.streamIdleTimeoutMs}ms (no events) and was treated as a dead connection;Command Code API 流式响应已 ${connection.streamIdleTimeoutMs} 毫秒无任何事件,被判定为死连接——长思考模型可在设置中调大流空闲超时`, "TIMEOUT");
4449
- if (buffer.trim()) for (const chunk of handle(parseStreamEventLine(buffer))) {
4450
- yield chunk;
4451
- if (chunk.type === "finish") finished = true;
4452
- }
4691
+ if (buffer.trim()) yield* handle(parseStreamEventLine(buffer));
4453
4692
  break;
4454
4693
  }
4455
- buffer += decoder.decode(value, { stream: true });
4694
+ const text = decoder.decode(value, { stream: true });
4695
+ buffer += text;
4696
+ chunkCount += 1;
4697
+ totalBytes += value.byteLength;
4698
+ lastChunkAt = Date.now();
4699
+ trace.record("chunk", {
4700
+ n: chunkCount,
4701
+ bytes: value.byteLength,
4702
+ text: boundTraceText(text)
4703
+ });
4456
4704
  const lines = buffer.split("\n");
4457
4705
  buffer = lines.pop() ?? "";
4458
- for (const line of lines) {
4459
- const chunks = handle(parseStreamEventLine(line));
4460
- for (const chunk of chunks) {
4461
- yield chunk;
4462
- if (chunk.type === "finish") finished = true;
4463
- }
4464
- }
4465
- if (finished) break;
4706
+ for (const line of lines) yield* handle(parseStreamEventLine(line));
4707
+ if (finish !== void 0) break;
4466
4708
  }
4467
- if (!finished) {
4468
- yield* closeText(asm);
4469
- yield* closeReasoning(asm);
4470
- if (protocol === "openai") yield* emitOpenAiToolCalls(asm);
4471
- if (!asm.sawContent) throw new LlmError("Command Code returned an empty response;Command Code 返回了空响应,重试通常可恢复", "EMPTY_RESPONSE");
4709
+ if (usage !== void 0) {
4710
+ usageEmitted = true;
4472
4711
  yield {
4473
- type: "finish",
4474
- reason: { kind: "stop" }
4712
+ type: "usage",
4713
+ usage
4475
4714
  };
4476
4715
  }
4716
+ if (finish !== void 0) {
4717
+ const failure = asm.sawContent ? void 0 : emptyCompletionError(asm.finishReason, maxTokens, usage);
4718
+ trace.record("end", {
4719
+ outcome: failure === void 0 ? "finished" : failure.code === "OUTPUT_TOKEN_LIMIT" ? "output-token-limit" : "empty",
4720
+ finishReason: asm.finishReason,
4721
+ ...failure === void 0 ? {} : { code: failure.code },
4722
+ maxTokens,
4723
+ ...usage === void 0 ? {} : { usage },
4724
+ chunks: chunkCount,
4725
+ bytes: totalBytes,
4726
+ totalMs: Date.now() - streamStartedAt,
4727
+ sawContent: asm.sawContent
4728
+ });
4729
+ endRecorded = true;
4730
+ if (failure !== void 0) throw failure;
4731
+ yield finish;
4732
+ } else {
4733
+ const elapsed = Date.now() - streamStartedAt;
4734
+ const detail = `${chunkCount} chunk(s), ${totalBytes} bytes, ${elapsed}ms`;
4735
+ trace.record("end", {
4736
+ outcome: asm.sawContent ? "stream-closed" : "empty",
4737
+ chunks: chunkCount,
4738
+ bytes: totalBytes,
4739
+ totalMs: elapsed,
4740
+ sawContent: asm.sawContent
4741
+ });
4742
+ endRecorded = true;
4743
+ if (!asm.sawContent) throw new LlmError("Command Code returned an empty response;Command Code 返回了空响应,重试通常可恢复", "EMPTY_RESPONSE");
4744
+ throw new LlmError(`Command Code API stream from ${connection.apiBase} ended before its finish event (${detail}) — the response was closed mid-generation, so this is not the model stopping by itself;Command Code API 流式响应在结束事件之前被关闭(生成中途断流),并非模型主动结束——请重试;若频繁出现,可用 ${STREAM_TRACE_ENV}=<文件路径> 抓取原始流以定位断流原因`, "STREAM_CLOSED");
4745
+ }
4746
+ } catch (error) {
4747
+ if (!usageEmitted && usage !== void 0) yield {
4748
+ type: "usage",
4749
+ usage
4750
+ };
4751
+ if (!endRecorded) trace.record("end", {
4752
+ outcome: options.signal?.aborted ? "aborted" : "error",
4753
+ code: error instanceof LlmError ? error.code : "unknown",
4754
+ finishReason: asm.finishReason,
4755
+ maxTokens,
4756
+ ...usage === void 0 ? {} : { usage },
4757
+ sawContent: asm.sawContent,
4758
+ chunks: chunkCount,
4759
+ bytes: totalBytes,
4760
+ totalMs: Date.now() - streamStartedAt
4761
+ });
4762
+ throw error;
4477
4763
  } finally {
4478
4764
  clearIdle();
4479
4765
  cleanup();
4766
+ trace.close();
4480
4767
  await reader.cancel().catch(() => void 0);
4481
4768
  reader.releaseLock();
4482
4769
  }
@@ -4567,7 +4854,7 @@ function handleCliEvent(asm, event) {
4567
4854
  }
4568
4855
  const delta = stringValue(event.text) ?? "";
4569
4856
  asm.textContent += delta;
4570
- asm.sawContent = true;
4857
+ if (delta.trim() !== "") asm.sawContent = true;
4571
4858
  chunks.push({
4572
4859
  type: "text-delta",
4573
4860
  index: asm.textIndex,
@@ -4630,6 +4917,7 @@ function handleCliEvent(asm, event) {
4630
4917
  break;
4631
4918
  }
4632
4919
  case "finish": {
4920
+ asm.finishReason = stringValue(event.finishReason);
4633
4921
  chunks.push(...closeText(asm), ...closeReasoning(asm));
4634
4922
  const usage = isRecord(event.totalUsage) ? event.totalUsage : void 0;
4635
4923
  if (usage) {
@@ -4643,6 +4931,8 @@ function handleCliEvent(asm, event) {
4643
4931
  cacheReadTokens: cacheRead,
4644
4932
  cacheWriteTokens: cacheWrite
4645
4933
  };
4934
+ const reasoningTokens = numberValue((isRecord(usage.outputTokenDetails) ? usage.outputTokenDetails : void 0)?.reasoningTokens) ?? numberValue(usage.reasoningTokens);
4935
+ if (reasoningTokens !== void 0) tokenUsage.reasoningTokens = reasoningTokens;
4646
4936
  chunks.push({
4647
4937
  type: "usage",
4648
4938
  usage: tokenUsage
@@ -4707,39 +4997,38 @@ function handleOpenAIEvent(asm, event) {
4707
4997
  });
4708
4998
  }
4709
4999
  asm.textContent += contentDelta;
4710
- asm.sawContent = true;
5000
+ if (contentDelta.trim() !== "") asm.sawContent = true;
4711
5001
  chunks.push({
4712
5002
  type: "text-delta",
4713
5003
  index: asm.textIndex,
4714
5004
  text: contentDelta
4715
5005
  });
4716
5006
  }
4717
- if (Array.isArray(delta.tool_calls)) {
5007
+ if (Array.isArray(delta.tool_calls)) for (const rawCall of delta.tool_calls) {
5008
+ if (!isRecord(rawCall)) continue;
4718
5009
  asm.sawContent = true;
4719
- for (const rawCall of delta.tool_calls) {
4720
- if (!isRecord(rawCall)) continue;
4721
- const callIndex = numberValue(rawCall.index) ?? 0;
4722
- let existing = asm.openAiToolCalls.find((call) => call.index === callIndex);
4723
- const fn = isRecord(rawCall.function) ? rawCall.function : void 0;
4724
- const id = stringValue(rawCall.id);
4725
- const name = fn === void 0 ? void 0 : stringValue(fn.name);
4726
- const argDelta = fn === void 0 ? void 0 : stringValue(fn.arguments) ?? (fn.arguments === void 0 ? "" : JSON.stringify(fn.arguments));
4727
- if (!existing) {
4728
- existing = {
4729
- index: callIndex,
4730
- name: name ?? "",
4731
- arguments: argDelta ?? ""
4732
- };
4733
- if (id !== void 0) existing.id = id;
4734
- asm.openAiToolCalls.push(existing);
4735
- } else {
4736
- if (id !== void 0 && existing.id === void 0) existing.id = id;
4737
- if (name !== void 0 && existing.name === "") existing.name = name;
4738
- if (argDelta !== void 0) existing.arguments += argDelta;
4739
- }
5010
+ const callIndex = numberValue(rawCall.index) ?? 0;
5011
+ let existing = asm.openAiToolCalls.find((call) => call.index === callIndex);
5012
+ const fn = isRecord(rawCall.function) ? rawCall.function : void 0;
5013
+ const id = stringValue(rawCall.id);
5014
+ const name = fn === void 0 ? void 0 : stringValue(fn.name);
5015
+ const argDelta = fn === void 0 ? void 0 : stringValue(fn.arguments) ?? (fn.arguments === void 0 ? "" : JSON.stringify(fn.arguments));
5016
+ if (!existing) {
5017
+ existing = {
5018
+ index: callIndex,
5019
+ name: name ?? "",
5020
+ arguments: argDelta ?? ""
5021
+ };
5022
+ if (id !== void 0) existing.id = id;
5023
+ asm.openAiToolCalls.push(existing);
5024
+ } else {
5025
+ if (id !== void 0 && existing.id === void 0) existing.id = id;
5026
+ if (name !== void 0 && existing.name === "") existing.name = name;
5027
+ if (argDelta !== void 0) existing.arguments += argDelta;
4740
5028
  }
4741
5029
  }
4742
5030
  if (choice.finish_reason !== void 0 && choice.finish_reason !== null) {
5031
+ asm.finishReason = stringValue(choice.finish_reason);
4743
5032
  chunks.push(...closeText(asm), ...closeReasoning(asm), ...emitOpenAiToolCalls(asm));
4744
5033
  if (event.usage !== void 0) chunks.push({
4745
5034
  type: "usage",
@@ -4787,6 +5076,13 @@ function mapFinishReason(reason) {
4787
5076
  if (reason === "length" || reason === "max_tokens" || reason === "max-tokens" || reason === "max_output_tokens") return { kind: "max-tokens" };
4788
5077
  return { kind: "stop" };
4789
5078
  }
5079
+ /** A terminal marker without text or tools is a failed attempt, not a completed turn. */
5080
+ function emptyCompletionError(reason, maxTokens, usage) {
5081
+ const detail = `finish_reason=${reason ?? "unknown"}, max_tokens=${maxTokens}, outputTokens=${usage?.outputTokens ?? "unknown"}, reasoningTokens=${usage?.reasoningTokens ?? "unknown"}`;
5082
+ if (mapFinishReason(reason).kind === "max-tokens") return new LlmError(`Command Code reached the output token limit without producing answer text or a tool call (${detail});Command Code 已达到输出 token 上限,但没有生成正文或工具调用;请调整思考强度,或在模型和接口上限内增加输出预算`, "OUTPUT_TOKEN_LIMIT");
5083
+ if (reason === "content_filter" || reason === "content-filter") return new LlmError(`Command Code filtered the response (${detail});Command Code 拦截了响应,未生成正文或工具调用`, "CONTENT_FILTER");
5084
+ return new LlmError(`Command Code finished without producing answer text or a tool call (${detail});Command Code 返回了空响应(可能只有思考内容),重试通常可恢复`, "EMPTY_RESPONSE");
5085
+ }
4790
5086
  /**
4791
5087
  * True when a Provider API pre-stream rejection is Command Code's Go-plan
4792
5088
  * gate (`upgrade_required`). Only this exact class of rejection should fall
@@ -4831,8 +5127,25 @@ function mapFinishReason(reason) {
4831
5127
  * or a model outside its plan (`MODEL_NOT_IN_PLAN`). Rotating must NOT mark
4832
5128
  * the account: the fact is about the model or the balance, it can change
4833
5129
  * without the key changing, and a `:free` model can still be served by an
4834
- * account with an empty balance.
5130
+ * account with an empty balance. These codes are recognized on EVERY
5131
+ * status — a 5xx that proxies the provider's own credits/plan body is
5132
+ * still an account fact, and only the prose scan is confined to 4xx.
4835
5133
  */
5134
+ /**
5135
+ * The provider's structured "this account cannot serve this request" codes.
5136
+ *
5137
+ * Status-independent on purpose, mirroring the CLI's classifier, which reads
5138
+ * the code before any status guard. A 5xx that carries one of these must not
5139
+ * be retried as a transient provider failure: the same request against the
5140
+ * same account cannot succeed, so the caller rotates past it (without marking
5141
+ * the key) instead of replaying it on the harness's retry cadence.
5142
+ */
5143
+ const ACCOUNT_UNAVAILABLE_CODES = /* @__PURE__ */ new Set([
5144
+ "USAGE_EXCEEDED",
5145
+ "INSUFFICIENT_CREDITS",
5146
+ "PREMIUM_CREDITS_EXHAUSTED",
5147
+ "MODEL_NOT_IN_PLAN"
5148
+ ]);
4836
5149
  function classifyAccountRejection(status, errText) {
4837
5150
  if (status === 429 || readProviderErrorCode(errText)?.toUpperCase() === "RATE_LIMITED") {
4838
5151
  const evidence = readWindowLimitEvidence(errText);
@@ -4843,10 +5156,11 @@ function classifyAccountRejection(status, errText) {
4843
5156
  };
4844
5157
  }
4845
5158
  if (status === 401) return { reason: "invalid-credential" };
5159
+ const code = readProviderErrorCode(errText)?.toUpperCase();
5160
+ if (code !== void 0 && ACCOUNT_UNAVAILABLE_CODES.has(code)) return { reason: "unavailable" };
4846
5161
  if (status < 400 || status >= 500) return void 0;
4847
5162
  const lower = errText.toLowerCase().replaceAll("_", " ");
4848
- const code = readProviderErrorCode(errText)?.toUpperCase();
4849
- if (code === "USAGE_EXCEEDED" || code === "INSUFFICIENT_CREDITS" || code === "PREMIUM_CREDITS_EXHAUSTED" || code === "MODEL_NOT_IN_PLAN" || lower.includes("insufficient credits") || lower.includes("premium credits exhausted") || lower.includes("model not in plan") || /insufficient (credit|balance)/.test(lower) || /out of credit/.test(lower) || /not (available|included)[^.]{0,40}plan/.test(lower) || lower.includes("upgrade your plan")) return { reason: "unavailable" };
5163
+ if (lower.includes("insufficient credits") || lower.includes("premium credits exhausted") || lower.includes("model not in plan") || /insufficient (credit|balance)/.test(lower) || /out of credit/.test(lower) || /not (available|included)[^.]{0,40}plan/.test(lower) || lower.includes("upgrade your plan")) return { reason: "unavailable" };
4850
5164
  }
4851
5165
  /** The machine-readable `error.code`/`error.type` of a pre-stream failure body. */
4852
5166
  function readProviderErrorCode(errText) {
@@ -4860,6 +5174,16 @@ function readProviderErrorCode(errText) {
4860
5174
  }
4861
5175
  }
4862
5176
  /**
5177
+ * How far ahead a provider-published reset may be and still be trusted.
5178
+ *
5179
+ * Generous on purpose: the weekly window is the longest this provider meters,
5180
+ * and the value is a plausibility bound rather than a policy. Beyond it the
5181
+ * reset is dropped rather than clamped, which leaves the account's mark
5182
+ * `unknown` — the state the pool's probe pass re-checks — instead of a
5183
+ * cooldown that never expires.
5184
+ */
5185
+ const MAX_TRUSTED_RESET_MS = 2592e6;
5186
+ /**
4863
5187
  * The usage-window evidence carried by a rejected request, mirroring the CLI's
4864
5188
  * `parseWindowLimitError` → `resolveWindowLabel`/`extractResetAtMs` pair.
4865
5189
  *
@@ -4893,7 +5217,8 @@ function readWindowLimitEvidence(errText) {
4893
5217
  const window = named === "fiveHour" || named === "weekly" || named === "daily" ? named : /usage limit for your plan/i.test(message) || /usage limit for your plan/i.test(errText) ? /weekly/i.test(message) ? "weekly" : "fiveHour" : void 0;
4894
5218
  const stamp = /resets at (\d{4}-\d{2}-\d{2}T[\d:.]+Z)/i.exec(message)?.[1] ?? /resets at (\d{4}-\d{2}-\d{2}T[\d:.]+Z)/i.exec(errText)?.[1];
4895
5219
  const stamped = stamp === void 0 ? void 0 : Date.parse(stamp);
4896
- const resetAtMs = seconds !== void 0 && seconds > 0 ? Math.round(seconds * 1e3) : stamped !== void 0 && !Number.isNaN(stamped) ? stamped : void 0;
5220
+ const published = seconds !== void 0 && seconds > 0 ? Math.round(seconds * 1e3) : stamped !== void 0 && !Number.isNaN(stamped) ? stamped : void 0;
5221
+ const resetAtMs = published !== void 0 && Number.isFinite(published) && published - Date.now() <= MAX_TRUSTED_RESET_MS ? published : void 0;
4897
5222
  return {
4898
5223
  ...window === void 0 ? {} : { window },
4899
5224
  ...resetAtMs === void 0 ? {} : { resetAtMs }
@@ -5693,7 +6018,10 @@ function isCallbackCredentials(value) {
5693
6018
  /**
5694
6019
  * One browser-login attempt machine. Single-flight by design: `begin()` while
5695
6020
  * waiting returns the live attempt's status instead of starting a second one;
5696
- * a terminal state makes the next `begin()` start fresh.
6021
+ * a start that is still binding its port is REJOINED through
6022
+ * {@link CommandCodeLoginFlow.begin}'s in-flight promise, because the status
6023
+ * face cannot say `waiting` until that bind settles; a terminal state makes the
6024
+ * next `begin()` start fresh.
5697
6025
  */
5698
6026
  var CommandCodeLoginFlow = class {
5699
6027
  deps;
@@ -5711,6 +6039,17 @@ var CommandCodeLoginFlow = class {
5711
6039
  * the user cancelled and flipping the page back to success.
5712
6040
  */
5713
6041
  attemptSeq = 0;
6042
+ /**
6043
+ * The start currently binding a port, if any. Every `begin()` that arrives
6044
+ * before it settles joins it: two independent starts would each bind their
6045
+ * own loopback server, and only the LAST one is reachable by
6046
+ * {@link CommandCodeLoginFlow.teardown} — the orphan keeps listening and
6047
+ * answering `/callback` for the process's lifetime, and ten of them exhaust
6048
+ * the port window so browser login dies until the Host restarts.
6049
+ */
6050
+ starting;
6051
+ /** A `cancel()` that arrived while a start was still binding (see {@link CommandCodeLoginFlow.cancel}). */
6052
+ cancelPending = false;
5714
6053
  disposed = false;
5715
6054
  constructor(deps) {
5716
6055
  this.deps = deps;
@@ -5732,6 +6071,22 @@ var CommandCodeLoginFlow = class {
5732
6071
  async begin() {
5733
6072
  if (this.disposed) throw new Error("login flow has been disposed");
5734
6073
  if (this.statusValue.state === "waiting" && this.server !== void 0) return this.statusValue;
6074
+ if (this.starting !== void 0) return this.starting;
6075
+ this.cancelPending = false;
6076
+ const starting = this.startAttempt();
6077
+ this.starting = starting;
6078
+ try {
6079
+ return await starting;
6080
+ } finally {
6081
+ if (this.starting === starting) this.starting = void 0;
6082
+ }
6083
+ }
6084
+ /**
6085
+ * The binding half of {@link CommandCodeLoginFlow.begin}: one attempt, one
6086
+ * server, one published `waiting` status. Callers reach it only through
6087
+ * `begin()`'s single-flight fence.
6088
+ */
6089
+ async startAttempt() {
5735
6090
  this.teardown();
5736
6091
  const attempt = ++this.attemptSeq;
5737
6092
  const port = await this.findPort();
@@ -5743,6 +6098,16 @@ var CommandCodeLoginFlow = class {
5743
6098
  };
5744
6099
  });
5745
6100
  await this.bindServer(port, expectedState);
6101
+ if (this.disposed || this.cancelPending) {
6102
+ const cancelled = !this.disposed;
6103
+ this.cancelPending = false;
6104
+ this.teardown();
6105
+ if (cancelled) this.setStatus({
6106
+ state: "failed",
6107
+ reason: "cancelled"
6108
+ });
6109
+ return this.statusValue;
6110
+ }
5746
6111
  const apiBase = this.readApiBase();
5747
6112
  this.setStatus({
5748
6113
  state: "waiting",
@@ -5764,19 +6129,29 @@ var CommandCodeLoginFlow = class {
5764
6129
  settled.then((credentials) => this.complete(attempt, credentials), (failure) => this.failFrom(attempt, failure));
5765
6130
  return this.statusValue;
5766
6131
  }
5767
- /** Cancel a waiting attempt; terminal states are untouched. */
6132
+ /**
6133
+ * Cancel a waiting attempt; terminal states are untouched. A start that is
6134
+ * still binding a port is flagged instead of ignored — it cannot be torn
6135
+ * down from here (its server does not exist yet), so `startAttempt` observes
6136
+ * the flag once the bind settles and retires it.
6137
+ */
5768
6138
  cancel() {
5769
- if (this.disposed || this.statusValue.state !== "waiting") return;
5770
- this.teardown();
5771
- this.setStatus({
5772
- state: "failed",
5773
- reason: "cancelled"
5774
- });
6139
+ if (this.disposed) return;
6140
+ if (this.statusValue.state === "waiting") {
6141
+ this.teardown();
6142
+ this.setStatus({
6143
+ state: "failed",
6144
+ reason: "cancelled"
6145
+ });
6146
+ return;
6147
+ }
6148
+ if (this.starting !== void 0) this.cancelPending = true;
5775
6149
  }
5776
6150
  /** Stop everything; a waiting attempt ends cancelled. Idempotent. */
5777
6151
  dispose() {
5778
6152
  if (this.disposed) return;
5779
6153
  this.disposed = true;
6154
+ this.cancelPending = false;
5780
6155
  const wasWaiting = this.statusValue.state === "waiting";
5781
6156
  this.teardown();
5782
6157
  if (wasWaiting) this.setStatus({
@@ -5812,8 +6187,12 @@ var CommandCodeLoginFlow = class {
5812
6187
  const server = createServer((request, response) => this.handleCallback(request, response, expectedState));
5813
6188
  this.server = server;
5814
6189
  server.once("error", (error) => {
5815
- if (this.server !== server) return;
6190
+ if (this.server !== server) {
6191
+ closeServer(server);
6192
+ return;
6193
+ }
5816
6194
  this.server = void 0;
6195
+ closeServer(server);
5817
6196
  const tagged = new LoginSettleError("error", `Could not bind the login callback server on port ${port}: ${error.code ?? error.message}`);
5818
6197
  if (binding) {
5819
6198
  binding = false;
@@ -5982,11 +6361,17 @@ var CommandCodeLoginFlow = class {
5982
6361
  /** Close the server and watchdog without touching the published status. */
5983
6362
  teardown() {
5984
6363
  this.clearTimer();
5985
- this.server?.close();
6364
+ closeServer(this.server);
5986
6365
  this.server = void 0;
5987
6366
  this.settle = void 0;
5988
6367
  }
5989
6368
  };
6369
+ /** Close one loopback server without letting "not running"/"already closed" escape. */
6370
+ function closeServer(server) {
6371
+ try {
6372
+ server?.close();
6373
+ } catch {}
6374
+ }
5990
6375
  /** A tagged settle failure carrying the stable copy reason. */
5991
6376
  var LoginSettleError = class extends Error {
5992
6377
  reason;
@@ -6060,7 +6445,8 @@ function selectCommandCodeSearchProvider(web, enable) {
6060
6445
  function commandCodeSearchSelection() {
6061
6446
  return {
6062
6447
  owner: false,
6063
- displaced: void 0
6448
+ displaced: void 0,
6449
+ preexisting: false
6064
6450
  };
6065
6451
  }
6066
6452
  /**
@@ -6078,8 +6464,14 @@ function commandCodeSearchSelection() {
6078
6464
  * auto-select — already says what the user wants.
6079
6465
  * - When the field already reads `commandcode` at first touch (e.g. a
6080
6466
  * surviving runtime the plugin did not set, or a manual
6081
- * `searchProvider: commandcode` pin), `displaced` stays undefined so the
6082
- * later disable is a no-op rather than a guess at the factory default.
6467
+ * `searchProvider: commandcode` pin), `displaced` stays undefined and
6468
+ * `preexisting` is set, so the later disable is a no-op rather than a guess
6469
+ * at the factory default. A "no-op" means the field is left ALONE: writing
6470
+ * that `undefined` back would destroy the user's own pin, and dsh-web reads
6471
+ * a cleared `searchProviderId` as auto-select — where a second usable
6472
+ * provider (the shipped `deepseek-official` is usable whenever a DeepSeek
6473
+ * key resolves) makes EVERY later search throw
6474
+ * `WEB_PROVIDER_AMBIGUOUS`.
6083
6475
  *
6084
6476
  * Never throws: like the low-level rewrite, a hardened runtime shape degrades
6085
6477
  * to registered-but-unselected.
@@ -6093,14 +6485,16 @@ function applyCommandCodeSearchSelection(web, state, enable) {
6093
6485
  return;
6094
6486
  }
6095
6487
  const prior = field.searchProviderId;
6096
- state.displaced = prior === "commandcode" ? void 0 : prior;
6488
+ state.preexisting = prior === COMMANDCODE_SEARCH_PROVIDER_ID;
6489
+ state.displaced = state.preexisting ? void 0 : prior;
6097
6490
  field.searchProviderId = COMMANDCODE_SEARCH_PROVIDER_ID;
6098
6491
  state.owner = true;
6099
6492
  return;
6100
6493
  }
6101
6494
  if (state.owner) {
6102
6495
  state.owner = false;
6103
- field.searchProviderId = state.displaced;
6496
+ if (!state.preexisting) field.searchProviderId = state.displaced;
6497
+ state.preexisting = false;
6104
6498
  return;
6105
6499
  }
6106
6500
  } catch {}
@@ -6402,8 +6796,8 @@ function buildCommandCodeTuiSection(deps) {
6402
6796
  label: "API key",
6403
6797
  descriptions: { zh: "API 密钥" },
6404
6798
  secret: { ref },
6405
- hint: `Stored in the credential store as ${ref}, never in settings.yaml.`,
6406
- hintDescriptions: { zh: `保存在凭据库(${ref}),不会写入 settings.yaml。` }
6799
+ hint: `Stored in the credential store as ${ref}, never in configuration files.`,
6800
+ hintDescriptions: { zh: `保存在凭据库(${ref}),不会写入配置文件。` }
6407
6801
  },
6408
6802
  {
6409
6803
  path: ["apiBase"],
@@ -6681,6 +7075,67 @@ function transportBudgetMessage(failureMessage, maxRetries) {
6681
7075
  return `${failureMessage};Command Code API 连接在连续重试后仍失败——已停止重试以免整轮卡死。插件已自动重试 ${maxRetries} 次。通常说明当前网络到 api.commandcode.ai 的连接不通,常见原因是代理未生效:DSH 只读取环境变量 HTTPS_PROXY/HTTP_PROXY(系统代理/PAC 不会被读取),请确认启动 DSH 的终端里已导出代理,或先关闭代理直连后重试。网络恢复后直接重发即可 — Command Code API transport still failed after ${maxRetries} automatic retries, which were stopped so the turn could not stall for minutes. This is a connectivity problem between this machine and api.commandcode.ai, not a context-window or request-size rejection. Common cause: the proxy is not reaching the harness — DSH reads HTTPS_PROXY/HTTP_PROXY from the environment only (a Windows system proxy/PAC setting is not consulted), so export it in the launching shell, or turn the proxy off and retry. Resend when the network recovers; the budget is per request and has been reset`;
6682
7076
  }
6683
7077
  //#endregion
7078
+ //#region src/config-volatile.ts
7079
+ /**
7080
+ * Mark one schema field volatile when the installed schemastery supports it.
7081
+ * @param schema - The field schema (any generation).
7082
+ * @returns The schema carrying `meta.volatile`, or the schema itself on an
7083
+ * engine whose schemastery predates `.volatile()`.
7084
+ */
7085
+ function markVolatile(schema) {
7086
+ const candidate = schema;
7087
+ if (typeof candidate.volatile !== "function") return schema;
7088
+ return candidate.volatile();
7089
+ }
7090
+ /**
7091
+ * Mark every field of a Config field dict volatile, except the named
7092
+ * composition-only secrets (`apiKey` here, which no settings surface writes).
7093
+ *
7094
+ * The result is a NEW dict of schema instances: callers that also need an
7095
+ * UNMARKED schema for the legacy settings generation must build the fields
7096
+ * twice (`configFields()`), never share one dict — `.volatile()` clones per
7097
+ * call, and a shared instance would carry the mark into the legacy schema.
7098
+ *
7099
+ * @param fields - Freshly built field schemas.
7100
+ * @param skip - Field names that must stay unmarked.
7101
+ * @returns The field dict with every non-skipped field marked.
7102
+ */
7103
+ function markVolatileFields(fields, skip = []) {
7104
+ return Object.fromEntries(Object.entries(fields).map(([name, schema]) => [name, skip.includes(name) ? schema : markVolatile(schema)]));
7105
+ }
7106
+ /**
7107
+ * Whether a resolved config value is a volatile reference.
7108
+ *
7109
+ * Duck-typed on the two properties `createVolatile` produces (frozen object
7110
+ * with a `get` method) rather than cosmokit's private `write` symbol, because
7111
+ * cosmokit is not a peer of this bundle. Config values are plain parsed JSON
7112
+ * (or `!!jsExpr` wrappers), which are never frozen, so the test cannot
7113
+ * misfire on an ordinary field.
7114
+ */
7115
+ function isVolatileRef(value) {
7116
+ return typeof value === "object" && value !== null && !Array.isArray(value) && Object.isFrozen(value) && typeof value.get === "function";
7117
+ }
7118
+ /**
7119
+ * Read one config object as PLAIN values across generations.
7120
+ *
7121
+ * Each top-level field found to be a volatile reference is read through
7122
+ * `.get()` — freshly on every call, so a live update lands on the very next
7123
+ * read. When no field is a reference the input object is returned as-is,
7124
+ * keeping reference identity stable for identity-keyed memos on engines that
7125
+ * hand `apply()` plain config.
7126
+ */
7127
+ function unwrapVolatileConfig(config) {
7128
+ const source = config;
7129
+ if (typeof source !== "object" || source === null) return config;
7130
+ let found = false;
7131
+ const plain = {};
7132
+ for (const [field, value] of Object.entries(source)) if (isVolatileRef(value)) {
7133
+ plain[field] = value.get();
7134
+ found = true;
7135
+ } else plain[field] = value;
7136
+ return found ? plain : config;
7137
+ }
7138
+ //#endregion
6684
7139
  //#region src/index.ts
6685
7140
  /**
6686
7141
  * dsh-commandcode-provider — DeepSeek Harness LLM provider plugin for Command
@@ -6690,9 +7145,10 @@ function transportBudgetMessage(failureMessage, maxRetries) {
6690
7145
  * the configurable-provider directory, so the web Models page shows a
6691
7146
  * "Command Code" card with an API-key field and the model picker lists the
6692
7147
  * live Command Code model catalog. Connection facts resolve per request over
6693
- * the optional `llm-commandcode` user-settings section and the credential
6694
- * seam, so a changed key, endpoint, or cache path reaches the next request
6695
- * without a restart.
7148
+ * the plugin's live config — the `llm-commandcode` settings section through
7149
+ * dsh 0.1.6, profile Config (volatile fields, unwrapped per read) from 0.1.7
7150
+ * — and the credential seam, so a changed key, endpoint, or cache path
7151
+ * reaches the next request without a restart.
6696
7152
  *
6697
7153
  * ```yaml
6698
7154
  * - id: llm-commandcode
@@ -6717,41 +7173,84 @@ const DEFAULT_API_KEY_ENV = "COMMANDCODE_API_KEY";
6717
7173
  const PROVIDER = "commandcode";
6718
7174
  /** Default models cache path (mirrors the pi plugin's on-disk cache). */
6719
7175
  const DEFAULT_MODELS_CACHE_PATH = join(homedir(), ".commandcode", "models-cache.json");
6720
- const Config = z.object({
6721
- apiKeyEnv: z.string().role("credential-ref").default(DEFAULT_API_KEY_ENV),
6722
- apiKey: z.string().role("secret"),
6723
- apiBase: z.string(),
6724
- workingDir: z.string(),
6725
- modelsCachePath: z.string(),
6726
- requestTimeoutMs: z.number().min(1).max(MAX_TIMER_DELAY_MS),
6727
- streamIdleTimeoutMs: z.number().min(1).max(MAX_TIMER_DELAY_MS),
6728
- transportMaxRetries: z.number().min(0).max(50),
6729
- filterModelsByPlan: z.boolean(),
6730
- visibleModels: z.array(z.string()),
6731
- /**
6732
- * Per-model visibility overrides for the terminal settings page's checkbox
6733
- * list, keyed by catalog id. dsh-TUI addresses a staged edit by its field
6734
- * PATH, so two checkboxes sharing one path would overwrite each other's
6735
- * draft and the section's last model would decide every write; a map gives
6736
- * each checkbox a path of its own. An id listed here wins over
6737
- * {@link visibleModels}; ids absent here keep following it.
6738
- */
6739
- modelVisibility: z.dict(z.boolean()),
6740
- webSearch: z.boolean().default(true),
6741
- showSidebarQuota: z.boolean().default(false),
6742
- accounts: z.array(z.object({
6743
- label: z.string(),
6744
- apiKeyEnv: z.string().role("credential-ref"),
6745
- /** Literal key for one extra slot; see the top-level `apiKey` secret note. */
6746
- apiKey: z.string().role("secret")
6747
- })),
6748
- activeAccount: z.string(),
6749
- modelAccountRules: z.array(z.object({
6750
- models: z.array(z.string()),
6751
- account: z.string()
6752
- })),
6753
- lang: z.string().pattern(/^(zh|en)$/).default("zh")
6754
- });
7176
+ /**
7177
+ * The Config schema's fields, freshly built on every call.
7178
+ *
7179
+ * A FACTORY, not a shared dict, and that is load-bearing: the exported
7180
+ * {@link Config} marks every field volatile while {@link LegacySettingsSchema}
7181
+ * must carry no mark at all, and schemastery's schema instances are
7182
+ * single-purpose objects — sharing one dict would leak the marks into the
7183
+ * legacy registration (see `config-volatile.ts` for why that is a boot-time
7184
+ * `ValidationError` on ≤0.1.6 engines whose schemastery resolved to ≥3.18.3).
7185
+ *
7186
+ * @returns One fresh schema instance per Config field.
7187
+ */
7188
+ function configFields() {
7189
+ return {
7190
+ apiKeyEnv: z.string().role("credential-ref").default(DEFAULT_API_KEY_ENV),
7191
+ apiKey: z.string().role("secret"),
7192
+ apiBase: z.string(),
7193
+ workingDir: z.string(),
7194
+ modelsCachePath: z.string(),
7195
+ requestTimeoutMs: z.number().min(1).max(MAX_TIMER_DELAY_MS),
7196
+ streamIdleTimeoutMs: z.number().min(1).max(MAX_TIMER_DELAY_MS),
7197
+ transportMaxRetries: z.number().min(0).max(50),
7198
+ filterModelsByPlan: z.boolean(),
7199
+ visibleModels: z.array(z.string()),
7200
+ /**
7201
+ * Per-model visibility overrides for the terminal settings page's checkbox
7202
+ * list, keyed by catalog id. dsh-TUI addresses a staged edit by its field
7203
+ * PATH, so two checkboxes sharing one path would overwrite each other's
7204
+ * draft and the section's last model would decide every write; a map gives
7205
+ * each checkbox a path of its own. An id listed here wins over
7206
+ * {@link Config.visibleModels}; ids absent here keep following it.
7207
+ */
7208
+ modelVisibility: z.dict(z.boolean()),
7209
+ webSearch: z.boolean().default(true),
7210
+ showSidebarQuota: z.boolean().default(false),
7211
+ accounts: z.array(z.object({
7212
+ label: z.string(),
7213
+ apiKeyEnv: z.string().role("credential-ref"),
7214
+ /** Literal key for one extra slot; see the top-level `apiKey` secret note. */
7215
+ apiKey: z.string().role("secret")
7216
+ })),
7217
+ activeAccount: z.string(),
7218
+ modelAccountRules: z.array(z.object({
7219
+ models: z.array(z.string()),
7220
+ account: z.string()
7221
+ })),
7222
+ lang: z.string().pattern(/^(zh|en)$/).default("zh")
7223
+ };
7224
+ }
7225
+ /**
7226
+ * The 0.1.7-generation schema: every field volatile except the
7227
+ * composition-only `apiKey` secret.
7228
+ *
7229
+ * dsh 0.1.7's settings forms are projected from the schema's `meta.volatile`
7230
+ * nodes, so an unmarked field would be invisible to AND unwritable from the
7231
+ * settings page and refused by form-edit path validation — and on that
7232
+ * generation the loader hands `apply()` a live reference per marked field,
7233
+ * committing later writes without remounting this fiber (the
7234
+ * `loader/volatile-update` listener at the bottom of `apply` covers the facts
7235
+ * that are not re-derived per read). The mark is inert on engines whose
7236
+ * schemastery predates `.volatile()`.
7237
+ */
7238
+ const Config = z.object(markVolatileFields(configFields(), ["apiKey"]));
7239
+ /**
7240
+ * The ≤0.1.6-generation registration schema: the same fields, NO volatile
7241
+ * marks.
7242
+ *
7243
+ * The legacy `installSection(owner, ns, schema, entry, hooks)` re-validates the
7244
+ * `entry` it is handed (`register()` → `resolve()` → `schema(mergeLayers(base,
7245
+ * section))`) and its `describe()` structuredClones that same `entry`. Since
7246
+ * schemastery ≥3.18.3 creates references at parse time on EVERY generation —
7247
+ * `dsh-settings` through 0.1.6 declares `schemastery: ^3.18.2`, so old engines
7248
+ * freshly installed today resolve 3.18.3 — a marked schema plus the raw
7249
+ * reference-carrying `config` is a boot-time `ValidationError` there. This
7250
+ * schema is therefore unmarked, and `apply` pairs it with
7251
+ * `unwrapVolatileConfig(config)`.
7252
+ */
7253
+ const LegacySettingsSchema = z.object(configFields());
6755
7254
  /**
6756
7255
  * The one explicit resolve step from raw config to validated connection
6757
7256
  * facts. Programmatic construction may bypass Schemastery normalization, so
@@ -6785,7 +7284,8 @@ function readModelVisibility(raw) {
6785
7284
  }
6786
7285
  function apply(ctx, config) {
6787
7286
  installCostProjection(ctx);
6788
- let current = () => config;
7287
+ let source = () => config;
7288
+ const current = () => unwrapVolatileConfig(source());
6789
7289
  let lastRaw;
6790
7290
  let lastGood;
6791
7291
  const options = () => {
@@ -6998,18 +7498,38 @@ function apply(ctx, config) {
6998
7498
  }, "dsh-commandcode-provider: tui settings handle");
6999
7499
  });
7000
7500
  ctx.inject(["settings"], (settingsCtx) => {
7001
- settingsCtx.settings.installSection(ctx, NS, Config, config, {
7002
- setSource: (source) => {
7003
- current = source;
7004
- },
7005
- onChange: () => {
7006
- applySearchSelection(current().webSearch ?? true);
7007
- refreshTuiSettings?.();
7008
- }
7009
- });
7501
+ const service = settingsCtx.settings;
7502
+ if (typeof service.installSection === "function") {
7503
+ service.installSection(ctx, NS, LegacySettingsSchema, unwrapVolatileConfig(config), {
7504
+ setSource: (next) => {
7505
+ source = next;
7506
+ },
7507
+ onChange: () => {
7508
+ applySearchSelection(current().webSearch ?? true);
7509
+ refreshTuiSettings?.();
7510
+ }
7511
+ });
7512
+ return;
7513
+ }
7514
+ if (typeof service.configure === "function") settingsCtx.effect(() => {
7515
+ const dispose = service.configure?.({ auto: false }, ctx.fiber);
7516
+ return () => {
7517
+ dispose?.();
7518
+ };
7519
+ }, "dsh-commandcode-provider: settings auto-form policy");
7010
7520
  });
7521
+ const loaderEvents = ctx;
7522
+ ctx.effect(() => {
7523
+ const off = loaderEvents.on("loader/volatile-update", () => {
7524
+ applySearchSelection(current().webSearch ?? true);
7525
+ refreshTuiSettings?.();
7526
+ });
7527
+ return () => {
7528
+ if (typeof off === "function") off();
7529
+ };
7530
+ }, "dsh-commandcode-provider: volatile config updates");
7011
7531
  }
7012
7532
  //#endregion
7013
- export { ACTIVE_ACCOUNT_AUTO, BILLING_ACCESS_TTL_MS, COMMANDCODE_SEARCH_PROVIDER_ID, COMMAND_CODE_CLI_VERSION, CommandCodeAccountPool, CommandCodeAdapter, CommandCodeLoginFlow, CommandCodeSearchProvider, CommandCodeUsageService, Config, DEFAULT_API_BASE, DEFAULT_GENERATE_MAX_TOKENS, DEFAULT_MAX_OUTPUT_TOKENS, DEFAULT_MODELS_CACHE_PATH, DEFAULT_REQUEST_TIMEOUT_MS, DEFAULT_STREAM_IDLE_TIMEOUT_MS, DEFAULT_WEB_SEARCH_PROVIDER_ID, KNOWN_DEALS, KNOWN_EFFORTS, KNOWN_IMAGE_MODELS, KNOWN_PEAK_PRICING, KNOWN_PLANS, KNOWN_SUBSCRIPTION_PLANS, KNOWN_THINKING_MODELS, LANG_AUTO, LOGIN_ALLOWED_ORIGINS, LOGIN_BEGIN_ENDPOINT, LOGIN_BODY_LIMIT_BYTES, LOGIN_CANCEL_ENDPOINT, LOGIN_MAX_PORT_ATTEMPTS, LOGIN_START_PORT, LOGIN_STATUS_ENDPOINT, LOGIN_TIMEOUT_MS, PLAN_LABELS, PLAN_ORDER, PROVIDER, USAGE_REPORT_ENDPOINT, accountUsable, apply, applyCommandCodeSearchSelection, applyCommandCodeTuiSettings, applyCommands, applyUsageRemote, buildCommandAuthUrl, buildCommandCodeTuiSection, capabilityDescription, commandCodeSearchSelection, commandDefinition, compareByPlan, dealLabel, formatContext, inject, loginStatusSchema, matchModelRule, modelVisibleInPlan, name, parseLoginStatus, peakPricingLabel, peakPricingState, planLabel, projectSlugFromPath, resolveAdapterOptions, resolveAuthFileApiKey, selectAccountForModel, selectActiveAccount, selectCommandCodeSearchProvider, studioBaseForApiBase, subscriptionPlanInfo, usageReportSchema, validateCommandApiKey };
7533
+ export { ACTIVE_ACCOUNT_AUTO, BILLING_ACCESS_TTL_MS, COMMANDCODE_SEARCH_PROVIDER_ID, COMMAND_CODE_CLI_VERSION, CommandCodeAccountPool, CommandCodeAdapter, CommandCodeLoginFlow, CommandCodeSearchProvider, CommandCodeUsageService, Config, DEFAULT_API_BASE, DEFAULT_GENERATE_MAX_TOKENS, DEFAULT_MAX_OUTPUT_TOKENS, DEFAULT_MODELS_CACHE_PATH, DEFAULT_REQUEST_TIMEOUT_MS, DEFAULT_STREAM_IDLE_TIMEOUT_MS, DEFAULT_WEB_SEARCH_PROVIDER_ID, KNOWN_DEALS, KNOWN_EFFORTS, KNOWN_IMAGE_MODELS, KNOWN_PEAK_PRICING, KNOWN_PLANS, KNOWN_SUBSCRIPTION_PLANS, KNOWN_THINKING_MODELS, LANG_AUTO, LOGIN_ALLOWED_ORIGINS, LOGIN_BEGIN_ENDPOINT, LOGIN_BODY_LIMIT_BYTES, LOGIN_CANCEL_ENDPOINT, LOGIN_MAX_PORT_ATTEMPTS, LOGIN_START_PORT, LOGIN_STATUS_ENDPOINT, LOGIN_TIMEOUT_MS, LegacySettingsSchema, PLAN_LABELS, PLAN_ORDER, PROVIDER, USAGE_REPORT_ENDPOINT, accountUsable, apply, applyCommandCodeSearchSelection, applyCommandCodeTuiSettings, applyCommands, applyUsageRemote, buildCommandAuthUrl, buildCommandCodeTuiSection, capabilityDescription, commandCodeSearchSelection, commandDefinition, compareByPlan, dealLabel, formatContext, inject, loginStatusSchema, matchModelRule, modelVisibleInPlan, name, parseLoginStatus, peakPricingLabel, peakPricingState, planLabel, projectSlugFromPath, resolveAdapterOptions, resolveAuthFileApiKey, selectAccountForModel, selectActiveAccount, selectCommandCodeSearchProvider, studioBaseForApiBase, subscriptionPlanInfo, usageReportSchema, validateCommandApiKey };
7014
7534
 
7015
7535
  //# sourceMappingURL=index.js.map