@mars-sea/dsh-commandcode-provider 0.14.4 → 0.15.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 +66 -0
- package/README.md +2 -1
- package/README.zh-CN.md +2 -1
- package/lib/client.js +1952 -839
- package/lib/index.d.ts +83 -11
- package/lib/index.js +362 -114
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -123,13 +123,15 @@ const PLAN_ORDER = Object.fromEntries(PLAN_TIER_ORDER.map((tier, index) => [tier
|
|
|
123
123
|
* subscription-plan labels, deals, and hourly (peak/off-peak) pricing.
|
|
124
124
|
*
|
|
125
125
|
* Everything here is synced from official sources — the command-code CLI
|
|
126
|
-
* bundle's model table (`dist/cli.mjs`, re-verified at command-code@1.
|
|
126
|
+
* bundle's model table (`dist/cli.mjs`, re-verified at command-code@1.86.3), the
|
|
127
127
|
* live model catalog (`GET /provider/v1/models?view=client`, the authority for
|
|
128
128
|
* capability fields since 1.82.4), and the official plan/pricing/model docs; see
|
|
129
129
|
* the dsh-commandcode-upstream skill for the extraction procedures. The
|
|
130
|
-
* 2026-10-
|
|
131
|
-
*
|
|
132
|
-
*
|
|
130
|
+
* 2026-10-21 sync moves the CLI from 1.84.0 to 1.86.1 and picks up the Ornith
|
|
131
|
+
* 1.5 122B launch pair in the catalog, the ZDR exclusion-set prune and the
|
|
132
|
+
* MiniMax M3.1 Flash promo extension. The follow-up check later that day moves
|
|
133
|
+
* the pinned constant to 1.86.3 (request header only): 1.86.2/1.86.3 leave
|
|
134
|
+
* every snapshot here untouched, so no data in this file moved for them. Keeping the snapshot in its own module
|
|
133
135
|
* confines those frequent sync diffs here: src/adapter.ts holds only the stable
|
|
134
136
|
* wire/runtime logic. The read helpers live here too, for the same reason.
|
|
135
137
|
*
|
|
@@ -589,6 +591,8 @@ const KNOWN_IMAGE_MODELS = /* @__PURE__ */ new Set([
|
|
|
589
591
|
"moonshotai/Kimi-K2.7-Code",
|
|
590
592
|
"moonshotai/Kimi-K2.7-Code-Highspeed",
|
|
591
593
|
"moonshotai/Kimi-K3",
|
|
594
|
+
"ornith/ornith-1.5-122b",
|
|
595
|
+
"ornith/ornith-1.5-122b:free",
|
|
592
596
|
"sakana/fugu-ultra",
|
|
593
597
|
"stepfun/Step-3.7-Flash",
|
|
594
598
|
"stepfun/Step-5-Preview",
|
|
@@ -633,7 +637,7 @@ const KNOWN_IMAGE_MODELS = /* @__PURE__ */ new Set([
|
|
|
633
637
|
* request can still name it. At the 1.84.0 sync both this row and
|
|
634
638
|
* `stealth/cat-jam:free` were also served by the `?view=client` catalog, which
|
|
635
639
|
* is where the 1.82.4 verdict came from; the catalog stopped serving cat-jam on
|
|
636
|
-
* 2026-10-20,
|
|
640
|
+
* 2026-10-20, and the artifact followed at 1.86.1 (see the prune note below).
|
|
637
641
|
*
|
|
638
642
|
* command-code@1.82.4 moved the authority for this verdict: `knownModelSupportsZdr`
|
|
639
643
|
* no longer negates a bundled Set, it returns `resolveKnownCatalogModel({model,
|
|
@@ -672,38 +676,44 @@ const KNOWN_IMAGE_MODELS = /* @__PURE__ */ new Set([
|
|
|
672
676
|
* neither source alone is complete, which is why this set is not simply "what
|
|
673
677
|
* the CLI says".
|
|
674
678
|
*
|
|
675
|
-
* The 2026-10-20 catalog-only sync
|
|
679
|
+
* The 2026-10-20 catalog-only sync changed no membership: `stealth/cat-jam:free`
|
|
676
680
|
* left both catalog views (its `zdr:false` verdict is no longer served), but the
|
|
677
|
-
* 1.84.0 artifact's exclusion set still
|
|
678
|
-
* membership
|
|
681
|
+
* 1.84.0 artifact's exclusion set still named it and this set is the union, so
|
|
682
|
+
* membership stayed at 25. The MiniMax M3.1 Flash launch pair is ZDR-covered
|
|
679
683
|
* (`zdr:true` on both ids) and joins no set.
|
|
684
|
+
*
|
|
685
|
+
* 2026-10-21 (command-code@1.86.1 sync) prunes the set 25 -> 18, and for the
|
|
686
|
+
* first time the two sources agree exactly: the 1.86.1 artifact's
|
|
687
|
+
* `modelSupportsZdr` set and the live catalog's `zdr:false` rows are the same 18
|
|
688
|
+
* ids (`knownModelSupportsZdr` is gone from the artifact). +3: `claude-fable-5`,
|
|
689
|
+
* `claude-fable-5-1` and `tencent/hy4-preview`. -10: the nine models the catalog
|
|
690
|
+
* flipped to `zdr:true` (Muse Spark 1.1 / 1.2 / 1.3, Muse Wonder 1.0, Grok 4.5 /
|
|
691
|
+
* 4.6, MiMo V2.6 Flash / Pro, GLM-5.2 Fast) plus `stealth/cat-jam:free`, which
|
|
692
|
+
* no source serves any more. Cat-jam is removed by explicit decision (see the
|
|
693
|
+
* decision record, chapter 7 item 13): keeping a retired id in the negative set
|
|
694
|
+
* is defense for stale sessions, not evidence, and the 1.86.1 artifact itself
|
|
695
|
+
* dropped it. A verdict flip here is positive evidence, never "listed before,
|
|
696
|
+
* absent now".
|
|
680
697
|
*/
|
|
681
698
|
const KNOWN_NON_ZDR_MODELS = /* @__PURE__ */ new Set([
|
|
682
699
|
"MiniMaxAI/MiniMax-M3",
|
|
683
700
|
"Qwen/Qwen3.8-Max-0902",
|
|
701
|
+
"claude-fable-5",
|
|
702
|
+
"claude-fable-5-1",
|
|
684
703
|
"deepseek/deepseek-v4.1-flash-fast",
|
|
685
|
-
"
|
|
686
|
-
"meta/muse-spark-1.2",
|
|
704
|
+
"meituan/LongCat-2.0",
|
|
687
705
|
"meta/muse-spark-1.2-contributor",
|
|
688
|
-
"meta/muse-spark-1.3",
|
|
689
706
|
"meta/muse-spark-1.3-contributor",
|
|
690
|
-
"meta/muse-wonder-1.0",
|
|
691
|
-
"meituan/LongCat-2.0",
|
|
692
707
|
"minimax/minimax-m3-free",
|
|
693
708
|
"poolside/laguna-s-2.1-free",
|
|
694
709
|
"reflection/beam:free",
|
|
695
710
|
"sakana/fugu-ultra",
|
|
696
711
|
"stealth/glyph-cluster:free",
|
|
697
|
-
"stealth/cat-jam:free",
|
|
698
712
|
"stepfun/Step-3.7-Flash",
|
|
699
713
|
"stepfun/Step-5-Preview",
|
|
700
|
-
"
|
|
701
|
-
"xai/grok-4.6",
|
|
702
|
-
"xiaomi/mimo-v2.6-flash",
|
|
703
|
-
"xiaomi/mimo-v2.6-pro",
|
|
714
|
+
"tencent/hy4-preview",
|
|
704
715
|
"xiaomi/mimo-v2.6-pro-ultraspeed",
|
|
705
|
-
"z-ai/glm-5.3-flashx"
|
|
706
|
-
"zai-org/GLM-5.2-Fast"
|
|
716
|
+
"z-ai/glm-5.3-flashx"
|
|
707
717
|
]);
|
|
708
718
|
/**
|
|
709
719
|
* Whether the CLI snapshot lists a ZDR-capable upstream for `modelId`. This is
|
|
@@ -742,7 +752,9 @@ const KNOWN_THINKING_MODELS = /* @__PURE__ */ new Set([
|
|
|
742
752
|
"thinkingmachines/inkling",
|
|
743
753
|
"thinkingmachines/inkling-small",
|
|
744
754
|
"poolside/laguna-s-2.1-free",
|
|
745
|
-
"meituan/LongCat-2.0"
|
|
755
|
+
"meituan/LongCat-2.0",
|
|
756
|
+
"ornith/ornith-1.5-122b",
|
|
757
|
+
"ornith/ornith-1.5-122b:free"
|
|
746
758
|
]);
|
|
747
759
|
/**
|
|
748
760
|
* Catalog models the Provider API serves ONLY through `/provider/v1/messages`
|
|
@@ -873,11 +885,12 @@ const MODEL_OUTPUT_TOKEN_LIMITS = /* @__PURE__ */ new Map([
|
|
|
873
885
|
["meta/muse-spark-1.3", 131072],
|
|
874
886
|
["meta/muse-spark-1.3-contributor", 131072],
|
|
875
887
|
["meta/muse-wonder-1.0", 131072],
|
|
876
|
-
["minimax/minimax-m3.1-flash",
|
|
877
|
-
["minimax/minimax-m3.1-flash:free",
|
|
888
|
+
["minimax/minimax-m3.1-flash", 524288],
|
|
889
|
+
["minimax/minimax-m3.1-flash:free", 524288],
|
|
878
890
|
["MiniMaxAI/MiniMax-M2.5", 131072],
|
|
879
891
|
["MiniMaxAI/MiniMax-M2.7", 131072],
|
|
880
|
-
["MiniMaxAI/MiniMax-M3",
|
|
892
|
+
["MiniMaxAI/MiniMax-M3", 524288],
|
|
893
|
+
["mistral/mistral-large-4", 262144],
|
|
881
894
|
["moonshotai/Kimi-K2.6", 262144],
|
|
882
895
|
["moonshotai/Kimi-K2.7-Code", 262144],
|
|
883
896
|
["moonshotai/Kimi-K2.7-Code-Highspeed", 262144],
|
|
@@ -920,8 +933,8 @@ const MODEL_OUTPUT_TOKEN_LIMITS = /* @__PURE__ */ new Map([
|
|
|
920
933
|
* `/docs/plans/max` and `/docs/resources/pricing-limits`). Each plan's model
|
|
921
934
|
* list is a superset of the one below it: Go ⊂ GOAT ⊂ Pro ⊂ Provider/Max.
|
|
922
935
|
* Models absent from every plan list (Claude Opus/Fable, Fugu Ultra) are
|
|
923
|
-
* Provider-tier. Re-verified at command-code@1.
|
|
924
|
-
* live `?view=client` catalog:
|
|
936
|
+
* Provider-tier. Re-verified at command-code@1.86.3 (2026-10-21) against the
|
|
937
|
+
* live `?view=client` catalog: 104 ids, still a strict superset chain. Mistral
|
|
925
938
|
* Large 4
|
|
926
939
|
* (1.75.0) was the only addition at the 1.77.0 check; the 1.78.0/1.79.0 pair
|
|
927
940
|
* added Claude Haiku 5.5 at Pro and the free Glyph Cluster preview at Go.
|
|
@@ -930,11 +943,12 @@ const MODEL_OUTPUT_TOKEN_LIMITS = /* @__PURE__ */ new Map([
|
|
|
930
943
|
* train adds Qwen 3.8 Max Prime at GOAT plus Muse Spark Open, Nemotron 3.5
|
|
931
944
|
* Super VL and the free Cat Jam preview at Go, and drops Ling 3.0 Flash Sante,
|
|
932
945
|
* which left the live catalog. Every other
|
|
933
|
-
* tier is unchanged.
|
|
946
|
+
* tier is unchanged. The 1.86.1 sync (2026-10-21) adds only the Ornith 1.5 122B
|
|
947
|
+
* launch pair at Go.
|
|
934
948
|
* The only removals since 1.49.0 are the two retired stealth previews (Pixel
|
|
935
|
-
* Canary at 1.73.1, Space Bunny Alpha at 1.74.3)
|
|
936
|
-
* (2026-10-16). Per-entry tags
|
|
937
|
-
* releases that added or moved each row.
|
|
949
|
+
* Canary at 1.73.1, Space Bunny Alpha at 1.74.3), Ling 3.0 Flash Sante
|
|
950
|
+
* (2026-10-16) and the retired Cat Jam preview (2026-10-20). Per-entry tags
|
|
951
|
+
* below name the releases that added or moved each row.
|
|
938
952
|
*
|
|
939
953
|
* The Provider API exposes no plan metadata, so this snapshot is the source of
|
|
940
954
|
* truth for the picker's plan annotation — it answers "which plan do I need to
|
|
@@ -982,6 +996,8 @@ const KNOWN_PLANS = {
|
|
|
982
996
|
"moonshotai/Kimi-K3": "go",
|
|
983
997
|
"nvidia/nemotron-3-ultra-550b-a55b": "go",
|
|
984
998
|
"nvidia/nemotron-3.5-super-vl": "go",
|
|
999
|
+
"ornith/ornith-1.5-122b": "go",
|
|
1000
|
+
"ornith/ornith-1.5-122b:free": "go",
|
|
985
1001
|
"poolside/laguna-s-2.1-free": "go",
|
|
986
1002
|
"reflection/beam:free": "go",
|
|
987
1003
|
"stepfun/Step-3.5-Flash": "go",
|
|
@@ -1176,7 +1192,7 @@ const KNOWN_DEALS = {
|
|
|
1176
1192
|
"minimax/minimax-m3.1-flash:free": {
|
|
1177
1193
|
label: "FREE",
|
|
1178
1194
|
free: true,
|
|
1179
|
-
expiresAt: "2026-10-
|
|
1195
|
+
expiresAt: "2026-10-22T10:45:00Z"
|
|
1180
1196
|
},
|
|
1181
1197
|
"xiaomi/mimo-v2.5-pro": { label: "99% off" },
|
|
1182
1198
|
"xiaomi/mimo-v2.5": { label: "98% off" },
|
|
@@ -1191,6 +1207,11 @@ const KNOWN_DEALS = {
|
|
|
1191
1207
|
"stealth/glyph-cluster:free": {
|
|
1192
1208
|
label: "FREE",
|
|
1193
1209
|
free: true
|
|
1210
|
+
},
|
|
1211
|
+
"ornith/ornith-1.5-122b:free": {
|
|
1212
|
+
label: "FREE",
|
|
1213
|
+
free: true,
|
|
1214
|
+
expiresAt: "2026-10-22T06:00:00Z"
|
|
1194
1215
|
}
|
|
1195
1216
|
};
|
|
1196
1217
|
/**
|
|
@@ -1628,8 +1649,8 @@ const MODEL_PRICE_ROWS = [
|
|
|
1628
1649
|
],
|
|
1629
1650
|
allowance: {
|
|
1630
1651
|
go: 10,
|
|
1631
|
-
goat:
|
|
1632
|
-
pro:
|
|
1652
|
+
goat: 100,
|
|
1653
|
+
pro: 110
|
|
1633
1654
|
}
|
|
1634
1655
|
},
|
|
1635
1656
|
{
|
|
@@ -2540,6 +2561,19 @@ const MODEL_PRICE_ROWS = [
|
|
|
2540
2561
|
pro: 30
|
|
2541
2562
|
}
|
|
2542
2563
|
},
|
|
2564
|
+
{
|
|
2565
|
+
id: "ornith-1.5-122b",
|
|
2566
|
+
rates: [
|
|
2567
|
+
.16,
|
|
2568
|
+
.64,
|
|
2569
|
+
.02
|
|
2570
|
+
],
|
|
2571
|
+
allowance: {
|
|
2572
|
+
go: 6,
|
|
2573
|
+
goat: 20,
|
|
2574
|
+
pro: 30
|
|
2575
|
+
}
|
|
2576
|
+
},
|
|
2543
2577
|
{
|
|
2544
2578
|
id: "qwen-3.6-max",
|
|
2545
2579
|
rates: [
|
|
@@ -3000,10 +3034,10 @@ function recordOrEmpty(value) {
|
|
|
3000
3034
|
}
|
|
3001
3035
|
//#endregion
|
|
3002
3036
|
//#region src/cost-projection.ts
|
|
3003
|
-
const record$
|
|
3037
|
+
const record$4 = (v) => isRecord(v) ? v : void 0;
|
|
3004
3038
|
const finite = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0;
|
|
3005
3039
|
function tokensOf$1(value) {
|
|
3006
|
-
const u = record$
|
|
3040
|
+
const u = record$4(value);
|
|
3007
3041
|
if (!u || !finite(u.inputTokens) || !finite(u.outputTokens)) return void 0;
|
|
3008
3042
|
if (u.cacheReadTokens !== void 0 && !finite(u.cacheReadTokens) || u.cacheWriteTokens !== void 0 && !finite(u.cacheWriteTokens)) return void 0;
|
|
3009
3043
|
return {
|
|
@@ -3015,14 +3049,14 @@ function tokensOf$1(value) {
|
|
|
3015
3049
|
}
|
|
3016
3050
|
/** The four buckets of an already-parsed record, or undefined when one is missing. */
|
|
3017
3051
|
function costTokensOf(value) {
|
|
3018
|
-
const t = record$
|
|
3052
|
+
const t = record$4(value);
|
|
3019
3053
|
if (!t || !COST_TOKEN_KEYS.every((k) => finite(t[k]))) return void 0;
|
|
3020
3054
|
return t;
|
|
3021
3055
|
}
|
|
3022
3056
|
/** Current v2 settlements store the last usage in their stream; v1 stores chunk events. */
|
|
3023
3057
|
function usageOf(type, data) {
|
|
3024
3058
|
if (type === "assistant/chunk") {
|
|
3025
|
-
const chunk = record$
|
|
3059
|
+
const chunk = record$4(data.chunk);
|
|
3026
3060
|
return chunk?.type === "usage" ? tokensOf$1(chunk.usage) : void 0;
|
|
3027
3061
|
}
|
|
3028
3062
|
if (type !== "assistant/message" && type !== "assistant/attempt") return void 0;
|
|
@@ -3030,19 +3064,19 @@ function usageOf(type, data) {
|
|
|
3030
3064
|
if (direct !== void 0) return direct;
|
|
3031
3065
|
if (!Array.isArray(data.stream)) return void 0;
|
|
3032
3066
|
for (let i = data.stream.length - 1; i >= 0; i--) {
|
|
3033
|
-
const item = record$
|
|
3034
|
-
const chunk = item?.type === "chunk" ? record$
|
|
3067
|
+
const item = record$4(data.stream[i]);
|
|
3068
|
+
const chunk = item?.type === "chunk" ? record$4(item.chunk) : void 0;
|
|
3035
3069
|
if (chunk?.type === "usage") return tokensOf$1(chunk.usage);
|
|
3036
3070
|
}
|
|
3037
3071
|
}
|
|
3038
3072
|
function parseState(value) {
|
|
3039
|
-
const s = record$
|
|
3073
|
+
const s = record$4(value), facts = record$4(s?.facts);
|
|
3040
3074
|
if (!s || !facts || !finite(facts.pricingKey) || !Array.isArray(facts.groups)) throw new TypeError("invalid commandCodeCost state");
|
|
3041
3075
|
const group = (v) => {
|
|
3042
|
-
const g = record$
|
|
3076
|
+
const g = record$4(v);
|
|
3043
3077
|
return !!g && typeof g.provider === "string" && typeof g.model === "string" && (g.at === null || finite(g.at)) && finite(g.contextTokens) && costTokensOf(g.tokens) !== void 0;
|
|
3044
3078
|
};
|
|
3045
|
-
const sel = record$
|
|
3079
|
+
const sel = record$4(s.selection), last = record$4(s.last);
|
|
3046
3080
|
const summaryTokens = facts.summaryTokens === void 0 ? zeroCostTokens() : costTokensOf(facts.summaryTokens);
|
|
3047
3081
|
if (summaryTokens === void 0 || !(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");
|
|
3048
3082
|
return {
|
|
@@ -3099,10 +3133,10 @@ function createCostProjection(table = modelPriceTable()) {
|
|
|
3099
3133
|
}
|
|
3100
3134
|
}),
|
|
3101
3135
|
apply(state, event) {
|
|
3102
|
-
const data = record$
|
|
3136
|
+
const data = record$4(event.data);
|
|
3103
3137
|
if (!data) return state;
|
|
3104
3138
|
if (event.type === "request/header") {
|
|
3105
|
-
const config = record$
|
|
3139
|
+
const config = record$4(record$4(data.header)?.config);
|
|
3106
3140
|
return {
|
|
3107
3141
|
...state,
|
|
3108
3142
|
selection: config && typeof config.provider === "string" && typeof config.model === "string" ? {
|
|
@@ -3194,10 +3228,10 @@ function installCostProjection(ctx) {
|
|
|
3194
3228
|
//#endregion
|
|
3195
3229
|
//#region src/analytics.ts
|
|
3196
3230
|
/** 在宿主内折叠原始历史;每次请求保留计价时间和上下文,禁止使用已合并的会话成本桶。 */
|
|
3197
|
-
const record$
|
|
3231
|
+
const record$3 = (v) => isRecord(v) ? v : void 0;
|
|
3198
3232
|
const number = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0;
|
|
3199
3233
|
function tokensOf(v) {
|
|
3200
|
-
const u = record$
|
|
3234
|
+
const u = record$3(v);
|
|
3201
3235
|
if (!u || !number(u.inputTokens) || !number(u.outputTokens)) return void 0;
|
|
3202
3236
|
if (u.cacheReadTokens !== void 0 && !number(u.cacheReadTokens) || u.cacheWriteTokens !== void 0 && !number(u.cacheWriteTokens)) return void 0;
|
|
3203
3237
|
return {
|
|
@@ -3227,10 +3261,10 @@ function foldRequests(session, log) {
|
|
|
3227
3261
|
if (listed) facts.push(current);
|
|
3228
3262
|
};
|
|
3229
3263
|
for (const event of log.events) {
|
|
3230
|
-
const d = record$
|
|
3264
|
+
const d = record$3(event.data);
|
|
3231
3265
|
if (!d) continue;
|
|
3232
3266
|
if (event.type === "request/header") {
|
|
3233
|
-
const config = record$
|
|
3267
|
+
const config = record$3(record$3(d.header)?.config);
|
|
3234
3268
|
provider = typeof config?.provider === "string" ? config.provider : "";
|
|
3235
3269
|
model = typeof config?.model === "string" ? config.model : "";
|
|
3236
3270
|
if (current && current.outcome === "unknown") {
|
|
@@ -3274,7 +3308,7 @@ function foldRequests(session, log) {
|
|
|
3274
3308
|
}
|
|
3275
3309
|
if (!current) continue;
|
|
3276
3310
|
if (event.type === "turn/end" && d.turn === turn) {
|
|
3277
|
-
const reason = record$
|
|
3311
|
+
const reason = record$3(d.reason)?.kind;
|
|
3278
3312
|
if (current.outcome === "unknown") {
|
|
3279
3313
|
if (reason === "error") current.outcome = "failed";
|
|
3280
3314
|
else if (reason === "aborted") current.outcome = "cancelled";
|
|
@@ -3292,11 +3326,11 @@ function foldRequests(session, log) {
|
|
|
3292
3326
|
"assistant/attempt",
|
|
3293
3327
|
"assistant/chunk"
|
|
3294
3328
|
].includes(event.type)) continue;
|
|
3295
|
-
const chunk = record$
|
|
3329
|
+
const chunk = record$3(d.chunk);
|
|
3296
3330
|
let tokens = tokensOf(d.usage);
|
|
3297
3331
|
if (event.type === "assistant/chunk" && chunk?.type === "usage") tokens = tokensOf(chunk.usage);
|
|
3298
3332
|
if (tokens === void 0 && Array.isArray(d.stream)) for (let i = d.stream.length - 1; i >= 0; i--) {
|
|
3299
|
-
const item = record$
|
|
3333
|
+
const item = record$3(d.stream[i]), c = item?.type === "chunk" ? record$3(item.chunk) : void 0;
|
|
3300
3334
|
if (c?.type === "usage") {
|
|
3301
3335
|
tokens = tokensOf(c.usage);
|
|
3302
3336
|
if (tokens) break;
|
|
@@ -3949,6 +3983,29 @@ const RETRY_MAX_DELAY_MS = 9e5;
|
|
|
3949
3983
|
* once per interval per key bounds a probe endpoint that keeps failing.
|
|
3950
3984
|
*/
|
|
3951
3985
|
const EXPLICIT_ACCOUNT_PROBE_INTERVAL_MS = 6e4;
|
|
3986
|
+
/**
|
|
3987
|
+
* How long a model-scoped `unavailable` rejection demotes an account.
|
|
3988
|
+
*
|
|
3989
|
+
* `unavailable` (no credits, or a model outside this account's plan) is a fact
|
|
3990
|
+
* about the balance or the model, NOT about the key — a credits-empty account
|
|
3991
|
+
* still serves `:free` models, which is why it must never become a permanent
|
|
3992
|
+
* account mark. But not remembering it at all made EVERY later request pick
|
|
3993
|
+
* the same doomed account first: one wasted round trip per step, and in a pool
|
|
3994
|
+
* where that account was the fallback, the user's error became "insufficient
|
|
3995
|
+
* credits" from an account they never chose (issue #51's remaining half,
|
|
3996
|
+
* live-probed 2026-10-21: a $0.0874 balance rejects every paid model with
|
|
3997
|
+
* `400 insufficient credits` while free models still answer 200).
|
|
3998
|
+
*
|
|
3999
|
+
* So the mark is a PREFERENCE, not a ban, and it is scoped per model to keep
|
|
4000
|
+
* the free-model escape hatch intact. Five minutes is long enough to stop a
|
|
4001
|
+
* single turn's steps from re-hitting the same rejection and short enough that
|
|
4002
|
+
* a top-up takes effect without a restart.
|
|
4003
|
+
*/
|
|
4004
|
+
const MODEL_UNAVAILABLE_TTL_MS = 3e5;
|
|
4005
|
+
/** One model-scoped soft mark's Map key (NUL keeps the two halves unambiguous). */
|
|
4006
|
+
function modelUnavailableKey(apiKey, model) {
|
|
4007
|
+
return `${apiKey}\u0000${model}`;
|
|
4008
|
+
}
|
|
3952
4009
|
/** A labeled, human-readable clock reading for error messages. */
|
|
3953
4010
|
function clockLabel(ms) {
|
|
3954
4011
|
return new Date(ms).toLocaleString();
|
|
@@ -4019,6 +4076,11 @@ var CommandCodeAccountPool = class CommandCodeAccountPool {
|
|
|
4019
4076
|
states = /* @__PURE__ */ new Map();
|
|
4020
4077
|
/** 当前网关与密钥的显式探测时间,限制失败探测的频率。 */
|
|
4021
4078
|
explicitProbes = /* @__PURE__ */ new Map();
|
|
4079
|
+
/**
|
|
4080
|
+
* 模型级 `unavailable` 软标记:`key\0model` → 过期时刻(毫秒)。
|
|
4081
|
+
* 只影响选择顺序,从不参与 {@link accountUsable},所以免费模型仍可用。
|
|
4082
|
+
*/
|
|
4083
|
+
modelUnavailable = /* @__PURE__ */ new Map();
|
|
4022
4084
|
scopedStates = /* @__PURE__ */ new Map();
|
|
4023
4085
|
constructor(deps) {
|
|
4024
4086
|
this.deps = deps;
|
|
@@ -4030,7 +4092,8 @@ var CommandCodeAccountPool = class CommandCodeAccountPool {
|
|
|
4030
4092
|
if (shared === void 0) {
|
|
4031
4093
|
shared = {
|
|
4032
4094
|
states: /* @__PURE__ */ new Map(),
|
|
4033
|
-
probes: /* @__PURE__ */ new Map()
|
|
4095
|
+
probes: /* @__PURE__ */ new Map(),
|
|
4096
|
+
modelUnavailable: /* @__PURE__ */ new Map()
|
|
4034
4097
|
};
|
|
4035
4098
|
this.scopedStates.set(context.apiBase, shared);
|
|
4036
4099
|
}
|
|
@@ -4043,6 +4106,7 @@ var CommandCodeAccountPool = class CommandCodeAccountPool {
|
|
|
4043
4106
|
});
|
|
4044
4107
|
scoped.states = shared.states;
|
|
4045
4108
|
scoped.explicitProbes = shared.probes;
|
|
4109
|
+
scoped.modelUnavailable = shared.modelUnavailable;
|
|
4046
4110
|
scoped.scopedStates = this.scopedStates;
|
|
4047
4111
|
return scoped;
|
|
4048
4112
|
}
|
|
@@ -4109,11 +4173,13 @@ var CommandCodeAccountPool = class CommandCodeAccountPool {
|
|
|
4109
4173
|
if (selectActiveAccount(accounts, preferred) !== void 0) return void 0;
|
|
4110
4174
|
throw allAccountsUnusable(accounts);
|
|
4111
4175
|
}
|
|
4112
|
-
const
|
|
4176
|
+
const model = options?.model ?? "";
|
|
4177
|
+
const serveable = this.withoutModelUnavailable(available, model);
|
|
4178
|
+
const routed = selectAccountForModel(serveable, model, this.deps.modelAccountRules?.());
|
|
4113
4179
|
if (routed !== void 0) return this.pick(routed);
|
|
4114
|
-
const chosen = selectActiveAccount(
|
|
4180
|
+
const chosen = selectActiveAccount(serveable, preferred);
|
|
4115
4181
|
if (chosen !== void 0) {
|
|
4116
|
-
const revived = await this.reviveExplicit(available,
|
|
4182
|
+
const revived = await this.reviveExplicit(available, model, preferred, chosen);
|
|
4117
4183
|
return this.pick(revived ?? chosen);
|
|
4118
4184
|
}
|
|
4119
4185
|
await this.probeAllMarked(available);
|
|
@@ -4166,13 +4232,15 @@ var CommandCodeAccountPool = class CommandCodeAccountPool {
|
|
|
4166
4232
|
* keeps its window unknown on purpose, so a probe can still find out.
|
|
4167
4233
|
*/
|
|
4168
4234
|
markRejected(apiKey, rejection, resetAtMs) {
|
|
4169
|
-
if (rejection === "invalid-credential")
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4235
|
+
if (rejection === "invalid-credential") {
|
|
4236
|
+
this.states.set(apiKey, {
|
|
4237
|
+
kind: "disabled",
|
|
4238
|
+
cause: "auth",
|
|
4239
|
+
reason: "invalid API key (401)",
|
|
4240
|
+
until: 0
|
|
4241
|
+
});
|
|
4242
|
+
this.clearModelUnavailable(apiKey);
|
|
4243
|
+
} else if (rejection === "throttled") this.states.set(apiKey, {
|
|
4176
4244
|
kind: "unknown",
|
|
4177
4245
|
cause: "throttle",
|
|
4178
4246
|
reason: "rate limited (429)",
|
|
@@ -4192,6 +4260,46 @@ var CommandCodeAccountPool = class CommandCodeAccountPool {
|
|
|
4192
4260
|
});
|
|
4193
4261
|
}
|
|
4194
4262
|
/**
|
|
4263
|
+
* Record a model-scoped `unavailable` rejection (no credits, or a model
|
|
4264
|
+
* outside this account's plan) as a SHORT-LIVED PREFERENCE demotion.
|
|
4265
|
+
*
|
|
4266
|
+
* 职责:让同一模型的后续请求先跳过这个账号,而不是每一步都重撞一次
|
|
4267
|
+
* `400 insufficient credits`;同时刻意不写进 `states`,所以
|
|
4268
|
+
* {@link accountUsable} 与 {@link activeAccount} 都不受影响——余额为空的
|
|
4269
|
+
* 账号仍然能服务 `:free` 模型,账号卡片也不会把它标成"不可用"。
|
|
4270
|
+
*
|
|
4271
|
+
* 为什么按时限而非永久:余额/套餐是可以不换密钥就改变的事实(充值、升级),
|
|
4272
|
+
* 而进程内没有任何事件能通知这改变,所以只能靠时钟自行恢复。
|
|
4273
|
+
*/
|
|
4274
|
+
markModelUnavailable(apiKey, model) {
|
|
4275
|
+
if (model === "") return;
|
|
4276
|
+
const now = this.now();
|
|
4277
|
+
this.modelUnavailable.set(modelUnavailableKey(apiKey, model), now + MODEL_UNAVAILABLE_TTL_MS);
|
|
4278
|
+
for (const [key, until] of this.modelUnavailable) if (until <= now) this.modelUnavailable.delete(key);
|
|
4279
|
+
}
|
|
4280
|
+
/** Drop every model-scoped mark for one key (a new credential invalidates them). */
|
|
4281
|
+
clearModelUnavailable(apiKey) {
|
|
4282
|
+
const prefix = `${apiKey}\u0000`;
|
|
4283
|
+
for (const key of this.modelUnavailable.keys()) if (key.startsWith(prefix)) this.modelUnavailable.delete(key);
|
|
4284
|
+
}
|
|
4285
|
+
/**
|
|
4286
|
+
* The candidates for THIS model, in preference order: accounts without a live
|
|
4287
|
+
* model-scoped mark first, everything else after them.
|
|
4288
|
+
*
|
|
4289
|
+
* 为什么要"排后"而不是"剔除":软标记只是偏好(见
|
|
4290
|
+
* {@link markModelUnavailable})。全部候选都被标记时必须退回全量,否则唯一
|
|
4291
|
+
* 账号的池子会变成"没有可用账号",把一次余额不足误报成额度耗尽。
|
|
4292
|
+
*/
|
|
4293
|
+
withoutModelUnavailable(accounts, model) {
|
|
4294
|
+
if (model === "") return accounts;
|
|
4295
|
+
const now = this.now();
|
|
4296
|
+
const healthy = accounts.filter((account) => {
|
|
4297
|
+
const until = this.modelUnavailable.get(modelUnavailableKey(account.key, model));
|
|
4298
|
+
return until === void 0 || until <= now;
|
|
4299
|
+
});
|
|
4300
|
+
return healthy.length > 0 ? healthy : accounts;
|
|
4301
|
+
}
|
|
4302
|
+
/**
|
|
4195
4303
|
* One account's key: literal → credential seam → auth file (default slot).
|
|
4196
4304
|
*
|
|
4197
4305
|
* Every source is normalized here, at the single point where a slot's key
|
|
@@ -4463,6 +4571,18 @@ function parseOutputCeiling(detail) {
|
|
|
4463
4571
|
return Number.isSafeInteger(ceiling) && ceiling > 0 ? ceiling : void 0;
|
|
4464
4572
|
}
|
|
4465
4573
|
/**
|
|
4574
|
+
* The gateway's cap on the request's JSON STRUCTURE: `JSON node count
|
|
4575
|
+
* cannot exceed 32768` (issue #96). A structural limit, not a token one —
|
|
4576
|
+
* a session heavy in messages, tool calls and images trips it while well
|
|
4577
|
+
* inside the context window, so it needs its own wording: reported as the
|
|
4578
|
+
* generic refusal, the one sentence naming the actual cap was dropped and
|
|
4579
|
+
* the user had no lever at all; reported as a context overflow, the
|
|
4580
|
+
* harness would compact tokens and resend a structure that still exceeds
|
|
4581
|
+
* the cap. The stated limit is captured because the gateway owns the
|
|
4582
|
+
* number and it can move.
|
|
4583
|
+
*/
|
|
4584
|
+
const JSON_NODE_LIMIT_REJECTION = /JSON node count cannot exceed\s+(\d+)/i;
|
|
4585
|
+
/**
|
|
4466
4586
|
* Whether one rejection says "ZDR requested, no ZDR-capable upstream": the
|
|
4467
4587
|
* provider spells the code `cmd_zdr_no_providers` in JSON error bodies and
|
|
4468
4588
|
* `CMD_ZDR_NO_PROVIDERS` in plain-text ones (the official CLI's own
|
|
@@ -4603,6 +4723,8 @@ function generateHttpError(status, errText, retryAfterMs, parsed = parseProvider
|
|
|
4603
4723
|
return bilingual("PROVIDER_HTTP_ERROR", `Command Code API error ${status}: this request's max_tokens is not valid for this model` + (stated ? ` (${stated})` : "") + " — the session is not oversized, so compacting it will not help; lower \"Max output tokens\" or pick a model with a larger output ceiling", `Command Code API 返回 ${status}:本次请求的 max_tokens 对该模型不合法` + (stated ? `(${stated})` : "") + "——会话内容并未超长,压缩上下文无效;请调低「最大输出 token」或改用输出上限更大的模型", { status });
|
|
4604
4724
|
}
|
|
4605
4725
|
if (status < 500 && isContextOverflowDetail(overflowDetail)) return bilingual(CONTEXT_WINDOW_EXCEEDED_CODE, `Command Code API error ${status}: the request exceeds the model's context window — this session is being compacted and the request retried`, `Command Code API 返回 ${status}:请求内容超出模型上下文窗口——正在压缩上下文后重试;如仍失败,请新建会话或减少上下文`, { status });
|
|
4726
|
+
const nodeLimit = status < 500 ? JSON_NODE_LIMIT_REJECTION.exec(overflowDetail)?.[1] : void 0;
|
|
4727
|
+
if (nodeLimit !== void 0) return bilingual("PROVIDER_HTTP_ERROR", `Command Code API error ${status}: the request exceeds the gateway's JSON node limit (node count cannot exceed ${nodeLimit}) — the cap is on the request's structure, not its token length, so compacting the session usually will not be enough; start a new session, or reduce the messages and images in this one`, `Command Code API 返回 ${status}:请求超过网关的 JSON 节点数上限(节点数不能超过 ${nodeLimit})——限制的是请求结构,不是 token 长度,压缩上下文通常不足以降到该上限以内;请新建会话,或减少本会话中的消息与图片`, { status });
|
|
4606
4728
|
if (status === 422 && isZdrNoProviders(providerCode, providerDetail, errText)) return bilingual("PROVIDER_HTTP_ERROR", `Command Code API error 422 (${detail}): zero data retention is enforced but no ZDR-capable upstream is available for this model — turn the ZDR setting off (or use another model)`, "Command Code API 返回 422:已开启零数据保留(ZDR),但该模型当前没有可用的 ZDR 上游——请关闭 ZDR 开关,或改用其他模型", { status: 422 });
|
|
4607
4729
|
if (status === 401) return bilingual("INVALID_CREDENTIAL", `Command Code API error 401 (${detail}): the API key is missing or invalid — check the key stored for COMMANDCODE_API_KEY (Models page) or the auth file`, "Command Code API 返回 401:API 密钥缺失或无效——请在设置页检查 COMMANDCODE_API_KEY 存储的密钥,或检查 auth 文件", { status: 401 });
|
|
4608
4730
|
if (status === 413) return bilingual("PROVIDER_HTTP_ERROR", "Command Code API error 413: the request body exceeds the provider's size limit (the cap is undocumented, measured at about 50 MB) — the session history is too large to send, usually because of accumulated image attachments. Start a new session, or drop the image-heavy part of this one, and retry", "Command Code API 返回 413:请求体超过服务端的体积上限(官方未公开,实测约 50 MB)——会话历史过大,通常是历史图片累积所致。请新建会话,或移除本会话中图片较多的部分后重试", { status: 413 });
|
|
@@ -6361,7 +6483,7 @@ var GatewayFacts = class {
|
|
|
6361
6483
|
* per-request options thunk and an API-key resolver from the plugin entry
|
|
6362
6484
|
* (src/index.ts), so a settings change reaches the very next request.
|
|
6363
6485
|
*/
|
|
6364
|
-
const COMMAND_CODE_CLI_VERSION = "1.
|
|
6486
|
+
const COMMAND_CODE_CLI_VERSION = "1.86.3";
|
|
6365
6487
|
const DEFAULT_API_BASE = "https://api.commandcode.ai";
|
|
6366
6488
|
/**
|
|
6367
6489
|
* The output budget this bundle asks for, and the ceiling it will never exceed.
|
|
@@ -8964,7 +9086,7 @@ const USAGE_REPORT_ENDPOINT = "commandcode/report";
|
|
|
8964
9086
|
* The shared read/validate helpers for the usage report endpoint, prefixed
|
|
8965
9087
|
* so rejection messages name the offending boundary.
|
|
8966
9088
|
*/
|
|
8967
|
-
const { reject: reject$
|
|
9089
|
+
const { reject: reject$2, record: record$2, stringField: stringField$2, numberField: numberField$1, booleanField } = makeBoundaryValidator("commandcode/report result:");
|
|
8968
9090
|
/**
|
|
8969
9091
|
* Parse one quota window, or undefined when the frame carries no such window.
|
|
8970
9092
|
*
|
|
@@ -8975,12 +9097,12 @@ const { reject: reject$1, record: record$1, stringField: stringField$1, numberFi
|
|
|
8975
9097
|
*/
|
|
8976
9098
|
function windowLimit(value, field) {
|
|
8977
9099
|
if (value === void 0) return void 0;
|
|
8978
|
-
const source = record$
|
|
9100
|
+
const source = record$2(value, field);
|
|
8979
9101
|
return {
|
|
8980
|
-
used: numberField(source, "used", `${field}.used`),
|
|
8981
|
-
cap: numberField(source, "cap", `${field}.cap`),
|
|
9102
|
+
used: numberField$1(source, "used", `${field}.used`),
|
|
9103
|
+
cap: numberField$1(source, "cap", `${field}.cap`),
|
|
8982
9104
|
exceeded: booleanField(source, "exceeded", `${field}.exceeded`),
|
|
8983
|
-
resetAt: numberField(source, "resetAt", `${field}.resetAt`)
|
|
9105
|
+
resetAt: numberField$1(source, "resetAt", `${field}.resetAt`)
|
|
8984
9106
|
};
|
|
8985
9107
|
}
|
|
8986
9108
|
/**
|
|
@@ -8989,44 +9111,44 @@ function windowLimit(value, field) {
|
|
|
8989
9111
|
* malformed frame fails the boundary instead of rendering garbage.
|
|
8990
9112
|
*/
|
|
8991
9113
|
function parseUsageReport(value) {
|
|
8992
|
-
const source = record$
|
|
9114
|
+
const source = record$2(value, "report");
|
|
8993
9115
|
const failures = source.failures;
|
|
8994
|
-
if (!Array.isArray(failures) || failures.some((entry) => typeof entry !== "string")) reject$
|
|
9116
|
+
if (!Array.isArray(failures) || failures.some((entry) => typeof entry !== "string")) reject$2("failures");
|
|
8995
9117
|
const report = { failures };
|
|
8996
9118
|
if (source.blocked !== void 0) {
|
|
8997
9119
|
const blocked = source.blocked;
|
|
8998
9120
|
if (blocked === "invalid-key" || blocked === "service-unavailable" || blocked === "invalid-response" || blocked === "network") report.blocked = blocked;
|
|
8999
|
-
else reject$
|
|
9121
|
+
else reject$2("blocked");
|
|
9000
9122
|
}
|
|
9001
9123
|
if (source.account !== void 0) {
|
|
9002
|
-
const account = record$
|
|
9124
|
+
const account = record$2(source.account, "account");
|
|
9003
9125
|
report.account = {
|
|
9004
|
-
id: stringField$
|
|
9005
|
-
name: stringField$
|
|
9006
|
-
userName: stringField$
|
|
9126
|
+
id: stringField$2(account, "id", "account.id"),
|
|
9127
|
+
name: stringField$2(account, "name", "account.name"),
|
|
9128
|
+
userName: stringField$2(account, "userName", "account.userName")
|
|
9007
9129
|
};
|
|
9008
9130
|
}
|
|
9009
9131
|
if (source.usage !== void 0) {
|
|
9010
|
-
const usage = record$
|
|
9132
|
+
const usage = record$2(source.usage, "usage");
|
|
9011
9133
|
report.usage = {
|
|
9012
|
-
totalCount: numberField(usage, "totalCount", "usage.totalCount"),
|
|
9013
|
-
totalCost: numberField(usage, "totalCost", "usage.totalCost"),
|
|
9014
|
-
successRate: numberField(usage, "successRate", "usage.successRate"),
|
|
9015
|
-
completedCount: numberField(usage, "completedCount", "usage.completedCount"),
|
|
9016
|
-
failedCount: numberField(usage, "failedCount", "usage.failedCount"),
|
|
9017
|
-
totalTokensIn: numberField(usage, "totalTokensIn", "usage.totalTokensIn"),
|
|
9018
|
-
totalTokensOut: numberField(usage, "totalTokensOut", "usage.totalTokensOut"),
|
|
9019
|
-
totalCredits: numberField(usage, "totalCredits", "usage.totalCredits"),
|
|
9020
|
-
periodBasis: stringField$
|
|
9134
|
+
totalCount: numberField$1(usage, "totalCount", "usage.totalCount"),
|
|
9135
|
+
totalCost: numberField$1(usage, "totalCost", "usage.totalCost"),
|
|
9136
|
+
successRate: numberField$1(usage, "successRate", "usage.successRate"),
|
|
9137
|
+
completedCount: numberField$1(usage, "completedCount", "usage.completedCount"),
|
|
9138
|
+
failedCount: numberField$1(usage, "failedCount", "usage.failedCount"),
|
|
9139
|
+
totalTokensIn: numberField$1(usage, "totalTokensIn", "usage.totalTokensIn"),
|
|
9140
|
+
totalTokensOut: numberField$1(usage, "totalTokensOut", "usage.totalTokensOut"),
|
|
9141
|
+
totalCredits: numberField$1(usage, "totalCredits", "usage.totalCredits"),
|
|
9142
|
+
periodBasis: stringField$2(usage, "periodBasis", "usage.periodBasis")
|
|
9021
9143
|
};
|
|
9022
|
-
if (usage.periodStart !== void 0) report.usage.periodStart = numberField(usage, "periodStart", "usage.periodStart");
|
|
9144
|
+
if (usage.periodStart !== void 0) report.usage.periodStart = numberField$1(usage, "periodStart", "usage.periodStart");
|
|
9023
9145
|
}
|
|
9024
9146
|
if (source.credits !== void 0) {
|
|
9025
|
-
const credits = record$
|
|
9147
|
+
const credits = record$2(source.credits, "credits");
|
|
9026
9148
|
const parsed = {
|
|
9027
|
-
monthlyCredits: numberField(credits, "monthlyCredits", "credits.monthlyCredits"),
|
|
9028
|
-
purchasedCredits: numberField(credits, "purchasedCredits", "credits.purchasedCredits"),
|
|
9029
|
-
freeCredits: numberField(credits, "freeCredits", "credits.freeCredits")
|
|
9149
|
+
monthlyCredits: numberField$1(credits, "monthlyCredits", "credits.monthlyCredits"),
|
|
9150
|
+
purchasedCredits: numberField$1(credits, "purchasedCredits", "credits.purchasedCredits"),
|
|
9151
|
+
freeCredits: numberField$1(credits, "freeCredits", "credits.freeCredits")
|
|
9030
9152
|
};
|
|
9031
9153
|
if (credits.monthlyReported !== void 0) parsed.monthlyReported = booleanField(credits, "monthlyReported", "credits.monthlyReported");
|
|
9032
9154
|
for (const flag of ["purchasedReported", "freeReported"]) if (credits[flag] !== void 0) parsed[flag] = booleanField(credits, flag, `credits.${flag}`);
|
|
@@ -9037,38 +9159,38 @@ function parseUsageReport(value) {
|
|
|
9037
9159
|
report.credits = parsed;
|
|
9038
9160
|
}
|
|
9039
9161
|
if (source.plan !== void 0) {
|
|
9040
|
-
const plan = record$
|
|
9162
|
+
const plan = record$2(source.plan, "plan");
|
|
9041
9163
|
const monthly = plan.monthlyCredits;
|
|
9042
|
-
if (monthly !== null && (typeof monthly !== "number" || !Number.isFinite(monthly))) reject$
|
|
9164
|
+
if (monthly !== null && (typeof monthly !== "number" || !Number.isFinite(monthly))) reject$2("plan.monthlyCredits");
|
|
9043
9165
|
report.plan = {
|
|
9044
|
-
planId: stringField$
|
|
9045
|
-
name: stringField$
|
|
9046
|
-
status: stringField$
|
|
9166
|
+
planId: stringField$2(plan, "planId", "plan.planId"),
|
|
9167
|
+
name: stringField$2(plan, "name", "plan.name"),
|
|
9168
|
+
status: stringField$2(plan, "status", "plan.status"),
|
|
9047
9169
|
monthlyCredits: monthly,
|
|
9048
|
-
currentPeriodEnd: numberField(plan, "currentPeriodEnd", "plan.currentPeriodEnd")
|
|
9170
|
+
currentPeriodEnd: numberField$1(plan, "currentPeriodEnd", "plan.currentPeriodEnd")
|
|
9049
9171
|
};
|
|
9050
|
-
if (plan.currentPeriodStart !== void 0) report.plan.currentPeriodStart = numberField(plan, "currentPeriodStart", "plan.currentPeriodStart");
|
|
9172
|
+
if (plan.currentPeriodStart !== void 0) report.plan.currentPeriodStart = numberField$1(plan, "currentPeriodStart", "plan.currentPeriodStart");
|
|
9051
9173
|
}
|
|
9052
9174
|
return report;
|
|
9053
9175
|
}
|
|
9054
9176
|
/** Parse one untrusted boundary value into a {@link CommandCodeAccountUsage}. */
|
|
9055
9177
|
function parseAccountUsage(value) {
|
|
9056
|
-
const source = record$
|
|
9178
|
+
const source = record$2(value, "account");
|
|
9057
9179
|
return {
|
|
9058
|
-
id: stringField$
|
|
9059
|
-
label: stringField$
|
|
9180
|
+
id: stringField$2(source, "id", "account.id"),
|
|
9181
|
+
label: stringField$2(source, "label", "account.label"),
|
|
9060
9182
|
configured: booleanField(source, "configured", "account.configured"),
|
|
9061
9183
|
active: booleanField(source, "active", "account.active"),
|
|
9062
|
-
mark: stringField$
|
|
9063
|
-
cooldownUntil: numberField(source, "cooldownUntil", "account.cooldownUntil"),
|
|
9184
|
+
mark: stringField$2(source, "mark", "account.mark"),
|
|
9185
|
+
cooldownUntil: numberField$1(source, "cooldownUntil", "account.cooldownUntil"),
|
|
9064
9186
|
report: parseUsageReport(source.report)
|
|
9065
9187
|
};
|
|
9066
9188
|
}
|
|
9067
9189
|
/** Parse the wire result into a {@link CommandCodeAccountsReport}. */
|
|
9068
9190
|
function parseAccountsReport(value) {
|
|
9069
|
-
const accounts = record$
|
|
9191
|
+
const accounts = record$2(value, "result").accounts;
|
|
9070
9192
|
if (Array.isArray(accounts)) return { accounts: accounts.map(parseAccountUsage) };
|
|
9071
|
-
return reject$
|
|
9193
|
+
return reject$2("accounts");
|
|
9072
9194
|
}
|
|
9073
9195
|
/**
|
|
9074
9196
|
* The strict result codec both halves attach to the descriptor. Hand-rolled:
|
|
@@ -9201,36 +9323,36 @@ const REASONS = [
|
|
|
9201
9323
|
];
|
|
9202
9324
|
/** The shared read/validate helpers, prefixed with the login endpoint so
|
|
9203
9325
|
* rejection messages name the offending boundary. */
|
|
9204
|
-
const { reject, record, stringField } = makeBoundaryValidator("commandcode/login result:");
|
|
9326
|
+
const { reject: reject$1, record: record$1, stringField: stringField$1 } = makeBoundaryValidator("commandcode/login result:");
|
|
9205
9327
|
/**
|
|
9206
9328
|
* Parse one untrusted boundary value into a {@link CommandCodeLoginStatus}.
|
|
9207
9329
|
* Every field is shape-checked so a malformed frame fails the boundary
|
|
9208
9330
|
* instead of leaking into the page.
|
|
9209
9331
|
*/
|
|
9210
9332
|
function parseLoginStatus(value) {
|
|
9211
|
-
const source = record(value, "status");
|
|
9333
|
+
const source = record$1(value, "status");
|
|
9212
9334
|
const state = source.state;
|
|
9213
9335
|
if (state === "idle" || state === "waiting" || state === "success" || state === "failed") {
|
|
9214
9336
|
const status = { state };
|
|
9215
|
-
if (source.authUrl !== void 0) status.authUrl = stringField(source, "authUrl", "authUrl");
|
|
9216
|
-
if (source.userName !== void 0) status.userName = stringField(source, "userName", "userName");
|
|
9217
|
-
if (source.keyName !== void 0) status.keyName = stringField(source, "keyName", "keyName");
|
|
9337
|
+
if (source.authUrl !== void 0) status.authUrl = stringField$1(source, "authUrl", "authUrl");
|
|
9338
|
+
if (source.userName !== void 0) status.userName = stringField$1(source, "userName", "userName");
|
|
9339
|
+
if (source.keyName !== void 0) status.keyName = stringField$1(source, "keyName", "keyName");
|
|
9218
9340
|
if (source.reason !== void 0) {
|
|
9219
9341
|
const reason = source.reason;
|
|
9220
9342
|
if (typeof reason === "string" && REASONS.includes(reason)) status.reason = reason;
|
|
9221
|
-
else reject("reason");
|
|
9343
|
+
else reject$1("reason");
|
|
9222
9344
|
}
|
|
9223
|
-
if (source.message !== void 0) status.message = stringField(source, "message", "message");
|
|
9345
|
+
if (source.message !== void 0) status.message = stringField$1(source, "message", "message");
|
|
9224
9346
|
return status;
|
|
9225
9347
|
}
|
|
9226
|
-
return reject("state");
|
|
9348
|
+
return reject$1("state");
|
|
9227
9349
|
}
|
|
9228
9350
|
/** The strict result codec shared by all three login endpoints. */
|
|
9229
9351
|
const loginStatusSchema = { parse: parseLoginStatus };
|
|
9230
9352
|
/** Optional account target; the Host still checks it against saved slots. */
|
|
9231
9353
|
const loginTargetSchema = { parse(value) {
|
|
9232
9354
|
if (value === void 0 || typeof value === "string") return value;
|
|
9233
|
-
return reject("targetRef");
|
|
9355
|
+
return reject$1("targetRef");
|
|
9234
9356
|
} };
|
|
9235
9357
|
/** Build one login invocation descriptor. Only begin accepts an account ref. */
|
|
9236
9358
|
function loginDescriptor(endpoint, method) {
|
|
@@ -9389,6 +9511,31 @@ const ENROLLMENT_DESCRIPTORS = [
|
|
|
9389
9511
|
}
|
|
9390
9512
|
];
|
|
9391
9513
|
//#endregion
|
|
9514
|
+
//#region src/service-probe-wire.ts
|
|
9515
|
+
/** Canonical `<namespace>/<method>` endpoint of the probe Remote. */
|
|
9516
|
+
const PROBE_ENDPOINT = "commandcode/probe";
|
|
9517
|
+
const OUTCOMES = [
|
|
9518
|
+
"ok",
|
|
9519
|
+
"http-error",
|
|
9520
|
+
"timeout",
|
|
9521
|
+
"unreachable"
|
|
9522
|
+
];
|
|
9523
|
+
const { reject, record, stringField, numberField } = makeBoundaryValidator("commandcode/probe result:");
|
|
9524
|
+
/** Strict result codec, shared verbatim by the Host registration and Client mount. */
|
|
9525
|
+
function parseProbeResult(value) {
|
|
9526
|
+
const source = record(value, "result");
|
|
9527
|
+
const outcome = stringField(source, "outcome", "outcome");
|
|
9528
|
+
if (!OUTCOMES.includes(outcome)) reject("outcome");
|
|
9529
|
+
return {
|
|
9530
|
+
outcome,
|
|
9531
|
+
status: numberField(source, "status", "status"),
|
|
9532
|
+
latencyMs: numberField(source, "latencyMs", "latencyMs"),
|
|
9533
|
+
checkedAt: numberField(source, "checkedAt", "checkedAt")
|
|
9534
|
+
};
|
|
9535
|
+
}
|
|
9536
|
+
/** The one invocation descriptor, shared by the Host registration and the Client mount. */
|
|
9537
|
+
const PROBE_DESCRIPTOR = makeRemoteDescriptor(PROBE_ENDPOINT, "probe", `${REMOTE_PACKAGE}#CommandCodeProbeResult`, { parse: parseProbeResult });
|
|
9538
|
+
//#endregion
|
|
9392
9539
|
//#region src/usage-remote.ts
|
|
9393
9540
|
/**
|
|
9394
9541
|
* The Remote receiver: a Cordis service the Gateway resolves by key
|
|
@@ -9439,6 +9586,17 @@ var CommandCodeUsageService = class extends TypertRemoteService {
|
|
|
9439
9586
|
async prices() {
|
|
9440
9587
|
return (this.deps.prices ?? modelPriceTable)();
|
|
9441
9588
|
}
|
|
9589
|
+
/**
|
|
9590
|
+
* One live connectivity probe of the configured API base, run on this Host.
|
|
9591
|
+
* The status page's own data lags by design (Upptime publishes on change or
|
|
9592
|
+
* once a day), so this is the only real-time half of the status row. The
|
|
9593
|
+
* plugin entry wires the probe; a programmatic setup without it gets an
|
|
9594
|
+
* explicit failure, which the row renders as unknown.
|
|
9595
|
+
*/
|
|
9596
|
+
async probe() {
|
|
9597
|
+
if (this.deps.probe === void 0) throw new Error("宿主未提供连通性探测");
|
|
9598
|
+
return this.deps.probe();
|
|
9599
|
+
}
|
|
9442
9600
|
async analytics(query) {
|
|
9443
9601
|
if (!this.deps.analytics) throw new Error("宿主不支持模型分析,请更新插件");
|
|
9444
9602
|
return this.deps.analytics(query, this.ctx.invocation?.signal);
|
|
@@ -9533,6 +9691,7 @@ function applyUsageRemote(ctx, deps) {
|
|
|
9533
9691
|
MODELS_DESCRIPTOR,
|
|
9534
9692
|
PRICES_DESCRIPTOR,
|
|
9535
9693
|
ANALYTICS_DESCRIPTOR,
|
|
9694
|
+
PROBE_DESCRIPTOR,
|
|
9536
9695
|
...LOGIN_HOST_CONTRIBUTION.invocations,
|
|
9537
9696
|
...ENROLLMENT_DESCRIPTORS
|
|
9538
9697
|
]
|
|
@@ -9540,6 +9699,93 @@ function applyUsageRemote(ctx, deps) {
|
|
|
9540
9699
|
remoteCtx.effect(() => () => void unregister(), "dsh-commandcode-provider: usage remote");
|
|
9541
9700
|
});
|
|
9542
9701
|
}
|
|
9702
|
+
/**
|
|
9703
|
+
* HTTP statuses Upptime's default monitor accepts as `up` (from
|
|
9704
|
+
* `uptime-monitor`'s `expectedStatusCodes` fallback). Kept in sync with the
|
|
9705
|
+
* page's monitor rather than invented here: a 401 or 404 means the endpoint
|
|
9706
|
+
* answered but is not the surface the official check calls healthy.
|
|
9707
|
+
*/
|
|
9708
|
+
const UP_STATUSES = /* @__PURE__ */ new Set([
|
|
9709
|
+
200,
|
|
9710
|
+
201,
|
|
9711
|
+
202,
|
|
9712
|
+
203,
|
|
9713
|
+
204,
|
|
9714
|
+
205,
|
|
9715
|
+
206,
|
|
9716
|
+
207,
|
|
9717
|
+
208,
|
|
9718
|
+
226,
|
|
9719
|
+
300,
|
|
9720
|
+
301,
|
|
9721
|
+
302,
|
|
9722
|
+
303,
|
|
9723
|
+
304,
|
|
9724
|
+
305,
|
|
9725
|
+
306,
|
|
9726
|
+
307,
|
|
9727
|
+
308
|
|
9728
|
+
]);
|
|
9729
|
+
/** Turn the response status into the outcome the row renders. */
|
|
9730
|
+
function probeOutcomeForStatus(status) {
|
|
9731
|
+
return UP_STATUSES.has(status) ? "ok" : "http-error";
|
|
9732
|
+
}
|
|
9733
|
+
/**
|
|
9734
|
+
* Probe the configured API base once. Never rejects: an invalid base, a
|
|
9735
|
+
* timeout and a transport failure all resolve to an outcome.
|
|
9736
|
+
*
|
|
9737
|
+
* `redirect: 'manual'` keeps the classification on the FIRST response, exactly
|
|
9738
|
+
* like the monitor's HTTP client; following a redirect would report the final
|
|
9739
|
+
* destination's status as if it were the API's own.
|
|
9740
|
+
*/
|
|
9741
|
+
async function probeCommandCode(options) {
|
|
9742
|
+
const now = options.now ?? Date.now;
|
|
9743
|
+
let url;
|
|
9744
|
+
try {
|
|
9745
|
+
url = new URL("/", options.apiBase).toString();
|
|
9746
|
+
} catch {
|
|
9747
|
+
return {
|
|
9748
|
+
outcome: "unreachable",
|
|
9749
|
+
status: 0,
|
|
9750
|
+
latencyMs: 0,
|
|
9751
|
+
checkedAt: now()
|
|
9752
|
+
};
|
|
9753
|
+
}
|
|
9754
|
+
const controller = new AbortController();
|
|
9755
|
+
let timedOut = false;
|
|
9756
|
+
const timer = setTimeout(() => {
|
|
9757
|
+
timedOut = true;
|
|
9758
|
+
controller.abort();
|
|
9759
|
+
}, options.timeoutMs ?? 5e3);
|
|
9760
|
+
const started = Date.now();
|
|
9761
|
+
try {
|
|
9762
|
+
const response = await (options.fetchImpl ?? fetch)(url, {
|
|
9763
|
+
method: "GET",
|
|
9764
|
+
redirect: "manual",
|
|
9765
|
+
signal: controller.signal,
|
|
9766
|
+
headers: { accept: "*/*" }
|
|
9767
|
+
});
|
|
9768
|
+
const latencyMs = Math.max(0, Date.now() - started);
|
|
9769
|
+
try {
|
|
9770
|
+
await response.body?.cancel();
|
|
9771
|
+
} catch {}
|
|
9772
|
+
return {
|
|
9773
|
+
outcome: probeOutcomeForStatus(response.status),
|
|
9774
|
+
status: response.status,
|
|
9775
|
+
latencyMs,
|
|
9776
|
+
checkedAt: now()
|
|
9777
|
+
};
|
|
9778
|
+
} catch {
|
|
9779
|
+
return {
|
|
9780
|
+
outcome: timedOut ? "timeout" : "unreachable",
|
|
9781
|
+
status: 0,
|
|
9782
|
+
latencyMs: 0,
|
|
9783
|
+
checkedAt: now()
|
|
9784
|
+
};
|
|
9785
|
+
} finally {
|
|
9786
|
+
clearTimeout(timer);
|
|
9787
|
+
}
|
|
9788
|
+
}
|
|
9543
9789
|
//#endregion
|
|
9544
9790
|
//#region src/login.ts
|
|
9545
9791
|
/**
|
|
@@ -11662,6 +11908,7 @@ function apply(ctx, config) {
|
|
|
11662
11908
|
rotateApiKey: async (rejectedKey, rejection, connection, model, rotation) => {
|
|
11663
11909
|
const accounts = scopedPool(connection);
|
|
11664
11910
|
if (rejection !== "unavailable") accounts.markRejected(rejectedKey, rejection, rotation?.resetAtMs);
|
|
11911
|
+
else if (model !== void 0) accounts.markModelUnavailable(rejectedKey, model);
|
|
11665
11912
|
const tried = rotation?.tried?.length ? rotation.tried : [rejectedKey];
|
|
11666
11913
|
const resolved = await accounts.resolveKey(model === void 0 ? { tried } : {
|
|
11667
11914
|
tried,
|
|
@@ -11816,7 +12063,8 @@ function apply(ctx, config) {
|
|
|
11816
12063
|
login: loginFlow,
|
|
11817
12064
|
enrollment,
|
|
11818
12065
|
listModels: catalogForEditors,
|
|
11819
|
-
analytics: createAnalyticsReader(ctx, adapter)
|
|
12066
|
+
analytics: createAnalyticsReader(ctx, adapter),
|
|
12067
|
+
probe: () => probeCommandCode({ apiBase: options().apiBase })
|
|
11820
12068
|
});
|
|
11821
12069
|
const selectionLogger = {
|
|
11822
12070
|
warn: (message) => {
|