@mars-sea/dsh-commandcode-provider 0.12.9 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -0
- package/README.md +11 -9
- package/README.zh-CN.md +6 -5
- package/lib/client.js +1201 -87
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +81 -8
- package/lib/index.js +733 -124
- package/lib/index.js.map +1 -1
- package/package.json +38 -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
|
|
@@ -3970,6 +4515,11 @@ function openStreamTrace(scope, options = {}) {
|
|
|
3970
4515
|
/** 仅供本次消费生命周期使用,不作为服务商错误或用户取消向外传播。 */
|
|
3971
4516
|
var ConsumerStreamClosed = class extends Error {};
|
|
3972
4517
|
/**
|
|
4518
|
+
* 读循环唤醒闸门的标记值:底层 read() 既不结算、取消也叫不醒时,由闸门结束这次等待。
|
|
4519
|
+
* 它不是错误,只在读循环内部出现,用来把「读取完成」与「读取卡住」区分开。
|
|
4520
|
+
*/
|
|
4521
|
+
const READ_STALLED = Symbol("readStalled");
|
|
4522
|
+
/**
|
|
3973
4523
|
* 异步生成器的 return 会排在正在等待的 next 后面,因此必须先通过独立信号
|
|
3974
4524
|
* 唤醒当前读取,再让生成器执行 finally。消费方已结束时不再交付用量或错误。
|
|
3975
4525
|
*/
|
|
@@ -4031,7 +4581,12 @@ async function* readResponse(response, context) {
|
|
|
4031
4581
|
reader.cancel().catch(() => void 0);
|
|
4032
4582
|
} catch {}
|
|
4033
4583
|
};
|
|
4034
|
-
|
|
4584
|
+
let openGate;
|
|
4585
|
+
const wakeRead = () => openGate?.();
|
|
4586
|
+
const onAbort = () => {
|
|
4587
|
+
wakeRead();
|
|
4588
|
+
cancelReader();
|
|
4589
|
+
};
|
|
4035
4590
|
const decoder = new TextDecoder();
|
|
4036
4591
|
let buffer = "";
|
|
4037
4592
|
const streamStartedAt = Date.now();
|
|
@@ -4044,6 +4599,7 @@ async function* readResponse(response, context) {
|
|
|
4044
4599
|
if (idleTimer !== void 0) clearTimeout(idleTimer);
|
|
4045
4600
|
idleTimer = setTimeout(() => {
|
|
4046
4601
|
idleFired = true;
|
|
4602
|
+
wakeRead();
|
|
4047
4603
|
cancelReader();
|
|
4048
4604
|
}, streamIdleTimeoutMs);
|
|
4049
4605
|
};
|
|
@@ -4053,12 +4609,24 @@ async function* readResponse(response, context) {
|
|
|
4053
4609
|
idleTimer = void 0;
|
|
4054
4610
|
}
|
|
4055
4611
|
};
|
|
4612
|
+
/** 空闲期限结束读循环时的唯一错误文本;读取结算与读取卡住两条路径共用。 */
|
|
4613
|
+
const idleTimeoutError = () => bilingual("TIMEOUT", `Command Code API stream from ${apiBase} was idle for ${streamIdleTimeoutMs}ms (no events) and was treated as a dead connection`, `Command Code API 流式响应已 ${streamIdleTimeoutMs} 毫秒无任何事件,被判定为死连接——长思考模型可在设置中调大流空闲超时`);
|
|
4056
4614
|
const asm = createBlockAssembler();
|
|
4057
4615
|
let finish;
|
|
4058
4616
|
let usage;
|
|
4059
4617
|
let usageEmitted = false;
|
|
4060
4618
|
let endRecorded = false;
|
|
4061
4619
|
let protocolStopped = false;
|
|
4620
|
+
/** 读循环收束(读到 EOF 或读取卡住)共用的诊断事实,两处记录字段保持一致。 */
|
|
4621
|
+
const eofFacts = () => ({
|
|
4622
|
+
chunks: chunkCount,
|
|
4623
|
+
bytes: totalBytes,
|
|
4624
|
+
silentMs: Date.now() - lastChunkAt,
|
|
4625
|
+
totalMs: Date.now() - streamStartedAt,
|
|
4626
|
+
finishSeen: finish !== void 0,
|
|
4627
|
+
idleFired,
|
|
4628
|
+
buffered: buffer.length
|
|
4629
|
+
});
|
|
4062
4630
|
function* handle(event) {
|
|
4063
4631
|
captureResponseIdentifiers(responseMetadata, event);
|
|
4064
4632
|
if (isRecord(event) && (protocol === "messages" && event.type === "message_stop" || protocol === "cli" && event.type === "finish")) protocolStopped = true;
|
|
@@ -4084,9 +4652,14 @@ async function* readResponse(response, context) {
|
|
|
4084
4652
|
for (;;) {
|
|
4085
4653
|
signal?.throwIfAborted();
|
|
4086
4654
|
let read;
|
|
4655
|
+
const gate = new Promise((resolve) => {
|
|
4656
|
+
openGate = () => resolve(READ_STALLED);
|
|
4657
|
+
});
|
|
4087
4658
|
armIdle();
|
|
4088
4659
|
try {
|
|
4089
|
-
|
|
4660
|
+
const pending = reader.read();
|
|
4661
|
+
pending.catch(() => void 0);
|
|
4662
|
+
read = await Promise.race([pending, gate]);
|
|
4090
4663
|
} catch (error) {
|
|
4091
4664
|
signal?.throwIfAborted();
|
|
4092
4665
|
trace.record("read-error", {
|
|
@@ -4098,20 +4671,21 @@ async function* readResponse(response, context) {
|
|
|
4098
4671
|
throw bilingual("TRANSPORT", `Command Code API stream from ${apiBase} failed while reading: ${errorChain(error)}`, "Command Code API 流式响应中途断开——网络波动所致,重试通常可恢复", { cause: error });
|
|
4099
4672
|
} finally {
|
|
4100
4673
|
clearIdle();
|
|
4674
|
+
openGate = void 0;
|
|
4675
|
+
}
|
|
4676
|
+
if (read === READ_STALLED) {
|
|
4677
|
+
trace.record("eof", {
|
|
4678
|
+
...eofFacts(),
|
|
4679
|
+
readStalled: true
|
|
4680
|
+
});
|
|
4681
|
+
signal?.throwIfAborted();
|
|
4682
|
+
throw idleTimeoutError();
|
|
4101
4683
|
}
|
|
4102
4684
|
signal?.throwIfAborted();
|
|
4103
4685
|
const { done, value } = read;
|
|
4104
4686
|
if (done) {
|
|
4105
|
-
trace.record("eof",
|
|
4106
|
-
|
|
4107
|
-
bytes: totalBytes,
|
|
4108
|
-
silentMs: Date.now() - lastChunkAt,
|
|
4109
|
-
totalMs: Date.now() - streamStartedAt,
|
|
4110
|
-
finishSeen: finish !== void 0,
|
|
4111
|
-
idleFired,
|
|
4112
|
-
buffered: buffer.length
|
|
4113
|
-
});
|
|
4114
|
-
if (idleFired) throw bilingual("TIMEOUT", `Command Code API stream from ${apiBase} was idle for ${streamIdleTimeoutMs}ms (no events) and was treated as a dead connection`, `Command Code API 流式响应已 ${streamIdleTimeoutMs} 毫秒无任何事件,被判定为死连接——长思考模型可在设置中调大流空闲超时`);
|
|
4687
|
+
trace.record("eof", eofFacts());
|
|
4688
|
+
if (idleFired) throw idleTimeoutError();
|
|
4115
4689
|
if (buffer.trim()) yield* handleLine(buffer);
|
|
4116
4690
|
break;
|
|
4117
4691
|
}
|
|
@@ -5328,7 +5902,7 @@ var GatewayFacts = class {
|
|
|
5328
5902
|
* per-request options thunk and an API-key resolver from the plugin entry
|
|
5329
5903
|
* (src/index.ts), so a settings change reaches the very next request.
|
|
5330
5904
|
*/
|
|
5331
|
-
const COMMAND_CODE_CLI_VERSION = "1.
|
|
5905
|
+
const COMMAND_CODE_CLI_VERSION = "1.82.1";
|
|
5332
5906
|
const DEFAULT_API_BASE = "https://api.commandcode.ai";
|
|
5333
5907
|
/**
|
|
5334
5908
|
* The output budget this bundle asks for, and the ceiling it will never exceed.
|
|
@@ -6404,6 +6978,70 @@ function parseAccountIdentity(whoami) {
|
|
|
6404
6978
|
orgId: orgData === void 0 ? void 0 : stringValue(orgData.id)
|
|
6405
6979
|
};
|
|
6406
6980
|
}
|
|
6981
|
+
const JSON_READ_ERROR_NAMES = /* @__PURE__ */ new Set([
|
|
6982
|
+
"Error",
|
|
6983
|
+
"TypeError",
|
|
6984
|
+
"SyntaxError",
|
|
6985
|
+
"AbortError",
|
|
6986
|
+
"TimeoutError"
|
|
6987
|
+
]);
|
|
6988
|
+
const JSON_READ_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
6989
|
+
"ECONNRESET",
|
|
6990
|
+
"ETIMEDOUT",
|
|
6991
|
+
"EPIPE",
|
|
6992
|
+
"ERR_STREAM_PREMATURE_CLOSE",
|
|
6993
|
+
"ERR_INVALID_STATE",
|
|
6994
|
+
"UND_ERR_SOCKET",
|
|
6995
|
+
"UND_ERR_BODY_TIMEOUT",
|
|
6996
|
+
"UND_ERR_ABORTED",
|
|
6997
|
+
"Z_DATA_ERROR",
|
|
6998
|
+
"Z_BUF_ERROR"
|
|
6999
|
+
]);
|
|
7000
|
+
const JSON_CONTENT_TYPES = /* @__PURE__ */ new Set([
|
|
7001
|
+
"application/json",
|
|
7002
|
+
"application/problem+json",
|
|
7003
|
+
"text/html",
|
|
7004
|
+
"text/plain",
|
|
7005
|
+
"application/octet-stream"
|
|
7006
|
+
]);
|
|
7007
|
+
const JSON_CONTENT_ENCODINGS = /* @__PURE__ */ new Set([
|
|
7008
|
+
"identity",
|
|
7009
|
+
"gzip",
|
|
7010
|
+
"x-gzip",
|
|
7011
|
+
"deflate",
|
|
7012
|
+
"br",
|
|
7013
|
+
"zstd"
|
|
7014
|
+
]);
|
|
7015
|
+
/** 原异常消息可能包含正文、账号或地址,只保留白名单标识,并有界读取 cause 链。 */
|
|
7016
|
+
function jsonReadErrorIdentifiers(error) {
|
|
7017
|
+
const identifiers = /* @__PURE__ */ new Set();
|
|
7018
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7019
|
+
let current = error;
|
|
7020
|
+
for (let depth = 0; depth < 4 && isRecord(current) && !seen.has(current); depth++) {
|
|
7021
|
+
seen.add(current);
|
|
7022
|
+
if (typeof current.name === "string" && JSON_READ_ERROR_NAMES.has(current.name)) identifiers.add(current.name);
|
|
7023
|
+
if (typeof current.code === "string" && JSON_READ_ERROR_CODES.has(current.code)) identifiers.add(current.code);
|
|
7024
|
+
current = current.cause;
|
|
7025
|
+
}
|
|
7026
|
+
return [...identifiers];
|
|
7027
|
+
}
|
|
7028
|
+
/** 响应头只作为线索:不据此拒绝可解析正文,也不把未经筛选的头值放入账号报告。 */
|
|
7029
|
+
function jsonResponseMetadata(response) {
|
|
7030
|
+
const metadata = [];
|
|
7031
|
+
const contentType = response.headers.get("content-type")?.split(";")[0]?.trim().toLowerCase();
|
|
7032
|
+
if (contentType) metadata.push(`内容类型:${JSON_CONTENT_TYPES.has(contentType) ? contentType : "其他"}`);
|
|
7033
|
+
const encoding = response.headers.get("content-encoding")?.trim().toLowerCase();
|
|
7034
|
+
if (encoding) metadata.push(`内容编码:${JSON_CONTENT_ENCODINGS.has(encoding) ? encoding : "其他"}`);
|
|
7035
|
+
return metadata;
|
|
7036
|
+
}
|
|
7037
|
+
function jsonResponseFailureDetail(response, failure) {
|
|
7038
|
+
let detail;
|
|
7039
|
+
if (failure.stage === "read") detail = `${failure.timedOut ? "读取响应正文超时" : "响应正文读取中断"}${failure.identifiers.length === 0 ? "" : `(${failure.identifiers.join(" / ")})`}`;
|
|
7040
|
+
else if (failure.stage === "parse") detail = "响应正文不是有效的 JSON(结构化数据解析失败;SyntaxError)";
|
|
7041
|
+
else detail = `响应 JSON 顶层应为对象,实际为${failure.shape}`;
|
|
7042
|
+
const metadata = jsonResponseMetadata(response);
|
|
7043
|
+
return `${detail}${metadata.length === 0 ? "" : `;${metadata.join(";")}`}`;
|
|
7044
|
+
}
|
|
6407
7045
|
function classifyTotalFailure(failures, failed) {
|
|
6408
7046
|
if (failures.length !== USAGE_ENDPOINT_COUNT) return void 0;
|
|
6409
7047
|
if (failed.every((item) => item.kind === "http" && item.status === 401)) return "invalid-key";
|
|
@@ -6977,34 +7615,59 @@ var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
|
|
|
6977
7615
|
/**
|
|
6978
7616
|
* Fetch one JSON GET and return its HTTP status with a parsed record. The
|
|
6979
7617
|
* catalog, billing probe, and usage report each apply their own failure
|
|
6980
|
-
* policy. Non-2xx and
|
|
6981
|
-
* 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 解析与顶层结构分别诊断,不能把已收到响应后的断流当成格式错误。
|
|
6982
7621
|
*
|
|
6983
7622
|
* `timeoutMs` is the caller's budget, unless it provides its own signal.
|
|
6984
7623
|
* The usage report passes the connection's request budget rather than the
|
|
6985
7624
|
* catalog/picker's shorter default.
|
|
6986
7625
|
*/
|
|
6987
7626
|
async fetchJson(url, headers, timeoutMs = MODELS_TIMEOUT_MS, signal) {
|
|
7627
|
+
const requestSignal = signal ?? AbortSignal.timeout(timeoutMs);
|
|
6988
7628
|
const response = await this.fetchImpl(url, {
|
|
6989
7629
|
headers,
|
|
6990
|
-
signal:
|
|
7630
|
+
signal: requestSignal
|
|
6991
7631
|
});
|
|
6992
7632
|
if (!response.ok) return { status: response.status };
|
|
7633
|
+
let text;
|
|
7634
|
+
try {
|
|
7635
|
+
text = await response.text();
|
|
7636
|
+
} catch (error) {
|
|
7637
|
+
const identifiers = jsonReadErrorIdentifiers(error);
|
|
7638
|
+
if (requestSignal.aborted) {
|
|
7639
|
+
for (const identifier of jsonReadErrorIdentifiers(requestSignal.reason)) if (!identifiers.includes(identifier)) identifiers.push(identifier);
|
|
7640
|
+
}
|
|
7641
|
+
const timedOut = identifiers.includes("TimeoutError") || identifiers.includes("ETIMEDOUT") || identifiers.includes("UND_ERR_BODY_TIMEOUT");
|
|
7642
|
+
return {
|
|
7643
|
+
status: response.status,
|
|
7644
|
+
responseFailure: jsonResponseFailureDetail(response, {
|
|
7645
|
+
stage: "read",
|
|
7646
|
+
identifiers,
|
|
7647
|
+
timedOut
|
|
7648
|
+
})
|
|
7649
|
+
};
|
|
7650
|
+
}
|
|
6993
7651
|
let parsed;
|
|
6994
7652
|
try {
|
|
6995
|
-
parsed =
|
|
7653
|
+
parsed = JSON.parse(text);
|
|
6996
7654
|
} catch {
|
|
6997
7655
|
return {
|
|
6998
7656
|
status: response.status,
|
|
6999
|
-
|
|
7657
|
+
responseFailure: jsonResponseFailureDetail(response, { stage: "parse" })
|
|
7000
7658
|
};
|
|
7001
7659
|
}
|
|
7002
|
-
|
|
7660
|
+
if (isRecord(parsed)) return {
|
|
7003
7661
|
status: response.status,
|
|
7004
7662
|
record: parsed
|
|
7005
|
-
}
|
|
7663
|
+
};
|
|
7664
|
+
const shape = parsed === null ? "空值" : Array.isArray(parsed) ? "数组" : typeof parsed === "string" ? "字符串" : typeof parsed === "number" ? "数字" : "布尔值";
|
|
7665
|
+
return {
|
|
7006
7666
|
status: response.status,
|
|
7007
|
-
|
|
7667
|
+
responseFailure: jsonResponseFailureDetail(response, {
|
|
7668
|
+
stage: "shape",
|
|
7669
|
+
shape
|
|
7670
|
+
})
|
|
7008
7671
|
};
|
|
7009
7672
|
}
|
|
7010
7673
|
/**
|
|
@@ -7194,10 +7857,10 @@ var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
|
|
|
7194
7857
|
const failed = [];
|
|
7195
7858
|
const getJson = async (path) => {
|
|
7196
7859
|
try {
|
|
7197
|
-
const { status, record,
|
|
7860
|
+
const { status, record, responseFailure } = await this.fetchJson(`${base}${path}`, headers, connection.requestTimeoutMs);
|
|
7198
7861
|
if (record === void 0) {
|
|
7199
|
-
failures.push(`${path}: HTTP ${status}${
|
|
7200
|
-
failed.push(
|
|
7862
|
+
failures.push(`${path}: HTTP ${status}${responseFailure ? `;${responseFailure}` : ""}`);
|
|
7863
|
+
failed.push(responseFailure ? {
|
|
7201
7864
|
kind: "invalid-response",
|
|
7202
7865
|
status
|
|
7203
7866
|
} : {
|
|
@@ -7560,7 +8223,7 @@ const commandcodeCommand = {
|
|
|
7560
8223
|
unconfigured: " (未配置 API 密钥)",
|
|
7561
8224
|
blockedInvalidKey: "⛔ API 密钥无效或已过期 — 服务端拒绝了全部请求(401),请检查该账户的密钥配置",
|
|
7562
8225
|
blockedServiceUnavailable: "⚠️ Command Code 服务暂时不可用(5xx),稍后重试",
|
|
7563
|
-
blockedInvalidResponse: "
|
|
8226
|
+
blockedInvalidResponse: "Command Code 响应正文读取或解析失败,请根据下方详情排查并重试",
|
|
7564
8227
|
blockedNetwork: "⚠️ 无法连接 Command Code 服务 — 请检查网络或 API 地址",
|
|
7565
8228
|
planLine: " 📦 套餐 {name}{status}{period}",
|
|
7566
8229
|
planPeriodSuffix: " · 账期截止 {date}",
|
|
@@ -7591,7 +8254,7 @@ const commandcodeCommand = {
|
|
|
7591
8254
|
unconfigured: " (no API key configured)",
|
|
7592
8255
|
blockedInvalidKey: "⛔ API key invalid or expired — the server rejected every request (401); check the key configured for this account",
|
|
7593
8256
|
blockedServiceUnavailable: "⚠️ Command Code service temporarily unavailable (5xx); try again later",
|
|
7594
|
-
blockedInvalidResponse: "
|
|
8257
|
+
blockedInvalidResponse: "Reading or parsing the Command Code response failed; check the details below and retry",
|
|
7595
8258
|
blockedNetwork: "⚠️ could not reach the Command Code service — check your network or the API base setting",
|
|
7596
8259
|
planLine: " 📦 Plan {name}{status}{period}",
|
|
7597
8260
|
planPeriodSuffix: " · period ends {date}",
|
|
@@ -7759,66 +8422,6 @@ function applyCommands(ctx, deps) {
|
|
|
7759
8422
|
ctx.commands.register(commandDefinition(deps));
|
|
7760
8423
|
}
|
|
7761
8424
|
//#endregion
|
|
7762
|
-
//#region src/wire-shared.ts
|
|
7763
|
-
/** The npm package identity every contribution and descriptor claims. */
|
|
7764
|
-
const REMOTE_PACKAGE = "@mars-sea/dsh-commandcode-provider";
|
|
7765
|
-
/** The Cordis service key the Gateway resolves every Command Code Remote from. */
|
|
7766
|
-
const REMOTE_SERVICE = "commandcodeUsage";
|
|
7767
|
-
/** The wire namespace all Command Code endpoints share. */
|
|
7768
|
-
const REMOTE_NAMESPACE = "commandcode";
|
|
7769
|
-
/**
|
|
7770
|
-
* Build the validator helpers one endpoint uses. `prefix` names the endpoint
|
|
7771
|
-
* in the rejection message (e.g. `commandcode/report result:`), so each wire
|
|
7772
|
-
* file keeps its own diagnostic phrasing while sharing the helper bodies.
|
|
7773
|
-
*
|
|
7774
|
-
* The helpers return the reject call directly in the failure branch: since
|
|
7775
|
-
* `reject` is typed `never`, the ternary's union collapses to the success type
|
|
7776
|
-
* without relying on TypeScript's control-flow analysis of a never-returning
|
|
7777
|
-
* call (which only recognizes function declarations, not the destructured
|
|
7778
|
-
* arrow `reject` callers receive from this factory). Callers that need the
|
|
7779
|
-
* narrowing in an `if` must therefore assign inside the positive branch.
|
|
7780
|
-
*/
|
|
7781
|
-
function makeBoundaryValidator(prefix) {
|
|
7782
|
-
const reject = (field) => {
|
|
7783
|
-
throw new TypeError(`${prefix} invalid ${field}`);
|
|
7784
|
-
};
|
|
7785
|
-
const record = (value, field) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : reject(field);
|
|
7786
|
-
const stringField = (source, key, field) => typeof source[key] === "string" ? source[key] : reject(field);
|
|
7787
|
-
const numberField = (source, key, field) => typeof source[key] === "number" && Number.isFinite(source[key]) ? source[key] : reject(field);
|
|
7788
|
-
const booleanField = (source, key, field) => typeof source[key] === "boolean" ? source[key] : reject(field);
|
|
7789
|
-
return {
|
|
7790
|
-
reject,
|
|
7791
|
-
record,
|
|
7792
|
-
stringField,
|
|
7793
|
-
numberField,
|
|
7794
|
-
booleanField
|
|
7795
|
-
};
|
|
7796
|
-
}
|
|
7797
|
-
/** Use the same strict-codec shape for invocation parameters and results. */
|
|
7798
|
-
function makeStrictCodec(typeSymbol, schema) {
|
|
7799
|
-
return {
|
|
7800
|
-
mode: "strict",
|
|
7801
|
-
typeSymbol,
|
|
7802
|
-
create: () => schema
|
|
7803
|
-
};
|
|
7804
|
-
}
|
|
7805
|
-
/**
|
|
7806
|
-
* Build one strict invocation descriptor. Every Command Code Remote shares the
|
|
7807
|
-
* `commandcode` namespace, the `commandcodeUsage` service, and a strict result
|
|
7808
|
-
* — only the endpoint, method, result type symbol, and schema differ.
|
|
7809
|
-
*/
|
|
7810
|
-
function makeRemoteDescriptor(endpoint, method, typeSymbol, schema) {
|
|
7811
|
-
return {
|
|
7812
|
-
id: `${REMOTE_PACKAGE}#${endpoint}`,
|
|
7813
|
-
service: REMOTE_SERVICE,
|
|
7814
|
-
namespace: REMOTE_NAMESPACE,
|
|
7815
|
-
method,
|
|
7816
|
-
invocation: { kind: "direct" },
|
|
7817
|
-
parameters: [],
|
|
7818
|
-
result: makeStrictCodec(typeSymbol, schema)
|
|
7819
|
-
};
|
|
7820
|
-
}
|
|
7821
|
-
//#endregion
|
|
7822
8425
|
//#region src/usage-wire.ts
|
|
7823
8426
|
/** The npm package identity both contribution registrations claim. */
|
|
7824
8427
|
const USAGE_REMOTE_PACKAGE = REMOTE_PACKAGE;
|
|
@@ -8275,6 +8878,10 @@ var CommandCodeUsageService = class extends TypertRemoteService {
|
|
|
8275
8878
|
async prices() {
|
|
8276
8879
|
return (this.deps.prices ?? modelPriceTable)();
|
|
8277
8880
|
}
|
|
8881
|
+
async analytics(query) {
|
|
8882
|
+
if (!this.deps.analytics) throw new Error("宿主不支持模型分析,请更新插件");
|
|
8883
|
+
return this.deps.analytics(query, this.ctx.invocation?.signal);
|
|
8884
|
+
}
|
|
8278
8885
|
/**
|
|
8279
8886
|
* Start (or rejoin) a browser-login attempt and return its fresh status —
|
|
8280
8887
|
* `waiting` carrying the Studio URL. Rejects when the flow cannot start
|
|
@@ -8364,6 +8971,7 @@ function applyUsageRemote(ctx, deps) {
|
|
|
8364
8971
|
...USAGE_HOST_CONTRIBUTION.invocations,
|
|
8365
8972
|
MODELS_DESCRIPTOR,
|
|
8366
8973
|
PRICES_DESCRIPTOR,
|
|
8974
|
+
ANALYTICS_DESCRIPTOR,
|
|
8367
8975
|
...LOGIN_HOST_CONTRIBUTION.invocations,
|
|
8368
8976
|
...ENROLLMENT_DESCRIPTORS
|
|
8369
8977
|
]
|
|
@@ -9922,8 +10530,8 @@ function transportBudgetMessage(failureMessage, maxRetries) {
|
|
|
9922
10530
|
/**
|
|
9923
10531
|
* Mark one schema field volatile.
|
|
9924
10532
|
*
|
|
9925
|
-
* `.volatile()` is unconditional: it is a schemastery 3.18.3 feature and
|
|
9926
|
-
*
|
|
10533
|
+
* `.volatile()` is unconditional: it is a schemastery 3.18.3 feature and every
|
|
10534
|
+
* supported engine ships `~3.18.4` or `~3.18.5-alpha.1`, so the
|
|
9927
10535
|
* method is always present. It is reached through a structural member so the caller's
|
|
9928
10536
|
* declared field type survives — the mark changes what the schema PARSES to (a
|
|
9929
10537
|
* live reference), not the plugin-facing `Config` shape.
|
|
@@ -10309,7 +10917,8 @@ function apply(ctx, config) {
|
|
|
10309
10917
|
reports: usageReports,
|
|
10310
10918
|
login: loginFlow,
|
|
10311
10919
|
enrollment,
|
|
10312
|
-
listModels: catalogForEditors
|
|
10920
|
+
listModels: catalogForEditors,
|
|
10921
|
+
analytics: createAnalyticsReader(ctx, adapter)
|
|
10313
10922
|
});
|
|
10314
10923
|
const searchSelection = commandCodeSearchSelection();
|
|
10315
10924
|
const applySearchSelection = (enabled) => {
|