@mars-sea/dsh-commandcode-provider 0.12.10 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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
@@ -5357,7 +5902,7 @@ var GatewayFacts = class {
5357
5902
  * per-request options thunk and an API-key resolver from the plugin entry
5358
5903
  * (src/index.ts), so a settings change reaches the very next request.
5359
5904
  */
5360
- const COMMAND_CODE_CLI_VERSION = "1.79.2";
5905
+ const COMMAND_CODE_CLI_VERSION = "1.82.1";
5361
5906
  const DEFAULT_API_BASE = "https://api.commandcode.ai";
5362
5907
  /**
5363
5908
  * The output budget this bundle asks for, and the ceiling it will never exceed.
@@ -6433,6 +6978,70 @@ function parseAccountIdentity(whoami) {
6433
6978
  orgId: orgData === void 0 ? void 0 : stringValue(orgData.id)
6434
6979
  };
6435
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
+ }
6436
7045
  function classifyTotalFailure(failures, failed) {
6437
7046
  if (failures.length !== USAGE_ENDPOINT_COUNT) return void 0;
6438
7047
  if (failed.every((item) => item.kind === "http" && item.status === 401)) return "invalid-key";
@@ -7006,34 +7615,59 @@ var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
7006
7615
  /**
7007
7616
  * Fetch one JSON GET and return its HTTP status with a parsed record. The
7008
7617
  * catalog, billing probe, and usage report each apply their own failure
7009
- * policy. Non-2xx and invalid JSON bodies come back without a record; only
7010
- * 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 解析与顶层结构分别诊断,不能把已收到响应后的断流当成格式错误。
7011
7621
  *
7012
7622
  * `timeoutMs` is the caller's budget, unless it provides its own signal.
7013
7623
  * The usage report passes the connection's request budget rather than the
7014
7624
  * catalog/picker's shorter default.
7015
7625
  */
7016
7626
  async fetchJson(url, headers, timeoutMs = MODELS_TIMEOUT_MS, signal) {
7627
+ const requestSignal = signal ?? AbortSignal.timeout(timeoutMs);
7017
7628
  const response = await this.fetchImpl(url, {
7018
7629
  headers,
7019
- signal: signal ?? AbortSignal.timeout(timeoutMs)
7630
+ signal: requestSignal
7020
7631
  });
7021
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
+ }
7022
7651
  let parsed;
7023
7652
  try {
7024
- parsed = await response.json();
7653
+ parsed = JSON.parse(text);
7025
7654
  } catch {
7026
7655
  return {
7027
7656
  status: response.status,
7028
- invalidResponse: true
7657
+ responseFailure: jsonResponseFailureDetail(response, { stage: "parse" })
7029
7658
  };
7030
7659
  }
7031
- return isRecord(parsed) ? {
7660
+ if (isRecord(parsed)) return {
7032
7661
  status: response.status,
7033
7662
  record: parsed
7034
- } : {
7663
+ };
7664
+ const shape = parsed === null ? "空值" : Array.isArray(parsed) ? "数组" : typeof parsed === "string" ? "字符串" : typeof parsed === "number" ? "数字" : "布尔值";
7665
+ return {
7035
7666
  status: response.status,
7036
- invalidResponse: true
7667
+ responseFailure: jsonResponseFailureDetail(response, {
7668
+ stage: "shape",
7669
+ shape
7670
+ })
7037
7671
  };
7038
7672
  }
7039
7673
  /**
@@ -7223,10 +7857,10 @@ var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
7223
7857
  const failed = [];
7224
7858
  const getJson = async (path) => {
7225
7859
  try {
7226
- 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);
7227
7861
  if (record === void 0) {
7228
- failures.push(`${path}: HTTP ${status}${invalidResponse ? " returned an unreadable or invalid JSON body" : ""}`);
7229
- failed.push(invalidResponse ? {
7862
+ failures.push(`${path}: HTTP ${status}${responseFailure ? `;${responseFailure}` : ""}`);
7863
+ failed.push(responseFailure ? {
7230
7864
  kind: "invalid-response",
7231
7865
  status
7232
7866
  } : {
@@ -7589,7 +8223,7 @@ const commandcodeCommand = {
7589
8223
  unconfigured: " (未配置 API 密钥)",
7590
8224
  blockedInvalidKey: "⛔ API 密钥无效或已过期 — 服务端拒绝了全部请求(401),请检查该账户的密钥配置",
7591
8225
  blockedServiceUnavailable: "⚠️ Command Code 服务暂时不可用(5xx),稍后重试",
7592
- blockedInvalidResponse: "⚠️ Command Code 返回了无法读取的响应 — 请检查宿主的 HTTP 代理或响应解压配置",
8226
+ blockedInvalidResponse: "Command Code 响应正文读取或解析失败,请根据下方详情排查并重试",
7593
8227
  blockedNetwork: "⚠️ 无法连接 Command Code 服务 — 请检查网络或 API 地址",
7594
8228
  planLine: " 📦 套餐 {name}{status}{period}",
7595
8229
  planPeriodSuffix: " · 账期截止 {date}",
@@ -7620,7 +8254,7 @@ const commandcodeCommand = {
7620
8254
  unconfigured: " (no API key configured)",
7621
8255
  blockedInvalidKey: "⛔ API key invalid or expired — the server rejected every request (401); check the key configured for this account",
7622
8256
  blockedServiceUnavailable: "⚠️ Command Code service temporarily unavailable (5xx); try again later",
7623
- 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",
7624
8258
  blockedNetwork: "⚠️ could not reach the Command Code service — check your network or the API base setting",
7625
8259
  planLine: " 📦 Plan {name}{status}{period}",
7626
8260
  planPeriodSuffix: " · period ends {date}",
@@ -7788,66 +8422,6 @@ function applyCommands(ctx, deps) {
7788
8422
  ctx.commands.register(commandDefinition(deps));
7789
8423
  }
7790
8424
  //#endregion
7791
- //#region src/wire-shared.ts
7792
- /** The npm package identity every contribution and descriptor claims. */
7793
- const REMOTE_PACKAGE = "@mars-sea/dsh-commandcode-provider";
7794
- /** The Cordis service key the Gateway resolves every Command Code Remote from. */
7795
- const REMOTE_SERVICE = "commandcodeUsage";
7796
- /** The wire namespace all Command Code endpoints share. */
7797
- const REMOTE_NAMESPACE = "commandcode";
7798
- /**
7799
- * Build the validator helpers one endpoint uses. `prefix` names the endpoint
7800
- * in the rejection message (e.g. `commandcode/report result:`), so each wire
7801
- * file keeps its own diagnostic phrasing while sharing the helper bodies.
7802
- *
7803
- * The helpers return the reject call directly in the failure branch: since
7804
- * `reject` is typed `never`, the ternary's union collapses to the success type
7805
- * without relying on TypeScript's control-flow analysis of a never-returning
7806
- * call (which only recognizes function declarations, not the destructured
7807
- * arrow `reject` callers receive from this factory). Callers that need the
7808
- * narrowing in an `if` must therefore assign inside the positive branch.
7809
- */
7810
- function makeBoundaryValidator(prefix) {
7811
- const reject = (field) => {
7812
- throw new TypeError(`${prefix} invalid ${field}`);
7813
- };
7814
- const record = (value, field) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : reject(field);
7815
- const stringField = (source, key, field) => typeof source[key] === "string" ? source[key] : reject(field);
7816
- const numberField = (source, key, field) => typeof source[key] === "number" && Number.isFinite(source[key]) ? source[key] : reject(field);
7817
- const booleanField = (source, key, field) => typeof source[key] === "boolean" ? source[key] : reject(field);
7818
- return {
7819
- reject,
7820
- record,
7821
- stringField,
7822
- numberField,
7823
- booleanField
7824
- };
7825
- }
7826
- /** Use the same strict-codec shape for invocation parameters and results. */
7827
- function makeStrictCodec(typeSymbol, schema) {
7828
- return {
7829
- mode: "strict",
7830
- typeSymbol,
7831
- create: () => schema
7832
- };
7833
- }
7834
- /**
7835
- * Build one strict invocation descriptor. Every Command Code Remote shares the
7836
- * `commandcode` namespace, the `commandcodeUsage` service, and a strict result
7837
- * — only the endpoint, method, result type symbol, and schema differ.
7838
- */
7839
- function makeRemoteDescriptor(endpoint, method, typeSymbol, schema) {
7840
- return {
7841
- id: `${REMOTE_PACKAGE}#${endpoint}`,
7842
- service: REMOTE_SERVICE,
7843
- namespace: REMOTE_NAMESPACE,
7844
- method,
7845
- invocation: { kind: "direct" },
7846
- parameters: [],
7847
- result: makeStrictCodec(typeSymbol, schema)
7848
- };
7849
- }
7850
- //#endregion
7851
8425
  //#region src/usage-wire.ts
7852
8426
  /** The npm package identity both contribution registrations claim. */
7853
8427
  const USAGE_REMOTE_PACKAGE = REMOTE_PACKAGE;
@@ -8304,6 +8878,10 @@ var CommandCodeUsageService = class extends TypertRemoteService {
8304
8878
  async prices() {
8305
8879
  return (this.deps.prices ?? modelPriceTable)();
8306
8880
  }
8881
+ async analytics(query) {
8882
+ if (!this.deps.analytics) throw new Error("宿主不支持模型分析,请更新插件");
8883
+ return this.deps.analytics(query, this.ctx.invocation?.signal);
8884
+ }
8307
8885
  /**
8308
8886
  * Start (or rejoin) a browser-login attempt and return its fresh status —
8309
8887
  * `waiting` carrying the Studio URL. Rejects when the flow cannot start
@@ -8393,6 +8971,7 @@ function applyUsageRemote(ctx, deps) {
8393
8971
  ...USAGE_HOST_CONTRIBUTION.invocations,
8394
8972
  MODELS_DESCRIPTOR,
8395
8973
  PRICES_DESCRIPTOR,
8974
+ ANALYTICS_DESCRIPTOR,
8396
8975
  ...LOGIN_HOST_CONTRIBUTION.invocations,
8397
8976
  ...ENROLLMENT_DESCRIPTORS
8398
8977
  ]
@@ -10338,7 +10917,8 @@ function apply(ctx, config) {
10338
10917
  reports: usageReports,
10339
10918
  login: loginFlow,
10340
10919
  enrollment,
10341
- listModels: catalogForEditors
10920
+ listModels: catalogForEditors,
10921
+ analytics: createAnalyticsReader(ctx, adapter)
10342
10922
  });
10343
10923
  const searchSelection = commandCodeSearchSelection();
10344
10924
  const applySearchSelection = (enabled) => {