@mars-sea/dsh-commandcode-provider 0.12.9 → 0.13.0

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,8 +1,8 @@
1
+ import { CONTEXT_WINDOW_EXCEEDED_CODE, IMAGE_OFFLOAD_REQUIRED_CODE, LlmAdapter, LlmError, ProviderRequestId, ReasoningEffortId, ToolCallId, assertUsableApiKey, assistantStreamFirstTokenTime, attributionHeaders, errorChain, isContextWindowExceededError, offloadedImageText, projectOffloadedImages, requiredImageOffload, resolveRetryPolicy, textOnlyImageText } from "@deepseek-ai/dsh-llm";
1
2
  import { homedir, tmpdir } from "node:os";
2
3
  import { dirname, join } from "node:path";
3
4
  import z from "@deepseek-ai/schemastery";
4
5
  import { MAX_TIMER_DELAY_MS } from "@deepseek-ai/dsh-timeout";
5
- import { CONTEXT_WINDOW_EXCEEDED_CODE, IMAGE_OFFLOAD_REQUIRED_CODE, LlmAdapter, LlmError, ProviderRequestId, ReasoningEffortId, ToolCallId, assertUsableApiKey, attributionHeaders, errorChain, isContextWindowExceededError, offloadedImageText, projectOffloadedImages, requiredImageOffload, resolveRetryPolicy, textOnlyImageText } from "@deepseek-ai/dsh-llm";
6
6
  import { credentialRef } from "@deepseek-ai/dsh-credentials";
7
7
  import { launchEnvironmentOf } from "@deepseek-ai/dsh-launch-environment";
8
8
  import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
