@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/CHANGELOG.md +28 -0
- package/README.md +10 -10
- package/README.zh-CN.md +5 -4
- package/lib/client.js +1354 -86
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +81 -8
- package/lib/index.js +690 -110
- package/lib/index.js.map +1 -1
- package/package.json +37 -38
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.
|
|
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.
|
|
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:
|
|
701
|
-
* catalog ids as of 2026-
|
|
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
|
|
705
|
-
*
|
|
706
|
-
*
|
|
707
|
-
* `
|
|
708
|
-
* family's uniform ceiling.
|
|
709
|
-
*
|
|
710
|
-
*
|
|
711
|
-
*
|
|
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.
|
|
808
|
-
* ids at 54/64/
|
|
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;
|
|
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:
|
|
1501
|
-
pro:
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
2792
|
-
const chunk = item?.type === "chunk" ? record$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
2873
|
+
const data = record$3(event.data);
|
|
2825
2874
|
if (!data) return state;
|
|
2826
2875
|
if (event.type === "request/header") {
|
|
2827
|
-
const config = record$
|
|
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.
|
|
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
|
|
7010
|
-
* a
|
|
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:
|
|
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 =
|
|
7653
|
+
parsed = JSON.parse(text);
|
|
7025
7654
|
} catch {
|
|
7026
7655
|
return {
|
|
7027
7656
|
status: response.status,
|
|
7028
|
-
|
|
7657
|
+
responseFailure: jsonResponseFailureDetail(response, { stage: "parse" })
|
|
7029
7658
|
};
|
|
7030
7659
|
}
|
|
7031
|
-
|
|
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
|
-
|
|
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,
|
|
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}${
|
|
7229
|
-
failed.push(
|
|
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: "
|
|
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: "
|
|
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) => {
|