@mars-sea/dsh-commandcode-provider 0.11.17 → 0.12.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 +37 -1
- package/README.md +16 -10
- package/README.zh-CN.md +13 -7
- package/lib/client.js +197 -123
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +166 -66
- package/lib/index.js +1249 -608
- package/lib/index.js.map +1 -1
- package/package.json +37 -37
package/lib/index.js
CHANGED
|
@@ -12,6 +12,90 @@ import { TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
|
|
|
12
12
|
import { createServer } from "node:http";
|
|
13
13
|
import { createServer as createServer$1 } from "node:net";
|
|
14
14
|
import { WebError } from "@deepseek-ai/dsh-web";
|
|
15
|
+
//#region src/cost-facts.ts
|
|
16
|
+
const zeroCostTokens = () => ({
|
|
17
|
+
uncachedInputTokens: 0,
|
|
18
|
+
outputTokens: 0,
|
|
19
|
+
cacheReadTokens: 0,
|
|
20
|
+
cacheWriteTokens: 0
|
|
21
|
+
});
|
|
22
|
+
const COST_TOKEN_KEYS = [
|
|
23
|
+
"uncachedInputTokens",
|
|
24
|
+
"outputTokens",
|
|
25
|
+
"cacheReadTokens",
|
|
26
|
+
"cacheWriteTokens"
|
|
27
|
+
];
|
|
28
|
+
function peakHour(at, windows) {
|
|
29
|
+
const time = new Date(at);
|
|
30
|
+
return time.getUTCDay() !== 0 && time.getUTCDay() !== 6 && windows.some(([start, end]) => time.getUTCHours() >= start && time.getUTCHours() < end);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* All prompt billing buckets determine a request's band, never cumulative
|
|
34
|
+
* session input.
|
|
35
|
+
*
|
|
36
|
+
* A tiered model returns the matching band and does NOT then apply `peak`: the
|
|
37
|
+
* page publishes those two dimensions independently and no row carries both
|
|
38
|
+
* today, so the combination becoming real is visible before it reaches a user.
|
|
39
|
+
* This ordering is a latent choice, not a verified upstream rule.
|
|
40
|
+
*/
|
|
41
|
+
function requestRates(price, at, contextTokens, table) {
|
|
42
|
+
const tier = price.contextTiers?.find((tier) => tier.maxContext === void 0 || contextTokens <= tier.maxContext);
|
|
43
|
+
if (tier !== void 0) return tier;
|
|
44
|
+
return price.peak !== void 0 && at !== null && peakHour(at, table.peakHours) ? price.peak : price;
|
|
45
|
+
}
|
|
46
|
+
/** Cache version and wire guard: changing bands/rates must refold historical groups. */
|
|
47
|
+
function pricingKey(table) {
|
|
48
|
+
const rates = (r) => [
|
|
49
|
+
r.inputCost,
|
|
50
|
+
r.outputCost,
|
|
51
|
+
r.cacheReadCost,
|
|
52
|
+
r.cacheWriteCost ?? null
|
|
53
|
+
];
|
|
54
|
+
const canonical = JSON.stringify([
|
|
55
|
+
"commandCodeCost-v1",
|
|
56
|
+
table.peakHours,
|
|
57
|
+
table.models.map((p) => [
|
|
58
|
+
p.id,
|
|
59
|
+
p.slug,
|
|
60
|
+
p.free === true,
|
|
61
|
+
rates(p),
|
|
62
|
+
p.peak ? rates(p.peak) : null,
|
|
63
|
+
p.contextTiers?.map((t) => [t.maxContext ?? null, rates(t)]) ?? null
|
|
64
|
+
])
|
|
65
|
+
]);
|
|
66
|
+
let hash = 2166136261;
|
|
67
|
+
for (const c of canonical) hash = Math.imul(hash ^ c.charCodeAt(0), 16777619) >>> 0;
|
|
68
|
+
return hash;
|
|
69
|
+
}
|
|
70
|
+
/** Only safe to merge requests whose billing classification is identical. */
|
|
71
|
+
function costGroupKey(group, table) {
|
|
72
|
+
const price = table.models.find((p) => p.id === group.model || p.slug === group.model);
|
|
73
|
+
return JSON.stringify([
|
|
74
|
+
group.provider,
|
|
75
|
+
group.model,
|
|
76
|
+
price === void 0 ? null : requestRates(price, group.at, group.contextTokens, table),
|
|
77
|
+
group.at === null
|
|
78
|
+
]);
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
81
|
+
//#region src/plan-tiers.ts
|
|
82
|
+
/** Shared plan-tier presentation facts; safe to inline into the browser bundle. */
|
|
83
|
+
const PLAN_TIER_ORDER = [
|
|
84
|
+
"go",
|
|
85
|
+
"goat",
|
|
86
|
+
"pro",
|
|
87
|
+
"provider",
|
|
88
|
+
"max"
|
|
89
|
+
];
|
|
90
|
+
const PLAN_LABELS = {
|
|
91
|
+
go: "Go",
|
|
92
|
+
goat: "GOAT",
|
|
93
|
+
pro: "Pro",
|
|
94
|
+
provider: "Provider",
|
|
95
|
+
max: "Max"
|
|
96
|
+
};
|
|
97
|
+
const PLAN_ORDER = Object.fromEntries(PLAN_TIER_ORDER.map((tier, index) => [tier, index]));
|
|
98
|
+
//#endregion
|
|
15
99
|
//#region src/capabilities.ts
|
|
16
100
|
/**
|
|
17
101
|
* Static capability snapshot for the Command Code provider: model →
|
|
@@ -19,7 +103,7 @@ import { WebError } from "@deepseek-ai/dsh-web";
|
|
|
19
103
|
* subscription-plan labels, deals, and hourly (peak/off-peak) pricing.
|
|
20
104
|
*
|
|
21
105
|
* Everything here is synced from official sources — the command-code CLI
|
|
22
|
-
* 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.68.0) and
|
|
23
107
|
* the official plan/pricing/model docs; see the dsh-commandcode-upstream skill
|
|
24
108
|
* for the extraction procedures. Keeping the snapshot in its own module
|
|
25
109
|
* confines those frequent sync diffs here: src/adapter.ts holds only the stable
|
|
@@ -103,6 +187,13 @@ const KNOWN_EFFORTS = {
|
|
|
103
187
|
"xhigh",
|
|
104
188
|
"max"
|
|
105
189
|
],
|
|
190
|
+
"claude-sonnet-5-5": [
|
|
191
|
+
"low",
|
|
192
|
+
"medium",
|
|
193
|
+
"high",
|
|
194
|
+
"xhigh",
|
|
195
|
+
"max"
|
|
196
|
+
],
|
|
106
197
|
"deepseek/deepseek-v4-flash-fast": [
|
|
107
198
|
"low",
|
|
108
199
|
"high",
|
|
@@ -113,6 +204,11 @@ const KNOWN_EFFORTS = {
|
|
|
113
204
|
"high",
|
|
114
205
|
"max"
|
|
115
206
|
],
|
|
207
|
+
"deepseek/deepseek-v4.1-flash-fast": [
|
|
208
|
+
"low",
|
|
209
|
+
"high",
|
|
210
|
+
"max"
|
|
211
|
+
],
|
|
116
212
|
"deepseek/deepseek-v4-flash": ["high", "max"],
|
|
117
213
|
"deepseek/deepseek-v4-flash-vision-exp": ["high", "max"],
|
|
118
214
|
"deepseek/deepseek-v4-pro": ["high", "max"],
|
|
@@ -348,8 +444,10 @@ const KNOWN_IMAGE_MODELS = /* @__PURE__ */ new Set([
|
|
|
348
444
|
"claude-opus-5",
|
|
349
445
|
"claude-sonnet-4-6",
|
|
350
446
|
"claude-sonnet-5",
|
|
447
|
+
"claude-sonnet-5-5",
|
|
351
448
|
"deepseek/deepseek-v4-flash-vision-exp",
|
|
352
449
|
"deepseek/deepseek-v4.1-flash",
|
|
450
|
+
"deepseek/deepseek-v4.1-flash-fast",
|
|
353
451
|
"google/gemini-3.1-flash-lite",
|
|
354
452
|
"google/gemini-3.5-flash",
|
|
355
453
|
"google/gemini-3.5-flash-lite",
|
|
@@ -395,12 +493,13 @@ const KNOWN_IMAGE_MODELS = /* @__PURE__ */ new Set([
|
|
|
395
493
|
]);
|
|
396
494
|
/**
|
|
397
495
|
* Models WITHOUT a zero-data-retention upstream, per the official CLI's own
|
|
398
|
-
* registry (`command-code@1.
|
|
496
|
+
* registry (`command-code@1.68.0` `dist/cli.mjs`): `modelSupportsZdr(id)` is
|
|
399
497
|
* exactly `!nonZdrSet.has(canonicalize(id))`, and `knownModelSupportsZdr`
|
|
400
498
|
* carries the same membership in the sibling route table — the UNION of both
|
|
401
499
|
* is this set. Reading only the sibling route table would drop `meituan/
|
|
402
|
-
* LongCat-2.0
|
|
403
|
-
*
|
|
500
|
+
* LongCat-2.0`, which appears in `modelSupportsZdr` alone (re-read from the
|
|
501
|
+
* 1.66.0 / 1.67.0 / 1.68.0 artifacts on 2026-09-29: the two stealth-preview
|
|
502
|
+
* rows sit in BOTH sets, so LongCat is the only divergence). The official docs (commandcode.ai/docs/resources/
|
|
404
503
|
* zdr) put it in prose — "99% of our models have ZDR-capable upstreams … only
|
|
405
504
|
* a small handful of models are affected" — so the CLI's exclusion list is
|
|
406
505
|
* the only per-model evidence there is; a ZDR request naming one of these
|
|
@@ -422,12 +521,15 @@ const KNOWN_IMAGE_MODELS = /* @__PURE__ */ new Set([
|
|
|
422
521
|
* (its `zdr:{only:[…]}` provider routes and the per-provider `zdr`/`noTraining`
|
|
423
522
|
* flags) is upstream-internal routing, not a per-model contract, so this table
|
|
424
523
|
* is the snapshot of the exclusion set and nothing more. It is a rare change:
|
|
425
|
-
* 20 members held across 1.62.0 → 1.64.0,
|
|
426
|
-
*
|
|
524
|
+
* 20 members held across 1.62.0 → 1.64.0, 1.65.0 and 1.66.0 each added exactly
|
|
525
|
+
* one (the two stealth-preview models below), 1.67.0 added one
|
|
526
|
+
* (`deepseek/deepseek-v4.1-flash-fast`), and 1.68.0 changed nothing — 23
|
|
527
|
+
* members as of 2026-09-29.
|
|
427
528
|
*/
|
|
428
529
|
const KNOWN_NON_ZDR_MODELS = /* @__PURE__ */ new Set([
|
|
429
530
|
"MiniMaxAI/MiniMax-M3",
|
|
430
531
|
"Qwen/Qwen3.8-Max-0902",
|
|
532
|
+
"deepseek/deepseek-v4.1-flash-fast",
|
|
431
533
|
"meituan/LongCat-2.0",
|
|
432
534
|
"meta/muse-spark-1.1",
|
|
433
535
|
"meta/muse-spark-1.2",
|
|
@@ -539,8 +641,8 @@ function requiresMessagesEndpoint(modelId) {
|
|
|
539
641
|
* `/docs/plans/max` and `/docs/resources/pricing-limits`). Each plan's model
|
|
540
642
|
* list is a superset of the one below it: Go ⊂ GOAT ⊂ Pro ⊂ Provider/Max.
|
|
541
643
|
* Models absent from every plan list (Claude Opus/Fable, Fugu Ultra) are
|
|
542
|
-
* Provider-tier. Re-verified at command-code@1.
|
|
543
|
-
* ids at
|
|
644
|
+
* Provider-tier. Re-verified at command-code@1.68.0 (2026-09-29): 84 catalog
|
|
645
|
+
* ids at 53/62/76/84 cumulative, a strict superset chain — every release since
|
|
544
646
|
* 1.49.0 has been additive with no tier move, and per-entry tags below name the
|
|
545
647
|
* release that added each row.
|
|
546
648
|
*
|
|
@@ -568,6 +670,7 @@ const KNOWN_PLANS = {
|
|
|
568
670
|
"Qwen/Qwen3.8-Omni-Flash": "go",
|
|
569
671
|
"deepseek/deepseek-v4-flash-fast": "go",
|
|
570
672
|
"deepseek/deepseek-v4.1-flash": "go",
|
|
673
|
+
"deepseek/deepseek-v4.1-flash-fast": "go",
|
|
571
674
|
"deepseek/deepseek-v4-flash": "go",
|
|
572
675
|
"deepseek/deepseek-v4-flash-vision-exp": "go",
|
|
573
676
|
"deepseek/deepseek-v4-pro": "go",
|
|
@@ -605,6 +708,7 @@ const KNOWN_PLANS = {
|
|
|
605
708
|
"zai-org/GLM-5.2": "go",
|
|
606
709
|
"zai-org/GLM-5.2-Fast": "go",
|
|
607
710
|
"zai-org/GLM-5.3": "go",
|
|
711
|
+
"claude-sonnet-5-5": "goat",
|
|
608
712
|
"google/gemini-3.7-flash": "goat",
|
|
609
713
|
"google/gemini-3.8-flash": "goat",
|
|
610
714
|
"gpt-5.6-sol": "goat",
|
|
@@ -636,25 +740,6 @@ const KNOWN_PLANS = {
|
|
|
636
740
|
"gpt-6-astra": "provider",
|
|
637
741
|
"sakana/fugu-ultra": "provider"
|
|
638
742
|
};
|
|
639
|
-
/** Official display labels for each plan tier. */
|
|
640
|
-
const PLAN_LABELS = {
|
|
641
|
-
go: "Go",
|
|
642
|
-
goat: "GOAT",
|
|
643
|
-
pro: "Pro",
|
|
644
|
-
provider: "Provider",
|
|
645
|
-
max: "Max"
|
|
646
|
-
};
|
|
647
|
-
/**
|
|
648
|
-
* Plan-tier sort weights, low to high. Models outside the snapshot (unknown
|
|
649
|
-
* plans) sort after every known tier, keeping known models predictable.
|
|
650
|
-
*/
|
|
651
|
-
const PLAN_ORDER = {
|
|
652
|
-
go: 0,
|
|
653
|
-
goat: 1,
|
|
654
|
-
pro: 2,
|
|
655
|
-
provider: 3,
|
|
656
|
-
max: 4
|
|
657
|
-
};
|
|
658
743
|
/**
|
|
659
744
|
* Whether a model is free (requests cost no credits), per the pricing page's
|
|
660
745
|
* deals (`KNOWN_DEALS` `free: true`). Free models lead the picker regardless
|
|
@@ -682,7 +767,7 @@ function compareByPlan(a, b) {
|
|
|
682
767
|
/**
|
|
683
768
|
* Subscription plan table, synced from the official CLI bundle's plan maps
|
|
684
769
|
* (located by the `"individual-go"` key in `dist/cli.mjs`, re-verified unchanged
|
|
685
|
-
* through command-code@1.
|
|
770
|
+
* through command-code@1.68.0): subscription `planId` prefix → display name and
|
|
686
771
|
* the plan's monthly credit total. This is the account's own subscription
|
|
687
772
|
* (from `/alpha/billing/subscriptions`) — distinct from {@link KNOWN_PLANS},
|
|
688
773
|
* which maps catalog models to their minimum tier.
|
|
@@ -812,7 +897,7 @@ const KNOWN_DEALS = {
|
|
|
812
897
|
* charges by the hour: peak hours are 01:00–04:00 and 06:00–10:00 UTC (7h per
|
|
813
898
|
* weekday, full price) **Monday to Friday only**; the other 17 hours of a
|
|
814
899
|
* weekday and every hour of Saturday/Sunday (UTC) are off-peak at half price.
|
|
815
|
-
* Exactly
|
|
900
|
+
* Exactly five models carry the page's `timeOfDay` block (the five rows below).
|
|
816
901
|
* The picker shows the *current* state as a compact label (`Peak`/`Half`)
|
|
817
902
|
* matching the English noun style of the other markers (`Image`, `FREE`), so a
|
|
818
903
|
* developer can tell at a glance whether calling the model right now is cheap
|
|
@@ -837,7 +922,8 @@ const KNOWN_PEAK_PRICING = /* @__PURE__ */ new Set([
|
|
|
837
922
|
"deepseek/deepseek-v4-pro",
|
|
838
923
|
"deepseek/deepseek-v4-flash",
|
|
839
924
|
"deepseek/deepseek-v4-flash-vision-exp",
|
|
840
|
-
"deepseek/deepseek-v4.1-flash"
|
|
925
|
+
"deepseek/deepseek-v4.1-flash",
|
|
926
|
+
"deepseek/deepseek-v4.1-flash-fast"
|
|
841
927
|
]);
|
|
842
928
|
/**
|
|
843
929
|
* Peak hours (UTC, hour-of-day range end-exclusive): 01–03 and 06–09.
|
|
@@ -860,11 +946,7 @@ const PEAK_HOUR_RANGES = [[1, 4], [6, 10]];
|
|
|
860
946
|
* the one in {@link KNOWN_PEAK_PRICING} still gets the right half of the day.
|
|
861
947
|
*/
|
|
862
948
|
function isPeakPricingHour(now = Date.now()) {
|
|
863
|
-
|
|
864
|
-
const day = at.getUTCDay();
|
|
865
|
-
if (day === 0 || day === 6) return false;
|
|
866
|
-
const hour = at.getUTCHours();
|
|
867
|
-
return PEAK_HOUR_RANGES.some(([start, end]) => hour >= start && hour < end);
|
|
949
|
+
return peakHour(now, PEAK_HOUR_RANGES);
|
|
868
950
|
}
|
|
869
951
|
/**
|
|
870
952
|
* As {@link isPeakPricingHour}, plus the {@link KNOWN_PEAK_PRICING} membership
|
|
@@ -945,6 +1027,27 @@ function capabilityDescription(modelId, contextWindow, now = Date.now()) {
|
|
|
945
1027
|
//#endregion
|
|
946
1028
|
//#region src/model-prices.ts
|
|
947
1029
|
/**
|
|
1030
|
+
* The plan tiers a per-model allowance exists for, cheapest first. Ordering
|
|
1031
|
+
* matters: {@link allowanceTierForWeight} matches a tier weight against these
|
|
1032
|
+
* rather than against hard-coded numbers, so this table and the subscription
|
|
1033
|
+
* table cannot drift apart silently.
|
|
1034
|
+
*/
|
|
1035
|
+
const ALLOWANCE_TIERS = ["goat", "pro"];
|
|
1036
|
+
/**
|
|
1037
|
+
* Which allowance bracket a plan-tier weight belongs to, or undefined when that
|
|
1038
|
+
* plan has no per-model allowance.
|
|
1039
|
+
*
|
|
1040
|
+
* Weights come from `KNOWN_SUBSCRIPTION_PLANS` (go 0 · goat 1 · pro 2 ·
|
|
1041
|
+
* provider 3 · max/ultra 4) and {@link PLAN_ORDER} is the same scale, which is
|
|
1042
|
+
* what makes the lookup exact: Go, Provider, Max and Ultra have no published
|
|
1043
|
+
* allowance, and answering with a neighbouring tier's figure would be a
|
|
1044
|
+
* fabricated number rather than a missing one.
|
|
1045
|
+
*/
|
|
1046
|
+
function allowanceTierForWeight(tierWeight) {
|
|
1047
|
+
if (tierWeight === void 0) return void 0;
|
|
1048
|
+
return ALLOWANCE_TIERS.find((tier) => PLAN_ORDER[tier] === tierWeight);
|
|
1049
|
+
}
|
|
1050
|
+
/**
|
|
948
1051
|
* Every price row the page publishes, ordered by its own slug.
|
|
949
1052
|
*
|
|
950
1053
|
* Note on the GPT rows: `gpt-5.3-codex`, `gpt-5.4`, `gpt-5.4-mini` and
|
|
@@ -964,7 +1067,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
964
1067
|
50,
|
|
965
1068
|
1,
|
|
966
1069
|
12.5
|
|
967
|
-
]
|
|
1070
|
+
],
|
|
1071
|
+
allowance: {
|
|
1072
|
+
goat: 20,
|
|
1073
|
+
pro: 20
|
|
1074
|
+
}
|
|
968
1075
|
},
|
|
969
1076
|
{
|
|
970
1077
|
id: "claude-fable-5-1",
|
|
@@ -973,7 +1080,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
973
1080
|
50,
|
|
974
1081
|
.25,
|
|
975
1082
|
12.5
|
|
976
|
-
]
|
|
1083
|
+
],
|
|
1084
|
+
allowance: {
|
|
1085
|
+
goat: 20,
|
|
1086
|
+
pro: 20
|
|
1087
|
+
}
|
|
977
1088
|
},
|
|
978
1089
|
{
|
|
979
1090
|
id: "claude-haiku-4-5",
|
|
@@ -982,7 +1093,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
982
1093
|
5,
|
|
983
1094
|
.1,
|
|
984
1095
|
1.25
|
|
985
|
-
]
|
|
1096
|
+
],
|
|
1097
|
+
allowance: {
|
|
1098
|
+
goat: 20,
|
|
1099
|
+
pro: 20
|
|
1100
|
+
}
|
|
986
1101
|
},
|
|
987
1102
|
{
|
|
988
1103
|
id: "claude-opus-4-6",
|
|
@@ -991,7 +1106,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
991
1106
|
25,
|
|
992
1107
|
.5,
|
|
993
1108
|
6.25
|
|
994
|
-
]
|
|
1109
|
+
],
|
|
1110
|
+
allowance: {
|
|
1111
|
+
goat: 20,
|
|
1112
|
+
pro: 20
|
|
1113
|
+
}
|
|
995
1114
|
},
|
|
996
1115
|
{
|
|
997
1116
|
id: "claude-opus-4-7",
|
|
@@ -1000,7 +1119,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1000
1119
|
25,
|
|
1001
1120
|
.5,
|
|
1002
1121
|
6.25
|
|
1003
|
-
]
|
|
1122
|
+
],
|
|
1123
|
+
allowance: {
|
|
1124
|
+
goat: 20,
|
|
1125
|
+
pro: 20
|
|
1126
|
+
}
|
|
1004
1127
|
},
|
|
1005
1128
|
{
|
|
1006
1129
|
id: "claude-opus-4-8",
|
|
@@ -1009,7 +1132,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1009
1132
|
25,
|
|
1010
1133
|
.5,
|
|
1011
1134
|
6.25
|
|
1012
|
-
]
|
|
1135
|
+
],
|
|
1136
|
+
allowance: {
|
|
1137
|
+
goat: 20,
|
|
1138
|
+
pro: 20
|
|
1139
|
+
}
|
|
1013
1140
|
},
|
|
1014
1141
|
{
|
|
1015
1142
|
id: "claude-opus-5",
|
|
@@ -1018,7 +1145,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1018
1145
|
25,
|
|
1019
1146
|
.5,
|
|
1020
1147
|
6.25
|
|
1021
|
-
]
|
|
1148
|
+
],
|
|
1149
|
+
allowance: {
|
|
1150
|
+
goat: 20,
|
|
1151
|
+
pro: 20
|
|
1152
|
+
}
|
|
1022
1153
|
},
|
|
1023
1154
|
{
|
|
1024
1155
|
id: "claude-opus-5-5",
|
|
@@ -1027,7 +1158,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1027
1158
|
20,
|
|
1028
1159
|
.2,
|
|
1029
1160
|
5
|
|
1030
|
-
]
|
|
1161
|
+
],
|
|
1162
|
+
allowance: {
|
|
1163
|
+
goat: 20,
|
|
1164
|
+
pro: 20
|
|
1165
|
+
}
|
|
1031
1166
|
},
|
|
1032
1167
|
{
|
|
1033
1168
|
id: "claude-sonnet-4-6",
|
|
@@ -1036,7 +1171,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1036
1171
|
15,
|
|
1037
1172
|
.3,
|
|
1038
1173
|
3.75
|
|
1039
|
-
]
|
|
1174
|
+
],
|
|
1175
|
+
allowance: {
|
|
1176
|
+
goat: 20,
|
|
1177
|
+
pro: 20
|
|
1178
|
+
}
|
|
1040
1179
|
},
|
|
1041
1180
|
{
|
|
1042
1181
|
id: "claude-sonnet-5",
|
|
@@ -1045,7 +1184,24 @@ const MODEL_PRICE_ROWS = [
|
|
|
1045
1184
|
10,
|
|
1046
1185
|
.2,
|
|
1047
1186
|
2.5
|
|
1048
|
-
]
|
|
1187
|
+
],
|
|
1188
|
+
allowance: {
|
|
1189
|
+
goat: 20,
|
|
1190
|
+
pro: 20
|
|
1191
|
+
}
|
|
1192
|
+
},
|
|
1193
|
+
{
|
|
1194
|
+
id: "claude-sonnet-5-5",
|
|
1195
|
+
rates: [
|
|
1196
|
+
2,
|
|
1197
|
+
10,
|
|
1198
|
+
.2,
|
|
1199
|
+
2.5
|
|
1200
|
+
],
|
|
1201
|
+
allowance: {
|
|
1202
|
+
goat: 10,
|
|
1203
|
+
pro: 20
|
|
1204
|
+
}
|
|
1049
1205
|
},
|
|
1050
1206
|
{
|
|
1051
1207
|
id: "deepseek-v4-flash",
|
|
@@ -1058,7 +1214,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1058
1214
|
.3,
|
|
1059
1215
|
1.2,
|
|
1060
1216
|
.006
|
|
1061
|
-
]
|
|
1217
|
+
],
|
|
1218
|
+
allowance: {
|
|
1219
|
+
goat: 60,
|
|
1220
|
+
pro: 70
|
|
1221
|
+
}
|
|
1062
1222
|
},
|
|
1063
1223
|
{
|
|
1064
1224
|
id: "deepseek-v4-flash-fast",
|
|
@@ -1066,7 +1226,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1066
1226
|
.28,
|
|
1067
1227
|
.56,
|
|
1068
1228
|
.07
|
|
1069
|
-
]
|
|
1229
|
+
],
|
|
1230
|
+
allowance: {
|
|
1231
|
+
goat: 20,
|
|
1232
|
+
pro: 30
|
|
1233
|
+
}
|
|
1070
1234
|
},
|
|
1071
1235
|
{
|
|
1072
1236
|
id: "deepseek-v4-flash-vision-exp",
|
|
@@ -1079,7 +1243,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1079
1243
|
.3,
|
|
1080
1244
|
1.2,
|
|
1081
1245
|
.006
|
|
1082
|
-
]
|
|
1246
|
+
],
|
|
1247
|
+
allowance: {
|
|
1248
|
+
goat: 20,
|
|
1249
|
+
pro: 30
|
|
1250
|
+
}
|
|
1083
1251
|
},
|
|
1084
1252
|
{
|
|
1085
1253
|
id: "deepseek-v4-pro",
|
|
@@ -1092,7 +1260,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1092
1260
|
1.32,
|
|
1093
1261
|
3.96,
|
|
1094
1262
|
.044
|
|
1095
|
-
]
|
|
1263
|
+
],
|
|
1264
|
+
allowance: {
|
|
1265
|
+
goat: 20,
|
|
1266
|
+
pro: 30
|
|
1267
|
+
}
|
|
1096
1268
|
},
|
|
1097
1269
|
{
|
|
1098
1270
|
id: "deepseek-v4.1-flash",
|
|
@@ -1105,7 +1277,28 @@ const MODEL_PRICE_ROWS = [
|
|
|
1105
1277
|
.3,
|
|
1106
1278
|
1.2,
|
|
1107
1279
|
.006
|
|
1108
|
-
]
|
|
1280
|
+
],
|
|
1281
|
+
allowance: {
|
|
1282
|
+
goat: 60,
|
|
1283
|
+
pro: 70
|
|
1284
|
+
}
|
|
1285
|
+
},
|
|
1286
|
+
{
|
|
1287
|
+
id: "deepseek-v4.1-flash-fast",
|
|
1288
|
+
rates: [
|
|
1289
|
+
.16,
|
|
1290
|
+
.58,
|
|
1291
|
+
.016
|
|
1292
|
+
],
|
|
1293
|
+
peak: [
|
|
1294
|
+
.32,
|
|
1295
|
+
1.16,
|
|
1296
|
+
.032
|
|
1297
|
+
],
|
|
1298
|
+
allowance: {
|
|
1299
|
+
goat: 60,
|
|
1300
|
+
pro: 70
|
|
1301
|
+
}
|
|
1109
1302
|
},
|
|
1110
1303
|
{
|
|
1111
1304
|
id: "fugu-ultra",
|
|
@@ -1113,7 +1306,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1113
1306
|
5,
|
|
1114
1307
|
30,
|
|
1115
1308
|
.5
|
|
1116
|
-
]
|
|
1309
|
+
],
|
|
1310
|
+
allowance: {
|
|
1311
|
+
goat: 20,
|
|
1312
|
+
pro: 20
|
|
1313
|
+
}
|
|
1117
1314
|
},
|
|
1118
1315
|
{
|
|
1119
1316
|
id: "gemini-3.1-flash-lite",
|
|
@@ -1121,7 +1318,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1121
1318
|
.25,
|
|
1122
1319
|
1.5,
|
|
1123
1320
|
.03
|
|
1124
|
-
]
|
|
1321
|
+
],
|
|
1322
|
+
allowance: {
|
|
1323
|
+
goat: 20,
|
|
1324
|
+
pro: 20
|
|
1325
|
+
}
|
|
1125
1326
|
},
|
|
1126
1327
|
{
|
|
1127
1328
|
id: "gemini-3.5-flash",
|
|
@@ -1129,7 +1330,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1129
1330
|
1.5,
|
|
1130
1331
|
9,
|
|
1131
1332
|
.15
|
|
1132
|
-
]
|
|
1333
|
+
],
|
|
1334
|
+
allowance: {
|
|
1335
|
+
goat: 20,
|
|
1336
|
+
pro: 20
|
|
1337
|
+
}
|
|
1133
1338
|
},
|
|
1134
1339
|
{
|
|
1135
1340
|
id: "gemini-3.5-flash-lite",
|
|
@@ -1137,7 +1342,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1137
1342
|
.3,
|
|
1138
1343
|
2.5,
|
|
1139
1344
|
.03
|
|
1140
|
-
]
|
|
1345
|
+
],
|
|
1346
|
+
allowance: {
|
|
1347
|
+
goat: 20,
|
|
1348
|
+
pro: 20
|
|
1349
|
+
}
|
|
1141
1350
|
},
|
|
1142
1351
|
{
|
|
1143
1352
|
id: "gemini-3.6-flash",
|
|
@@ -1145,7 +1354,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1145
1354
|
1.5,
|
|
1146
1355
|
7.5,
|
|
1147
1356
|
.15
|
|
1148
|
-
]
|
|
1357
|
+
],
|
|
1358
|
+
allowance: {
|
|
1359
|
+
goat: 20,
|
|
1360
|
+
pro: 20
|
|
1361
|
+
}
|
|
1149
1362
|
},
|
|
1150
1363
|
{
|
|
1151
1364
|
id: "gemini-3.7-flash",
|
|
@@ -1154,7 +1367,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1154
1367
|
7.5,
|
|
1155
1368
|
.15,
|
|
1156
1369
|
.08334
|
|
1157
|
-
]
|
|
1370
|
+
],
|
|
1371
|
+
allowance: {
|
|
1372
|
+
goat: 40,
|
|
1373
|
+
pro: 50
|
|
1374
|
+
}
|
|
1158
1375
|
},
|
|
1159
1376
|
{
|
|
1160
1377
|
id: "gemini-3.8-flash",
|
|
@@ -1162,7 +1379,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1162
1379
|
1.5,
|
|
1163
1380
|
7.5,
|
|
1164
1381
|
.15
|
|
1165
|
-
]
|
|
1382
|
+
],
|
|
1383
|
+
allowance: {
|
|
1384
|
+
goat: 40,
|
|
1385
|
+
pro: 50
|
|
1386
|
+
}
|
|
1166
1387
|
},
|
|
1167
1388
|
{
|
|
1168
1389
|
id: "glm-5",
|
|
@@ -1170,7 +1391,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1170
1391
|
1,
|
|
1171
1392
|
3.2,
|
|
1172
1393
|
.2
|
|
1173
|
-
]
|
|
1394
|
+
],
|
|
1395
|
+
allowance: {
|
|
1396
|
+
goat: 20,
|
|
1397
|
+
pro: 30
|
|
1398
|
+
}
|
|
1174
1399
|
},
|
|
1175
1400
|
{
|
|
1176
1401
|
id: "glm-5.1",
|
|
@@ -1178,7 +1403,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1178
1403
|
1.4,
|
|
1179
1404
|
4.4,
|
|
1180
1405
|
.26
|
|
1181
|
-
]
|
|
1406
|
+
],
|
|
1407
|
+
allowance: {
|
|
1408
|
+
goat: 20,
|
|
1409
|
+
pro: 30
|
|
1410
|
+
}
|
|
1182
1411
|
},
|
|
1183
1412
|
{
|
|
1184
1413
|
id: "glm-5.2",
|
|
@@ -1186,7 +1415,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1186
1415
|
1.4,
|
|
1187
1416
|
4.4,
|
|
1188
1417
|
.26
|
|
1189
|
-
]
|
|
1418
|
+
],
|
|
1419
|
+
allowance: {
|
|
1420
|
+
goat: 70,
|
|
1421
|
+
pro: 80
|
|
1422
|
+
}
|
|
1190
1423
|
},
|
|
1191
1424
|
{
|
|
1192
1425
|
id: "glm-5.2-fast",
|
|
@@ -1194,7 +1427,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1194
1427
|
3,
|
|
1195
1428
|
10.25,
|
|
1196
1429
|
.5
|
|
1197
|
-
]
|
|
1430
|
+
],
|
|
1431
|
+
allowance: {
|
|
1432
|
+
goat: 20,
|
|
1433
|
+
pro: 30
|
|
1434
|
+
}
|
|
1198
1435
|
},
|
|
1199
1436
|
{
|
|
1200
1437
|
id: "glm-5.3",
|
|
@@ -1202,7 +1439,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1202
1439
|
1.4,
|
|
1203
1440
|
4.4,
|
|
1204
1441
|
.26
|
|
1205
|
-
]
|
|
1442
|
+
],
|
|
1443
|
+
allowance: {
|
|
1444
|
+
goat: 20,
|
|
1445
|
+
pro: 30
|
|
1446
|
+
}
|
|
1206
1447
|
},
|
|
1207
1448
|
{
|
|
1208
1449
|
id: "glm-5.3-flash",
|
|
@@ -1210,7 +1451,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1210
1451
|
.15,
|
|
1211
1452
|
.5,
|
|
1212
1453
|
.03
|
|
1213
|
-
]
|
|
1454
|
+
],
|
|
1455
|
+
allowance: {
|
|
1456
|
+
goat: 40,
|
|
1457
|
+
pro: 50
|
|
1458
|
+
}
|
|
1214
1459
|
},
|
|
1215
1460
|
{
|
|
1216
1461
|
id: "glm-5.3-flashx",
|
|
@@ -1218,7 +1463,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1218
1463
|
.37,
|
|
1219
1464
|
1.25,
|
|
1220
1465
|
.075
|
|
1221
|
-
]
|
|
1466
|
+
],
|
|
1467
|
+
allowance: {
|
|
1468
|
+
goat: 20,
|
|
1469
|
+
pro: 30
|
|
1470
|
+
}
|
|
1222
1471
|
},
|
|
1223
1472
|
{
|
|
1224
1473
|
id: "gpt-5.3-codex",
|
|
@@ -1227,7 +1476,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1227
1476
|
8,
|
|
1228
1477
|
.5,
|
|
1229
1478
|
0
|
|
1230
|
-
]
|
|
1479
|
+
],
|
|
1480
|
+
allowance: {
|
|
1481
|
+
goat: 20,
|
|
1482
|
+
pro: 20
|
|
1483
|
+
}
|
|
1231
1484
|
},
|
|
1232
1485
|
{
|
|
1233
1486
|
id: "gpt-5.4",
|
|
@@ -1236,7 +1489,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1236
1489
|
15,
|
|
1237
1490
|
.25,
|
|
1238
1491
|
0
|
|
1239
|
-
]
|
|
1492
|
+
],
|
|
1493
|
+
allowance: {
|
|
1494
|
+
goat: 20,
|
|
1495
|
+
pro: 20
|
|
1496
|
+
}
|
|
1240
1497
|
},
|
|
1241
1498
|
{
|
|
1242
1499
|
id: "gpt-5.4-mini",
|
|
@@ -1245,7 +1502,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1245
1502
|
4.5,
|
|
1246
1503
|
.075,
|
|
1247
1504
|
0
|
|
1248
|
-
]
|
|
1505
|
+
],
|
|
1506
|
+
allowance: {
|
|
1507
|
+
goat: 20,
|
|
1508
|
+
pro: 20
|
|
1509
|
+
}
|
|
1249
1510
|
},
|
|
1250
1511
|
{
|
|
1251
1512
|
id: "gpt-5.5",
|
|
@@ -1254,7 +1515,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1254
1515
|
30,
|
|
1255
1516
|
.5,
|
|
1256
1517
|
0
|
|
1257
|
-
]
|
|
1518
|
+
],
|
|
1519
|
+
allowance: {
|
|
1520
|
+
goat: 20,
|
|
1521
|
+
pro: 20
|
|
1522
|
+
}
|
|
1258
1523
|
},
|
|
1259
1524
|
{
|
|
1260
1525
|
id: "gpt-5.6-luna",
|
|
@@ -1264,6 +1529,10 @@ const MODEL_PRICE_ROWS = [
|
|
|
1264
1529
|
.02,
|
|
1265
1530
|
.25
|
|
1266
1531
|
],
|
|
1532
|
+
allowance: {
|
|
1533
|
+
goat: 20,
|
|
1534
|
+
pro: 30
|
|
1535
|
+
},
|
|
1267
1536
|
contextTiers: [{
|
|
1268
1537
|
"maxContext": 272e3,
|
|
1269
1538
|
"rates": [
|
|
@@ -1287,6 +1556,10 @@ const MODEL_PRICE_ROWS = [
|
|
|
1287
1556
|
.5,
|
|
1288
1557
|
6.25
|
|
1289
1558
|
],
|
|
1559
|
+
allowance: {
|
|
1560
|
+
goat: 70,
|
|
1561
|
+
pro: 80
|
|
1562
|
+
},
|
|
1290
1563
|
contextTiers: [{
|
|
1291
1564
|
"maxContext": 272e3,
|
|
1292
1565
|
"rates": [
|
|
@@ -1310,6 +1583,10 @@ const MODEL_PRICE_ROWS = [
|
|
|
1310
1583
|
.2,
|
|
1311
1584
|
2.5
|
|
1312
1585
|
],
|
|
1586
|
+
allowance: {
|
|
1587
|
+
goat: 20,
|
|
1588
|
+
pro: 20
|
|
1589
|
+
},
|
|
1313
1590
|
contextTiers: [{
|
|
1314
1591
|
"maxContext": 272e3,
|
|
1315
1592
|
"rates": [
|
|
@@ -1333,6 +1610,10 @@ const MODEL_PRICE_ROWS = [
|
|
|
1333
1610
|
1,
|
|
1334
1611
|
12.5
|
|
1335
1612
|
],
|
|
1613
|
+
allowance: {
|
|
1614
|
+
goat: 20,
|
|
1615
|
+
pro: 20
|
|
1616
|
+
},
|
|
1336
1617
|
contextTiers: [{
|
|
1337
1618
|
"maxContext": 272e3,
|
|
1338
1619
|
"rates": [
|
|
@@ -1356,6 +1637,10 @@ const MODEL_PRICE_ROWS = [
|
|
|
1356
1637
|
.01,
|
|
1357
1638
|
.125
|
|
1358
1639
|
],
|
|
1640
|
+
allowance: {
|
|
1641
|
+
goat: 20,
|
|
1642
|
+
pro: 30
|
|
1643
|
+
},
|
|
1359
1644
|
contextTiers: [{
|
|
1360
1645
|
"maxContext": 272e3,
|
|
1361
1646
|
"rates": [
|
|
@@ -1379,6 +1664,10 @@ const MODEL_PRICE_ROWS = [
|
|
|
1379
1664
|
.2,
|
|
1380
1665
|
2.5
|
|
1381
1666
|
],
|
|
1667
|
+
allowance: {
|
|
1668
|
+
goat: 20,
|
|
1669
|
+
pro: 20
|
|
1670
|
+
},
|
|
1382
1671
|
contextTiers: [{
|
|
1383
1672
|
"maxContext": 272e3,
|
|
1384
1673
|
"rates": [
|
|
@@ -1400,7 +1689,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1400
1689
|
2,
|
|
1401
1690
|
6,
|
|
1402
1691
|
.5
|
|
1403
|
-
]
|
|
1692
|
+
],
|
|
1693
|
+
allowance: {
|
|
1694
|
+
goat: 20,
|
|
1695
|
+
pro: 30
|
|
1696
|
+
}
|
|
1404
1697
|
},
|
|
1405
1698
|
{
|
|
1406
1699
|
id: "grok-4.6",
|
|
@@ -1409,6 +1702,10 @@ const MODEL_PRICE_ROWS = [
|
|
|
1409
1702
|
6,
|
|
1410
1703
|
.5
|
|
1411
1704
|
],
|
|
1705
|
+
allowance: {
|
|
1706
|
+
goat: 20,
|
|
1707
|
+
pro: 30
|
|
1708
|
+
},
|
|
1412
1709
|
contextTiers: [{
|
|
1413
1710
|
"maxContext": 2e5,
|
|
1414
1711
|
"rates": [
|
|
@@ -1429,18 +1726,39 @@ const MODEL_PRICE_ROWS = [
|
|
|
1429
1726
|
3.6,
|
|
1430
1727
|
.3
|
|
1431
1728
|
],
|
|
1729
|
+
listRates: [
|
|
1730
|
+
2,
|
|
1731
|
+
6,
|
|
1732
|
+
.5
|
|
1733
|
+
],
|
|
1734
|
+
allowance: {
|
|
1735
|
+
goat: 20,
|
|
1736
|
+
pro: 30
|
|
1737
|
+
},
|
|
1432
1738
|
contextTiers: [{
|
|
1433
1739
|
"maxContext": 2e5,
|
|
1434
1740
|
"rates": [
|
|
1435
1741
|
1.2,
|
|
1436
1742
|
3.6,
|
|
1437
1743
|
.3
|
|
1744
|
+
],
|
|
1745
|
+
"listRates": [
|
|
1746
|
+
2,
|
|
1747
|
+
6,
|
|
1748
|
+
.5
|
|
1438
1749
|
]
|
|
1439
|
-
}, {
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1750
|
+
}, {
|
|
1751
|
+
"rates": [
|
|
1752
|
+
2.4,
|
|
1753
|
+
7.2,
|
|
1754
|
+
.6
|
|
1755
|
+
],
|
|
1756
|
+
"listRates": [
|
|
1757
|
+
4,
|
|
1758
|
+
12,
|
|
1759
|
+
1
|
|
1760
|
+
]
|
|
1761
|
+
}]
|
|
1444
1762
|
},
|
|
1445
1763
|
{
|
|
1446
1764
|
id: "inkling",
|
|
@@ -1448,7 +1766,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1448
1766
|
1,
|
|
1449
1767
|
4.05,
|
|
1450
1768
|
.17
|
|
1451
|
-
]
|
|
1769
|
+
],
|
|
1770
|
+
allowance: {
|
|
1771
|
+
goat: 20,
|
|
1772
|
+
pro: 30
|
|
1773
|
+
}
|
|
1452
1774
|
},
|
|
1453
1775
|
{
|
|
1454
1776
|
id: "inkling-small",
|
|
@@ -1456,7 +1778,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1456
1778
|
.5,
|
|
1457
1779
|
1.2,
|
|
1458
1780
|
.1
|
|
1459
|
-
]
|
|
1781
|
+
],
|
|
1782
|
+
allowance: {
|
|
1783
|
+
goat: 20,
|
|
1784
|
+
pro: 30
|
|
1785
|
+
}
|
|
1460
1786
|
},
|
|
1461
1787
|
{
|
|
1462
1788
|
id: "kimi-k2.5",
|
|
@@ -1464,7 +1790,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1464
1790
|
.6,
|
|
1465
1791
|
3,
|
|
1466
1792
|
.1
|
|
1467
|
-
]
|
|
1793
|
+
],
|
|
1794
|
+
allowance: {
|
|
1795
|
+
goat: 20,
|
|
1796
|
+
pro: 30
|
|
1797
|
+
}
|
|
1468
1798
|
},
|
|
1469
1799
|
{
|
|
1470
1800
|
id: "kimi-k2.6",
|
|
@@ -1472,7 +1802,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1472
1802
|
.95,
|
|
1473
1803
|
4,
|
|
1474
1804
|
.16
|
|
1475
|
-
]
|
|
1805
|
+
],
|
|
1806
|
+
allowance: {
|
|
1807
|
+
goat: 20,
|
|
1808
|
+
pro: 30
|
|
1809
|
+
}
|
|
1476
1810
|
},
|
|
1477
1811
|
{
|
|
1478
1812
|
id: "kimi-k2.7-code",
|
|
@@ -1480,7 +1814,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1480
1814
|
.95,
|
|
1481
1815
|
4,
|
|
1482
1816
|
.19
|
|
1483
|
-
]
|
|
1817
|
+
],
|
|
1818
|
+
allowance: {
|
|
1819
|
+
goat: 60,
|
|
1820
|
+
pro: 70
|
|
1821
|
+
}
|
|
1484
1822
|
},
|
|
1485
1823
|
{
|
|
1486
1824
|
id: "kimi-k2.7-code-highspeed",
|
|
@@ -1488,7 +1826,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1488
1826
|
1.9,
|
|
1489
1827
|
8,
|
|
1490
1828
|
.38
|
|
1491
|
-
]
|
|
1829
|
+
],
|
|
1830
|
+
allowance: {
|
|
1831
|
+
goat: 20,
|
|
1832
|
+
pro: 30
|
|
1833
|
+
}
|
|
1492
1834
|
},
|
|
1493
1835
|
{
|
|
1494
1836
|
id: "kimi-k3",
|
|
@@ -1496,7 +1838,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1496
1838
|
3,
|
|
1497
1839
|
15,
|
|
1498
1840
|
.3
|
|
1499
|
-
]
|
|
1841
|
+
],
|
|
1842
|
+
allowance: {
|
|
1843
|
+
goat: 20,
|
|
1844
|
+
pro: 30
|
|
1845
|
+
}
|
|
1500
1846
|
},
|
|
1501
1847
|
{
|
|
1502
1848
|
id: "longcat-2.0",
|
|
@@ -1504,7 +1850,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1504
1850
|
.3,
|
|
1505
1851
|
1.2,
|
|
1506
1852
|
.006
|
|
1507
|
-
]
|
|
1853
|
+
],
|
|
1854
|
+
allowance: {
|
|
1855
|
+
goat: 50,
|
|
1856
|
+
pro: 60
|
|
1857
|
+
}
|
|
1508
1858
|
},
|
|
1509
1859
|
{
|
|
1510
1860
|
id: "mimo-v2.5",
|
|
@@ -1512,7 +1862,16 @@ const MODEL_PRICE_ROWS = [
|
|
|
1512
1862
|
.14,
|
|
1513
1863
|
.28,
|
|
1514
1864
|
.0028
|
|
1515
|
-
]
|
|
1865
|
+
],
|
|
1866
|
+
listRates: [
|
|
1867
|
+
.8,
|
|
1868
|
+
4,
|
|
1869
|
+
.16
|
|
1870
|
+
],
|
|
1871
|
+
allowance: {
|
|
1872
|
+
goat: 30,
|
|
1873
|
+
pro: 40
|
|
1874
|
+
}
|
|
1516
1875
|
},
|
|
1517
1876
|
{
|
|
1518
1877
|
id: "mimo-v2.5-pro",
|
|
@@ -1520,7 +1879,16 @@ const MODEL_PRICE_ROWS = [
|
|
|
1520
1879
|
.435,
|
|
1521
1880
|
.87,
|
|
1522
1881
|
.0036
|
|
1523
|
-
]
|
|
1882
|
+
],
|
|
1883
|
+
listRates: [
|
|
1884
|
+
2,
|
|
1885
|
+
6,
|
|
1886
|
+
.4
|
|
1887
|
+
],
|
|
1888
|
+
allowance: {
|
|
1889
|
+
goat: 20,
|
|
1890
|
+
pro: 30
|
|
1891
|
+
}
|
|
1524
1892
|
},
|
|
1525
1893
|
{
|
|
1526
1894
|
id: "mimo-v2.6-flash",
|
|
@@ -1528,7 +1896,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1528
1896
|
.14,
|
|
1529
1897
|
.28,
|
|
1530
1898
|
.0028
|
|
1531
|
-
]
|
|
1899
|
+
],
|
|
1900
|
+
allowance: {
|
|
1901
|
+
goat: 20,
|
|
1902
|
+
pro: 30
|
|
1903
|
+
}
|
|
1532
1904
|
},
|
|
1533
1905
|
{
|
|
1534
1906
|
id: "mimo-v2.6-pro",
|
|
@@ -1536,7 +1908,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1536
1908
|
.435,
|
|
1537
1909
|
.87,
|
|
1538
1910
|
.0036
|
|
1539
|
-
]
|
|
1911
|
+
],
|
|
1912
|
+
allowance: {
|
|
1913
|
+
goat: 20,
|
|
1914
|
+
pro: 30
|
|
1915
|
+
}
|
|
1540
1916
|
},
|
|
1541
1917
|
{
|
|
1542
1918
|
id: "mimo-v2.6-pro-ultraspeed",
|
|
@@ -1544,7 +1920,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1544
1920
|
4.35,
|
|
1545
1921
|
8.7,
|
|
1546
1922
|
.036
|
|
1547
|
-
]
|
|
1923
|
+
],
|
|
1924
|
+
allowance: {
|
|
1925
|
+
goat: 10,
|
|
1926
|
+
pro: 20
|
|
1927
|
+
}
|
|
1548
1928
|
},
|
|
1549
1929
|
{
|
|
1550
1930
|
id: "minimax-m2.5",
|
|
@@ -1552,7 +1932,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1552
1932
|
.3,
|
|
1553
1933
|
1.2,
|
|
1554
1934
|
.03
|
|
1555
|
-
]
|
|
1935
|
+
],
|
|
1936
|
+
allowance: {
|
|
1937
|
+
goat: 20,
|
|
1938
|
+
pro: 30
|
|
1939
|
+
}
|
|
1556
1940
|
},
|
|
1557
1941
|
{
|
|
1558
1942
|
id: "minimax-m2.7",
|
|
@@ -1560,7 +1944,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1560
1944
|
.3,
|
|
1561
1945
|
1.2,
|
|
1562
1946
|
.06
|
|
1563
|
-
]
|
|
1947
|
+
],
|
|
1948
|
+
allowance: {
|
|
1949
|
+
goat: 20,
|
|
1950
|
+
pro: 30
|
|
1951
|
+
}
|
|
1564
1952
|
},
|
|
1565
1953
|
{
|
|
1566
1954
|
id: "minimax-m3",
|
|
@@ -1568,7 +1956,16 @@ const MODEL_PRICE_ROWS = [
|
|
|
1568
1956
|
.3,
|
|
1569
1957
|
1.2,
|
|
1570
1958
|
.06
|
|
1571
|
-
]
|
|
1959
|
+
],
|
|
1960
|
+
listRates: [
|
|
1961
|
+
.6,
|
|
1962
|
+
2.4,
|
|
1963
|
+
.12
|
|
1964
|
+
],
|
|
1965
|
+
allowance: {
|
|
1966
|
+
goat: 47,
|
|
1967
|
+
pro: 57
|
|
1968
|
+
}
|
|
1572
1969
|
},
|
|
1573
1970
|
{
|
|
1574
1971
|
id: "muse-spark-1.1",
|
|
@@ -1576,7 +1973,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1576
1973
|
1.25,
|
|
1577
1974
|
4.25,
|
|
1578
1975
|
.15
|
|
1579
|
-
]
|
|
1976
|
+
],
|
|
1977
|
+
allowance: {
|
|
1978
|
+
goat: 20,
|
|
1979
|
+
pro: 20
|
|
1980
|
+
}
|
|
1580
1981
|
},
|
|
1581
1982
|
{
|
|
1582
1983
|
id: "muse-spark-1.2",
|
|
@@ -1584,7 +1985,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1584
1985
|
1.25,
|
|
1585
1986
|
4.25,
|
|
1586
1987
|
.15
|
|
1587
|
-
]
|
|
1988
|
+
],
|
|
1989
|
+
allowance: {
|
|
1990
|
+
goat: 20,
|
|
1991
|
+
pro: 30
|
|
1992
|
+
}
|
|
1588
1993
|
},
|
|
1589
1994
|
{
|
|
1590
1995
|
id: "muse-spark-1.2-contributor",
|
|
@@ -1592,7 +1997,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1592
1997
|
.1,
|
|
1593
1998
|
.2,
|
|
1594
1999
|
.002
|
|
1595
|
-
]
|
|
2000
|
+
],
|
|
2001
|
+
allowance: {
|
|
2002
|
+
goat: 20,
|
|
2003
|
+
pro: 30
|
|
2004
|
+
}
|
|
1596
2005
|
},
|
|
1597
2006
|
{
|
|
1598
2007
|
id: "muse-spark-1.3",
|
|
@@ -1600,7 +2009,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1600
2009
|
1.25,
|
|
1601
2010
|
4.25,
|
|
1602
2011
|
.15
|
|
1603
|
-
]
|
|
2012
|
+
],
|
|
2013
|
+
allowance: {
|
|
2014
|
+
goat: 20,
|
|
2015
|
+
pro: 30
|
|
2016
|
+
}
|
|
1604
2017
|
},
|
|
1605
2018
|
{
|
|
1606
2019
|
id: "muse-spark-1.3-contributor",
|
|
@@ -1608,7 +2021,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1608
2021
|
.1,
|
|
1609
2022
|
.2,
|
|
1610
2023
|
.002
|
|
1611
|
-
]
|
|
2024
|
+
],
|
|
2025
|
+
allowance: {
|
|
2026
|
+
goat: 20,
|
|
2027
|
+
pro: 30
|
|
2028
|
+
}
|
|
1612
2029
|
},
|
|
1613
2030
|
{
|
|
1614
2031
|
id: "nemotron-3-ultra",
|
|
@@ -1616,7 +2033,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1616
2033
|
.6,
|
|
1617
2034
|
2.4,
|
|
1618
2035
|
.12
|
|
1619
|
-
]
|
|
2036
|
+
],
|
|
2037
|
+
allowance: {
|
|
2038
|
+
goat: 20,
|
|
2039
|
+
pro: 30
|
|
2040
|
+
}
|
|
1620
2041
|
},
|
|
1621
2042
|
{
|
|
1622
2043
|
id: "qwen-3.6-max",
|
|
@@ -1625,7 +2046,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1625
2046
|
7.8,
|
|
1626
2047
|
.26,
|
|
1627
2048
|
1.63
|
|
1628
|
-
]
|
|
2049
|
+
],
|
|
2050
|
+
allowance: {
|
|
2051
|
+
goat: 20,
|
|
2052
|
+
pro: 30
|
|
2053
|
+
}
|
|
1629
2054
|
},
|
|
1630
2055
|
{
|
|
1631
2056
|
id: "qwen-3.6-plus",
|
|
@@ -1634,6 +2059,10 @@ const MODEL_PRICE_ROWS = [
|
|
|
1634
2059
|
3,
|
|
1635
2060
|
.1
|
|
1636
2061
|
],
|
|
2062
|
+
allowance: {
|
|
2063
|
+
goat: 33,
|
|
2064
|
+
pro: 43
|
|
2065
|
+
},
|
|
1637
2066
|
contextTiers: [{
|
|
1638
2067
|
"maxContext": 256e3,
|
|
1639
2068
|
"rates": [
|
|
@@ -1655,6 +2084,10 @@ const MODEL_PRICE_ROWS = [
|
|
|
1655
2084
|
.006,
|
|
1656
2085
|
.038
|
|
1657
2086
|
],
|
|
2087
|
+
allowance: {
|
|
2088
|
+
goat: 20,
|
|
2089
|
+
pro: 30
|
|
2090
|
+
},
|
|
1658
2091
|
contextTiers: [
|
|
1659
2092
|
{
|
|
1660
2093
|
"maxContext": 32e3,
|
|
@@ -1689,7 +2122,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1689
2122
|
7.5,
|
|
1690
2123
|
.5,
|
|
1691
2124
|
3.13
|
|
1692
|
-
]
|
|
2125
|
+
],
|
|
2126
|
+
allowance: {
|
|
2127
|
+
goat: 33,
|
|
2128
|
+
pro: 43
|
|
2129
|
+
}
|
|
1693
2130
|
},
|
|
1694
2131
|
{
|
|
1695
2132
|
id: "qwen-3.7-plus",
|
|
@@ -1699,6 +2136,10 @@ const MODEL_PRICE_ROWS = [
|
|
|
1699
2136
|
.08,
|
|
1700
2137
|
.5
|
|
1701
2138
|
],
|
|
2139
|
+
allowance: {
|
|
2140
|
+
goat: 33,
|
|
2141
|
+
pro: 43
|
|
2142
|
+
},
|
|
1702
2143
|
contextTiers: [{
|
|
1703
2144
|
"maxContext": 256e3,
|
|
1704
2145
|
"rates": [
|
|
@@ -1720,7 +2161,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1720
2161
|
.4,
|
|
1721
2162
|
3,
|
|
1722
2163
|
.04
|
|
1723
|
-
]
|
|
2164
|
+
],
|
|
2165
|
+
allowance: {
|
|
2166
|
+
goat: 70,
|
|
2167
|
+
pro: 80
|
|
2168
|
+
}
|
|
1724
2169
|
},
|
|
1725
2170
|
{
|
|
1726
2171
|
id: "qwen-3.8-flash",
|
|
@@ -1728,7 +2173,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1728
2173
|
.16,
|
|
1729
2174
|
.47,
|
|
1730
2175
|
.016
|
|
1731
|
-
]
|
|
2176
|
+
],
|
|
2177
|
+
allowance: {
|
|
2178
|
+
goat: 20,
|
|
2179
|
+
pro: 30
|
|
2180
|
+
}
|
|
1732
2181
|
},
|
|
1733
2182
|
{
|
|
1734
2183
|
id: "qwen-3.8-max",
|
|
@@ -1737,7 +2186,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1737
2186
|
6,
|
|
1738
2187
|
.25,
|
|
1739
2188
|
2.5
|
|
1740
|
-
]
|
|
2189
|
+
],
|
|
2190
|
+
allowance: {
|
|
2191
|
+
goat: 20,
|
|
2192
|
+
pro: 30
|
|
2193
|
+
}
|
|
1741
2194
|
},
|
|
1742
2195
|
{
|
|
1743
2196
|
id: "qwen-3.8-max-0902",
|
|
@@ -1745,7 +2198,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1745
2198
|
2,
|
|
1746
2199
|
6,
|
|
1747
2200
|
.25
|
|
1748
|
-
]
|
|
2201
|
+
],
|
|
2202
|
+
allowance: {
|
|
2203
|
+
goat: 20,
|
|
2204
|
+
pro: 30
|
|
2205
|
+
}
|
|
1749
2206
|
},
|
|
1750
2207
|
{
|
|
1751
2208
|
id: "qwen-3.8-omni-flash",
|
|
@@ -1753,7 +2210,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1753
2210
|
.15,
|
|
1754
2211
|
.47,
|
|
1755
2212
|
.016
|
|
1756
|
-
]
|
|
2213
|
+
],
|
|
2214
|
+
allowance: {
|
|
2215
|
+
goat: 20,
|
|
2216
|
+
pro: 30
|
|
2217
|
+
}
|
|
1757
2218
|
},
|
|
1758
2219
|
{
|
|
1759
2220
|
id: "step-3.5-flash",
|
|
@@ -1761,7 +2222,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1761
2222
|
.09,
|
|
1762
2223
|
.3,
|
|
1763
2224
|
.02
|
|
1764
|
-
]
|
|
2225
|
+
],
|
|
2226
|
+
allowance: {
|
|
2227
|
+
goat: 20,
|
|
2228
|
+
pro: 30
|
|
2229
|
+
}
|
|
1765
2230
|
},
|
|
1766
2231
|
{
|
|
1767
2232
|
id: "step-3.7-flash",
|
|
@@ -1769,7 +2234,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1769
2234
|
.2,
|
|
1770
2235
|
1.15,
|
|
1771
2236
|
.04
|
|
1772
|
-
]
|
|
2237
|
+
],
|
|
2238
|
+
allowance: {
|
|
2239
|
+
goat: 20,
|
|
2240
|
+
pro: 30
|
|
2241
|
+
}
|
|
1773
2242
|
},
|
|
1774
2243
|
{
|
|
1775
2244
|
id: "step-5-preview",
|
|
@@ -1777,7 +2246,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1777
2246
|
1,
|
|
1778
2247
|
2.7,
|
|
1779
2248
|
.05
|
|
1780
|
-
]
|
|
2249
|
+
],
|
|
2250
|
+
allowance: {
|
|
2251
|
+
goat: 20,
|
|
2252
|
+
pro: 30
|
|
2253
|
+
}
|
|
1781
2254
|
},
|
|
1782
2255
|
{
|
|
1783
2256
|
id: "tencent/hy3-paid",
|
|
@@ -1785,7 +2258,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1785
2258
|
.14,
|
|
1786
2259
|
.58,
|
|
1787
2260
|
.035
|
|
1788
|
-
]
|
|
2261
|
+
],
|
|
2262
|
+
allowance: {
|
|
2263
|
+
goat: 70,
|
|
2264
|
+
pro: 80
|
|
2265
|
+
}
|
|
1789
2266
|
},
|
|
1790
2267
|
{
|
|
1791
2268
|
id: "tencent/hy4-preview",
|
|
@@ -1793,7 +2270,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1793
2270
|
.834,
|
|
1794
2271
|
2.501,
|
|
1795
2272
|
.042
|
|
1796
|
-
]
|
|
2273
|
+
],
|
|
2274
|
+
allowance: {
|
|
2275
|
+
goat: 20,
|
|
2276
|
+
pro: 30
|
|
2277
|
+
}
|
|
1797
2278
|
},
|
|
1798
2279
|
{
|
|
1799
2280
|
id: "typesafe/jev",
|
|
@@ -1801,7 +2282,11 @@ const MODEL_PRICE_ROWS = [
|
|
|
1801
2282
|
.042,
|
|
1802
2283
|
0,
|
|
1803
2284
|
0
|
|
1804
|
-
]
|
|
2285
|
+
],
|
|
2286
|
+
allowance: {
|
|
2287
|
+
goat: 20,
|
|
2288
|
+
pro: 30
|
|
2289
|
+
}
|
|
1805
2290
|
}
|
|
1806
2291
|
];
|
|
1807
2292
|
/**
|
|
@@ -1854,16 +2339,23 @@ function ratesOf(values) {
|
|
|
1854
2339
|
if (values[3] !== void 0) rates.cacheWriteCost = values[3];
|
|
1855
2340
|
return rates;
|
|
1856
2341
|
}
|
|
1857
|
-
/**
|
|
1858
|
-
|
|
2342
|
+
/**
|
|
2343
|
+
* Build the full price row the browser prices a session with.
|
|
2344
|
+
*
|
|
2345
|
+
* Every figure switches together: once the deal stops being live, the base
|
|
2346
|
+
* rates AND each context band come from the pre-discount set, so a session
|
|
2347
|
+
* priced after a promotion ends can never mix the two.
|
|
2348
|
+
*/
|
|
2349
|
+
function wireRow(id, slug, row, now) {
|
|
2350
|
+
const promotional = dealLabel(id, now) !== void 0;
|
|
1859
2351
|
const price = {
|
|
1860
2352
|
id,
|
|
1861
2353
|
slug,
|
|
1862
|
-
...ratesOf(row.rates)
|
|
2354
|
+
...ratesOf(promotional ? row.rates : row.listRates ?? row.rates)
|
|
1863
2355
|
};
|
|
1864
2356
|
if (row.peak !== void 0) price.peak = ratesOf(row.peak);
|
|
1865
2357
|
if (row.contextTiers !== void 0) price.contextTiers = row.contextTiers.map((tier) => ({
|
|
1866
|
-
...ratesOf(tier.rates),
|
|
2358
|
+
...ratesOf(promotional ? tier.rates : tier.listRates ?? tier.rates),
|
|
1867
2359
|
...tier.maxContext === void 0 ? {} : { maxContext: tier.maxContext }
|
|
1868
2360
|
}));
|
|
1869
2361
|
return price;
|
|
@@ -1878,8 +2370,13 @@ function wireRow(id, slug, row) {
|
|
|
1878
2370
|
* explicitly at zero so the composer can say so instead of showing nothing; the
|
|
1879
2371
|
* peak windows travel with the table so the browser applies the very schedule
|
|
1880
2372
|
* this snapshot knows instead of restating it.
|
|
2373
|
+
*
|
|
2374
|
+
* `now` (defaults to `Date.now()`) resolves lapsed promotions: a discounted row
|
|
2375
|
+
* whose `expiresAt` has passed is served at its pre-discount `listRates`, the
|
|
2376
|
+
* same instant {@link dealLabel} stops showing its badge. The table is rebuilt
|
|
2377
|
+
* per request, so that switch needs no redeploy.
|
|
1881
2378
|
*/
|
|
1882
|
-
function modelPriceTable() {
|
|
2379
|
+
function modelPriceTable(now = Date.now()) {
|
|
1883
2380
|
const bySlug = new Map(MODEL_PRICE_ROWS.map((row) => [row.id, row]));
|
|
1884
2381
|
const known = new Set(bySlug.keys());
|
|
1885
2382
|
const models = [];
|
|
@@ -1901,86 +2398,56 @@ function modelPriceTable() {
|
|
|
1901
2398
|
const row = bySlug.get(slug);
|
|
1902
2399
|
if (row === void 0) continue;
|
|
1903
2400
|
claimed.add(slug);
|
|
1904
|
-
models.push(wireRow(catalogId, slug, row));
|
|
2401
|
+
models.push(wireRow(catalogId, slug, row, now));
|
|
1905
2402
|
}
|
|
1906
2403
|
for (const row of MODEL_PRICE_ROWS) {
|
|
1907
2404
|
if (claimed.has(row.id)) continue;
|
|
1908
|
-
models.push(wireRow(row.id, row.id, row));
|
|
2405
|
+
models.push(wireRow(row.id, row.id, row, now));
|
|
1909
2406
|
}
|
|
1910
2407
|
return {
|
|
1911
2408
|
models,
|
|
1912
2409
|
peakHours: PEAK_HOUR_RANGES.map(([start, end]) => [start, end])
|
|
1913
2410
|
};
|
|
1914
2411
|
}
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
const
|
|
1918
|
-
uncachedInputTokens: 0,
|
|
1919
|
-
outputTokens: 0,
|
|
1920
|
-
cacheReadTokens: 0,
|
|
1921
|
-
cacheWriteTokens: 0
|
|
1922
|
-
});
|
|
1923
|
-
const COST_TOKEN_KEYS = [
|
|
1924
|
-
"uncachedInputTokens",
|
|
1925
|
-
"outputTokens",
|
|
1926
|
-
"cacheReadTokens",
|
|
1927
|
-
"cacheWriteTokens"
|
|
1928
|
-
];
|
|
1929
|
-
function peakHour(at, windows) {
|
|
1930
|
-
const time = new Date(at);
|
|
1931
|
-
return time.getUTCDay() !== 0 && time.getUTCDay() !== 6 && windows.some(([start, end]) => time.getUTCHours() >= start && time.getUTCHours() < end);
|
|
1932
|
-
}
|
|
2412
|
+
/** Slug-keyed view of the rows, built once: neither table nor allowance mutates. */
|
|
2413
|
+
const ROWS_BY_SLUG = new Map(MODEL_PRICE_ROWS.map((row) => [row.id, row]));
|
|
2414
|
+
const KNOWN_SLUGS = new Set(ROWS_BY_SLUG.keys());
|
|
1933
2415
|
/**
|
|
1934
|
-
*
|
|
1935
|
-
*
|
|
2416
|
+
* The per-model monthly allowance for a CATALOG model id, or undefined when the
|
|
2417
|
+
* page carries none (a model without a price row, or the free stealth previews
|
|
2418
|
+
* the estimator array omits).
|
|
1936
2419
|
*
|
|
1937
|
-
*
|
|
1938
|
-
*
|
|
1939
|
-
*
|
|
1940
|
-
*
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
2420
|
+
* Resolution rides the same slug rules as the price table, so
|
|
2421
|
+
* `MiniMaxAI/MiniMax-M3` finds the row the page files under `minimax-m3` — the
|
|
2422
|
+
* settings page must not grow a second mapping that can drift from this one.
|
|
2423
|
+
* The settings page asks for a model only while rendering a list it already
|
|
2424
|
+
* has, so this stays a pure lookup with no clock: an allowance is not
|
|
2425
|
+
* time-dependent (a plan's figure does not lapse the way a promotional rate
|
|
2426
|
+
* does; when the page re-prices it, the next sync carries it).
|
|
2427
|
+
*/
|
|
2428
|
+
function modelAllowanceFor(modelId) {
|
|
2429
|
+
const slug = priceSlugFor(modelId, KNOWN_SLUGS);
|
|
2430
|
+
if (slug === void 0) return void 0;
|
|
2431
|
+
return ROWS_BY_SLUG.get(slug)?.allowance;
|
|
1946
2432
|
}
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
r.cacheReadCost,
|
|
1953
|
-
r.cacheWriteCost ?? null
|
|
1954
|
-
];
|
|
1955
|
-
const canonical = JSON.stringify([
|
|
1956
|
-
"commandCodeCost-v1",
|
|
1957
|
-
table.peakHours,
|
|
1958
|
-
table.models.map((p) => [
|
|
1959
|
-
p.id,
|
|
1960
|
-
p.slug,
|
|
1961
|
-
p.free === true,
|
|
1962
|
-
rates(p),
|
|
1963
|
-
p.peak ? rates(p.peak) : null,
|
|
1964
|
-
p.contextTiers?.map((t) => [t.maxContext ?? null, rates(t)]) ?? null
|
|
1965
|
-
])
|
|
1966
|
-
]);
|
|
1967
|
-
let hash = 2166136261;
|
|
1968
|
-
for (const c of canonical) hash = Math.imul(hash ^ c.charCodeAt(0), 16777619) >>> 0;
|
|
1969
|
-
return hash;
|
|
2433
|
+
//#endregion
|
|
2434
|
+
//#region src/wire-guards.ts
|
|
2435
|
+
/** Non-throwing value guards for untrusted provider payloads. */
|
|
2436
|
+
function isRecord(value) {
|
|
2437
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1970
2438
|
}
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
]);
|
|
2439
|
+
function stringValue(value) {
|
|
2440
|
+
return typeof value === "string" ? value : void 0;
|
|
2441
|
+
}
|
|
2442
|
+
function numberValue(value) {
|
|
2443
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
2444
|
+
}
|
|
2445
|
+
function booleanValue(value) {
|
|
2446
|
+
return typeof value === "boolean" ? value : void 0;
|
|
1980
2447
|
}
|
|
1981
2448
|
//#endregion
|
|
1982
2449
|
//#region src/cost-projection.ts
|
|
1983
|
-
const record$2 = (v) =>
|
|
2450
|
+
const record$2 = (v) => isRecord(v) ? v : void 0;
|
|
1984
2451
|
const finite = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0;
|
|
1985
2452
|
function tokensOf(value) {
|
|
1986
2453
|
const u = record$2(value);
|
|
@@ -2069,11 +2536,18 @@ function createCostProjection(table = modelPriceTable()) {
|
|
|
2069
2536
|
...g,
|
|
2070
2537
|
tokens: { ...g.tokens }
|
|
2071
2538
|
}));
|
|
2539
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
2540
|
+
groups.forEach((g, i) => {
|
|
2541
|
+
const key = costGroupKey(g, table);
|
|
2542
|
+
if (!byKey.has(key)) byKey.set(key, i);
|
|
2543
|
+
});
|
|
2544
|
+
const groupKey = costGroupKey(group, table);
|
|
2072
2545
|
if (previous) {
|
|
2073
|
-
const old =
|
|
2074
|
-
if (old) for (const k of COST_TOKEN_KEYS) old.tokens[k] -= previous.tokens[k];
|
|
2546
|
+
const old = byKey.get(costGroupKey(previous, table));
|
|
2547
|
+
if (old !== void 0) for (const k of COST_TOKEN_KEYS) groups[old].tokens[k] -= previous.tokens[k];
|
|
2075
2548
|
}
|
|
2076
|
-
|
|
2549
|
+
const targetIdx = byKey.get(groupKey);
|
|
2550
|
+
let target = targetIdx === void 0 ? void 0 : groups[targetIdx];
|
|
2077
2551
|
if (!target) {
|
|
2078
2552
|
target = {
|
|
2079
2553
|
...group,
|
|
@@ -2143,24 +2617,18 @@ const IDENTITY_ENCODING_HEADER = { "accept-encoding": "identity" };
|
|
|
2143
2617
|
* @module dsh-commandcode-provider/accounts
|
|
2144
2618
|
*/
|
|
2145
2619
|
/**
|
|
2146
|
-
* Upper bound on the retry wait
|
|
2147
|
-
*
|
|
2148
|
-
*
|
|
2149
|
-
*
|
|
2150
|
-
*
|
|
2151
|
-
* (in normal mode), turning "poll until the window opens" into "fail now".
|
|
2620
|
+
* Upper bound on the retry wait attached to the all-exhausted `RATE_LIMIT`.
|
|
2621
|
+
* Must equal the adapter's `backoff.maxDelayMs`: dsh-llm-retry honors a
|
|
2622
|
+
* provider wait verbatim only at or below that cap — a longer one abandons the
|
|
2623
|
+
* retry instead of falling back to local backoff, turning "poll until the
|
|
2624
|
+
* window opens" into "fail now".
|
|
2152
2625
|
*/
|
|
2153
2626
|
const RETRY_MAX_DELAY_MS = 9e5;
|
|
2154
2627
|
/**
|
|
2155
2628
|
* How long an explicitly selected account's rate-limit mark may stand before
|
|
2156
|
-
* its window is probed again.
|
|
2157
|
-
*
|
|
2158
|
-
*
|
|
2159
|
-
* {@link accountUsable} refuses — for a key nobody asked for that is correct
|
|
2160
|
-
* (only the all-marked pass clears it, and it costs one billing call), but for
|
|
2161
|
-
* the user's own pinned account a single 429 would demote it until the process
|
|
2162
|
-
* restarts (issue #51). Once per interval per key bounds a probe endpoint that
|
|
2163
|
-
* keeps failing; a successful probe resolves the mark for good.
|
|
2629
|
+
* its window is probed again. Without this, a single 429 on the user's pinned
|
|
2630
|
+
* account demotes it until the process restarts (issue #51); probing at most
|
|
2631
|
+
* once per interval per key bounds a probe endpoint that keeps failing.
|
|
2164
2632
|
*/
|
|
2165
2633
|
const EXPLICIT_ACCOUNT_PROBE_INTERVAL_MS = 6e4;
|
|
2166
2634
|
/** A labeled, human-readable clock reading for error messages. */
|
|
@@ -2265,26 +2733,18 @@ var CommandCodeAccountPool = class {
|
|
|
2265
2733
|
return out;
|
|
2266
2734
|
}
|
|
2267
2735
|
/**
|
|
2268
|
-
* Hand out the key for a request: the model-routed account when
|
|
2269
|
-
*
|
|
2270
|
-
*
|
|
2271
|
-
*
|
|
2272
|
-
*
|
|
2273
|
-
*
|
|
2274
|
-
* `RATE_LIMIT` (naming the earliest window reset) or `INVALID_CREDENTIAL`
|
|
2275
|
-
* when accounts exist but every one of them is marked.
|
|
2736
|
+
* Hand out the key for a request: the model-routed account when usable, else
|
|
2737
|
+
* the manually preferred account when usable, else the first usable account
|
|
2738
|
+
* in rotation order. Returns `undefined` when no account resolves any key,
|
|
2739
|
+
* or when an account this request already used is still UNMARKED — the
|
|
2740
|
+
* caller's own rejection is the honest answer then. Throws `RATE_LIMIT` /
|
|
2741
|
+
* `INVALID_CREDENTIAL` when every account is marked.
|
|
2276
2742
|
*
|
|
2277
|
-
* `options.
|
|
2278
|
-
* resolution,
|
|
2279
|
-
*
|
|
2280
|
-
*
|
|
2281
|
-
*
|
|
2282
|
-
* (no credits, a model outside the account's plan) would otherwise be offered
|
|
2283
|
-
* again on every attempt and the accounts behind it never reached.
|
|
2284
|
-
*
|
|
2285
|
-
* An explicit selection (the pin or a model rule) that a rate-limit mark would
|
|
2286
|
-
* demote is probed before the fallback serves, so "falls back while exhausted"
|
|
2287
|
-
* never becomes "stays demoted until the process restarts" (issue #51).
|
|
2743
|
+
* `options.tried` lists the keys this request already used; they are removed
|
|
2744
|
+
* from the resolution entirely, which is what lets one request walk a
|
|
2745
|
+
* four-account pool. An explicit selection (pin or model rule) that a
|
|
2746
|
+
* rate-limit mark would demote is probed first, so a fallback never becomes
|
|
2747
|
+
* permanent (issue #51). Rules re-read per resolution, so settings apply live.
|
|
2288
2748
|
*/
|
|
2289
2749
|
async resolveKey(options) {
|
|
2290
2750
|
const accounts = await this.resolvedAccounts();
|
|
@@ -2320,18 +2780,7 @@ var CommandCodeAccountPool = class {
|
|
|
2320
2780
|
this.states.delete(account.key);
|
|
2321
2781
|
return;
|
|
2322
2782
|
}
|
|
2323
|
-
|
|
2324
|
-
kind: "cooldown",
|
|
2325
|
-
cause: "window",
|
|
2326
|
-
reason: account.state?.reason ?? "usage window exhausted (429)",
|
|
2327
|
-
until: probe.resetAt
|
|
2328
|
-
});
|
|
2329
|
-
else if (account.state?.kind !== "cooldown") this.states.set(account.key, {
|
|
2330
|
-
kind: "unknown",
|
|
2331
|
-
cause: "window",
|
|
2332
|
-
reason: account.state?.reason ?? "usage window exhausted (429)",
|
|
2333
|
-
until: 0
|
|
2334
|
-
});
|
|
2783
|
+
this.stampWindowMark(account, probe);
|
|
2335
2784
|
}));
|
|
2336
2785
|
const latestAccounts = await this.resolvedAccounts();
|
|
2337
2786
|
const revived = selectActiveAccount(tried === void 0 || tried.length === 0 ? latestAccounts : latestAccounts.filter((account) => !tried.includes(account.key)), preferred);
|
|
@@ -2464,6 +2913,18 @@ var CommandCodeAccountPool = class {
|
|
|
2464
2913
|
state: void 0
|
|
2465
2914
|
};
|
|
2466
2915
|
}
|
|
2916
|
+
this.stampWindowMark(account, probe);
|
|
2917
|
+
}
|
|
2918
|
+
/**
|
|
2919
|
+
* Stamp a probe-read window onto one key. A known reset becomes a cooldown
|
|
2920
|
+
* that expires by itself; without one the mark stays `unknown`, so a later
|
|
2921
|
+
* probe can still learn it — and a mark that already carries a cooldown
|
|
2922
|
+
* keeps it, never trading a known reset for an unknown one. The cause is
|
|
2923
|
+
* `window` either way: a probe that read a window as exceeded IS window
|
|
2924
|
+
* evidence, whatever the key was marked for. An `until: 0` cooldown would
|
|
2925
|
+
* read as "never usable again" to {@link accountUsable}, so `unknown` it is.
|
|
2926
|
+
*/
|
|
2927
|
+
stampWindowMark(account, probe) {
|
|
2467
2928
|
if (probe.resetAt > 0) this.states.set(account.key, {
|
|
2468
2929
|
kind: "cooldown",
|
|
2469
2930
|
cause: "window",
|
|
@@ -2647,12 +3108,12 @@ function commandCodeImageTokens(modelId, width, height) {
|
|
|
2647
3108
|
return imageTokenCost(imageTokenFamily(modelId), width, height);
|
|
2648
3109
|
}
|
|
2649
3110
|
/**
|
|
2650
|
-
* Anthropic: the image is first scaled
|
|
2651
|
-
*
|
|
3111
|
+
* Anthropic: the image is first scaled to the request long edge (never
|
|
3112
|
+
* enlarged), then charged roughly one token per 750 pixels.
|
|
2652
3113
|
* Source: Anthropic vision documentation, "Image size and token usage".
|
|
2653
3114
|
*/
|
|
2654
3115
|
function anthropicImageTokens(width, height) {
|
|
2655
|
-
const projected = longEdgeDimensions(width, height,
|
|
3116
|
+
const projected = longEdgeDimensions(width, height, REQUEST_IMAGE_MAX_LONG_EDGE);
|
|
2656
3117
|
return Math.max(1, Math.ceil(projected.width * projected.height / 750));
|
|
2657
3118
|
}
|
|
2658
3119
|
/**
|
|
@@ -2944,17 +3405,52 @@ function openStreamTrace(scope, options = {}) {
|
|
|
2944
3405
|
* Owns the two chat transports (`/alpha/generate`, `/provider/v1/chat/completions`),
|
|
2945
3406
|
* message conversion, SSE/JSONL parsing, the catalog + its on-disk cache, and
|
|
2946
3407
|
* the pre-stream account rotation loop. The wire protocol is
|
|
2947
|
-
* reverse-engineered (command-code@1.28.4, re-verified through 1.
|
|
3408
|
+
* reverse-engineered (command-code@1.28.4, re-verified through 1.68.0);
|
|
2948
3409
|
* AGENTS.md holds the full protocol record.
|
|
2949
3410
|
*
|
|
2950
3411
|
* The adapter is deliberately free of cordis/schemastery: it receives a
|
|
2951
3412
|
* per-request options thunk and an API-key resolver from the plugin entry
|
|
2952
3413
|
* (src/index.ts), so a settings change reaches the very next request.
|
|
2953
3414
|
*/
|
|
2954
|
-
|
|
3415
|
+
/** Keep the two user-facing languages and error metadata together. */
|
|
3416
|
+
function bilingual(code, en, zh, options) {
|
|
3417
|
+
return new LlmError(`${en};${zh}`, code, options);
|
|
3418
|
+
}
|
|
3419
|
+
const COMMAND_CODE_CLI_VERSION = "1.68.0";
|
|
2955
3420
|
const DEFAULT_API_BASE = "https://api.commandcode.ai";
|
|
2956
|
-
|
|
2957
|
-
|
|
3421
|
+
/**
|
|
3422
|
+
* The output budget this bundle asks for, and the ceiling it will never exceed.
|
|
3423
|
+
*
|
|
3424
|
+
* 131 072, chosen against a measured provider limit rather than a round guess.
|
|
3425
|
+
* Probing `/alpha/generate` directly on 2026-09-28: 64 000 → 200, 131 072 → 200,
|
|
3426
|
+
* 196 608 → 200, **200 000 → 200, 200 704 → 400**, 1 000 000 → 400. The server
|
|
3427
|
+
* therefore caps `max_tokens` at 200 000, and it does so identically for a
|
|
3428
|
+
* 262 144-window model (`stealth/pixel-canary`) and a 1 000 000-window one
|
|
3429
|
+
* (`stealth/space-bunny-alpha`) — the cap is global, not per model. 131 072
|
|
3430
|
+
* keeps a 35 % margin under it while covering the real output ceiling of the
|
|
3431
|
+
* models that have one (Claude 64 K, Gemini 64 K, several at 128 K).
|
|
3432
|
+
*
|
|
3433
|
+
* The old 64 000 was never a provider requirement: it cost nothing in latency
|
|
3434
|
+
* (a 64 000 request answered headers in 1.28 s against 1.57 s for 131 072) but
|
|
3435
|
+
* it did cap what a model could ever say in one turn.
|
|
3436
|
+
*
|
|
3437
|
+
* This is only an UPPER bound. `requestContextBudget` lowers it per request
|
|
3438
|
+
* from the prompt actually being sent, so raising the ceiling cannot push a
|
|
3439
|
+
* large session over its window.
|
|
3440
|
+
*/
|
|
3441
|
+
const DEFAULT_GENERATE_MAX_TOKENS = 131072;
|
|
3442
|
+
/**
|
|
3443
|
+
* The fallback when the catalog carries no output ceiling for a model.
|
|
3444
|
+
*
|
|
3445
|
+
* `/provider/v1/models` publishes only `context_length` — no output limit — so
|
|
3446
|
+
* the catalog cannot learn one per model and derives a stand-in from the
|
|
3447
|
+
* window instead. Kept equal to {@link DEFAULT_GENERATE_MAX_TOKENS} on purpose:
|
|
3448
|
+
* a lower value here would silently re-cap every request through
|
|
3449
|
+
* `Math.min(contextLength, this)` before the ceiling above is ever consulted.
|
|
3450
|
+
*/
|
|
3451
|
+
const DEFAULT_MAX_OUTPUT_TOKENS = 131072;
|
|
3452
|
+
/** Preserve a small usable answer budget when the heuristic overshoots. */
|
|
3453
|
+
const MIN_CONTEXT_OUTPUT_TOKENS = 1024;
|
|
2958
3454
|
const MODELS_TIMEOUT_MS = 1e4;
|
|
2959
3455
|
/** How long the picker's plan-filter billing facts stay cached before refetching. */
|
|
2960
3456
|
const BILLING_ACCESS_TTL_MS = 3e5;
|
|
@@ -2971,20 +3467,29 @@ const ACTIVE_SUBSCRIPTION_STATUSES = /* @__PURE__ */ new Set([
|
|
|
2971
3467
|
"trialing",
|
|
2972
3468
|
"past_due"
|
|
2973
3469
|
]);
|
|
2974
|
-
/**
|
|
2975
|
-
|
|
3470
|
+
/**
|
|
3471
|
+
* Head-of-request timeout: how long to wait for the first response byte.
|
|
3472
|
+
*
|
|
3473
|
+
* 300 s, and that number is the official CLI's, not a round choice. Decompiled
|
|
3474
|
+
* from `command-code@1.66.0`: its `createNodeTransport` fetch passes only the
|
|
3475
|
+
* caller's `signal` and sets no timeout at all, so the model request inherits
|
|
3476
|
+
* Node/undici's own default — measured at 301.0 s against a local server that
|
|
3477
|
+
* accepts the connection and never sends headers. The previous 60 s default was
|
|
3478
|
+
* five times stricter than the CLI it emulates, and being stricter only ever
|
|
3479
|
+
* costs legitimate requests: a slow upstream first token on the Provider API
|
|
3480
|
+
* route can outlast 60 s, and the official CLI simply waits it out. Measured
|
|
3481
|
+
* headers (2026-09-28, 26 250-token prompt, `max_tokens: 16` to separate the
|
|
3482
|
+
* header from the generation): `/alpha/generate` 2.12 / 2.56 / 2.75 s,
|
|
3483
|
+
* `/provider/v1/chat/completions` 5.52 / 2.98 / 3.70 s — a ~1.1 s median gap
|
|
3484
|
+
* with the Provider API route varying over a 2.5 s span against the CLI route's
|
|
3485
|
+
* 0.6 s. A gateway that is genuinely dead now takes longer to name itself,
|
|
3486
|
+
* which is what the consecutive-timeout streak exists to bound (three
|
|
3487
|
+
* attempts, then a diagnosis instead of another re-send).
|
|
3488
|
+
*/
|
|
3489
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = 3e5;
|
|
2976
3490
|
/** Stream idle timeout: a generation that stalls this long is a dead connection. */
|
|
2977
3491
|
const DEFAULT_STREAM_IDLE_TIMEOUT_MS = 3e5;
|
|
2978
3492
|
const MODEL_CACHE_VERSION = 1;
|
|
2979
|
-
function isRecord(value) {
|
|
2980
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2981
|
-
}
|
|
2982
|
-
function stringValue(value) {
|
|
2983
|
-
return typeof value === "string" ? value : void 0;
|
|
2984
|
-
}
|
|
2985
|
-
function numberValue(value) {
|
|
2986
|
-
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
2987
|
-
}
|
|
2988
3493
|
/** Hash one request value without retaining or logging its contents. */
|
|
2989
3494
|
function fingerprintValue(value) {
|
|
2990
3495
|
let serialized;
|
|
@@ -3076,9 +3581,6 @@ function failureWithRequestId(error, metadata) {
|
|
|
3076
3581
|
...error.cause === void 0 ? {} : { cause: error.cause }
|
|
3077
3582
|
});
|
|
3078
3583
|
}
|
|
3079
|
-
function booleanValue(value) {
|
|
3080
|
-
return typeof value === "boolean" ? value : void 0;
|
|
3081
|
-
}
|
|
3082
3584
|
/**
|
|
3083
3585
|
* Thinking text carried by an OpenRouter-shaped `reasoning_details` array,
|
|
3084
3586
|
* concatenated in the order the gateway sent it.
|
|
@@ -3160,6 +3662,78 @@ const CLI_CONTEXT_OVERFLOW_PATTERN = /prompt is too long|context.*(length|window
|
|
|
3160
3662
|
function isContextOverflowDetail(detail) {
|
|
3161
3663
|
return isContextWindowExceededError(detail) || CLI_CONTEXT_OVERFLOW_PATTERN.test(detail);
|
|
3162
3664
|
}
|
|
3665
|
+
/** A gateway refusal observed for an over-reserved request, but not unique to that cause. */
|
|
3666
|
+
const AMBIGUOUS_SIZE_REJECTION = /\bmodel could not complete the request\b/i;
|
|
3667
|
+
/**
|
|
3668
|
+
* The advice floor for a Provider-API timeout: doubling a 60 s budget is still
|
|
3669
|
+
* short for a slow upstream first token, so the suggestion never goes below 5
|
|
3670
|
+
* minutes. Capped by `RETRY_MAX_DELAY_MS` rather than the schema's timer
|
|
3671
|
+
* ceiling on purpose — a single header wait that outlasts the whole retry
|
|
3672
|
+
* backoff is not a budget, and clamping to the engine constant would also pull
|
|
3673
|
+
* a runtime dependency into the bundle.
|
|
3674
|
+
*/
|
|
3675
|
+
const MIN_SUGGESTED_TIMEOUT_MS = 3e5;
|
|
3676
|
+
/**
|
|
3677
|
+
* Why one attempt saw no response headers, and what the user can do about it.
|
|
3678
|
+
*
|
|
3679
|
+
* The TRANSPORT is the reliable signal; the request size is not — and the
|
|
3680
|
+
* mechanism is read straight out of the official CLI rather than inferred from
|
|
3681
|
+
* a timing. `createNodeTransport` passes only the caller's `signal` and sets no
|
|
3682
|
+
* timeout, so the two routes differ in WHO answers the headers:
|
|
3683
|
+
* `/alpha/generate` has the gateway answer them before the model starts, while
|
|
3684
|
+
* `/provider/v1/chat/completions` cannot answer until its upstream emits a
|
|
3685
|
+
* first token, so whatever that upstream is doing is charged to the header
|
|
3686
|
+
* wait. Measured 2026-09-28 (26 250-token prompt, `max_tokens: 16`):
|
|
3687
|
+
* `/alpha/generate` 2.12 / 2.56 / 2.75 s, `/provider/v1/chat/completions`
|
|
3688
|
+
* 5.52 / 2.98 / 3.70 s — a ~1.1 s median gap, with the Provider API route
|
|
3689
|
+
* swinging across 2.5 s where the CLI route moved 0.6 s. A larger prompt moves
|
|
3690
|
+
* neither, which is why an earlier draft that branched on a 1 MiB body
|
|
3691
|
+
* threshold was wrong: size is not the variable here. Only the route decides.
|
|
3692
|
+
*
|
|
3693
|
+
* @param protocol - the transport the attempt used.
|
|
3694
|
+
* @param timeoutMs - the header budget that just expired.
|
|
3695
|
+
*/
|
|
3696
|
+
function headersTimeoutAdvice(protocol, timeoutMs) {
|
|
3697
|
+
if (protocol === "openai") return {
|
|
3698
|
+
cause: "upstream-first-token",
|
|
3699
|
+
suggestionMs: Math.min(RETRY_MAX_DELAY_MS, Math.max(timeoutMs * 2, MIN_SUGGESTED_TIMEOUT_MS))
|
|
3700
|
+
};
|
|
3701
|
+
return { cause: "gateway" };
|
|
3702
|
+
}
|
|
3703
|
+
/**
|
|
3704
|
+
* How many consecutive header timeouts on ONE unchanged payload stop being a
|
|
3705
|
+
* blip. The retry policy whitelists `TIMEOUT` with `maxRetries: 1000` and a
|
|
3706
|
+
* 500 ms → 15 min backoff, which is right for a provider that says when to
|
|
3707
|
+
* come back but wrong for a gateway that simply never answers: the reporter
|
|
3708
|
+
* saw the identical multi-megabyte body re-sent for hours (issue #67) instead
|
|
3709
|
+
* of a diagnosis. Three is where a 60 s budget has already spent three
|
|
3710
|
+
* minutes waiting, so the cost of being wrong is bounded and the user gets an
|
|
3711
|
+
* answer either way.
|
|
3712
|
+
*/
|
|
3713
|
+
const MAX_CONSECUTIVE_HEADER_TIMEOUTS = 3;
|
|
3714
|
+
/** UTF-8 byte length, computed only on the timeout path so a large body is encoded once, not per attempt. */
|
|
3715
|
+
function utf8ByteLength(text) {
|
|
3716
|
+
return new TextEncoder().encode(text).length;
|
|
3717
|
+
}
|
|
3718
|
+
/**
|
|
3719
|
+
* The thrown headers timeout, worded for the cause it actually has.
|
|
3720
|
+
*
|
|
3721
|
+
* Every earlier version of this message said "通常是网络或代理问题" for both
|
|
3722
|
+
* transports, which is actively wrong for a large `/provider/v1/chat/completions`
|
|
3723
|
+
* prompt: there the model simply needs longer than any budget, and a user sent
|
|
3724
|
+
* to check their proxy never finds anything. The code stays `TIMEOUT` (still
|
|
3725
|
+
* retryable) in both branches — only the diagnosis and the advice change.
|
|
3726
|
+
*/
|
|
3727
|
+
function headersTimeoutError(input) {
|
|
3728
|
+
const { protocol, endpoint, timeoutMs, cause } = input;
|
|
3729
|
+
const megabytes = `${(utf8ByteLength(input.body) / 1048576).toFixed(1)} MB`;
|
|
3730
|
+
const advice = headersTimeoutAdvice(protocol, timeoutMs);
|
|
3731
|
+
if (advice.cause === "upstream-first-token") {
|
|
3732
|
+
const suggestion = advice.suggestionMs ?? timeoutMs;
|
|
3733
|
+
return bilingual("TIMEOUT", `Command Code API request to ${endpoint} did not respond within ${timeoutMs}ms: this route withholds response headers until the upstream model emits its first token, and it did not start within the budget. Raise "Request timeout" in the plugin's advanced settings to ${suggestion}ms, or compact the session or start a new one. The request body was ${megabytes}, but size is not the cause here — a slow upstream is. ${errorChain(cause)}`, `Command Code API 请求在 ${timeoutMs} 毫秒内未收到响应:该路由要等上游模型吐出第一个 token 才返回响应头,而上游在预算内没有开始作答。可在插件「高级」设置里把「请求超时」调到 ${suggestion} 毫秒,或压缩上下文 / 新开会话。本次请求体约 ${megabytes},但体积不是原因——慢的是上游。(${errorChain(cause)})`, { cause });
|
|
3734
|
+
}
|
|
3735
|
+
return bilingual("TIMEOUT", `Command Code API request to ${endpoint} did not respond within ${timeoutMs}ms: the gateway returned no response headers at all — it answers headers itself before the model replies. This is usually provider queueing, a Go-plan capacity limit, or a network/proxy path problem; a longer timeout will not help, but retrying usually recovers. The request body was ${megabytes}, and size is not the cause on this route. ${errorChain(cause)}`, `Command Code API 请求在 ${timeoutMs} 毫秒内未收到响应:网关完全没有返回响应头——它会在模型开始回答前先返回响应头。这通常是服务商排队、Go 套餐容量限制,或网络/代理链路问题;调大超时不会有帮助,重试通常可恢复。本次请求体约 ${megabytes},而在该路由上体积不是原因。(${errorChain(cause)})`, { cause });
|
|
3736
|
+
}
|
|
3163
3737
|
/**
|
|
3164
3738
|
* Whether one rejection says "ZDR requested, no ZDR-capable upstream": the
|
|
3165
3739
|
* provider spells the code `cmd_zdr_no_providers` in JSON error bodies and
|
|
@@ -3203,7 +3777,7 @@ function streamErrorToLlmError(value, fallbackMessage) {
|
|
|
3203
3777
|
message
|
|
3204
3778
|
].filter((part) => part !== void 0 && part !== "").join(" ");
|
|
3205
3779
|
const statusOption = statusCode !== void 0 ? { status: statusCode } : void 0;
|
|
3206
|
-
if (isContextOverflowDetail(detail)) return
|
|
3780
|
+
if (isContextOverflowDetail(detail)) return bilingual(CONTEXT_WINDOW_EXCEEDED_CODE, `Command Code stream error: ${message}`, "Command Code API 拒绝了这次请求:内容超出模型上下文窗口。正在压缩上下文后重试;如仍失败,请新建会话或减少上下文", statusOption);
|
|
3207
3781
|
const terminal = hasTerminalStreamMarker(detail);
|
|
3208
3782
|
if (!(isRetryable === true || (isRetryable === false || terminal ? false : statusCode !== void 0 ? statusCode !== void 0 && (statusCode === 429 || statusCode >= 500) : true))) return new LlmError(`Command Code stream error: ${message}`, "PROVIDER_STREAM_ERROR", statusOption);
|
|
3209
3783
|
return new LlmError(`Command Code stream error: ${message}`, "SERVER", statusOption);
|
|
@@ -3581,9 +4155,9 @@ const ENGINE_IMAGE_POLICY = {
|
|
|
3581
4155
|
* dsh-llm-retry. On a profile that does not mount that plugin the failure is
|
|
3582
4156
|
* terminal — every shipped profile reaches it through dsh-base.
|
|
3583
4157
|
*/
|
|
3584
|
-
function imageOffloadRequired(missing) {
|
|
4158
|
+
function imageOffloadRequired(missing, reason = "budget") {
|
|
3585
4159
|
const options = { offloadImages: missing };
|
|
3586
|
-
return new LlmError(`commandcode request images exceed the route budget; ${missing} more oldest occurrence(s) must be offloaded.`, IMAGE_OFFLOAD_REQUIRED_CODE, options);
|
|
4160
|
+
return new LlmError(reason === "cache" ? `commandcode cache-aware image history: ${missing} already-seen oldest image occurrence(s) must be offloaded before replay.` : `commandcode request images exceed the route budget; ${missing} more oldest occurrence(s) must be offloaded.`, IMAGE_OFFLOAD_REQUIRED_CODE, options);
|
|
3587
4161
|
}
|
|
3588
4162
|
/** The budget in the shape the core counter takes (representation + the rung). */
|
|
3589
4163
|
function coreBudget(budget) {
|
|
@@ -3609,17 +4183,40 @@ const imageVersionBytes = (block) => block.attachment.bytes;
|
|
|
3609
4183
|
* otherwise the 413 retry could reintroduce images the first rung had already
|
|
3610
4184
|
* evicted.
|
|
3611
4185
|
*/
|
|
3612
|
-
function withSurfaceOffload(options, policy, budget) {
|
|
4186
|
+
function withSurfaceOffload(options, policy, budget, cacheOffloadModel) {
|
|
3613
4187
|
const marked = policy.projectOffloadedImages(options.messages, (ref) => offloadedImageText(ref));
|
|
3614
4188
|
const messages = marked === options.messages ? options.messages : [...marked];
|
|
3615
|
-
const
|
|
3616
|
-
|
|
4189
|
+
const budgetMissing = policy.requiredImageOffload(messages, coreBudget(budget), imageVersionBytes);
|
|
4190
|
+
const cacheMissing = cacheOffloadModel === void 0 ? 0 : seenImageOffloadCount(messages, cacheOffloadModel);
|
|
4191
|
+
const missing = Math.max(budgetMissing, cacheMissing);
|
|
4192
|
+
if (missing > 0) throw imageOffloadRequired(missing, cacheMissing > budgetMissing ? "cache" : "budget");
|
|
3617
4193
|
return messages === options.messages ? options : {
|
|
3618
4194
|
...options,
|
|
3619
4195
|
messages
|
|
3620
4196
|
};
|
|
3621
4197
|
}
|
|
3622
4198
|
/**
|
|
4199
|
+
* Count retained images already followed by an answer from this same model.
|
|
4200
|
+
* The current tool result's image has not yet been seen by the model and must
|
|
4201
|
+
* stay on the wire. The engine can only offload durable user/tool input nodes;
|
|
4202
|
+
* assistant output and request-only input have no selectable surface event.
|
|
4203
|
+
*/
|
|
4204
|
+
function seenImageOffloadCount(messages, model) {
|
|
4205
|
+
let lastAnswer = -1;
|
|
4206
|
+
for (let index = 0; index < messages.length; index++) {
|
|
4207
|
+
const message = messages[index];
|
|
4208
|
+
if (message.role === "assistant" && message.source.kind === "model" && message.source.provider === "commandcode" && message.source.model === model) lastAnswer = index;
|
|
4209
|
+
}
|
|
4210
|
+
if (lastAnswer < 0) return 0;
|
|
4211
|
+
let count = 0;
|
|
4212
|
+
for (let index = 0; index < lastAnswer; index++) {
|
|
4213
|
+
const message = messages[index];
|
|
4214
|
+
if (message.role !== "user" && message.role !== "tool" || message.id === void 0) continue;
|
|
4215
|
+
for (const block of message.content) if (block.type === "image") count++;
|
|
4216
|
+
}
|
|
4217
|
+
return count;
|
|
4218
|
+
}
|
|
4219
|
+
/**
|
|
3623
4220
|
* One image's bytes for the wire: the attachment service's REQUEST VERSION.
|
|
3624
4221
|
*
|
|
3625
4222
|
* `readImage()` answers the stored original, which is what admission accepted —
|
|
@@ -3630,7 +4227,7 @@ function withSurfaceOffload(options, policy, budget) {
|
|
|
3630
4227
|
* the point here, the encoded bytes).
|
|
3631
4228
|
*/
|
|
3632
4229
|
async function readRequestImage(attachments, ref) {
|
|
3633
|
-
return
|
|
4230
|
+
return attachments.readImageRequest(ref, requestImageTarget(ref));
|
|
3634
4231
|
}
|
|
3635
4232
|
/**
|
|
3636
4233
|
* Price one request occurrence.
|
|
@@ -3661,23 +4258,25 @@ function priceRequestImage(block, model, imageCapable) {
|
|
|
3661
4258
|
};
|
|
3662
4259
|
}
|
|
3663
4260
|
/**
|
|
3664
|
-
* Convert one image reference to the
|
|
3665
|
-
* CLI
|
|
3666
|
-
*
|
|
3667
|
-
*
|
|
4261
|
+
* Convert one image reference to the final `/alpha/generate` wire format.
|
|
4262
|
+
* The official CLI accepts a base64 source internally, then toWireMessages()
|
|
4263
|
+
* sends a data URL plus mimeType. Use the request version's media type because
|
|
4264
|
+
* readImageRequest() can transcode the stored image to JPEG or WebP.
|
|
3668
4265
|
*/
|
|
3669
4266
|
async function imageToCommandCode(ref, readImage) {
|
|
3670
|
-
const
|
|
4267
|
+
const version = await readImage(ref);
|
|
3671
4268
|
return {
|
|
3672
4269
|
type: "image",
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
media_type: ref.mediaType,
|
|
3676
|
-
data: Buffer.from(data).toString("base64")
|
|
3677
|
-
}
|
|
4270
|
+
image: `data:${version.mediaType};base64,${Buffer.from(version.data).toString("base64")}`,
|
|
4271
|
+
mimeType: version.mediaType
|
|
3678
4272
|
};
|
|
3679
4273
|
}
|
|
3680
|
-
|
|
4274
|
+
/**
|
|
4275
|
+
* The common pairing and image-carrier ordering for both transports. Each
|
|
4276
|
+
* codec owns its wire shape: CLI content blocks and Chat Completions
|
|
4277
|
+
* reasoning/tool calls differ beyond the image envelope.
|
|
4278
|
+
*/
|
|
4279
|
+
async function convertMessages(messages, readImage, codec) {
|
|
3681
4280
|
const out = [];
|
|
3682
4281
|
const { ids: paired, names: toolNames } = pairedToolCalls(messages);
|
|
3683
4282
|
const wireIds = wireToolCallIds(paired);
|
|
@@ -3686,6 +4285,10 @@ async function messagesToCC(messages, readImage) {
|
|
|
3686
4285
|
for (const message of pendingImages) out.push(message);
|
|
3687
4286
|
pendingImages.length = 0;
|
|
3688
4287
|
};
|
|
4288
|
+
const encodeImage = (ref) => {
|
|
4289
|
+
if (!readImage) throw new LlmError("Image input requires the durable attachment service", "UNSUPPORTED_CONTENT");
|
|
4290
|
+
return codec.encodeImage(ref, readImage);
|
|
4291
|
+
};
|
|
3689
4292
|
for (const message of messages) {
|
|
3690
4293
|
if (message.role === "system") continue;
|
|
3691
4294
|
const result = toolResultOf(message);
|
|
@@ -3697,86 +4300,88 @@ async function messagesToCC(messages, readImage) {
|
|
|
3697
4300
|
type: "text",
|
|
3698
4301
|
text: block.text
|
|
3699
4302
|
});
|
|
3700
|
-
if (block.type === "image")
|
|
3701
|
-
if (!readImage) throw new LlmError("Image input requires the durable attachment service", "UNSUPPORTED_CONTENT");
|
|
3702
|
-
parts.push(await imageToCommandCode(block.attachment, readImage));
|
|
3703
|
-
}
|
|
4303
|
+
if (block.type === "image") parts.push(await encodeImage(block.attachment));
|
|
3704
4304
|
}
|
|
3705
|
-
if (parts.length
|
|
3706
|
-
out.push({
|
|
3707
|
-
role: "user",
|
|
3708
|
-
content: parts
|
|
3709
|
-
});
|
|
4305
|
+
if (parts.length > 0) out.push(codec.encodeUser(parts));
|
|
3710
4306
|
continue;
|
|
3711
4307
|
}
|
|
3712
4308
|
if (message.role === "assistant") {
|
|
3713
4309
|
flushPendingImages();
|
|
3714
|
-
const
|
|
3715
|
-
|
|
3716
|
-
type: "text",
|
|
3717
|
-
text: block.text
|
|
3718
|
-
});
|
|
3719
|
-
else if (block.type === "reasoning") parts.push({
|
|
3720
|
-
type: "reasoning",
|
|
3721
|
-
text: block.text
|
|
3722
|
-
});
|
|
3723
|
-
else if (block.type === "tool-call" && paired.has(block.id)) parts.push({
|
|
3724
|
-
type: "tool-call",
|
|
3725
|
-
toolCallId: wireIds.get(block.id) ?? block.id,
|
|
3726
|
-
toolName: block.name,
|
|
3727
|
-
input: recordOrEmpty(block.arguments)
|
|
3728
|
-
});
|
|
3729
|
-
if (parts.length > 0) out.push({
|
|
3730
|
-
role: "assistant",
|
|
3731
|
-
content: parts
|
|
3732
|
-
});
|
|
4310
|
+
const assistant = codec.encodeAssistant(message, paired, wireIds);
|
|
4311
|
+
if (assistant !== void 0) out.push(assistant);
|
|
3733
4312
|
continue;
|
|
3734
4313
|
}
|
|
3735
|
-
if (result)
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
type: "error-text",
|
|
3747
|
-
value: toolResultTextForWire(media)
|
|
3748
|
-
} : {
|
|
3749
|
-
type: "text",
|
|
3750
|
-
value: toolResultTextForWire(media)
|
|
3751
|
-
}
|
|
3752
|
-
}]
|
|
3753
|
-
});
|
|
3754
|
-
if (media.images.length > 0) {
|
|
3755
|
-
if (!readImage) throw new LlmError("Image input requires the durable attachment service", "UNSUPPORTED_CONTENT");
|
|
3756
|
-
const carried = [{
|
|
3757
|
-
type: "text",
|
|
3758
|
-
text: toolResultImageNote(media, wireToolCallId)
|
|
3759
|
-
}];
|
|
3760
|
-
for (const attachment of media.images) carried.push(await imageToCommandCode(attachment, readImage));
|
|
3761
|
-
pendingImages.push({
|
|
3762
|
-
role: "user",
|
|
3763
|
-
content: carried
|
|
3764
|
-
});
|
|
3765
|
-
}
|
|
4314
|
+
if (!result || !paired.has(result.toolCallId)) continue;
|
|
4315
|
+
const media = toolResultMedia(result);
|
|
4316
|
+
const wireId = wireIds.get(result.toolCallId) ?? result.toolCallId;
|
|
4317
|
+
out.push(codec.encodeTool(result, media, wireId, toolNames.get(result.toolCallId) || "unknown"));
|
|
4318
|
+
if (media.images.length > 0) {
|
|
4319
|
+
const carried = [{
|
|
4320
|
+
type: "text",
|
|
4321
|
+
text: toolResultImageNote(media, wireId)
|
|
4322
|
+
}];
|
|
4323
|
+
for (const attachment of media.images) carried.push(await encodeImage(attachment));
|
|
4324
|
+
pendingImages.push(codec.encodeUser(carried));
|
|
3766
4325
|
}
|
|
3767
4326
|
}
|
|
3768
4327
|
flushPendingImages();
|
|
3769
4328
|
return out;
|
|
3770
4329
|
}
|
|
4330
|
+
const ccMessageCodec = {
|
|
4331
|
+
encodeImage: imageToCommandCode,
|
|
4332
|
+
encodeUser: (parts) => ({
|
|
4333
|
+
role: "user",
|
|
4334
|
+
content: parts
|
|
4335
|
+
}),
|
|
4336
|
+
encodeAssistant: (message, paired, wireIds) => {
|
|
4337
|
+
const parts = [];
|
|
4338
|
+
for (const block of message.content) if (block.type === "text") parts.push({
|
|
4339
|
+
type: "text",
|
|
4340
|
+
text: block.text
|
|
4341
|
+
});
|
|
4342
|
+
else if (block.type === "reasoning") parts.push({
|
|
4343
|
+
type: "reasoning",
|
|
4344
|
+
text: block.text
|
|
4345
|
+
});
|
|
4346
|
+
else if (block.type === "tool-call" && paired.has(block.id)) parts.push({
|
|
4347
|
+
type: "tool-call",
|
|
4348
|
+
toolCallId: wireIds.get(block.id) ?? block.id,
|
|
4349
|
+
toolName: block.name,
|
|
4350
|
+
input: recordOrEmpty(block.arguments)
|
|
4351
|
+
});
|
|
4352
|
+
return parts.length > 0 ? {
|
|
4353
|
+
role: "assistant",
|
|
4354
|
+
content: parts
|
|
4355
|
+
} : void 0;
|
|
4356
|
+
},
|
|
4357
|
+
encodeTool: (result, media, wireId, toolName) => ({
|
|
4358
|
+
role: "tool",
|
|
4359
|
+
content: [{
|
|
4360
|
+
type: "tool-result",
|
|
4361
|
+
toolCallId: wireId,
|
|
4362
|
+
toolName,
|
|
4363
|
+
output: result.isError ? {
|
|
4364
|
+
type: "error-text",
|
|
4365
|
+
value: toolResultTextForWire(media)
|
|
4366
|
+
} : {
|
|
4367
|
+
type: "text",
|
|
4368
|
+
value: toolResultTextForWire(media)
|
|
4369
|
+
}
|
|
4370
|
+
}]
|
|
4371
|
+
})
|
|
4372
|
+
};
|
|
4373
|
+
async function messagesToCC(messages, readImage) {
|
|
4374
|
+
return convertMessages(messages, readImage, ccMessageCodec);
|
|
4375
|
+
}
|
|
3771
4376
|
/**
|
|
3772
4377
|
* Convert one image reference to the OpenAI Chat Completions wire format:
|
|
3773
4378
|
* `{ type: 'image_url', image_url: { url: 'data:...;base64,...' } }`.
|
|
3774
4379
|
*/
|
|
3775
4380
|
async function imageToOpenAI(ref, readImage) {
|
|
3776
|
-
const
|
|
4381
|
+
const version = await readImage(ref);
|
|
3777
4382
|
return {
|
|
3778
4383
|
type: "image_url",
|
|
3779
|
-
image_url: { url: `data:${
|
|
4384
|
+
image_url: { url: `data:${version.mediaType};base64,${Buffer.from(version.data).toString("base64")}` }
|
|
3780
4385
|
};
|
|
3781
4386
|
}
|
|
3782
4387
|
/**
|
|
@@ -3789,89 +4394,45 @@ async function imageToOpenAI(ref, readImage) {
|
|
|
3789
4394
|
* on assistant messages. Only tool calls with a paired tool result are
|
|
3790
4395
|
* replayed (same policy as the CLI path).
|
|
3791
4396
|
*/
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
if (block.type === "image") {
|
|
3813
|
-
if (!readImage) throw new LlmError("Image input requires the durable attachment service", "UNSUPPORTED_CONTENT");
|
|
3814
|
-
parts.push(await imageToOpenAI(block.attachment, readImage));
|
|
3815
|
-
}
|
|
3816
|
-
}
|
|
3817
|
-
if (parts.length === 0) continue;
|
|
3818
|
-
if (!parts.some((part) => part.type === "image_url") && parts.length === 1) out.push({
|
|
3819
|
-
role: "user",
|
|
3820
|
-
content: parts[0].text
|
|
3821
|
-
});
|
|
3822
|
-
else out.push({
|
|
3823
|
-
role: "user",
|
|
3824
|
-
content: parts
|
|
3825
|
-
});
|
|
3826
|
-
continue;
|
|
3827
|
-
}
|
|
3828
|
-
if (message.role === "assistant") {
|
|
3829
|
-
flushPendingImages();
|
|
3830
|
-
const text = message.content.filter((block) => block.type === "text").map((block) => block.text).join("");
|
|
3831
|
-
const reasoning = message.content.filter((block) => block.type === "reasoning").map((block) => block.text).join("");
|
|
3832
|
-
const toolCalls = message.content.filter((block) => block.type === "tool-call" && paired.has(block.id)).map((block) => ({
|
|
3833
|
-
id: wireIds.get(block.id) ?? block.id,
|
|
3834
|
-
type: "function",
|
|
3835
|
-
function: {
|
|
3836
|
-
name: block.name,
|
|
3837
|
-
arguments: block.arguments
|
|
3838
|
-
}
|
|
3839
|
-
}));
|
|
3840
|
-
if (text === "" && reasoning === "" && toolCalls.length === 0) continue;
|
|
3841
|
-
const assistant = {
|
|
3842
|
-
role: "assistant",
|
|
3843
|
-
content: text === "" ? null : text
|
|
3844
|
-
};
|
|
3845
|
-
if (reasoning !== "") assistant.reasoning_content = reasoning;
|
|
3846
|
-
if (toolCalls.length > 0) assistant.tool_calls = toolCalls;
|
|
3847
|
-
out.push(assistant);
|
|
3848
|
-
continue;
|
|
3849
|
-
}
|
|
3850
|
-
if (result) {
|
|
3851
|
-
if (!paired.has(result.toolCallId)) continue;
|
|
3852
|
-
const media = toolResultMedia(result);
|
|
3853
|
-
const wireToolCallId = wireIds.get(result.toolCallId) ?? result.toolCallId;
|
|
3854
|
-
out.push({
|
|
3855
|
-
role: "tool",
|
|
3856
|
-
tool_call_id: wireToolCallId,
|
|
3857
|
-
content: toolResultTextForWire(media)
|
|
3858
|
-
});
|
|
3859
|
-
if (media.images.length > 0) {
|
|
3860
|
-
if (!readImage) throw new LlmError("Image input requires the durable attachment service", "UNSUPPORTED_CONTENT");
|
|
3861
|
-
const carried = [{
|
|
3862
|
-
type: "text",
|
|
3863
|
-
text: toolResultImageNote(media, wireToolCallId)
|
|
3864
|
-
}];
|
|
3865
|
-
for (const attachment of media.images) carried.push(await imageToOpenAI(attachment, readImage));
|
|
3866
|
-
pendingImages.push({
|
|
3867
|
-
role: "user",
|
|
3868
|
-
content: carried
|
|
3869
|
-
});
|
|
4397
|
+
const openAiMessageCodec = {
|
|
4398
|
+
encodeImage: imageToOpenAI,
|
|
4399
|
+
encodeUser: (parts) => {
|
|
4400
|
+
return !parts.some((part) => part.type === "image_url") && parts.length === 1 ? {
|
|
4401
|
+
role: "user",
|
|
4402
|
+
content: parts[0].text
|
|
4403
|
+
} : {
|
|
4404
|
+
role: "user",
|
|
4405
|
+
content: parts
|
|
4406
|
+
};
|
|
4407
|
+
},
|
|
4408
|
+
encodeAssistant: (message, paired, wireIds) => {
|
|
4409
|
+
const text = message.content.filter((block) => block.type === "text").map((block) => block.text).join("");
|
|
4410
|
+
const reasoning = message.content.filter((block) => block.type === "reasoning").map((block) => block.text).join("");
|
|
4411
|
+
const toolCalls = message.content.filter((block) => block.type === "tool-call" && paired.has(block.id)).map((block) => ({
|
|
4412
|
+
id: wireIds.get(block.id) ?? block.id,
|
|
4413
|
+
type: "function",
|
|
4414
|
+
function: {
|
|
4415
|
+
name: block.name,
|
|
4416
|
+
arguments: block.arguments
|
|
3870
4417
|
}
|
|
3871
|
-
}
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
4418
|
+
}));
|
|
4419
|
+
if (text === "" && reasoning === "" && toolCalls.length === 0) return void 0;
|
|
4420
|
+
const assistant = {
|
|
4421
|
+
role: "assistant",
|
|
4422
|
+
content: text === "" ? null : text
|
|
4423
|
+
};
|
|
4424
|
+
if (reasoning !== "") assistant.reasoning_content = reasoning;
|
|
4425
|
+
if (toolCalls.length > 0) assistant.tool_calls = toolCalls;
|
|
4426
|
+
return assistant;
|
|
4427
|
+
},
|
|
4428
|
+
encodeTool: (_result, media, wireId) => ({
|
|
4429
|
+
role: "tool",
|
|
4430
|
+
tool_call_id: wireId,
|
|
4431
|
+
content: toolResultTextForWire(media)
|
|
4432
|
+
})
|
|
4433
|
+
};
|
|
4434
|
+
async function messagesToOpenAI(messages, readImage) {
|
|
4435
|
+
return convertMessages(messages, readImage, openAiMessageCodec);
|
|
3875
4436
|
}
|
|
3876
4437
|
/** Account endpoints fetched by one `getUsage()` run (see the classification there). */
|
|
3877
4438
|
const USAGE_ENDPOINT_COUNT = 4;
|
|
@@ -4060,6 +4621,41 @@ async function buildOpenAIBody(options, facts) {
|
|
|
4060
4621
|
};
|
|
4061
4622
|
}
|
|
4062
4623
|
/**
|
|
4624
|
+
* Estimate the text the selected transport actually sends. JSON framing and
|
|
4625
|
+
* punctuation each get a token; ordinary ASCII words get one, with an extra
|
|
4626
|
+
* allowance for long identifiers. UTF-8 bytes / 4 tracks the measured CJK
|
|
4627
|
+
* case better than the engine's UTF-16 characters / 4. This is a heuristic,
|
|
4628
|
+
* never proof that a prompt cannot fit: a valid long English prompt can still
|
|
4629
|
+
* be larger under this estimate than under the provider's tokenizer.
|
|
4630
|
+
*/
|
|
4631
|
+
function estimateWireInputTokens(protocol, body, messages, model) {
|
|
4632
|
+
const params = protocol === "cli" ? recordOrEmpty(body.params) : body;
|
|
4633
|
+
const wire = JSON.stringify(params, (_key, value) => typeof value === "string" && /^data:[^,]*;base64,/.test(value) ? "[image]" : value);
|
|
4634
|
+
let tokens = 0;
|
|
4635
|
+
for (const match of wire.matchAll(/[A-Za-z0-9_]+|[^\x00-\x7f]+|[^\s]/gu)) {
|
|
4636
|
+
const part = match[0];
|
|
4637
|
+
if (/^[A-Za-z0-9_]+$/.test(part)) tokens += 1 + Math.ceil(Math.max(0, part.length - 12) / 4);
|
|
4638
|
+
else if (part.charCodeAt(0) > 127) tokens += Math.ceil(Buffer.byteLength(part) / 4);
|
|
4639
|
+
else tokens += 1;
|
|
4640
|
+
}
|
|
4641
|
+
for (const message of messages) for (const block of message.content) if (block.type === "image") tokens += priceRequestImage(block, model, true).visualTokens;
|
|
4642
|
+
return tokens;
|
|
4643
|
+
}
|
|
4644
|
+
function requestContextBudget(protocol, body, messages, model, contextWindow, requestedMaxTokens) {
|
|
4645
|
+
if (contextWindow === void 0) return {
|
|
4646
|
+
maxTokens: requestedMaxTokens,
|
|
4647
|
+
sizePressure: false
|
|
4648
|
+
};
|
|
4649
|
+
const estimatedInput = estimateWireInputTokens(protocol, body, messages, model);
|
|
4650
|
+
const headroom = Math.min(16384, Math.max(2048, Math.ceil(contextWindow * .02)));
|
|
4651
|
+
const available = Math.floor(contextWindow - estimatedInput - headroom);
|
|
4652
|
+
const floor = Math.min(requestedMaxTokens, MIN_CONTEXT_OUTPUT_TOKENS, Math.max(1, Math.floor(contextWindow / 8)));
|
|
4653
|
+
return {
|
|
4654
|
+
maxTokens: Math.min(requestedMaxTokens, Math.max(floor, available)),
|
|
4655
|
+
sizePressure: estimatedInput >= contextWindow / 4 && estimatedInput + requestedMaxTokens + headroom >= contextWindow
|
|
4656
|
+
};
|
|
4657
|
+
}
|
|
4658
|
+
/**
|
|
4063
4659
|
* One pre-stream connect attempt: POST the body and wait for response
|
|
4064
4660
|
* headers only (`requestTimeoutMs` must never bound the body stream). Returns
|
|
4065
4661
|
* the live response plus its cleanup, or the rejection facts for the rotation
|
|
@@ -4108,19 +4704,26 @@ async function connectGenerate(deps, key, protocol, body) {
|
|
|
4108
4704
|
...zdr ? { "x-cmd-zdr": "1" } : {},
|
|
4109
4705
|
...attributionHeaders()
|
|
4110
4706
|
};
|
|
4707
|
+
const serializedBody = JSON.stringify(body);
|
|
4111
4708
|
try {
|
|
4112
4709
|
response = await fetchImpl(endpoint, {
|
|
4113
4710
|
method: "POST",
|
|
4114
4711
|
headers,
|
|
4115
|
-
body:
|
|
4712
|
+
body: serializedBody,
|
|
4116
4713
|
signal: connectAbort.signal
|
|
4117
4714
|
});
|
|
4118
4715
|
clearTimeout(connectTimer);
|
|
4119
4716
|
} catch (error) {
|
|
4120
4717
|
cleanup();
|
|
4121
4718
|
if (options.signal?.aborted) throw error;
|
|
4122
|
-
if (connectTimedOut || error instanceof DOMException && error.name === "TimeoutError") throw
|
|
4123
|
-
|
|
4719
|
+
if (connectTimedOut || error instanceof DOMException && error.name === "TimeoutError") throw headersTimeoutError({
|
|
4720
|
+
protocol,
|
|
4721
|
+
endpoint,
|
|
4722
|
+
body: serializedBody,
|
|
4723
|
+
timeoutMs: connection.requestTimeoutMs,
|
|
4724
|
+
cause: error
|
|
4725
|
+
});
|
|
4726
|
+
throw bilingual("TRANSPORT", `Command Code API request to ${endpoint} failed: ${errorChain(error)}`, "Command Code API 请求连接失败——通常是网络或代理问题,请检查网络或代理设置后重试", { cause: error });
|
|
4124
4727
|
}
|
|
4125
4728
|
deps.onResponse(response);
|
|
4126
4729
|
if (!response.ok) {
|
|
@@ -4226,6 +4829,13 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
4226
4829
|
billingAccess = /* @__PURE__ */ new Map();
|
|
4227
4830
|
billingAccessInflight = /* @__PURE__ */ new Map();
|
|
4228
4831
|
protocolCache = /* @__PURE__ */ new Map();
|
|
4832
|
+
/**
|
|
4833
|
+
* Consecutive header timeouts for the last request shape, counted ACROSS
|
|
4834
|
+
* `stream()` calls: dsh-llm-retry re-invokes this same instance, so this is
|
|
4835
|
+
* the only place a streak can live. One slot rather than a map is the point —
|
|
4836
|
+
* the claim is "consecutive", and a single record cannot grow.
|
|
4837
|
+
*/
|
|
4838
|
+
headerTimeoutStreak;
|
|
4229
4839
|
constructor(deps) {
|
|
4230
4840
|
super();
|
|
4231
4841
|
this.deps = deps;
|
|
@@ -4234,6 +4844,32 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
4234
4844
|
this.surfaceOffload = deps.imageOffload ?? ENGINE_IMAGE_POLICY;
|
|
4235
4845
|
}
|
|
4236
4846
|
/**
|
|
4847
|
+
* Grade one failed attempt: a headers timeout that repeats on an unchanged
|
|
4848
|
+
* payload eventually leaves the retryable whitelist.
|
|
4849
|
+
*
|
|
4850
|
+
* `providerRetryPolicy()` cannot express this itself — dsh-llm's
|
|
4851
|
+
* `NormalRetryPolicyConfig` carries a single scalar `maxRetries` next to the
|
|
4852
|
+
* code list, so there is no per-code limit to lower. Escalating from here is
|
|
4853
|
+
* the only way to keep `RATE_LIMIT` / `SERVER` / `TRANSPORT` retryable (each
|
|
4854
|
+
* carries a real "try again later" signal) while stopping the one code that,
|
|
4855
|
+
* repeated unchanged, only burns the user's session.
|
|
4856
|
+
*
|
|
4857
|
+
* @returns the error to surface: unchanged for any other failure, the
|
|
4858
|
+
* original timeout below the threshold, and a terminal one at it.
|
|
4859
|
+
*/
|
|
4860
|
+
gradeHeaderTimeout(error, protocol, body, timeoutMs) {
|
|
4861
|
+
if (!(error instanceof LlmError && error.code === "TIMEOUT")) return error;
|
|
4862
|
+
const fingerprint = `${protocol}:${fingerprintValue(body).sha256}`;
|
|
4863
|
+
const count = this.headerTimeoutStreak?.fingerprint === fingerprint ? this.headerTimeoutStreak.count + 1 : 1;
|
|
4864
|
+
this.headerTimeoutStreak = {
|
|
4865
|
+
fingerprint,
|
|
4866
|
+
count
|
|
4867
|
+
};
|
|
4868
|
+
if (count < MAX_CONSECUTIVE_HEADER_TIMEOUTS) return error;
|
|
4869
|
+
this.headerTimeoutStreak = void 0;
|
|
4870
|
+
return bilingual("PROVIDER_HTTP_ERROR", `Command Code API stopped retrying: the gateway returned no response headers on ${count} consecutive attempts at the same request, each after the full ${timeoutMs}ms budget, so this is not a network blip — it is provider queueing, a Go-plan capacity limit, or a network/proxy path problem. Resending the identical payload again is unlikely to help; wait for the provider to recover, or switch model or account.`, `Command Code API 已停止重试:同一请求连续 ${count} 次在 ${timeoutMs} 毫秒内都没有收到任何响应头,这不是网络抖动,而是服务商排队、Go 套餐容量限制或网络/代理链路问题。继续原样重发同一个请求通常无济于事——可以等服务商恢复,或换模型/换账号。`);
|
|
4871
|
+
}
|
|
4872
|
+
/**
|
|
4237
4873
|
* Display metadata for the picker's provider group header. The base class
|
|
4238
4874
|
* returns the raw route id (`commandcode`, all lowercase) as the name, which
|
|
4239
4875
|
* is what the model selector shows as this group's sticky title; return the
|
|
@@ -4300,16 +4936,13 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
4300
4936
|
async loadCatalog(signal) {
|
|
4301
4937
|
const { apiBase, modelsCachePath } = this.deps.options();
|
|
4302
4938
|
try {
|
|
4303
|
-
const
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
});
|
|
4311
|
-
if (!response.ok) throw new Error(`models endpoint returned ${response.status}`);
|
|
4312
|
-
this.catalog = parseCatalogResponse(await response.json());
|
|
4939
|
+
const { status, record } = await this.fetchJson(`${apiBase}/provider/v1/models`, {
|
|
4940
|
+
accept: "application/json",
|
|
4941
|
+
...IDENTITY_ENCODING_HEADER,
|
|
4942
|
+
...attributionHeaders()
|
|
4943
|
+
}, MODELS_TIMEOUT_MS, signal);
|
|
4944
|
+
if (status < 200 || status >= 300) throw new Error(`models endpoint returned ${status}`);
|
|
4945
|
+
this.catalog = parseCatalogResponse(record);
|
|
4313
4946
|
await writeModelsCache(modelsCachePath, this.catalog).catch(() => void 0);
|
|
4314
4947
|
} catch (error) {
|
|
4315
4948
|
if (signal?.aborted) throw error;
|
|
@@ -4340,6 +4973,31 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
4340
4973
|
return allow === void 0 || allow.has(model.id);
|
|
4341
4974
|
}).map(toInfo).sort(compareByPlan);
|
|
4342
4975
|
}
|
|
4976
|
+
/**
|
|
4977
|
+
* The per-model allowance bracket this account POOL falls into, or undefined
|
|
4978
|
+
* when there is nothing honest to show.
|
|
4979
|
+
*
|
|
4980
|
+
* The pricing page publishes a per-model monthly allowance for GOAT and Pro
|
|
4981
|
+
* only — its own words are "the boost is a per-model allowance, so it lives on
|
|
4982
|
+
* the plans that have them" — so a pool whose highest plan is Go, Provider,
|
|
4983
|
+
* Max or Ultra gets undefined rather than a neighbouring tier's figure. Taking
|
|
4984
|
+
* the HIGHEST tier matches the question the picker already answers for a pool
|
|
4985
|
+
* (see `modelVisibleForAnyAccount`): what the user can reach, not what the one
|
|
4986
|
+
* account serving this request happens to be. The fail-open shape is the same
|
|
4987
|
+
* too — an unreachable billing endpoint yields undefined, which hides the
|
|
4988
|
+
* allowance instead of inventing one.
|
|
4989
|
+
*/
|
|
4990
|
+
async allowanceTier() {
|
|
4991
|
+
const accesses = await this.loadPoolBillingAccess();
|
|
4992
|
+
if (accesses === void 0) return void 0;
|
|
4993
|
+
let highest;
|
|
4994
|
+
for (const access of accesses) {
|
|
4995
|
+
const weight = access?.tierWeight;
|
|
4996
|
+
if (weight === void 0) continue;
|
|
4997
|
+
if (highest === void 0 || weight > highest) highest = weight;
|
|
4998
|
+
}
|
|
4999
|
+
return allowanceTierForWeight(highest);
|
|
5000
|
+
}
|
|
4343
5001
|
async resolveModel(provider, model, signal) {
|
|
4344
5002
|
const entry = this.catalog.find((m) => m.id === model) ?? (await this.loadCatalog(signal)).find((m) => m.id === model);
|
|
4345
5003
|
const efforts = KNOWN_EFFORTS[model];
|
|
@@ -4375,21 +5033,19 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
4375
5033
|
};
|
|
4376
5034
|
}
|
|
4377
5035
|
/**
|
|
4378
|
-
* Fetch one
|
|
4379
|
-
*
|
|
4380
|
-
*
|
|
4381
|
-
*
|
|
4382
|
-
* record; only a fetch failure propagates to the caller.
|
|
5036
|
+
* Fetch one JSON GET and return its HTTP status with a parsed record. The
|
|
5037
|
+
* catalog, billing probe, and usage report each apply their own failure
|
|
5038
|
+
* policy. Non-2xx and invalid JSON bodies come back without a record; only
|
|
5039
|
+
* a fetch failure propagates to the caller.
|
|
4383
5040
|
*
|
|
4384
|
-
* `timeoutMs` is the caller's budget
|
|
4385
|
-
*
|
|
4386
|
-
*
|
|
4387
|
-
* account query is not held to a stricter (and invisible) limit than chat.
|
|
5041
|
+
* `timeoutMs` is the caller's budget, unless it provides its own signal.
|
|
5042
|
+
* The usage report passes the connection's request budget rather than the
|
|
5043
|
+
* catalog/picker's shorter default.
|
|
4388
5044
|
*/
|
|
4389
|
-
async
|
|
5045
|
+
async fetchJson(url, headers, timeoutMs = MODELS_TIMEOUT_MS, signal) {
|
|
4390
5046
|
const response = await this.fetchImpl(url, {
|
|
4391
5047
|
headers,
|
|
4392
|
-
signal: AbortSignal.timeout(timeoutMs)
|
|
5048
|
+
signal: signal ?? AbortSignal.timeout(timeoutMs)
|
|
4393
5049
|
});
|
|
4394
5050
|
if (!response.ok) return { status: response.status };
|
|
4395
5051
|
let parsed;
|
|
@@ -4475,7 +5131,7 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
4475
5131
|
const connection = this.deps.options();
|
|
4476
5132
|
const headers = await this.accountHeaders(apiKey);
|
|
4477
5133
|
const base = connection.apiBase;
|
|
4478
|
-
const getJson = async (path) => (await this.
|
|
5134
|
+
const getJson = async (path) => (await this.fetchJson(`${base}${path}`, headers)).record;
|
|
4479
5135
|
const whoami = await getJson("/alpha/whoami");
|
|
4480
5136
|
const orgData = whoami && isRecord(whoami.org) ? whoami.org : void 0;
|
|
4481
5137
|
const orgId = orgData === void 0 ? void 0 : stringValue(orgData.id);
|
|
@@ -4582,7 +5238,7 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
4582
5238
|
const failed = [];
|
|
4583
5239
|
const getJson = async (path) => {
|
|
4584
5240
|
try {
|
|
4585
|
-
const { status, record, invalidResponse } = await this.
|
|
5241
|
+
const { status, record, invalidResponse } = await this.fetchJson(`${base}${path}`, headers, connection.requestTimeoutMs);
|
|
4586
5242
|
if (record === void 0) {
|
|
4587
5243
|
failures.push(`${path}: HTTP ${status}${invalidResponse ? " returned an unreadable or invalid JSON body" : ""}`);
|
|
4588
5244
|
failed.push(invalidResponse ? {
|
|
@@ -4655,13 +5311,8 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
4655
5311
|
async probeWindowLimits(apiKey) {
|
|
4656
5312
|
try {
|
|
4657
5313
|
const connection = this.deps.options();
|
|
4658
|
-
const
|
|
4659
|
-
|
|
4660
|
-
signal: AbortSignal.timeout(MODELS_TIMEOUT_MS)
|
|
4661
|
-
});
|
|
4662
|
-
if (!response.ok) return void 0;
|
|
4663
|
-
const parsed = await response.json();
|
|
4664
|
-
if (!isRecord(parsed)) return void 0;
|
|
5314
|
+
const { record: parsed } = await this.fetchJson(`${connection.apiBase}/alpha/billing/credits`, await this.accountHeaders(apiKey));
|
|
5315
|
+
if (parsed === void 0) return void 0;
|
|
4665
5316
|
const windowLimits = isRecord(parsed.windowLimits) ? parsed.windowLimits : void 0;
|
|
4666
5317
|
if (windowLimits === void 0) return void 0;
|
|
4667
5318
|
let reported = false;
|
|
@@ -4696,25 +5347,34 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
4696
5347
|
}
|
|
4697
5348
|
const connection = this.deps.options();
|
|
4698
5349
|
let apiKey = await this.deps.resolveApiKey(connection, options.model);
|
|
4699
|
-
const
|
|
4700
|
-
const
|
|
5350
|
+
const modelEntry = this.catalog.find((m) => m.id === options.model);
|
|
5351
|
+
const modelMax = modelEntry?.maxTokens ?? 131072;
|
|
5352
|
+
const requestedMaxTokens = Math.min(options.maxTokens ?? modelMax, modelMax, DEFAULT_GENERATE_MAX_TOKENS);
|
|
5353
|
+
let maxTokens = requestedMaxTokens;
|
|
5354
|
+
let contextBudget = {
|
|
5355
|
+
maxTokens,
|
|
5356
|
+
sizePressure: false
|
|
5357
|
+
};
|
|
4701
5358
|
const effort = options.reasoningEffort;
|
|
4702
5359
|
const supported = KNOWN_EFFORTS[options.model];
|
|
4703
5360
|
const reasoningEffort = effort && effort !== "off" && supported?.includes(effort) ? effort : void 0;
|
|
4704
5361
|
const systemText = [options.system ?? "", ...options.messages.filter((m) => m.role === "system").map((m) => m.content.map(blockText).filter(Boolean).join("\n"))].filter(Boolean).join("\n\n");
|
|
4705
5362
|
let protocol = this.resolveProtocol(apiKey, options.model);
|
|
4706
|
-
let requestOptions = withSurfaceOffload(options, this.surfaceOffload, REQUEST_IMAGE_BUDGETS[0]);
|
|
4707
|
-
const buildBody = async (target) =>
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
5363
|
+
let requestOptions = withSurfaceOffload(options, this.surfaceOffload, REQUEST_IMAGE_BUDGETS[0], connection.offloadSeenImagesForCache === true && protocol === "cli" ? options.model : void 0);
|
|
5364
|
+
const buildBody = async (target) => {
|
|
5365
|
+
const facts = {
|
|
5366
|
+
maxTokens: requestedMaxTokens,
|
|
5367
|
+
reasoningEffort,
|
|
5368
|
+
systemText,
|
|
5369
|
+
readImage
|
|
5370
|
+
};
|
|
5371
|
+
const built = target === "cli" ? await buildCliBody(requestOptions, connection, facts) : await buildOpenAIBody(requestOptions, facts);
|
|
5372
|
+
contextBudget = requestContextBudget(target, built, requestOptions.messages, options.model, modelEntry?.contextWindow, requestedMaxTokens);
|
|
5373
|
+
maxTokens = contextBudget.maxTokens;
|
|
5374
|
+
if (target === "cli") recordOrEmpty(built.params).max_tokens = maxTokens;
|
|
5375
|
+
else built.max_tokens = maxTokens;
|
|
5376
|
+
return built;
|
|
5377
|
+
};
|
|
4718
5378
|
let body = await buildBody(protocol);
|
|
4719
5379
|
const trace = openStreamTrace(`${options.provider}/${options.model}`);
|
|
4720
5380
|
const tried = /* @__PURE__ */ new Set();
|
|
@@ -4728,6 +5388,7 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
4728
5388
|
connection,
|
|
4729
5389
|
fetchImpl: this.fetchImpl,
|
|
4730
5390
|
onResponse: (response) => {
|
|
5391
|
+
this.headerTimeoutStreak = void 0;
|
|
4731
5392
|
responseMetadata = {
|
|
4732
5393
|
protocol,
|
|
4733
5394
|
headers: responseIdentifiers(response.headers)
|
|
@@ -4763,9 +5424,11 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
4763
5424
|
connected = attempt;
|
|
4764
5425
|
break;
|
|
4765
5426
|
}
|
|
4766
|
-
|
|
5427
|
+
const providerError = parseProviderError(attempt.errText);
|
|
5428
|
+
if (protocol === "openai" && isUpgradeRequiredError(attempt.status, attempt.errText, providerError)) {
|
|
4767
5429
|
protocol = "cli";
|
|
4768
5430
|
this.rememberProtocol(apiKey, true);
|
|
5431
|
+
requestOptions = withSurfaceOffload(options, this.surfaceOffload, REQUEST_IMAGE_BUDGETS[0], connection.offloadSeenImagesForCache === true ? options.model : void 0);
|
|
4769
5432
|
body = await buildBody("cli");
|
|
4770
5433
|
continue;
|
|
4771
5434
|
}
|
|
@@ -4774,7 +5437,7 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
4774
5437
|
if (missing > 0) throw imageOffloadRequired(missing);
|
|
4775
5438
|
}
|
|
4776
5439
|
const rotate = this.deps.rotateApiKey;
|
|
4777
|
-
const rejection = classifyAccountRejection(attempt.status, attempt.errText);
|
|
5440
|
+
const rejection = classifyAccountRejection(attempt.status, attempt.errText, providerError);
|
|
4778
5441
|
if (rejection !== void 0 && rotate !== void 0 && options.signal?.aborted !== true && tried.size < MAX_ACCOUNT_ROTATIONS) {
|
|
4779
5442
|
const next = await rotate(apiKey, rejection.reason, connection, options.model, {
|
|
4780
5443
|
tried: [...tried],
|
|
@@ -4790,9 +5453,9 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
4790
5453
|
const wait = reset === void 0 ? 0 : Math.min(Math.max(1e3, reset - Date.now()), RETRY_MAX_DELAY_MS);
|
|
4791
5454
|
const when = reset === void 0 ? void 0 : new Date(reset).toISOString();
|
|
4792
5455
|
const window = rejection.reason === "rate-limit";
|
|
4793
|
-
throw
|
|
5456
|
+
throw bilingual("RATE_LIMIT", (window ? "llm-commandcode: the Command Code account is rate limited" : "llm-commandcode: the Command Code account is rate limited (429) — the provider did not report an exhausted usage window") + (when === void 0 ? "" : ` — the provider reports it resets at ${when}`), (window ? "当前 Command Code 账户已被限流" : "当前 Command Code 账户被限流(429),服务商未报告用量窗口用尽") + (when === void 0 ? "" : `,服务商给出的重置时间为 ${when}`), wait > 0 ? { providerRetryAfterMs: wait } : void 0);
|
|
4794
5457
|
}
|
|
4795
|
-
throw generateHttpError(attempt.status, attempt.errText, attempt.retryAfterMs);
|
|
5458
|
+
throw generateHttpError(attempt.status, attempt.errText, attempt.retryAfterMs, providerError, contextBudget.sizePressure);
|
|
4796
5459
|
}
|
|
4797
5460
|
} catch (error) {
|
|
4798
5461
|
trace.record("connect-error", {
|
|
@@ -4800,7 +5463,8 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
4800
5463
|
responseMetadata
|
|
4801
5464
|
});
|
|
4802
5465
|
trace.close();
|
|
4803
|
-
|
|
5466
|
+
if (options.signal?.aborted) throw error;
|
|
5467
|
+
throw failureWithRequestId(this.gradeHeaderTimeout(error, protocol, body, connection.requestTimeoutMs), responseMetadata);
|
|
4804
5468
|
}
|
|
4805
5469
|
if (connected === void 0) throw new LlmError("Command Code API connection failed without a response", "TRANSPORT");
|
|
4806
5470
|
const { response, cleanup } = connected;
|
|
@@ -4860,7 +5524,7 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
4860
5524
|
silentMs: Date.now() - lastChunkAt,
|
|
4861
5525
|
message: errorChain(error)
|
|
4862
5526
|
});
|
|
4863
|
-
throw
|
|
5527
|
+
throw bilingual("TRANSPORT", `Command Code API stream from ${connection.apiBase} failed while reading: ${errorChain(error)}`, "Command Code API 流式响应中途断开——网络波动所致,重试通常可恢复", { cause: error });
|
|
4864
5528
|
} finally {
|
|
4865
5529
|
clearIdle();
|
|
4866
5530
|
}
|
|
@@ -4875,7 +5539,7 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
4875
5539
|
idleFired,
|
|
4876
5540
|
buffered: buffer.length
|
|
4877
5541
|
});
|
|
4878
|
-
if (idleFired) throw
|
|
5542
|
+
if (idleFired) throw bilingual("TIMEOUT", `Command Code API stream from ${connection.apiBase} was idle for ${connection.streamIdleTimeoutMs}ms (no events) and was treated as a dead connection`, `Command Code API 流式响应已 ${connection.streamIdleTimeoutMs} 毫秒无任何事件,被判定为死连接——长思考模型可在设置中调大流空闲超时`);
|
|
4879
5543
|
if (buffer.trim()) yield* handle(parseStreamEventLine(buffer));
|
|
4880
5544
|
break;
|
|
4881
5545
|
}
|
|
@@ -4933,8 +5597,8 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
4933
5597
|
responseMetadata
|
|
4934
5598
|
});
|
|
4935
5599
|
endRecorded = true;
|
|
4936
|
-
if (!asm.sawContent) throw
|
|
4937
|
-
throw
|
|
5600
|
+
if (!asm.sawContent) throw bilingual("EMPTY_RESPONSE", "Command Code returned an empty response", "Command Code 返回了空响应,重试通常可恢复");
|
|
5601
|
+
throw bilingual("STREAM_CLOSED", `Command Code API stream from ${connection.apiBase} ended before its finish event (${detail}) — the response was closed mid-generation, so this is not the model stopping by itself`, `Command Code API 流式响应在结束事件之前被关闭(生成中途断流),并非模型主动结束——请重试;若频繁出现,可用 ${STREAM_TRACE_ENV}=<文件路径> 抓取原始流以定位断流原因`);
|
|
4938
5602
|
}
|
|
4939
5603
|
} catch (error) {
|
|
4940
5604
|
if (!usageEmitted && usage !== void 0) yield {
|
|
@@ -4963,44 +5627,39 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
4963
5627
|
}
|
|
4964
5628
|
}
|
|
4965
5629
|
};
|
|
4966
|
-
/**
|
|
4967
|
-
|
|
4968
|
-
* Code folds several business rejections into 403 (plan limits, CLI version,
|
|
4969
|
-
* model access): prefer the machine-readable `error.code` when present; the
|
|
4970
|
-
* status alone cannot distinguish them. The status also decides the failure
|
|
4971
|
-
* CODE via {@link httpErrorCode}, which is what the retry policy routes on —
|
|
4972
|
-
* a transient 5xx must not be reported as a permanent provider rejection. A
|
|
4973
|
-
* 429's `Retry-After` header rides along as `providerRetryAfterMs` so
|
|
4974
|
-
* dsh-llm-retry can wait exactly that long instead of guessing at the backoff
|
|
4975
|
-
* cadence — capped at RETRY_MAX_DELAY_MS, because in normal mode a longer
|
|
4976
|
-
* attached wait makes the executor abandon the retry outright.
|
|
4977
|
-
*
|
|
4978
|
-
* One body is read, not just its status: a client-side rejection whose
|
|
4979
|
-
* `error.code`/`type`/`message` names the model context window is reported as
|
|
4980
|
-
* `CONTEXT_WINDOW_EXCEEDED`, because that failure has a recovery the harness
|
|
4981
|
-
* performs and a generic provider error does not. Every message built here is
|
|
4982
|
-
* bilingual — the harness renders it verbatim in its retry chrome.
|
|
4983
|
-
*/
|
|
4984
|
-
function generateHttpError(status, errText, retryAfterMs) {
|
|
4985
|
-
let providerCode;
|
|
4986
|
-
let providerDetail = "";
|
|
5630
|
+
/** Parse a rejected body once, retaining the nested/root distinction. */
|
|
5631
|
+
function parseProviderError(errText) {
|
|
4987
5632
|
try {
|
|
4988
5633
|
const parsed = JSON.parse(errText);
|
|
4989
|
-
if (isRecord(parsed)
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
5634
|
+
if (!isRecord(parsed)) return {};
|
|
5635
|
+
const nested = isRecord(parsed.error) ? parsed.error : void 0;
|
|
5636
|
+
const body = nested ?? parsed;
|
|
5637
|
+
return {
|
|
5638
|
+
root: parsed,
|
|
5639
|
+
nested,
|
|
5640
|
+
code: stringValue(body.code),
|
|
5641
|
+
type: stringValue(body.type),
|
|
5642
|
+
message: stringValue(body.message),
|
|
5643
|
+
rateLimit: isRecord(body.rateLimit) ? body.rateLimit : void 0
|
|
5644
|
+
};
|
|
5645
|
+
} catch {
|
|
5646
|
+
return {};
|
|
5647
|
+
}
|
|
5648
|
+
}
|
|
5649
|
+
function generateHttpError(status, errText, retryAfterMs, parsed = parseProviderError(errText), sizePressure = false) {
|
|
5650
|
+
const providerCode = parsed.nested === void 0 ? void 0 : parsed.code;
|
|
5651
|
+
const providerDetail = parsed.nested === void 0 ? "" : [
|
|
5652
|
+
parsed.code,
|
|
5653
|
+
parsed.type,
|
|
5654
|
+
parsed.message
|
|
5655
|
+
].filter((part) => part !== void 0 && part !== "").join(" ");
|
|
4998
5656
|
const detail = providerCode ?? `HTTP ${status}`;
|
|
4999
5657
|
const overflowDetail = providerDetail !== "" ? providerDetail : errText.slice(0, 500);
|
|
5000
|
-
if (status < 500 && isContextOverflowDetail(overflowDetail)) return
|
|
5001
|
-
if (status ===
|
|
5002
|
-
if (status ===
|
|
5003
|
-
if (status ===
|
|
5658
|
+
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 });
|
|
5659
|
+
if (status === 400 && sizePressure && AMBIGUOUS_SIZE_REJECTION.test(providerDetail) && /\bprovider_error\b/i.test(providerDetail)) return bilingual(CONTEXT_WINDOW_EXCEEDED_CODE, "Command Code API error 400: the provider could not complete a request near the model context limit — compacting the session and retrying", "Command Code API 返回 400:请求接近模型上下文上限,服务商无法完成——正在压缩上下文后重试", { status });
|
|
5660
|
+
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 });
|
|
5661
|
+
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 });
|
|
5662
|
+
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 });
|
|
5004
5663
|
return new LlmError(`Command Code API error ${status}${detail === `HTTP ${status}` ? "" : ` (${detail})`}: ${errText.slice(0, 500)}`, httpErrorCode(status), {
|
|
5005
5664
|
status,
|
|
5006
5665
|
...retryAfterMs !== void 0 && retryAfterMs > 0 && retryAfterMs <= 9e5 ? { providerRetryAfterMs: retryAfterMs } : {}
|
|
@@ -5276,9 +5935,9 @@ function mapFinishReason(reason) {
|
|
|
5276
5935
|
/** A terminal marker without text or tools is a failed attempt, not a completed turn. */
|
|
5277
5936
|
function emptyCompletionError(reason, maxTokens, usage) {
|
|
5278
5937
|
const detail = `finish_reason=${reason ?? "unknown"}, max_tokens=${maxTokens}, outputTokens=${usage?.outputTokens ?? "unknown"}, reasoningTokens=${usage?.reasoningTokens ?? "unknown"}`;
|
|
5279
|
-
if (mapFinishReason(reason).kind === "max-tokens") return
|
|
5280
|
-
if (reason === "content_filter" || reason === "content-filter") return
|
|
5281
|
-
return
|
|
5938
|
+
if (mapFinishReason(reason).kind === "max-tokens") return bilingual("OUTPUT_TOKEN_LIMIT", `Command Code reached the output token limit without producing answer text or a tool call (${detail})`, "Command Code 已达到输出 token 上限,但没有生成正文或工具调用;请调整思考强度,或在模型和接口上限内增加输出预算");
|
|
5939
|
+
if (reason === "content_filter" || reason === "content-filter") return bilingual("CONTENT_FILTER", `Command Code filtered the response (${detail})`, "Command Code 拦截了响应,未生成正文或工具调用");
|
|
5940
|
+
return bilingual("EMPTY_RESPONSE", `Command Code finished without producing answer text or a tool call (${detail})`, "Command Code 返回了空响应(可能只有思考内容),重试通常可恢复");
|
|
5282
5941
|
}
|
|
5283
5942
|
/**
|
|
5284
5943
|
* Classify a pre-stream rejection that is about the ACCOUNT rather than about
|
|
@@ -5322,9 +5981,10 @@ const ACCOUNT_UNAVAILABLE_CODES = /* @__PURE__ */ new Set([
|
|
|
5322
5981
|
"PREMIUM_CREDITS_EXHAUSTED",
|
|
5323
5982
|
"MODEL_NOT_IN_PLAN"
|
|
5324
5983
|
]);
|
|
5325
|
-
function classifyAccountRejection(status, errText) {
|
|
5326
|
-
|
|
5327
|
-
|
|
5984
|
+
function classifyAccountRejection(status, errText, parsed = parseProviderError(errText)) {
|
|
5985
|
+
const code = (parsed.code ?? parsed.type)?.toUpperCase();
|
|
5986
|
+
if (status === 429 || code === "RATE_LIMITED") {
|
|
5987
|
+
const evidence = readWindowLimitEvidence(errText, parsed);
|
|
5328
5988
|
if (evidence.window === void 0) return { reason: "throttled" };
|
|
5329
5989
|
return evidence.resetAtMs === void 0 ? { reason: "rate-limit" } : {
|
|
5330
5990
|
reason: "rate-limit",
|
|
@@ -5332,23 +5992,11 @@ function classifyAccountRejection(status, errText) {
|
|
|
5332
5992
|
};
|
|
5333
5993
|
}
|
|
5334
5994
|
if (status === 401) return { reason: "invalid-credential" };
|
|
5335
|
-
const code = readProviderErrorCode(errText)?.toUpperCase();
|
|
5336
5995
|
if (code !== void 0 && ACCOUNT_UNAVAILABLE_CODES.has(code)) return { reason: "unavailable" };
|
|
5337
5996
|
if (status < 400 || status >= 500) return void 0;
|
|
5338
5997
|
const lower = errText.toLowerCase().replaceAll("_", " ");
|
|
5339
5998
|
if (lower.includes("insufficient credits") || lower.includes("premium credits exhausted") || lower.includes("model not in plan") || /insufficient (credit|balance)/.test(lower) || /out of credit/.test(lower) || /not (available|included)[^.]{0,40}plan/.test(lower) || lower.includes("upgrade your plan")) return { reason: "unavailable" };
|
|
5340
5999
|
}
|
|
5341
|
-
/** The machine-readable `error.code`/`error.type` of a pre-stream failure body. */
|
|
5342
|
-
function readProviderErrorCode(errText) {
|
|
5343
|
-
try {
|
|
5344
|
-
const parsed = JSON.parse(errText);
|
|
5345
|
-
if (!isRecord(parsed)) return void 0;
|
|
5346
|
-
const error = isRecord(parsed.error) ? parsed.error : parsed;
|
|
5347
|
-
return stringValue(error.code) ?? stringValue(error.type);
|
|
5348
|
-
} catch {
|
|
5349
|
-
return;
|
|
5350
|
-
}
|
|
5351
|
-
}
|
|
5352
6000
|
/**
|
|
5353
6001
|
* How far ahead a provider-published reset may be and still be trusted.
|
|
5354
6002
|
*
|
|
@@ -5376,20 +6024,10 @@ const MAX_TRUSTED_RESET_MS = 2592e6;
|
|
|
5376
6024
|
* message is honored as the fallback. Either can be present without the other;
|
|
5377
6025
|
* the window label is what decides the classification.
|
|
5378
6026
|
*/
|
|
5379
|
-
function readWindowLimitEvidence(errText) {
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
try {
|
|
5384
|
-
const parsed = JSON.parse(errText);
|
|
5385
|
-
if (isRecord(parsed)) {
|
|
5386
|
-
const error = isRecord(parsed.error) ? parsed.error : parsed;
|
|
5387
|
-
const rateLimit = isRecord(error.rateLimit) ? error.rateLimit : void 0;
|
|
5388
|
-
named = stringValue(rateLimit?.window);
|
|
5389
|
-
message = stringValue(error.message) ?? stringValue(parsed.message) ?? "";
|
|
5390
|
-
seconds = numberValue(rateLimit?.reset);
|
|
5391
|
-
}
|
|
5392
|
-
} catch {}
|
|
6027
|
+
function readWindowLimitEvidence(errText, parsed) {
|
|
6028
|
+
const named = stringValue(parsed.rateLimit?.window);
|
|
6029
|
+
const message = parsed.message ?? stringValue(parsed.root?.message) ?? "";
|
|
6030
|
+
const seconds = numberValue(parsed.rateLimit?.reset);
|
|
5393
6031
|
const window = named === "fiveHour" || named === "weekly" || named === "daily" ? named : /usage limit for your plan/i.test(message) || /usage limit for your plan/i.test(errText) ? /weekly/i.test(message) ? "weekly" : "fiveHour" : void 0;
|
|
5394
6032
|
const stamp = /resets at (\d{4}-\d{2}-\d{2}T[\d:.]+Z)/i.exec(message)?.[1] ?? /resets at (\d{4}-\d{2}-\d{2}T[\d:.]+Z)/i.exec(errText)?.[1];
|
|
5395
6033
|
const stamped = stamp === void 0 ? void 0 : Date.parse(stamp);
|
|
@@ -5407,24 +6045,15 @@ function readWindowLimitEvidence(errText) {
|
|
|
5407
6045
|
* other 4xx/5xx must surface as ordinary errors so real account/model
|
|
5408
6046
|
* problems are not masked.
|
|
5409
6047
|
*/
|
|
5410
|
-
function isUpgradeRequiredError(status, errText) {
|
|
6048
|
+
function isUpgradeRequiredError(status, errText, parsed = parseProviderError(errText)) {
|
|
5411
6049
|
if (status !== 403) return false;
|
|
5412
6050
|
const lower = errText.toLowerCase();
|
|
5413
6051
|
if (lower.includes("upgrade_required")) return true;
|
|
5414
6052
|
if (lower.includes("go plan") && lower.includes("api access")) return true;
|
|
5415
6053
|
if (lower.includes("only plan without api access")) return true;
|
|
5416
6054
|
if (lower.includes("upgrade to goat or higher")) return true;
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
if (isRecord(parsed)) {
|
|
5420
|
-
const error = isRecord(parsed.error) ? parsed.error : parsed;
|
|
5421
|
-
if ((stringValue(error.code) ?? stringValue(error.type))?.toLowerCase() === "upgrade_required") return true;
|
|
5422
|
-
const message = stringValue(error.message) ?? "";
|
|
5423
|
-
if (message.toLowerCase().includes("go plan") && message.toLowerCase().includes("api access")) return true;
|
|
5424
|
-
}
|
|
5425
|
-
} catch {
|
|
5426
|
-
return false;
|
|
5427
|
-
}
|
|
6055
|
+
if ((parsed.code ?? parsed.type)?.toLowerCase() === "upgrade_required") return true;
|
|
6056
|
+
if (parsed.message?.toLowerCase().includes("go plan") && parsed.message.toLowerCase().includes("api access")) return true;
|
|
5428
6057
|
return false;
|
|
5429
6058
|
}
|
|
5430
6059
|
/** Map an OpenAI Chat Completions usage payload to harness disjoint counts. */
|
|
@@ -5532,33 +6161,42 @@ function commandCopy(locale, key) {
|
|
|
5532
6161
|
return commandcodeCommand[locale][key] ?? commandcodeCommand.en[key] ?? key;
|
|
5533
6162
|
}
|
|
5534
6163
|
//#endregion
|
|
5535
|
-
//#region src/
|
|
5536
|
-
|
|
5537
|
-
|
|
5538
|
-
}
|
|
5539
|
-
/** Compact dollar amount (2 decimals), for the summary and window rows. */
|
|
5540
|
-
function moneyShort(value) {
|
|
6164
|
+
//#region src/display-format.ts
|
|
6165
|
+
/** Shared, dependency-free formatting for Host commands and browser surfaces. */
|
|
6166
|
+
function formatMoney(value) {
|
|
5541
6167
|
return `$${value.toFixed(2)}`;
|
|
5542
6168
|
}
|
|
5543
|
-
|
|
5544
|
-
|
|
6169
|
+
function formatMoneyExact(value) {
|
|
6170
|
+
return `$${value.toFixed(4)}`;
|
|
6171
|
+
}
|
|
6172
|
+
function formatTokensCompact(value) {
|
|
5545
6173
|
if (value >= 1e9) return `${(value / 1e9).toFixed(1)}B`;
|
|
5546
6174
|
if (value >= 1e6) return `${(value / 1e6).toFixed(1)}M`;
|
|
5547
6175
|
if (value >= 1e3) return `${(value / 1e3).toFixed(1)}K`;
|
|
5548
6176
|
return String(value);
|
|
5549
6177
|
}
|
|
5550
|
-
/**
|
|
5551
|
-
|
|
5552
|
-
* trailing zeros trimmed — mirrors `formatSuccessRate` in
|
|
5553
|
-
* `./client/usage.ts`, which the settings card uses.
|
|
5554
|
-
*/
|
|
5555
|
-
function successRateText(value) {
|
|
6178
|
+
/** The input is already in percent units; callers add the percent sign. */
|
|
6179
|
+
function formatSuccessRate(value) {
|
|
5556
6180
|
return String(Number(value.toFixed(2)));
|
|
5557
6181
|
}
|
|
6182
|
+
/** One window's fill ratio in [0, 1]; zero when uncapped. */
|
|
6183
|
+
function windowRatio(used, cap) {
|
|
6184
|
+
if (cap <= 0) return 0;
|
|
6185
|
+
return Math.max(0, Math.min(1, used / cap));
|
|
6186
|
+
}
|
|
6187
|
+
/** Local date-time, with the empty state chosen by the caller. */
|
|
6188
|
+
function formatResetAt(ms) {
|
|
6189
|
+
return ms <= 0 ? "" : new Date(ms).toLocaleString();
|
|
6190
|
+
}
|
|
6191
|
+
//#endregion
|
|
6192
|
+
//#region src/commands.ts
|
|
6193
|
+
const money = formatMoneyExact;
|
|
6194
|
+
const moneyShort = formatMoney;
|
|
6195
|
+
const tokensCompact = formatTokensCompact;
|
|
6196
|
+
const successRateText = formatSuccessRate;
|
|
5558
6197
|
/** A local date, or `n/a` when the provider published no reset time. */
|
|
5559
6198
|
function resetLabel(ms) {
|
|
5560
|
-
|
|
5561
|
-
return new Date(ms).toLocaleString();
|
|
6199
|
+
return formatResetAt(ms) || "n/a";
|
|
5562
6200
|
}
|
|
5563
6201
|
/**
|
|
5564
6202
|
* A 10-cell horizontal bar: `██████████` for 100%, `███░░░░░░░` for ~33%.
|
|
@@ -5566,7 +6204,7 @@ function resetLabel(ms) {
|
|
|
5566
6204
|
*/
|
|
5567
6205
|
function bar(used, cap) {
|
|
5568
6206
|
if (cap <= 0) return "—";
|
|
5569
|
-
const ratio =
|
|
6207
|
+
const ratio = windowRatio(used, cap);
|
|
5570
6208
|
const filled = Math.round(ratio * 10);
|
|
5571
6209
|
return "█".repeat(filled) + "░".repeat(10 - filled);
|
|
5572
6210
|
}
|
|
@@ -5849,7 +6487,7 @@ const MODELS_ENDPOINT = "commandcode/models";
|
|
|
5849
6487
|
* separate instance so catalog boundary errors name `commandcode/models`,
|
|
5850
6488
|
* not the report endpoint.
|
|
5851
6489
|
*/
|
|
5852
|
-
const { record: catalogRecord, stringField: catalogString } = makeBoundaryValidator("commandcode/models result:");
|
|
6490
|
+
const { record: catalogRecord, stringField: catalogString, numberField: catalogNumber } = makeBoundaryValidator("commandcode/models result:");
|
|
5853
6491
|
/** Parse one untrusted boundary value into a {@link CommandCodeCatalogModel}. */
|
|
5854
6492
|
function parseCatalogModel(value) {
|
|
5855
6493
|
const source = catalogRecord(value, "model");
|
|
@@ -5858,6 +6496,7 @@ function parseCatalogModel(value) {
|
|
|
5858
6496
|
name: catalogString(source, "name", "model.name")
|
|
5859
6497
|
};
|
|
5860
6498
|
if (source.tier !== void 0) model.tier = catalogString(source, "tier", "model.tier");
|
|
6499
|
+
if (source.allowance !== void 0) model.allowance = catalogNumber(source, "allowance", "model.allowance");
|
|
5861
6500
|
return model;
|
|
5862
6501
|
}
|
|
5863
6502
|
/** Parse the wire result into a {@link CommandCodeCatalog}. */
|
|
@@ -5999,12 +6638,17 @@ function loginDescriptor(endpoint, method) {
|
|
|
5999
6638
|
}]
|
|
6000
6639
|
} : descriptor;
|
|
6001
6640
|
}
|
|
6002
|
-
/** The
|
|
6003
|
-
const
|
|
6004
|
-
|
|
6005
|
-
|
|
6006
|
-
|
|
6007
|
-
|
|
6641
|
+
/** The Host-face contribution fragment registered on `ctx.typert`. */
|
|
6642
|
+
const LOGIN_HOST_CONTRIBUTION = {
|
|
6643
|
+
package: REMOTE_PACKAGE,
|
|
6644
|
+
face: "host",
|
|
6645
|
+
schemas: [],
|
|
6646
|
+
invocations: [
|
|
6647
|
+
loginDescriptor(LOGIN_BEGIN_ENDPOINT, "loginBegin"),
|
|
6648
|
+
loginDescriptor(LOGIN_STATUS_ENDPOINT, "loginStatus"),
|
|
6649
|
+
loginDescriptor(LOGIN_CANCEL_ENDPOINT, "loginCancel")
|
|
6650
|
+
]
|
|
6651
|
+
};
|
|
6008
6652
|
//#endregion
|
|
6009
6653
|
//#region src/usage-remote.ts
|
|
6010
6654
|
/**
|
|
@@ -6093,7 +6737,7 @@ function applyUsageRemote(ctx, deps) {
|
|
|
6093
6737
|
...USAGE_HOST_CONTRIBUTION.invocations,
|
|
6094
6738
|
MODELS_DESCRIPTOR,
|
|
6095
6739
|
PRICES_DESCRIPTOR,
|
|
6096
|
-
...
|
|
6740
|
+
...LOGIN_HOST_CONTRIBUTION.invocations
|
|
6097
6741
|
]
|
|
6098
6742
|
});
|
|
6099
6743
|
remoteCtx.effect(() => () => void unregister(), "dsh-commandcode-provider: usage remote");
|
|
@@ -6810,22 +7454,8 @@ var CommandCodeSearchProvider = class {
|
|
|
6810
7454
|
const ACTIVE_ACCOUNT_AUTO = "auto";
|
|
6811
7455
|
/** The selector value meaning "no language override — follow the shell locale". */
|
|
6812
7456
|
const LANG_AUTO = "auto";
|
|
6813
|
-
/** Tier display names, mirroring the web dropdown's headings (`model-select.ts`). */
|
|
6814
|
-
const TIER_TITLES = {
|
|
6815
|
-
go: "Go",
|
|
6816
|
-
goat: "GOAT",
|
|
6817
|
-
pro: "Pro",
|
|
6818
|
-
provider: "Provider",
|
|
6819
|
-
max: "Max"
|
|
6820
|
-
};
|
|
6821
7457
|
/** Tiers in picker order; a model outside this set joins the "Other" group. */
|
|
6822
|
-
const TIER_ORDER =
|
|
6823
|
-
"go",
|
|
6824
|
-
"goat",
|
|
6825
|
-
"pro",
|
|
6826
|
-
"provider",
|
|
6827
|
-
"max"
|
|
6828
|
-
];
|
|
7458
|
+
const TIER_ORDER = PLAN_TIER_ORDER;
|
|
6829
7459
|
/** The group holding models this build's catalog does not know. */
|
|
6830
7460
|
const OTHER_GROUP_ID = "models-other";
|
|
6831
7461
|
/**
|
|
@@ -6883,8 +7513,8 @@ function buildCommandCodeTuiSection(deps) {
|
|
|
6883
7513
|
const extras = [.../* @__PURE__ */ new Set([...stored, ...Object.keys(overrides)])].filter((id) => !known.has(id));
|
|
6884
7514
|
const tierGroups = TIER_ORDER.filter((tier) => choices.some((choice) => choice.tier === tier)).map((tier) => ({
|
|
6885
7515
|
id: `models-${tier}`,
|
|
6886
|
-
title: `${
|
|
6887
|
-
descriptions: { zh: `${
|
|
7516
|
+
title: `${PLAN_LABELS[tier] ?? tier} models`,
|
|
7517
|
+
descriptions: { zh: `${PLAN_LABELS[tier] ?? tier} 模型` }
|
|
6888
7518
|
}));
|
|
6889
7519
|
const unranked = choices.filter((choice) => tierRank(choice.tier) === TIER_ORDER.length);
|
|
6890
7520
|
const otherGroup = unranked.length > 0 || extras.length > 0 ? [{
|
|
@@ -6989,6 +7619,20 @@ function buildCommandCodeTuiSection(deps) {
|
|
|
6989
7619
|
value: text.trim() === "true"
|
|
6990
7620
|
})
|
|
6991
7621
|
},
|
|
7622
|
+
{
|
|
7623
|
+
path: ["offloadSeenImagesForCache"],
|
|
7624
|
+
group: "advanced",
|
|
7625
|
+
kind: "boolean",
|
|
7626
|
+
label: "Stop replaying images already seen",
|
|
7627
|
+
descriptions: { zh: "已看过的图片不再重复发送" },
|
|
7628
|
+
hint: "CLI transport only. After this model has answered, durably replace older images with text to keep later prompt text cacheable. Old pixels need a fresh file read or attachment to inspect again. Off by default.",
|
|
7629
|
+
hintDescriptions: { zh: "仅 CLI 传输。模型看过图片并回复后,永久用文字替代旧图,以保留后续文本缓存;重看旧图需要重新读取文件或附加。默认关闭。" },
|
|
7630
|
+
format: (value) => value === true ? "true" : "false",
|
|
7631
|
+
parse: (text) => ({
|
|
7632
|
+
kind: "set",
|
|
7633
|
+
value: text.trim() === "true"
|
|
7634
|
+
})
|
|
7635
|
+
},
|
|
6992
7636
|
{
|
|
6993
7637
|
path: ["filterModelsByPlan"],
|
|
6994
7638
|
group: "models",
|
|
@@ -7149,27 +7793,18 @@ function applyCommandCodeTuiSettings(ctx, deps) {
|
|
|
7149
7793
|
* A bounded retry budget for `TRANSPORT` failures (issue #39, second report).
|
|
7150
7794
|
*
|
|
7151
7795
|
* The route policy (`providerRetryPolicy` in `./adapter.ts`) is near-unbounded
|
|
7152
|
-
* on purpose — 1000 attempts doubling to a 15-minute cap —
|
|
7153
|
-
*
|
|
7154
|
-
*
|
|
7155
|
-
*
|
|
7156
|
-
*
|
|
7157
|
-
*
|
|
7158
|
-
*
|
|
7159
|
-
*
|
|
7160
|
-
* Not counted here: the pre-stream recovery the ADAPTER performs inside one
|
|
7161
|
-
* `stream()` call (account rotation, the 413 image-budget step-down, the
|
|
7162
|
-
* Provider-API → CLI switch) — invisible to the caller and bounded on its own.
|
|
7163
|
-
* This budget caps only the harness's retry loop.
|
|
7796
|
+
* on purpose — 1000 attempts doubling to a 15-minute cap — for failures the
|
|
7797
|
+
* provider ASKS to have retried. A connection that cannot be established is
|
|
7798
|
+
* not that: a blip the first few attempts absorb, or an outage no in-request
|
|
7799
|
+
* waiting fixes (attempt 11 alone already waits 512 s). So transport failures
|
|
7800
|
+
* get their own budget, cleared at each new STEP (one step = one model
|
|
7801
|
+
* request). `agent/status` → `idle` is NOT the reset (one `running` phase spans
|
|
7802
|
+
* a turn's steps), and neither is `assistant/attempt` (appended for the FAILED
|
|
7803
|
+
* attempt itself, which would clear the budget on every failure).
|
|
7164
7804
|
*
|
|
7165
|
-
*
|
|
7166
|
-
*
|
|
7167
|
-
*
|
|
7168
|
-
* one `running` phase, so a reset there would fail every later step of an outage
|
|
7169
|
-
* turn at once instead of retrying each. `assistant/attempt` is wrong the other
|
|
7170
|
-
* way — the loop appends it for the FAILED attempt itself, before dispatching
|
|
7171
|
-
* `agent/request-error`, so resetting on it would clear the budget on every
|
|
7172
|
-
* failure and restore the very loop this module exists to stop.
|
|
7805
|
+
* Not counted here: the adapter's in-`stream()` pre-stream recovery (account
|
|
7806
|
+
* rotation, the 413 step-down, the Provider-API → CLI switch) — invisible to
|
|
7807
|
+
* the caller and bounded on its own.
|
|
7173
7808
|
*
|
|
7174
7809
|
* @module
|
|
7175
7810
|
*/
|
|
@@ -7251,7 +7886,7 @@ function transportBudgetMessage(failureMessage, maxRetries) {
|
|
|
7251
7886
|
* Mark one schema field volatile.
|
|
7252
7887
|
*
|
|
7253
7888
|
* `.volatile()` is unconditional: it is a schemastery 3.18.3 feature and the
|
|
7254
|
-
* only supported engine (`dsh 0.
|
|
7889
|
+
* only supported engine (`dsh 0.2.0-rc.1`) pins `~3.18.4`, so the method is
|
|
7255
7890
|
* always present. It is reached through a structural member so the caller's
|
|
7256
7891
|
* declared field type survives — the mark changes what the schema PARSES to (a
|
|
7257
7892
|
* live reference), not the plugin-facing `Config` shape.
|
|
@@ -7348,6 +7983,7 @@ const Config = z.object(markVolatileFields({
|
|
|
7348
7983
|
modelsCachePath: z.string(),
|
|
7349
7984
|
requestTimeoutMs: z.number().min(1).max(MAX_TIMER_DELAY_MS),
|
|
7350
7985
|
streamIdleTimeoutMs: z.number().min(1).max(MAX_TIMER_DELAY_MS),
|
|
7986
|
+
offloadSeenImagesForCache: z.boolean().default(false),
|
|
7351
7987
|
transportMaxRetries: z.number().min(0).max(50),
|
|
7352
7988
|
filterModelsByPlan: z.boolean(),
|
|
7353
7989
|
visibleModels: z.array(z.string()),
|
|
@@ -7380,8 +8016,9 @@ function resolveAdapterOptions(config) {
|
|
|
7380
8016
|
apiBase: config.apiBase ?? "https://api.commandcode.ai",
|
|
7381
8017
|
workingDir: config.workingDir ?? process.cwd(),
|
|
7382
8018
|
modelsCachePath: config.modelsCachePath ?? DEFAULT_MODELS_CACHE_PATH,
|
|
7383
|
-
requestTimeoutMs: config.requestTimeoutMs ??
|
|
8019
|
+
requestTimeoutMs: config.requestTimeoutMs ?? 3e5,
|
|
7384
8020
|
streamIdleTimeoutMs: config.streamIdleTimeoutMs ?? 3e5,
|
|
8021
|
+
offloadSeenImagesForCache: config.offloadSeenImagesForCache === true,
|
|
7385
8022
|
filterModelsByPlan: config.filterModelsByPlan ?? true,
|
|
7386
8023
|
visibleModels: Array.isArray(config.visibleModels) ? config.visibleModels.filter((id) => typeof id === "string" && id !== "") : void 0,
|
|
7387
8024
|
modelVisibility: readModelVisibility(config.modelVisibility),
|
|
@@ -7555,12 +8192,16 @@ function apply(ctx, config) {
|
|
|
7555
8192
|
});
|
|
7556
8193
|
ctx.effect(() => () => loginFlow.dispose(), "dsh-commandcode-provider: login flow");
|
|
7557
8194
|
const catalogForEditors = async () => {
|
|
7558
|
-
|
|
8195
|
+
const models = await adapter.listModels(PROVIDER, { unfiltered: true });
|
|
8196
|
+
const bracket = await adapter.allowanceTier();
|
|
8197
|
+
return { models: models.map((model) => {
|
|
7559
8198
|
const tier = KNOWN_PLANS[model.id];
|
|
8199
|
+
const allowance = bracket === void 0 ? void 0 : modelAllowanceFor(model.id)?.[bracket];
|
|
7560
8200
|
return {
|
|
7561
8201
|
id: model.id,
|
|
7562
8202
|
name: model.name.replace(/\s*\(CC\)$/, ""),
|
|
7563
|
-
...tier === void 0 ? {} : { tier }
|
|
8203
|
+
...tier === void 0 ? {} : { tier },
|
|
8204
|
+
...allowance === void 0 ? {} : { allowance }
|
|
7564
8205
|
};
|
|
7565
8206
|
}) };
|
|
7566
8207
|
};
|