@@ -103,7 +103,7 @@ const PLAN_ORDER = Object.fromEntries(PLAN_TIER_ORDER.map((tier, index) => [tier
103
103
  * subscription-plan labels, deals, and hourly (peak/off-peak) pricing.
104
104
  *
105
105
  * Everything here is synced from official sources — the command-code CLI
106
- * bundle's model table (`dist/cli.mjs`, re-verified at command-code@1.79.2) and
106
+ * bundle's model table (`dist/cli.mjs`, re-verified at command-code@1.82.1) and
107
107
  * the official plan/pricing/model docs; see the dsh-commandcode-upstream skill
108
108
  * for the extraction procedures. Keeping the snapshot in its own module
109
109
  * confines those frequent sync diffs here: src/adapter.ts holds only the stable
@@ -226,6 +226,11 @@ const KNOWN_EFFORTS = {
226
226
  "high",
227
227
  "max"
228
228
  ],
229
+ "google/gemini-4-argon": [
230
+ "low",
231
+ "medium",
232
+ "high"
233
+ ],
229
234
  "google/gemini-3.1-flash-lite": [
230
235
  "low",
231
236
  "medium",
@@ -380,6 +385,12 @@ const KNOWN_EFFORTS = {
380
385
  "high",
381
386
  "xhigh"
382
387
  ],
388
+ "meta/muse-wonder-1.0": [
389
+ "low",
390
+ "medium",
391
+ "high",
392
+ "xhigh"
393
+ ],
383
394
  "gpt-6-astra": [
384
395
  "low",
385
396
  "medium",
@@ -483,6 +494,7 @@ const KNOWN_IMAGE_MODELS = /* @__PURE__ */ new Set([
483
494
  "deepseek/deepseek-v4-flash-vision-exp",
484
495
  "deepseek/deepseek-v4.1-flash",
485
496
  "deepseek/deepseek-v4.1-flash-fast",
497
+ "google/gemini-4-argon",
486
498
  "google/gemini-3.1-flash-lite",
487
499
  "google/gemini-3.5-flash",
488
500
  "google/gemini-3.5-flash-lite",
@@ -505,6 +517,7 @@ const KNOWN_IMAGE_MODELS = /* @__PURE__ */ new Set([
505
517
  "meta/muse-spark-1.2-contributor",
506
518
  "meta/muse-spark-1.3",
507
519
  "meta/muse-spark-1.3-contributor",
520
+ "meta/muse-wonder-1.0",
508
521
  "mistral/mistral-large-4",
509
522
  "moonshotai/Kimi-K2.5",
510
523
  "moonshotai/Kimi-K2.6",
@@ -528,7 +541,7 @@ const KNOWN_IMAGE_MODELS = /* @__PURE__ */ new Set([
528
541
  ]);
529
542
  /**
530
543
  * Models WITHOUT a zero-data-retention upstream, per the official CLI's own
531
- * registry (`command-code@1.79.2` `dist/cli.mjs`): `modelSupportsZdr(id)` is
544
+ * registry (`command-code@1.82.1` `dist/cli.mjs`): `modelSupportsZdr(id)` is
532
545
  * exactly `!nonZdrSet.has(canonicalize(id))`, and `knownModelSupportsZdr`
533
546
  * carries the same membership in the sibling route table — the UNION of both
534
547
  * is this set. Reading only the sibling route table dropped
@@ -581,6 +594,7 @@ const KNOWN_NON_ZDR_MODELS = /* @__PURE__ */ new Set([
581
594
  "meta/muse-spark-1.2-contributor",
582
595
  "meta/muse-spark-1.3",
583
596
  "meta/muse-spark-1.3-contributor",
597
+ "meta/muse-wonder-1.0",
584
598
  "minimax/minimax-m3-free",
585
599
  "poolside/laguna-s-2.1-free",
586
600
  "sakana/fugu-ultra",
@@ -697,18 +711,22 @@ function requiresMessagesEndpoint(modelId) {
697
711
  * **Generated — do not hand-edit.** `scripts/sync-output-limits.mjs` rewrites
698
712
  * this literal from models.dev; run it after a catalog sync, and use its
699
713
  * `--check` in CI. Command Code resells these models through their VENDOR's own
700
- * API, so the vendor's published ceiling is the value recorded: 76 of the 86
701
- * catalog ids as of 2026-09-30. Each row carries a comment naming the entry it
714
+ * API, so the vendor's published ceiling is the value recorded: 77 of the 89
715
+ * catalog ids as of 2026-10-14. Each row carries a comment naming the entry it
702
716
  * came from.
703
717
  *
704
- * Two of those 76 rest on more than a vendor lookup. `gpt-6.1-sol` (128000) comes
705
- * from OpenAI's own row and is corroborated by every other provider carrying it.
706
- * `inclusionai/ling-3.1-flash:free` (32768) is hand-recorded in the script's
707
- * `MANUAL_CEILINGS`: models.dev has no `ling-3.1` entry yet, so it takes the Ling
708
- * family's uniform ceiling. Both are still correctable — a stricter gateway costs
709
- * one refused request, and `streamRequest()` remembers what the endpoint said.
710
- *
711
- * The 10 unlisted models are ones no vendor publishes and the aggregators
718
+ * Two of those 77 rest on more than a vendor lookup, both hand-recorded in the
719
+ * script's `MANUAL_CEILINGS`. `gpt-6.1-sol` (128000) comes from OpenAI's own row
720
+ * and is corroborated by every other provider carrying it.
721
+ * `inclusionai/ling-3.1-flash:free` (32768) has no models.dev row at all, so it
722
+ * takes the Ling family's uniform ceiling; `inclusionai/ling-3.0-flash-sante:free`
723
+ * (32768) lost both its vendor row and its unanimous third-party vote (vercel
724
+ * files 32000), and is pinned so a regeneration cannot drop a live model's
725
+ * ceiling back to the default. All are still correctable — a stricter gateway
726
+ * costs one refused request, and `streamRequest()` remembers what the endpoint
727
+ * said.
728
+ *
729
+ * The 12 unlisted models are ones no vendor publishes and the aggregators
712
730
  * disagree about. They are NOT a gap: a refusal states the ceiling in full, and
713
731
  * `streamRequest()` reads it off, retries once and remembers it for the model
714
732
  * (issue #71). A vendor figure the gateway turns out to be stricter than costs
@@ -724,6 +742,7 @@ const MODEL_OUTPUT_TOKEN_LIMITS = /* @__PURE__ */ new Map([
724
742
  ["claude-fable-5", 128e3],
725
743
  ["claude-fable-5-1", 128e3],
726
744
  ["claude-haiku-4-5-20251001", 64e3],
745
+ ["claude-haiku-5-5", 128e3],
727
746
  ["claude-opus-4-7", 128e3],
728
747
  ["claude-opus-4-8", 128e3],
729
748
  ["claude-opus-5", 128e3],
@@ -740,6 +759,7 @@ const MODEL_OUTPUT_TOKEN_LIMITS = /* @__PURE__ */ new Map([
740
759
  ["google/gemini-3.6-flash", 65536],
741
760
  ["google/gemini-3.7-flash", 65536],
742
761
  ["google/gemini-3.8-flash", 65536],
762
+ ["google/gemini-4-argon", 32768],
743
763
  ["gpt-5.3-codex", 128e3],
744
764
  ["gpt-5.4", 128e3],
745
765
  ["gpt-5.4-mini", 128e3],
@@ -759,6 +779,7 @@ const MODEL_OUTPUT_TOKEN_LIMITS = /* @__PURE__ */ new Map([
759
779
  ["meta/muse-spark-1.2-contributor", 131072],
760
780
  ["meta/muse-spark-1.3", 131072],
761
781
  ["meta/muse-spark-1.3-contributor", 131072],
782
+ ["meta/muse-wonder-1.0", 131072],
762
783
  ["MiniMaxAI/MiniMax-M2.5", 131072],
763
784
  ["MiniMaxAI/MiniMax-M2.7", 131072],
764
785
  ["MiniMaxAI/MiniMax-M3", 512e3],
@@ -777,8 +798,6 @@ const MODEL_OUTPUT_TOKEN_LIMITS = /* @__PURE__ */ new Map([
777
798
  ["Qwen/Qwen3.8-Max", 131072],
778
799
  ["Qwen/Qwen3.8-Omni-Flash", 131072],
779
800
  ["sakana/fugu-ultra", 1e6],
780
- ["stealth/pixel-canary", 131072],
781
- ["stealth/space-bunny-alpha", 524288],
782
801
  ["stepfun/Step-3.5-Flash", 256e3],
783
802
  ["stepfun/Step-3.7-Flash", 256e3],
784
803
  ["stepfun/Step-5-Preview", 65536],
@@ -804,11 +823,13 @@ const MODEL_OUTPUT_TOKEN_LIMITS = /* @__PURE__ */ new Map([
804
823
  * `/docs/plans/max` and `/docs/resources/pricing-limits`). Each plan's model
805
824
  * list is a superset of the one below it: Go ⊂ GOAT ⊂ Pro ⊂ Provider/Max.
806
825
  * Models absent from every plan list (Claude Opus/Fable, Fugu Ultra) are
807
- * Provider-tier. Re-verified at command-code@1.79.2 (2026-10-09): 87 catalog
808
- * ids at 54/64/78/86 cumulative, a strict superset chain. Mistral Large 4
826
+ * Provider-tier. Re-verified at command-code@1.82.1 (2026-10-14): 89 catalog
827
+ * ids at 54/64/79/87/89 cumulative, a strict superset chain. Mistral Large 4
809
828
  * (1.75.0) was the only addition at the 1.77.0 check; the 1.78.0/1.79.0 pair
810
829
  * added Claude Haiku 5.5 at Pro and the free Glyph Cluster preview at Go.
811
- * 1.79.2 lowers Haiku 5.5's minimum plan to GOAT; every other tier is unchanged.
830
+ * 1.79.2 lowers Haiku 5.5's minimum plan to GOAT; 1.80.0 adds Muse Wonder at
831
+ * Max; 1.82.0 adds Gemini 4 Argon at Pro and nothing else moves. Every other
832
+ * tier is unchanged.
812
833
  * The only removals since 1.49.0 are the two retired stealth previews (Pixel
813
834
  * Canary at 1.73.1, Space Bunny Alpha at 1.74.3). Per-entry tags below name the
814
835
  * releases that added or moved each row.
@@ -889,6 +910,7 @@ const KNOWN_PLANS = {
889
910
  "claude-haiku-4-5-20251001": "pro",
890
911
  "claude-sonnet-4-6": "pro",
891
912
  "claude-sonnet-5": "pro",
913
+ "google/gemini-4-argon": "pro",
892
914
  "google/gemini-3.1-flash-lite": "pro",
893
915
  "google/gemini-3.5-flash": "pro",
894
916
  "google/gemini-3.5-flash-lite": "pro",
@@ -908,7 +930,8 @@ const KNOWN_PLANS = {
908
930
  "claude-opus-5": "provider",
909
931
  "gpt-6-astra": "provider",
910
932
  "sakana/fugu-ultra": "provider",
911
- "gpt-6.1-sol": "max"
933
+ "gpt-6.1-sol": "max",
934
+ "meta/muse-wonder-1.0": "max"
912
935
  };
913
936
  /**
914
937
  * Whether a model is free (requests cost no credits), per the pricing page's
@@ -1497,8 +1520,8 @@ const MODEL_PRICE_ROWS = [
1497
1520
  ],
1498
1521
  allowance: {
1499
1522
  go: 10,
1500
- goat: 60,
1501
- pro: 70
1523
+ goat: 100,
1524
+ pro: 100
1502
1525
  }
1503
1526
  },
1504
1527
  {
@@ -1611,6 +1634,19 @@ const MODEL_PRICE_ROWS = [
1611
1634
  pro: 50
1612
1635
  }
1613
1636
  },
1637
+ {
1638
+ id: "gemini-4-argon",
1639
+ rates: [
1640
+ 2,
1641
+ 10,
1642
+ .1
1643
+ ],
1644
+ allowance: {
1645
+ go: 6,
1646
+ goat: 20,
1647
+ pro: 20
1648
+ }
1649
+ },
1614
1650
  {
1615
1651
  id: "glm-5",
1616
1652
  rates: [
@@ -2318,6 +2354,19 @@ const MODEL_PRICE_ROWS = [
2318
2354
  pro: 30
2319
2355
  }
2320
2356
  },
2357
+ {
2358
+ id: "muse-wonder-1.0",
2359
+ rates: [
2360
+ 4,
2361
+ 20,
2362
+ .2
2363
+ ],
2364
+ allowance: {
2365
+ go: 6,
2366
+ goat: 20,
2367
+ pro: 20
2368
+ }
2369
+ },
2321
2370
  {
2322
2371
  id: "nemotron-3-ultra",
2323
2372
  rates: [
@@ -2764,10 +2813,10 @@ function recordOrEmpty(value) {
2764
2813
  }
2765
2814
  //#endregion
2766
2815
  //#region src/cost-projection.ts
2767
- const record$2 = (v) => isRecord(v) ? v : void 0;
2816
+ const record$3 = (v) => isRecord(v) ? v : void 0;
2768
2817
  const finite = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0;
2769
- function tokensOf(value) {
2770
- const u = record$2(value);
2818
+ function tokensOf$1(value) {
2819
+ const u = record$3(value);
2771
2820
  if (!u || !finite(u.inputTokens) || !finite(u.outputTokens)) return void 0;
2772
2821
  if (u.cacheReadTokens !== void 0 && !finite(u.cacheReadTokens) || u.cacheWriteTokens !== void 0 && !finite(u.cacheWriteTokens)) return void 0;
2773
2822
  return {
@@ -2780,27 +2829,27 @@ function tokensOf(value) {
2780
2829
  /** Current v2 settlements store the last usage in their stream; v1 stores chunk events. */
2781
2830
  function usageOf(type, data) {
2782
2831
  if (type === "assistant/chunk") {
2783
- const chunk = record$2(data.chunk);
2784
- return chunk?.type === "usage" ? tokensOf(chunk.usage) : void 0;
2832
+ const chunk = record$3(data.chunk);
2833
+ return chunk?.type === "usage" ? tokensOf$1(chunk.usage) : void 0;
2785
2834
  }
2786
2835
  if (type !== "assistant/message" && type !== "assistant/attempt") return void 0;
2787
- const direct = tokensOf(data.usage);
2836
+ const direct = tokensOf$1(data.usage);
2788
2837
  if (direct !== void 0) return direct;
2789
2838
  if (!Array.isArray(data.stream)) return void 0;
2790
2839
  for (let i = data.stream.length - 1; i >= 0; i--) {
2791
- const item = record$2(data.stream[i]);
2792
- const chunk = item?.type === "chunk" ? record$2(item.chunk) : void 0;
2793
- if (chunk?.type === "usage") return tokensOf(chunk.usage);
2840
+ const item = record$3(data.stream[i]);
2841
+ const chunk = item?.type === "chunk" ? record$3(item.chunk) : void 0;
2842
+ if (chunk?.type === "usage") return tokensOf$1(chunk.usage);
2794
2843
  }
2795
2844
  }
2796
2845
  function parseState(value) {
2797
- const s = record$2(value), facts = record$2(s?.facts);
2846
+ const s = record$3(value), facts = record$3(s?.facts);
2798
2847
  if (!s || !facts || !finite(facts.pricingKey) || !Array.isArray(facts.groups)) throw new TypeError("invalid commandCodeCost state");
2799
2848
  const group = (v) => {
2800
- const g = record$2(v), t = record$2(g?.tokens);
2849
+ const g = record$3(v), t = record$3(g?.tokens);
2801
2850
  return !!g && typeof g.provider === "string" && typeof g.model === "string" && (g.at === null || finite(g.at)) && finite(g.contextTokens) && !!t && COST_TOKEN_KEYS.every((k) => finite(t[k]));
2802
2851
  };
2803
- const sel = record$2(s.selection), last = record$2(s.last);
2852
+ const sel = record$3(s.selection), last = record$3(s.last);
2804
2853
  if (!(s.selection === null || sel && typeof sel.provider === "string" && typeof sel.model === "string") || !(s.at === null || finite(s.at)) || !facts.groups.every(group) || !(s.last === null || last && finite(last.turn) && finite(last.step) && group(last.group))) throw new TypeError("invalid commandCodeCost state");
2805
2854
  return value;
2806
2855
  }
@@ -2821,10 +2870,10 @@ function createCostProjection(table = modelPriceTable()) {
2821
2870
  }
2822
2871
  }),
2823
2872
  apply(state, event) {
2824
- const data = record$2(event.data);
2873
+ const data = record$3(event.data);
2825
2874
  if (!data) return state;
2826
2875
  if (event.type === "request/header") {
2827
- const config = record$2(record$2(data.header)?.config);
2876
+ const config = record$3(record$3(data.header)?.config);
2828
2877
  return {
2829
2878
  ...state,
2830
2879
  selection: config && typeof config.provider === "string" && typeof config.model === "string" ? {
@@ -2906,6 +2955,502 @@ function installCostProjection(ctx) {
2906
2955
  });
2907
2956
  }
2908
2957
  //#endregion
2958
+ //#region src/analytics.ts
2959
+ /** 在宿主内折叠原始历史;每次请求保留计价时间和上下文,禁止使用已合并的会话成本桶。 */
2960
+ const record$2 = (v) => isRecord(v) ? v : void 0;
2961
+ const number = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0;
2962
+ function tokensOf(v) {
2963
+ const u = record$2(v);
2964
+ if (!u || !number(u.inputTokens) || !number(u.outputTokens)) return void 0;
2965
+ if (u.cacheReadTokens !== void 0 && !number(u.cacheReadTokens) || u.cacheWriteTokens !== void 0 && !number(u.cacheWriteTokens)) return void 0;
2966
+ return {
2967
+ uncachedInputTokens: u.inputTokens,
2968
+ outputTokens: u.outputTokens,
2969
+ cacheReadTokens: u.cacheReadTokens ?? 0,
2970
+ cacheWriteTokens: u.cacheWriteTokens ?? 0
2971
+ };
2972
+ }
2973
+ /** 同一次尝试的用量覆盖前值;重试开始才新建请求,继承前缀只恢复模型选择。 */
2974
+ function foldRequests(session, log) {
2975
+ const facts = [];
2976
+ let provider = "", model = "", current, own = false, started = false, listed = false, turn = -1, step = -1;
2977
+ const begin = (event, data, retry) => {
2978
+ turn = Number(data.turn);
2979
+ step = Number(data.step);
2980
+ own = event.seq >= log.inheritedEventCount;
2981
+ started = true;
2982
+ current = {
2983
+ session,
2984
+ model,
2985
+ at: event.time,
2986
+ retry,
2987
+ outcome: "unknown"
2988
+ };
2989
+ listed = own && provider === "commandcode";
2990
+ if (listed) facts.push(current);
2991
+ };
2992
+ for (const event of log.events) {
2993
+ const d = record$2(event.data);
2994
+ if (!d) continue;
2995
+ if (event.type === "request/header") {
2996
+ const config = record$2(record$2(d.header)?.config);
2997
+ provider = typeof config?.provider === "string" ? config.provider : "";
2998
+ model = typeof config?.model === "string" ? config.model : "";
2999
+ if (current && current.outcome === "unknown") {
3000
+ current.model = model;
3001
+ if (own && provider === "commandcode" && !listed) {
3002
+ facts.push(current);
3003
+ listed = true;
3004
+ }
3005
+ if (provider !== "commandcode" && listed) {
3006
+ facts.pop();
3007
+ listed = false;
3008
+ }
3009
+ }
3010
+ continue;
3011
+ }
3012
+ if (event.type === "step/start" || event.type === "llm/retry-started") {
3013
+ begin(event, d, event.type === "llm/retry-started");
3014
+ continue;
3015
+ }
3016
+ if (!current && [
3017
+ "assistant/message",
3018
+ "assistant/attempt",
3019
+ "assistant/chunk"
3020
+ ].includes(event.type)) {
3021
+ begin(event, d, false);
3022
+ started = false;
3023
+ }
3024
+ if (!current) continue;
3025
+ if (event.type === "turn/end" && d.turn === turn) {
3026
+ const reason = record$2(d.reason)?.kind;
3027
+ if (current.outcome === "unknown") {
3028
+ if (reason === "error") current.outcome = "failed";
3029
+ else if (reason === "aborted") current.outcome = "cancelled";
3030
+ }
3031
+ current = void 0;
3032
+ continue;
3033
+ }
3034
+ if (d.turn !== turn || d.step !== step) continue;
3035
+ if (event.type === "llm/retry") {
3036
+ current.outcome = "failed";
3037
+ continue;
3038
+ }
3039
+ if (![
3040
+ "assistant/message",
3041
+ "assistant/attempt",
3042
+ "assistant/chunk"
3043
+ ].includes(event.type)) continue;
3044
+ const chunk = record$2(d.chunk);
3045
+ let tokens = tokensOf(d.usage);
3046
+ if (event.type === "assistant/chunk" && chunk?.type === "usage") tokens = tokensOf(chunk.usage);
3047
+ if (tokens === void 0 && Array.isArray(d.stream)) for (let i = d.stream.length - 1; i >= 0; i--) {
3048
+ const item = record$2(d.stream[i]), c = item?.type === "chunk" ? record$2(item.chunk) : void 0;
3049
+ if (c?.type === "usage") {
3050
+ tokens = tokensOf(c.usage);
3051
+ if (tokens) break;
3052
+ }
3053
+ }
3054
+ if (tokens) current.tokens = tokens;
3055
+ if (event.type === "assistant/message") current.outcome = d.interrupted === true ? "cancelled" : "completed";
3056
+ if (started && Array.isArray(d.stream) && event.time >= current.at) {
3057
+ current.latencyMs = event.time - current.at;
3058
+ const first = assistantStreamFirstTokenTime(d.stream);
3059
+ if (first !== void 0 && first >= current.at && first <= event.time) {
3060
+ current.ttftMs = first - current.at;
3061
+ if (current.tokens) current.decodeMs = event.time - first;
3062
+ }
3063
+ }
3064
+ }
3065
+ return facts;
3066
+ }
3067
+ const emptyMetrics = () => ({
3068
+ sessions: 0,
3069
+ requests: 0,
3070
+ completed: 0,
3071
+ failed: 0,
3072
+ cancelled: 0,
3073
+ unknown: 0,
3074
+ retries: 0,
3075
+ usageSamples: 0,
3076
+ pricedRequests: 0,
3077
+ cost: 0,
3078
+ tokens: zeroCostTokens(),
3079
+ latencySamples: 0,
3080
+ latencyMs: 0,
3081
+ ttftSamples: 0,
3082
+ ttftMs: 0,
3083
+ decodeSamples: 0,
3084
+ decodeMs: 0,
3085
+ decodeTokens: 0
3086
+ });
3087
+ function priceRequest(f, id, table, prices) {
3088
+ if (!f.tokens) return void 0;
3089
+ const price = prices.get(id);
3090
+ if (!price) return void 0;
3091
+ if (price.free === true) return 0;
3092
+ const context = f.tokens.uncachedInputTokens + f.tokens.cacheReadTokens + f.tokens.cacheWriteTokens;
3093
+ const r = requestRates(price, f.at, context, table);
3094
+ if (f.tokens.cacheWriteTokens > 0 && r.cacheWriteCost === void 0) return void 0;
3095
+ return (f.tokens.uncachedInputTokens * r.inputCost + f.tokens.outputTokens * r.outputCost + f.tokens.cacheReadTokens * r.cacheReadCost + f.tokens.cacheWriteTokens * (r.cacheWriteCost ?? 0)) / 1e6;
3096
+ }
3097
+ function aggregate(facts, table, prices) {
3098
+ const m = emptyMetrics();
3099
+ m.sessions = new Set(facts.map((f) => f.session)).size;
3100
+ for (const f of facts) {
3101
+ m.requests++;
3102
+ m[f.outcome]++;
3103
+ if (f.retry) m.retries++;
3104
+ if (f.tokens) {
3105
+ m.usageSamples++;
3106
+ for (const k of COST_TOKEN_KEYS) m.tokens[k] += f.tokens[k];
3107
+ }
3108
+ const cost = priceRequest(f, f.model, table, prices);
3109
+ if (cost !== void 0) {
3110
+ m.pricedRequests++;
3111
+ m.cost += cost;
3112
+ }
3113
+ if (f.latencyMs !== void 0) {
3114
+ m.latencySamples++;
3115
+ m.latencyMs += f.latencyMs;
3116
+ }
3117
+ if (f.ttftMs !== void 0) {
3118
+ m.ttftSamples++;
3119
+ m.ttftMs += f.ttftMs;
3120
+ }
3121
+ if (f.decodeMs !== void 0 && f.decodeMs > 0 && f.tokens) {
3122
+ m.decodeSamples++;
3123
+ m.decodeMs += f.decodeMs;
3124
+ m.decodeTokens += f.tokens.outputTokens;
3125
+ }
3126
+ }
3127
+ return m;
3128
+ }
3129
+ function buildAnalytics(facts, query, table, catalog, now = Date.now()) {
3130
+ const selected = query.mode === "range" ? facts.filter((f) => f.at >= query.from && f.at < query.to) : facts;
3131
+ const prices = /* @__PURE__ */ new Map();
3132
+ for (const p of table.models) for (const id of [p.id, p.slug]) if (!prices.has(id)) prices.set(id, p);
3133
+ const totals = aggregate(selected, table, prices);
3134
+ const models = [...new Set(selected.map((f) => f.model))].map((id) => ({
3135
+ id,
3136
+ ...aggregate(selected.filter((f) => f.model === id), table, prices)
3137
+ })).sort((a, b) => b.cost - a.cost || a.id.localeCompare(b.id));
3138
+ const scenarios = catalog.map((model) => {
3139
+ let cost = 0, pricedRequests = 0, excludedRequests = 0, unknownRequests = 0;
3140
+ for (const f of selected) {
3141
+ if (!f.tokens) {
3142
+ unknownRequests++;
3143
+ continue;
3144
+ }
3145
+ const context = f.tokens.uncachedInputTokens + f.tokens.cacheReadTokens + f.tokens.cacheWriteTokens;
3146
+ if (model.contextWindow !== null && context + f.tokens.outputTokens > model.contextWindow) {
3147
+ excludedRequests++;
3148
+ continue;
3149
+ }
3150
+ const priced = priceRequest(f, model.id, table, prices);
3151
+ if (priced === void 0) unknownRequests++;
3152
+ else {
3153
+ cost += priced;
3154
+ pricedRequests++;
3155
+ }
3156
+ }
3157
+ return {
3158
+ ...model,
3159
+ cost,
3160
+ pricedRequests,
3161
+ excludedRequests,
3162
+ unknownRequests
3163
+ };
3164
+ });
3165
+ const elapsed = query.mode === "range" ? Math.min(query.to, now) - query.from : 0;
3166
+ const monthlyEstimate = elapsed >= 864e5 && totals.requests > 0 && totals.pricedRequests === totals.requests ? totals.cost * (2592e6 / elapsed) : null;
3167
+ return {
3168
+ generatedAt: now,
3169
+ pricingKey: pricingKey(table),
3170
+ query,
3171
+ status: "ready",
3172
+ scannedSessions: 0,
3173
+ matchedSessions: totals.sessions,
3174
+ unreadableSessions: 0,
3175
+ skippedSessions: 0,
3176
+ truncated: false,
3177
+ catalogUnavailable: false,
3178
+ totals,
3179
+ models,
3180
+ scenarios,
3181
+ monthlyEstimate
3182
+ };
3183
+ }
3184
+ //#endregion
3185
+ //#region src/wire-shared.ts
3186
+ /** The npm package identity every contribution and descriptor claims. */
3187
+ const REMOTE_PACKAGE = "@mars-sea/dsh-commandcode-provider";
3188
+ /** The Cordis service key the Gateway resolves every Command Code Remote from. */
3189
+ const REMOTE_SERVICE = "commandcodeUsage";
3190
+ /** The wire namespace all Command Code endpoints share. */
3191
+ const REMOTE_NAMESPACE = "commandcode";
3192
+ /**
3193
+ * Build the validator helpers one endpoint uses. `prefix` names the endpoint
3194
+ * in the rejection message (e.g. `commandcode/report result:`), so each wire
3195
+ * file keeps its own diagnostic phrasing while sharing the helper bodies.
3196
+ *
3197
+ * The helpers return the reject call directly in the failure branch: since
3198
+ * `reject` is typed `never`, the ternary's union collapses to the success type
3199
+ * without relying on TypeScript's control-flow analysis of a never-returning
3200
+ * call (which only recognizes function declarations, not the destructured
3201
+ * arrow `reject` callers receive from this factory). Callers that need the
3202
+ * narrowing in an `if` must therefore assign inside the positive branch.
3203
+ */
3204
+ function makeBoundaryValidator(prefix) {
3205
+ const reject = (field) => {
3206
+ throw new TypeError(`${prefix} invalid ${field}`);
3207
+ };
3208
+ const record = (value, field) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : reject(field);
3209
+ const stringField = (source, key, field) => typeof source[key] === "string" ? source[key] : reject(field);
3210
+ const numberField = (source, key, field) => typeof source[key] === "number" && Number.isFinite(source[key]) ? source[key] : reject(field);
3211
+ const booleanField = (source, key, field) => typeof source[key] === "boolean" ? source[key] : reject(field);
3212
+ return {
3213
+ reject,
3214
+ record,
3215
+ stringField,
3216
+ numberField,
3217
+ booleanField
3218
+ };
3219
+ }
3220
+ /** Use the same strict-codec shape for invocation parameters and results. */
3221
+ function makeStrictCodec(typeSymbol, schema) {
3222
+ return {
3223
+ mode: "strict",
3224
+ typeSymbol,
3225
+ create: () => schema
3226
+ };
3227
+ }
3228
+ /**
3229
+ * Build one strict invocation descriptor. Every Command Code Remote shares the
3230
+ * `commandcode` namespace, the `commandcodeUsage` service, and a strict result
3231
+ * — only the endpoint, method, result type symbol, and schema differ.
3232
+ */
3233
+ function makeRemoteDescriptor(endpoint, method, typeSymbol, schema) {
3234
+ return {
3235
+ id: `${REMOTE_PACKAGE}#${endpoint}`,
3236
+ service: REMOTE_SERVICE,
3237
+ namespace: REMOTE_NAMESPACE,
3238
+ method,
3239
+ invocation: { kind: "direct" },
3240
+ parameters: [],
3241
+ result: makeStrictCodec(typeSymbol, schema)
3242
+ };
3243
+ }
3244
+ //#endregion
3245
+ //#region src/analytics-wire.ts
3246
+ const g = makeBoundaryValidator("commandcode/analytics");
3247
+ const nonnegative = (s, k) => {
3248
+ const n = g.numberField(s, k, k);
3249
+ return n >= 0 ? n : g.reject(k);
3250
+ };
3251
+ const count = (s, k) => {
3252
+ const n = nonnegative(s, k);
3253
+ return Number.isSafeInteger(n) ? n : g.reject(k);
3254
+ };
3255
+ function parseAnalyticsQuery(value) {
3256
+ const s = g.record(value, "query");
3257
+ if (s.mode === "recent") {
3258
+ const limit = count(s, "limit");
3259
+ if (limit < 1 || limit > 100) return g.reject("limit");
3260
+ return {
3261
+ mode: "recent",
3262
+ limit
3263
+ };
3264
+ }
3265
+ if (s.mode !== "range") return g.reject("mode");
3266
+ const from = count(s, "from"), to = count(s, "to");
3267
+ if (to <= from || to - from > 316224e5 || to > 864e13) return g.reject("range");
3268
+ return {
3269
+ mode: "range",
3270
+ from,
3271
+ to
3272
+ };
3273
+ }
3274
+ const counts$2 = [
3275
+ "sessions",
3276
+ "requests",
3277
+ "completed",
3278
+ "failed",
3279
+ "cancelled",
3280
+ "unknown",
3281
+ "retries",
3282
+ "usageSamples",
3283
+ "pricedRequests",
3284
+ "latencySamples",
3285
+ "ttftSamples",
3286
+ "decodeSamples"
3287
+ ];
3288
+ const quantities = [
3289
+ "cost",
3290
+ "latencyMs",
3291
+ "ttftMs",
3292
+ "decodeMs",
3293
+ "decodeTokens"
3294
+ ];
3295
+ const tokenKeys = [
3296
+ "uncachedInputTokens",
3297
+ "outputTokens",
3298
+ "cacheReadTokens",
3299
+ "cacheWriteTokens"
3300
+ ];
3301
+ function metrics(value) {
3302
+ const s = g.record(value, "metrics"), result = {};
3303
+ for (const k of counts$2) result[k] = count(s, k);
3304
+ for (const k of quantities) result[k] = nonnegative(s, k);
3305
+ const tokens = g.record(s.tokens, "tokens");
3306
+ result.tokens = Object.fromEntries(tokenKeys.map((k) => [k, nonnegative(tokens, k)]));
3307
+ if (result.completed + result.failed + result.cancelled + result.unknown !== result.requests || result.pricedRequests > result.usageSamples || result.usageSamples > result.requests || result.retries > result.requests || result.ttftSamples > result.requests || result.latencySamples > result.requests || result.decodeSamples > result.requests) return g.reject("coverage");
3308
+ return result;
3309
+ }
3310
+ const nullable = (s, k) => s[k] === null ? null : nonnegative(s, k);
3311
+ function parseAnalyticsReport(value) {
3312
+ const s = g.record(value, "report");
3313
+ if (s.status !== "ready" && s.status !== "unavailable") return g.reject("status");
3314
+ if (!Array.isArray(s.models) || !Array.isArray(s.scenarios) || s.models.length > 1e3 || s.scenarios.length > 1e3) return g.reject("models");
3315
+ return {
3316
+ generatedAt: count(s, "generatedAt"),
3317
+ pricingKey: count(s, "pricingKey"),
3318
+ query: parseAnalyticsQuery(s.query),
3319
+ status: s.status,
3320
+ scannedSessions: count(s, "scannedSessions"),
3321
+ matchedSessions: count(s, "matchedSessions"),
3322
+ unreadableSessions: count(s, "unreadableSessions"),
3323
+ skippedSessions: count(s, "skippedSessions"),
3324
+ truncated: g.booleanField(s, "truncated", "truncated"),
3325
+ catalogUnavailable: g.booleanField(s, "catalogUnavailable", "catalogUnavailable"),
3326
+ totals: metrics(s.totals),
3327
+ monthlyEstimate: nullable(s, "monthlyEstimate"),
3328
+ models: s.models.map((value) => {
3329
+ const m = g.record(value, "model");
3330
+ return {
3331
+ ...metrics(m),
3332
+ id: g.stringField(m, "id", "id")
3333
+ };
3334
+ }),
3335
+ scenarios: s.scenarios.map((value) => {
3336
+ const m = g.record(value, "scenario");
3337
+ if (!Array.isArray(m.efforts) || !m.efforts.every((v) => typeof v === "string")) return g.reject("efforts");
3338
+ return {
3339
+ id: g.stringField(m, "id", "id"),
3340
+ name: g.stringField(m, "name", "name"),
3341
+ contextWindow: nullable(m, "contextWindow"),
3342
+ images: g.booleanField(m, "images", "images"),
3343
+ efforts: m.efforts,
3344
+ cost: nonnegative(m, "cost"),
3345
+ pricedRequests: count(m, "pricedRequests"),
3346
+ excludedRequests: count(m, "excludedRequests"),
3347
+ unknownRequests: count(m, "unknownRequests")
3348
+ };
3349
+ })
3350
+ };
3351
+ }
3352
+ const ANALYTICS_DESCRIPTOR = {
3353
+ ...makeRemoteDescriptor("commandcode/analytics", "analytics", `${REMOTE_PACKAGE}#AnalyticsReport`, { parse: parseAnalyticsReport }),
3354
+ parameters: [{
3355
+ name: "query",
3356
+ wire: "query",
3357
+ source: "json",
3358
+ codec: makeStrictCodec(`${REMOTE_PACKAGE}#AnalyticsQuery`, { parse: parseAnalyticsQuery })
3359
+ }]
3360
+ };
3361
+ /** 逐会话失败隔离;上限和遗漏计数会返回界面,部分结果不能伪装成完整历史。 */
3362
+ async function readAnalyticsHistory(history, input, catalog, signal) {
3363
+ const query = parseAnalyticsQuery(input), table = modelPriceTable();
3364
+ if (!history) return {
3365
+ ...buildAnalytics([], query, table, []),
3366
+ status: "unavailable"
3367
+ };
3368
+ const records = await history.listSessions(signal);
3369
+ const facts = [];
3370
+ let scannedSessions = 0, unreadableSessions = 0, skippedSessions = 0, events = 0, truncated = records.length > 500;
3371
+ for (const entry of records.slice(0, 500)) {
3372
+ if (signal?.aborted) throw new Error("模型分析已取消");
3373
+ if (events >= 2e5) {
3374
+ truncated = true;
3375
+ break;
3376
+ }
3377
+ scannedSessions++;
3378
+ try {
3379
+ const log = await history.readSession(entry.header.id);
3380
+ if (!Number.isSafeInteger(log.inheritedEventCount) || log.inheritedEventCount < 0 || log.inheritedEventCount > log.events.length) {
3381
+ skippedSessions++;
3382
+ continue;
3383
+ }
3384
+ events += log.events.length;
3385
+ if (events > 2e5) {
3386
+ skippedSessions++;
3387
+ truncated = true;
3388
+ break;
3389
+ }
3390
+ facts.push(...foldRequests(entry.header.id, log));
3391
+ } catch {
3392
+ unreadableSessions++;
3393
+ }
3394
+ }
3395
+ let selected = facts;
3396
+ if (query.mode === "recent") {
3397
+ const latest = /* @__PURE__ */ new Map();
3398
+ for (const f of facts) latest.set(f.session, Math.max(latest.get(f.session) ?? 0, f.at));
3399
+ const ids = new Set([...latest].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, query.limit).map(([id]) => id));
3400
+ selected = facts.filter((f) => ids.has(f.session));
3401
+ }
3402
+ let models = [], catalogUnavailable = false;
3403
+ if (signal?.aborted) throw new Error("模型分析已取消");
3404
+ try {
3405
+ models = await catalog();
3406
+ } catch {
3407
+ catalogUnavailable = true;
3408
+ }
3409
+ const report = buildAnalytics(selected, query, table, models);
3410
+ return {
3411
+ ...report,
3412
+ scannedSessions,
3413
+ unreadableSessions,
3414
+ skippedSessions,
3415
+ truncated,
3416
+ catalogUnavailable,
3417
+ monthlyEstimate: truncated || unreadableSessions > 0 || skippedSessions > 0 ? null : report.monthlyEstimate
3418
+ };
3419
+ }
3420
+ /** 用反射和可选 fiber 对接,保持未挂载历史服务的无界面/TUI 配置可用。 */
3421
+ function createAnalyticsReader(ctx, adapter) {
3422
+ let history, busy = false;
3423
+ ctx.inject(["sessionQuery"], (c) => {
3424
+ const service = c.get("sessionQuery");
3425
+ if (typeof service?.listSessions !== "function" || typeof service.readSession !== "function") return;
3426
+ history = service;
3427
+ c.effect(() => () => {
3428
+ if (history === service) history = void 0;
3429
+ }, "commandcode analytics history");
3430
+ });
3431
+ return async (query, signal) => {
3432
+ if (busy) throw new Error("模型分析正在读取历史,请稍后刷新");
3433
+ busy = true;
3434
+ try {
3435
+ return await readAnalyticsHistory(history, query, async () => {
3436
+ const available = await adapter.listModels("commandcode");
3437
+ return Promise.all(available.map(async (m) => {
3438
+ const resolved = await adapter.resolveModel("commandcode", m.id);
3439
+ return {
3440
+ id: m.id,
3441
+ name: m.name.replace(/ \(CC\)$/, ""),
3442
+ contextWindow: resolved.context?.contextWindow ?? null,
3443
+ images: resolved.inputModalities?.includes("image") ?? false,
3444
+ efforts: [...KNOWN_EFFORTS[m.id] ?? []]
3445
+ };
3446
+ }));
3447
+ }, signal);
3448
+ } finally {
3449
+ busy = false;
3450
+ }
3451
+ };
3452
+ }
3453
+ //#endregion
2909
3454
  //#region src/response-encoding.ts
2910
3455
  /**
2911
3456
  * Ask Command Code for plain response bodies. A DSH host using an incompatible
@@ -3970,6 +4515,11 @@ function openStreamTrace(scope, options = {}) {
3970
4515
  /** 仅供本次消费生命周期使用,不作为服务商错误或用户取消向外传播。 */
3971
4516
  var ConsumerStreamClosed = class extends Error {};
3972
4517
  /**
4518
+ * 读循环唤醒闸门的标记值:底层 read() 既不结算、取消也叫不醒时,由闸门结束这次等待。
4519
+ * 它不是错误,只在读循环内部出现,用来把「读取完成」与「读取卡住」区分开。
4520
+ */
4521
+ const READ_STALLED = Symbol("readStalled");
4522
+ /**
3973
4523
  * 异步生成器的 return 会排在正在等待的 next 后面,因此必须先通过独立信号
3974
4524
  * 唤醒当前读取,再让生成器执行 finally。消费方已结束时不再交付用量或错误。
3975
4525
  */
@@ -4031,7 +4581,12 @@ async function* readResponse(response, context) {
4031
4581
  reader.cancel().catch(() => void 0);
4032
4582
  } catch {}
4033
4583
  };
4034
- const onAbort = () => cancelReader();
4584
+ let openGate;
4585
+ const wakeRead = () => openGate?.();
4586
+ const onAbort = () => {
4587
+ wakeRead();
4588
+ cancelReader();
4589
+ };
4035
4590
  const decoder = new TextDecoder();
4036
4591
  let buffer = "";
4037
4592
  const streamStartedAt = Date.now();
@@ -4044,6 +4599,7 @@ async function* readResponse(response, context) {
4044
4599
  if (idleTimer !== void 0) clearTimeout(idleTimer);
4045
4600
  idleTimer = setTimeout(() => {
4046
4601
  idleFired = true;
4602
+ wakeRead();
4047
4603
  cancelReader();
4048
4604
  }, streamIdleTimeoutMs);
4049
4605
  };
@@ -4053,12 +4609,24 @@ async function* readResponse(response, context) {
4053
4609
  idleTimer = void 0;
4054
4610
  }
4055
4611
  };
4612
+ /** 空闲期限结束读循环时的唯一错误文本;读取结算与读取卡住两条路径共用。 */
4613
+ const idleTimeoutError = () => bilingual("TIMEOUT", `Command Code API stream from ${apiBase} was idle for ${streamIdleTimeoutMs}ms (no events) and was treated as a dead connection`, `Command Code API 流式响应已 ${streamIdleTimeoutMs} 毫秒无任何事件,被判定为死连接——长思考模型可在设置中调大流空闲超时`);
4056
4614
  const asm = createBlockAssembler();
4057
4615
  let finish;
4058
4616
  let usage;
4059
4617
  let usageEmitted = false;
4060
4618
  let endRecorded = false;
4061
4619
  let protocolStopped = false;
4620
+ /** 读循环收束(读到 EOF 或读取卡住)共用的诊断事实,两处记录字段保持一致。 */
4621
+ const eofFacts = () => ({
4622
+ chunks: chunkCount,
4623
+ bytes: totalBytes,
4624
+ silentMs: Date.now() - lastChunkAt,
4625
+ totalMs: Date.now() - streamStartedAt,
4626
+ finishSeen: finish !== void 0,
4627
+ idleFired,
4628
+ buffered: buffer.length
4629
+ });
4062
4630
  function* handle(event) {
4063
4631
  captureResponseIdentifiers(responseMetadata, event);
4064
4632
  if (isRecord(event) && (protocol === "messages" && event.type === "message_stop" || protocol === "cli" && event.type === "finish")) protocolStopped = true;
@@ -4084,9 +4652,14 @@ async function* readResponse(response, context) {
4084
4652
  for (;;) {
4085
4653
  signal?.throwIfAborted();
4086
4654
  let read;
4655
+ const gate = new Promise((resolve) => {
4656
+ openGate = () => resolve(READ_STALLED);
4657
+ });
4087
4658
  armIdle();
4088
4659
  try {
4089
- read = await reader.read();
4660
+ const pending = reader.read();
4661
+ pending.catch(() => void 0);
4662
+ read = await Promise.race([pending, gate]);
4090
4663
  } catch (error) {
4091
4664
  signal?.throwIfAborted();
4092
4665
  trace.record("read-error", {
@@ -4098,20 +4671,21 @@ async function* readResponse(response, context) {
4098
4671
  throw bilingual("TRANSPORT", `Command Code API stream from ${apiBase} failed while reading: ${errorChain(error)}`, "Command Code API 流式响应中途断开——网络波动所致,重试通常可恢复", { cause: error });
4099
4672
  } finally {
4100
4673
  clearIdle();
4674
+ openGate = void 0;
4675
+ }
4676
+ if (read === READ_STALLED) {
4677
+ trace.record("eof", {
4678
+ ...eofFacts(),
4679
+ readStalled: true
4680
+ });
4681
+ signal?.throwIfAborted();
4682
+ throw idleTimeoutError();
4101
4683
  }
4102
4684
  signal?.throwIfAborted();
4103
4685
  const { done, value } = read;
4104
4686
  if (done) {
4105
- trace.record("eof", {
4106
- chunks: chunkCount,
4107
- bytes: totalBytes,
4108
- silentMs: Date.now() - lastChunkAt,
4109
- totalMs: Date.now() - streamStartedAt,
4110
- finishSeen: finish !== void 0,
4111
- idleFired,
4112
- buffered: buffer.length
4113
- });
4114
- if (idleFired) throw bilingual("TIMEOUT", `Command Code API stream from ${apiBase} was idle for ${streamIdleTimeoutMs}ms (no events) and was treated as a dead connection`, `Command Code API 流式响应已 ${streamIdleTimeoutMs} 毫秒无任何事件,被判定为死连接——长思考模型可在设置中调大流空闲超时`);
4687
+ trace.record("eof", eofFacts());
4688
+ if (idleFired) throw idleTimeoutError();
4115
4689
  if (buffer.trim()) yield* handleLine(buffer);
4116
4690
  break;
4117
4691
  }
@@ -5328,7 +5902,7 @@ var GatewayFacts = class {
5328
5902
  * per-request options thunk and an API-key resolver from the plugin entry
5329
5903
  * (src/index.ts), so a settings change reaches the very next request.
5330
5904
  */
5331
- const COMMAND_CODE_CLI_VERSION = "1.79.2";
5905
+ const COMMAND_CODE_CLI_VERSION = "1.82.1";
5332
5906
  const DEFAULT_API_BASE = "https://api.commandcode.ai";
5333
5907
  /**
5334
5908
  * The output budget this bundle asks for, and the ceiling it will never exceed.
@@ -6404,6 +6978,70 @@ function parseAccountIdentity(whoami) {
6404
6978
  orgId: orgData === void 0 ? void 0 : stringValue(orgData.id)
6405
6979
  };
6406
6980
  }
6981
+ const JSON_READ_ERROR_NAMES = /* @__PURE__ */ new Set([
6982
+ "Error",
6983
+ "TypeError",
6984
+ "SyntaxError",
6985
+ "AbortError",
6986
+ "TimeoutError"
6987
+ ]);
6988
+ const JSON_READ_ERROR_CODES = /* @__PURE__ */ new Set([
6989
+ "ECONNRESET",
6990
+ "ETIMEDOUT",
6991
+ "EPIPE",
6992
+ "ERR_STREAM_PREMATURE_CLOSE",
6993
+ "ERR_INVALID_STATE",
6994
+ "UND_ERR_SOCKET",
6995
+ "UND_ERR_BODY_TIMEOUT",
6996
+ "UND_ERR_ABORTED",
6997
+ "Z_DATA_ERROR",
6998
+ "Z_BUF_ERROR"
6999
+ ]);
7000
+ const JSON_CONTENT_TYPES = /* @__PURE__ */ new Set([
7001
+ "application/json",
7002
+ "application/problem+json",
7003
+ "text/html",
7004
+ "text/plain",
7005
+ "application/octet-stream"
7006
+ ]);
7007
+ const JSON_CONTENT_ENCODINGS = /* @__PURE__ */ new Set([
7008
+ "identity",
7009
+ "gzip",
7010
+ "x-gzip",
7011
+ "deflate",
7012
+ "br",
7013
+ "zstd"
7014
+ ]);
7015
+ /** 原异常消息可能包含正文、账号或地址,只保留白名单标识,并有界读取 cause 链。 */
7016
+ function jsonReadErrorIdentifiers(error) {
7017
+ const identifiers = /* @__PURE__ */ new Set();
7018
+ const seen = /* @__PURE__ */ new Set();
7019
+ let current = error;
7020
+ for (let depth = 0; depth < 4 && isRecord(current) && !seen.has(current); depth++) {
7021
+ seen.add(current);
7022
+ if (typeof current.name === "string" && JSON_READ_ERROR_NAMES.has(current.name)) identifiers.add(current.name);
7023
+ if (typeof current.code === "string" && JSON_READ_ERROR_CODES.has(current.code)) identifiers.add(current.code);
7024
+ current = current.cause;
7025
+ }
7026
+ return [...identifiers];
7027
+ }
7028
+ /** 响应头只作为线索:不据此拒绝可解析正文,也不把未经筛选的头值放入账号报告。 */
7029
+ function jsonResponseMetadata(response) {
7030
+ const metadata = [];
7031
+ const contentType = response.headers.get("content-type")?.split(";")[0]?.trim().toLowerCase();
7032
+ if (contentType) metadata.push(`内容类型:${JSON_CONTENT_TYPES.has(contentType) ? contentType : "其他"}`);
7033
+ const encoding = response.headers.get("content-encoding")?.trim().toLowerCase();
7034
+ if (encoding) metadata.push(`内容编码:${JSON_CONTENT_ENCODINGS.has(encoding) ? encoding : "其他"}`);
7035
+ return metadata;
7036
+ }
7037
+ function jsonResponseFailureDetail(response, failure) {
7038
+ let detail;
7039
+ if (failure.stage === "read") detail = `${failure.timedOut ? "读取响应正文超时" : "响应正文读取中断"}${failure.identifiers.length === 0 ? "" : `(${failure.identifiers.join(" / ")})`}`;
7040
+ else if (failure.stage === "parse") detail = "响应正文不是有效的 JSON(结构化数据解析失败;SyntaxError)";
7041
+ else detail = `响应 JSON 顶层应为对象,实际为${failure.shape}`;
7042
+ const metadata = jsonResponseMetadata(response);
7043
+ return `${detail}${metadata.length === 0 ? "" : `;${metadata.join(";")}`}`;
7044
+ }
6407
7045
  function classifyTotalFailure(failures, failed) {
6408
7046
  if (failures.length !== USAGE_ENDPOINT_COUNT) return void 0;
6409
7047
  if (failed.every((item) => item.kind === "http" && item.status === 401)) return "invalid-key";
@@ -6977,34 +7615,59 @@ var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
6977
7615
  /**
6978
7616
  * Fetch one JSON GET and return its HTTP status with a parsed record. The
6979
7617
  * catalog, billing probe, and usage report each apply their own failure
6980
- * policy. Non-2xx and invalid JSON bodies come back without a record; only
6981
- * a fetch failure propagates to the caller.
7618
+ * policy. Non-2xx and unusable bodies come back without a record; only
7619
+ * a failure before receiving the response propagates to the caller.
7620
+ * 正文读取、JSON 解析与顶层结构分别诊断,不能把已收到响应后的断流当成格式错误。
6982
7621
  *
6983
7622
  * `timeoutMs` is the caller's budget, unless it provides its own signal.
6984
7623
  * The usage report passes the connection's request budget rather than the
6985
7624
  * catalog/picker's shorter default.
6986
7625
  */
6987
7626
  async fetchJson(url, headers, timeoutMs = MODELS_TIMEOUT_MS, signal) {
7627
+ const requestSignal = signal ?? AbortSignal.timeout(timeoutMs);
6988
7628
  const response = await this.fetchImpl(url, {
6989
7629
  headers,
6990
- signal: signal ?? AbortSignal.timeout(timeoutMs)
7630
+ signal: requestSignal
6991
7631
  });
6992
7632
  if (!response.ok) return { status: response.status };
7633
+ let text;
7634
+ try {
7635
+ text = await response.text();
7636
+ } catch (error) {
7637
+ const identifiers = jsonReadErrorIdentifiers(error);
7638
+ if (requestSignal.aborted) {
7639
+ for (const identifier of jsonReadErrorIdentifiers(requestSignal.reason)) if (!identifiers.includes(identifier)) identifiers.push(identifier);
7640
+ }
7641
+ const timedOut = identifiers.includes("TimeoutError") || identifiers.includes("ETIMEDOUT") || identifiers.includes("UND_ERR_BODY_TIMEOUT");
7642
+ return {
7643
+ status: response.status,
7644
+ responseFailure: jsonResponseFailureDetail(response, {
7645
+ stage: "read",
7646
+ identifiers,
7647
+ timedOut
7648
+ })
7649
+ };
7650
+ }
6993
7651
  let parsed;
6994
7652
  try {
6995
- parsed = await response.json();
7653
+ parsed = JSON.parse(text);
6996
7654
  } catch {
6997
7655
  return {
6998
7656
  status: response.status,
6999
- invalidResponse: true
7657
+ responseFailure: jsonResponseFailureDetail(response, { stage: "parse" })
7000
7658
  };
7001
7659
  }
7002
- return isRecord(parsed) ? {
7660
+ if (isRecord(parsed)) return {
7003
7661
  status: response.status,
7004
7662
  record: parsed
7005
- } : {
7663
+ };
7664
+ const shape = parsed === null ? "空值" : Array.isArray(parsed) ? "数组" : typeof parsed === "string" ? "字符串" : typeof parsed === "number" ? "数字" : "布尔值";
7665
+ return {
7006
7666
  status: response.status,
7007
- invalidResponse: true
7667
+ responseFailure: jsonResponseFailureDetail(response, {
7668
+ stage: "shape",
7669
+ shape
7670
+ })
7008
7671
  };
7009
7672
  }
7010
7673
  /**
@@ -7194,10 +7857,10 @@ var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
7194
7857
  const failed = [];
7195
7858
  const getJson = async (path) => {
7196
7859
  try {
7197
- const { status, record, invalidResponse } = await this.fetchJson(`${base}${path}`, headers, connection.requestTimeoutMs);
7860
+ const { status, record, responseFailure } = await this.fetchJson(`${base}${path}`, headers, connection.requestTimeoutMs);
7198
7861
  if (record === void 0) {
7199
- failures.push(`${path}: HTTP ${status}${invalidResponse ? " returned an unreadable or invalid JSON body" : ""}`);
7200
- failed.push(invalidResponse ? {
7862
+ failures.push(`${path}: HTTP ${status}${responseFailure ? `;${responseFailure}` : ""}`);
7863
+ failed.push(responseFailure ? {
7201
7864
  kind: "invalid-response",
7202
7865
  status
7203
7866
  } : {
@@ -7560,7 +8223,7 @@ const commandcodeCommand = {
7560
8223
  unconfigured: " (未配置 API 密钥)",
7561
8224
  blockedInvalidKey: "⛔ API 密钥无效或已过期 — 服务端拒绝了全部请求(401),请检查该账户的密钥配置",
7562
8225
  blockedServiceUnavailable: "⚠️ Command Code 服务暂时不可用(5xx),稍后重试",
7563
- blockedInvalidResponse: "⚠️ Command Code 返回了无法读取的响应 — 请检查宿主的 HTTP 代理或响应解压配置",
8226
+ blockedInvalidResponse: "Command Code 响应正文读取或解析失败,请根据下方详情排查并重试",
7564
8227
  blockedNetwork: "⚠️ 无法连接 Command Code 服务 — 请检查网络或 API 地址",
7565
8228
  planLine: " 📦 套餐 {name}{status}{period}",
7566
8229
  planPeriodSuffix: " · 账期截止 {date}",
@@ -7591,7 +8254,7 @@ const commandcodeCommand = {
7591
8254
  unconfigured: " (no API key configured)",
7592
8255
  blockedInvalidKey: "⛔ API key invalid or expired — the server rejected every request (401); check the key configured for this account",
7593
8256
  blockedServiceUnavailable: "⚠️ Command Code service temporarily unavailable (5xx); try again later",
7594
- blockedInvalidResponse: "⚠️ Command Code returned an unreadable response — check the host HTTP proxy or response decoding",
8257
+ blockedInvalidResponse: "Reading or parsing the Command Code response failed; check the details below and retry",
7595
8258
  blockedNetwork: "⚠️ could not reach the Command Code service — check your network or the API base setting",
7596
8259
  planLine: " 📦 Plan {name}{status}{period}",
7597
8260
  planPeriodSuffix: " · period ends {date}",
@@ -7759,66 +8422,6 @@ function applyCommands(ctx, deps) {
7759
8422
  ctx.commands.register(commandDefinition(deps));
7760
8423
  }
7761
8424
  //#endregion
7762
- //#region src/wire-shared.ts
7763
- /** The npm package identity every contribution and descriptor claims. */
7764
- const REMOTE_PACKAGE = "@mars-sea/dsh-commandcode-provider";
7765
- /** The Cordis service key the Gateway resolves every Command Code Remote from. */
7766
- const REMOTE_SERVICE = "commandcodeUsage";
7767
- /** The wire namespace all Command Code endpoints share. */
7768
- const REMOTE_NAMESPACE = "commandcode";
7769
- /**
7770
- * Build the validator helpers one endpoint uses. `prefix` names the endpoint
7771
- * in the rejection message (e.g. `commandcode/report result:`), so each wire
7772
- * file keeps its own diagnostic phrasing while sharing the helper bodies.
7773
- *
7774
- * The helpers return the reject call directly in the failure branch: since
7775
- * `reject` is typed `never`, the ternary's union collapses to the success type
7776
- * without relying on TypeScript's control-flow analysis of a never-returning
7777
- * call (which only recognizes function declarations, not the destructured
7778
- * arrow `reject` callers receive from this factory). Callers that need the
7779
- * narrowing in an `if` must therefore assign inside the positive branch.
7780
- */
7781
- function makeBoundaryValidator(prefix) {
7782
- const reject = (field) => {
7783
- throw new TypeError(`${prefix} invalid ${field}`);
7784
- };
7785
- const record = (value, field) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : reject(field);
7786
- const stringField = (source, key, field) => typeof source[key] === "string" ? source[key] : reject(field);
7787
- const numberField = (source, key, field) => typeof source[key] === "number" && Number.isFinite(source[key]) ? source[key] : reject(field);
7788
- const booleanField = (source, key, field) => typeof source[key] === "boolean" ? source[key] : reject(field);
7789
- return {
7790
- reject,
7791
- record,
7792
- stringField,
7793
- numberField,
7794
- booleanField
7795
- };
7796
- }
7797
- /** Use the same strict-codec shape for invocation parameters and results. */
7798
- function makeStrictCodec(typeSymbol, schema) {
7799
- return {
7800
- mode: "strict",
7801
- typeSymbol,
7802
- create: () => schema
7803
- };
7804
- }
7805
- /**
7806
- * Build one strict invocation descriptor. Every Command Code Remote shares the
7807
- * `commandcode` namespace, the `commandcodeUsage` service, and a strict result
7808
- * — only the endpoint, method, result type symbol, and schema differ.
7809
- */
7810
- function makeRemoteDescriptor(endpoint, method, typeSymbol, schema) {
7811
- return {
7812
- id: `${REMOTE_PACKAGE}#${endpoint}`,
7813
- service: REMOTE_SERVICE,
7814
- namespace: REMOTE_NAMESPACE,
7815
- method,
7816
- invocation: { kind: "direct" },
7817
- parameters: [],
7818
- result: makeStrictCodec(typeSymbol, schema)
7819
- };
7820
- }
7821
- //#endregion
7822
8425
  //#region src/usage-wire.ts
7823
8426
  /** The npm package identity both contribution registrations claim. */
7824
8427
  const USAGE_REMOTE_PACKAGE = REMOTE_PACKAGE;
@@ -8275,6 +8878,10 @@ var CommandCodeUsageService = class extends TypertRemoteService {
8275
8878
  async prices() {
8276
8879
  return (this.deps.prices ?? modelPriceTable)();
8277
8880
  }
8881
+ async analytics(query) {
8882
+ if (!this.deps.analytics) throw new Error("宿主不支持模型分析,请更新插件");
8883
+ return this.deps.analytics(query, this.ctx.invocation?.signal);
8884
+ }
8278
8885
  /**
8279
8886
  * Start (or rejoin) a browser-login attempt and return its fresh status —
8280
8887
  * `waiting` carrying the Studio URL. Rejects when the flow cannot start
@@ -8364,6 +8971,7 @@ function applyUsageRemote(ctx, deps) {
8364
8971
  ...USAGE_HOST_CONTRIBUTION.invocations,
8365
8972
  MODELS_DESCRIPTOR,
8366
8973
  PRICES_DESCRIPTOR,
8974
+ ANALYTICS_DESCRIPTOR,
8367
8975
  ...LOGIN_HOST_CONTRIBUTION.invocations,
8368
8976
  ...ENROLLMENT_DESCRIPTORS
8369
8977
  ]
@@ -9922,8 +10530,8 @@ function transportBudgetMessage(failureMessage, maxRetries) {
9922
10530
  /**
9923
10531
  * Mark one schema field volatile.
9924
10532
  *
9925
- * `.volatile()` is unconditional: it is a schemastery 3.18.3 feature and the
9926
- * only supported engine (`dsh 0.2.1-alpha.1`) pins `~3.18.5-alpha.1`, so the
10533
+ * `.volatile()` is unconditional: it is a schemastery 3.18.3 feature and every
10534
+ * supported engine ships `~3.18.4` or `~3.18.5-alpha.1`, so the
9927
10535
  * method is always present. It is reached through a structural member so the caller's
9928
10536
  * declared field type survives — the mark changes what the schema PARSES to (a
9929
10537
  * live reference), not the plugin-facing `Config` shape.
@@ -10309,7 +10917,8 @@ function apply(ctx, config) {
10309
10917
  reports: usageReports,
10310
10918
  login: loginFlow,
10311
10919
  enrollment,
10312
- listModels: catalogForEditors
10920
+ listModels: catalogForEditors,
10921
+ analytics: createAnalyticsReader(ctx, adapter)
10313
10922
  });
10314
10923
  const searchSelection = commandCodeSearchSelection();
10315
10924
  const applySearchSelection = (enabled) => {