@mars-sea/dsh-commandcode-provider 0.14.5 → 0.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +59 -0
- package/README.md +1 -0
- package/README.zh-CN.md +1 -0
- package/lib/client.js +1952 -839
- package/lib/index.d.ts +62 -3
- package/lib/index.js +312 -91
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -123,13 +123,18 @@ const PLAN_ORDER = Object.fromEntries(PLAN_TIER_ORDER.map((tier, index) => [tier
|
|
|
123
123
|
* subscription-plan labels, deals, and hourly (peak/off-peak) pricing.
|
|
124
124
|
*
|
|
125
125
|
* Everything here is synced from official sources — the command-code CLI
|
|
126
|
-
* bundle's model table (`dist/cli.mjs`, re-verified at command-code@1.
|
|
126
|
+
* bundle's model table (`dist/cli.mjs`, re-verified at command-code@1.87.1), the
|
|
127
127
|
* live model catalog (`GET /provider/v1/models?view=client`, the authority for
|
|
128
128
|
* capability fields since 1.82.4), and the official plan/pricing/model docs; see
|
|
129
129
|
* the dsh-commandcode-upstream skill for the extraction procedures. The
|
|
130
|
-
* 2026-10-21 sync
|
|
130
|
+
* 2026-10-21 sync moved the CLI from 1.84.0 to 1.86.1 and picked up the Ornith
|
|
131
131
|
* 1.5 122B launch pair in the catalog, the ZDR exclusion-set prune and the
|
|
132
|
-
* MiniMax M3.1 Flash promo extension
|
|
132
|
+
* MiniMax M3.1 Flash promo extension; the follow-up check later that day moved
|
|
133
|
+
* the pinned constant to 1.86.3 (request header only). The 2026-10-22 check
|
|
134
|
+
* moves it to 1.87.1: the CLI bundle's registry, effort map, subscription plans
|
|
135
|
+
* and ZDR exception set are identical, and the live catalog's capability fields
|
|
136
|
+
* did not move either, so the only data added here is one output-limit row
|
|
137
|
+
* (`thinkingmachines/inkling-small`). Keeping the snapshot in its own module
|
|
133
138
|
* confines those frequent sync diffs here: src/adapter.ts holds only the stable
|
|
134
139
|
* wire/runtime logic. The read helpers live here too, for the same reason.
|
|
135
140
|
*
|
|
@@ -910,6 +915,7 @@ const MODEL_OUTPUT_TOKEN_LIMITS = /* @__PURE__ */ new Map([
|
|
|
910
915
|
["stepfun/Step-5-Preview", 65536],
|
|
911
916
|
["tencent/hy3-paid", 128e3],
|
|
912
917
|
["thinkingmachines/inkling", 65536],
|
|
918
|
+
["thinkingmachines/inkling-small", 65536],
|
|
913
919
|
["xai/grok-4.5", 5e5],
|
|
914
920
|
["xai/grok-4.6", 5e5],
|
|
915
921
|
["xai/grok-4.7", 5e5],
|
|
@@ -931,7 +937,7 @@ const MODEL_OUTPUT_TOKEN_LIMITS = /* @__PURE__ */ new Map([
|
|
|
931
937
|
* `/docs/plans/max` and `/docs/resources/pricing-limits`). Each plan's model
|
|
932
938
|
* list is a superset of the one below it: Go ⊂ GOAT ⊂ Pro ⊂ Provider/Max.
|
|
933
939
|
* Models absent from every plan list (Claude Opus/Fable, Fugu Ultra) are
|
|
934
|
-
* Provider-tier. Re-verified at command-code@1.
|
|
940
|
+
* Provider-tier. Re-verified at command-code@1.87.1 (2026-10-22) against the
|
|
935
941
|
* live `?view=client` catalog: 104 ids, still a strict superset chain. Mistral
|
|
936
942
|
* Large 4
|
|
937
943
|
* (1.75.0) was the only addition at the 1.77.0 check; the 1.78.0/1.79.0 pair
|
|
@@ -942,7 +948,8 @@ const MODEL_OUTPUT_TOKEN_LIMITS = /* @__PURE__ */ new Map([
|
|
|
942
948
|
* Super VL and the free Cat Jam preview at Go, and drops Ling 3.0 Flash Sante,
|
|
943
949
|
* which left the live catalog. Every other
|
|
944
950
|
* tier is unchanged. The 1.86.1 sync (2026-10-21) adds only the Ornith 1.5 122B
|
|
945
|
-
* launch pair at Go.
|
|
951
|
+
* launch pair at Go; the 1.87.1 check (2026-10-22) adds no model and moves no
|
|
952
|
+
* tier.
|
|
946
953
|
* The only removals since 1.49.0 are the two retired stealth previews (Pixel
|
|
947
954
|
* Canary at 1.73.1, Space Bunny Alpha at 1.74.3), Ling 3.0 Flash Sante
|
|
948
955
|
* (2026-10-16) and the retired Cat Jam preview (2026-10-20). Per-entry tags
|
|
@@ -1647,8 +1654,8 @@ const MODEL_PRICE_ROWS = [
|
|
|
1647
1654
|
],
|
|
1648
1655
|
allowance: {
|
|
1649
1656
|
go: 10,
|
|
1650
|
-
goat:
|
|
1651
|
-
pro:
|
|
1657
|
+
goat: 80,
|
|
1658
|
+
pro: 90
|
|
1652
1659
|
}
|
|
1653
1660
|
},
|
|
1654
1661
|
{
|
|
@@ -2437,9 +2444,9 @@ const MODEL_PRICE_ROWS = [
|
|
|
2437
2444
|
.14
|
|
2438
2445
|
],
|
|
2439
2446
|
allowance: {
|
|
2440
|
-
go:
|
|
2441
|
-
goat:
|
|
2442
|
-
pro:
|
|
2447
|
+
go: 6,
|
|
2448
|
+
goat: 20,
|
|
2449
|
+
pro: 30
|
|
2443
2450
|
}
|
|
2444
2451
|
},
|
|
2445
2452
|
{
|
|
@@ -3032,10 +3039,10 @@ function recordOrEmpty(value) {
|
|
|
3032
3039
|
}
|
|
3033
3040
|
//#endregion
|
|
3034
3041
|
//#region src/cost-projection.ts
|
|
3035
|
-
const record$
|
|
3042
|
+
const record$4 = (v) => isRecord(v) ? v : void 0;
|
|
3036
3043
|
const finite = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0;
|
|
3037
3044
|
function tokensOf$1(value) {
|
|
3038
|
-
const u = record$
|
|
3045
|
+
const u = record$4(value);
|
|
3039
3046
|
if (!u || !finite(u.inputTokens) || !finite(u.outputTokens)) return void 0;
|
|
3040
3047
|
if (u.cacheReadTokens !== void 0 && !finite(u.cacheReadTokens) || u.cacheWriteTokens !== void 0 && !finite(u.cacheWriteTokens)) return void 0;
|
|
3041
3048
|
return {
|
|
@@ -3047,14 +3054,14 @@ function tokensOf$1(value) {
|
|
|
3047
3054
|
}
|
|
3048
3055
|
/** The four buckets of an already-parsed record, or undefined when one is missing. */
|
|
3049
3056
|
function costTokensOf(value) {
|
|
3050
|
-
const t = record$
|
|
3057
|
+
const t = record$4(value);
|
|
3051
3058
|
if (!t || !COST_TOKEN_KEYS.every((k) => finite(t[k]))) return void 0;
|
|
3052
3059
|
return t;
|
|
3053
3060
|
}
|
|
3054
3061
|
/** Current v2 settlements store the last usage in their stream; v1 stores chunk events. */
|
|
3055
3062
|
function usageOf(type, data) {
|
|
3056
3063
|
if (type === "assistant/chunk") {
|
|
3057
|
-
const chunk = record$
|
|
3064
|
+
const chunk = record$4(data.chunk);
|
|
3058
3065
|
return chunk?.type === "usage" ? tokensOf$1(chunk.usage) : void 0;
|
|
3059
3066
|
}
|
|
3060
3067
|
if (type !== "assistant/message" && type !== "assistant/attempt") return void 0;
|
|
@@ -3062,19 +3069,19 @@ function usageOf(type, data) {
|
|
|
3062
3069
|
if (direct !== void 0) return direct;
|
|
3063
3070
|
if (!Array.isArray(data.stream)) return void 0;
|
|
3064
3071
|
for (let i = data.stream.length - 1; i >= 0; i--) {
|
|
3065
|
-
const item = record$
|
|
3066
|
-
const chunk = item?.type === "chunk" ? record$
|
|
3072
|
+
const item = record$4(data.stream[i]);
|
|
3073
|
+
const chunk = item?.type === "chunk" ? record$4(item.chunk) : void 0;
|
|
3067
3074
|
if (chunk?.type === "usage") return tokensOf$1(chunk.usage);
|
|
3068
3075
|
}
|
|
3069
3076
|
}
|
|
3070
3077
|
function parseState(value) {
|
|
3071
|
-
const s = record$
|
|
3078
|
+
const s = record$4(value), facts = record$4(s?.facts);
|
|
3072
3079
|
if (!s || !facts || !finite(facts.pricingKey) || !Array.isArray(facts.groups)) throw new TypeError("invalid commandCodeCost state");
|
|
3073
3080
|
const group = (v) => {
|
|
3074
|
-
const g = record$
|
|
3081
|
+
const g = record$4(v);
|
|
3075
3082
|
return !!g && typeof g.provider === "string" && typeof g.model === "string" && (g.at === null || finite(g.at)) && finite(g.contextTokens) && costTokensOf(g.tokens) !== void 0;
|
|
3076
3083
|
};
|
|
3077
|
-
const sel = record$
|
|
3084
|
+
const sel = record$4(s.selection), last = record$4(s.last);
|
|
3078
3085
|
const summaryTokens = facts.summaryTokens === void 0 ? zeroCostTokens() : costTokensOf(facts.summaryTokens);
|
|
3079
3086
|
if (summaryTokens === void 0 || !(s.selection === null || sel && typeof sel.provider === "string" && typeof sel.model === "string") || !(s.at === null || finite(s.at)) || !facts.groups.every(group) || !(s.last === null || last && finite(last.turn) && finite(last.step) && group(last.group))) throw new TypeError("invalid commandCodeCost state");
|
|
3080
3087
|
return {
|
|
@@ -3131,10 +3138,10 @@ function createCostProjection(table = modelPriceTable()) {
|
|
|
3131
3138
|
}
|
|
3132
3139
|
}),
|
|
3133
3140
|
apply(state, event) {
|
|
3134
|
-
const data = record$
|
|
3141
|
+
const data = record$4(event.data);
|
|
3135
3142
|
if (!data) return state;
|
|
3136
3143
|
if (event.type === "request/header") {
|
|
3137
|
-
const config = record$
|
|
3144
|
+
const config = record$4(record$4(data.header)?.config);
|
|
3138
3145
|
return {
|
|
3139
3146
|
...state,
|
|
3140
3147
|
selection: config && typeof config.provider === "string" && typeof config.model === "string" ? {
|
|
@@ -3226,10 +3233,10 @@ function installCostProjection(ctx) {
|
|
|
3226
3233
|
//#endregion
|
|
3227
3234
|
//#region src/analytics.ts
|
|
3228
3235
|
/** 在宿主内折叠原始历史;每次请求保留计价时间和上下文,禁止使用已合并的会话成本桶。 */
|
|
3229
|
-
const record$
|
|
3236
|
+
const record$3 = (v) => isRecord(v) ? v : void 0;
|
|
3230
3237
|
const number = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0;
|
|
3231
3238
|
function tokensOf(v) {
|
|
3232
|
-
const u = record$
|
|
3239
|
+
const u = record$3(v);
|
|
3233
3240
|
if (!u || !number(u.inputTokens) || !number(u.outputTokens)) return void 0;
|
|
3234
3241
|
if (u.cacheReadTokens !== void 0 && !number(u.cacheReadTokens) || u.cacheWriteTokens !== void 0 && !number(u.cacheWriteTokens)) return void 0;
|
|
3235
3242
|
return {
|
|
@@ -3259,10 +3266,10 @@ function foldRequests(session, log) {
|
|
|
3259
3266
|
if (listed) facts.push(current);
|
|
3260
3267
|
};
|
|
3261
3268
|
for (const event of log.events) {
|
|
3262
|
-
const d = record$
|
|
3269
|
+
const d = record$3(event.data);
|
|
3263
3270
|
if (!d) continue;
|
|
3264
3271
|
if (event.type === "request/header") {
|
|
3265
|
-
const config = record$
|
|
3272
|
+
const config = record$3(record$3(d.header)?.config);
|
|
3266
3273
|
provider = typeof config?.provider === "string" ? config.provider : "";
|
|
3267
3274
|
model = typeof config?.model === "string" ? config.model : "";
|
|
3268
3275
|
if (current && current.outcome === "unknown") {
|
|
@@ -3306,7 +3313,7 @@ function foldRequests(session, log) {
|
|
|
3306
3313
|
}
|
|
3307
3314
|
if (!current) continue;
|
|
3308
3315
|
if (event.type === "turn/end" && d.turn === turn) {
|
|
3309
|
-
const reason = record$
|
|
3316
|
+
const reason = record$3(d.reason)?.kind;
|
|
3310
3317
|
if (current.outcome === "unknown") {
|
|
3311
3318
|
if (reason === "error") current.outcome = "failed";
|
|
3312
3319
|
else if (reason === "aborted") current.outcome = "cancelled";
|
|
@@ -3324,11 +3331,11 @@ function foldRequests(session, log) {
|
|
|
3324
3331
|
"assistant/attempt",
|
|
3325
3332
|
"assistant/chunk"
|
|
3326
3333
|
].includes(event.type)) continue;
|
|
3327
|
-
const chunk = record$
|
|
3334
|
+
const chunk = record$3(d.chunk);
|
|
3328
3335
|
let tokens = tokensOf(d.usage);
|
|
3329
3336
|
if (event.type === "assistant/chunk" && chunk?.type === "usage") tokens = tokensOf(chunk.usage);
|
|
3330
3337
|
if (tokens === void 0 && Array.isArray(d.stream)) for (let i = d.stream.length - 1; i >= 0; i--) {
|
|
3331
|
-
const item = record$
|
|
3338
|
+
const item = record$3(d.stream[i]), c = item?.type === "chunk" ? record$3(item.chunk) : void 0;
|
|
3332
3339
|
if (c?.type === "usage") {
|
|
3333
3340
|
tokens = tokensOf(c.usage);
|
|
3334
3341
|
if (tokens) break;
|
|
@@ -3981,6 +3988,29 @@ const RETRY_MAX_DELAY_MS = 9e5;
|
|
|
3981
3988
|
* once per interval per key bounds a probe endpoint that keeps failing.
|
|
3982
3989
|
*/
|
|
3983
3990
|
const EXPLICIT_ACCOUNT_PROBE_INTERVAL_MS = 6e4;
|
|
3991
|
+
/**
|
|
3992
|
+
* How long a model-scoped `unavailable` rejection demotes an account.
|
|
3993
|
+
*
|
|
3994
|
+
* `unavailable` (no credits, or a model outside this account's plan) is a fact
|
|
3995
|
+
* about the balance or the model, NOT about the key — a credits-empty account
|
|
3996
|
+
* still serves `:free` models, which is why it must never become a permanent
|
|
3997
|
+
* account mark. But not remembering it at all made EVERY later request pick
|
|
3998
|
+
* the same doomed account first: one wasted round trip per step, and in a pool
|
|
3999
|
+
* where that account was the fallback, the user's error became "insufficient
|
|
4000
|
+
* credits" from an account they never chose (issue #51's remaining half,
|
|
4001
|
+
* live-probed 2026-10-21: a $0.0874 balance rejects every paid model with
|
|
4002
|
+
* `400 insufficient credits` while free models still answer 200).
|
|
4003
|
+
*
|
|
4004
|
+
* So the mark is a PREFERENCE, not a ban, and it is scoped per model to keep
|
|
4005
|
+
* the free-model escape hatch intact. Five minutes is long enough to stop a
|
|
4006
|
+
* single turn's steps from re-hitting the same rejection and short enough that
|
|
4007
|
+
* a top-up takes effect without a restart.
|
|
4008
|
+
*/
|
|
4009
|
+
const MODEL_UNAVAILABLE_TTL_MS = 3e5;
|
|
4010
|
+
/** One model-scoped soft mark's Map key (NUL keeps the two halves unambiguous). */
|
|
4011
|
+
function modelUnavailableKey(apiKey, model) {
|
|
4012
|
+
return `${apiKey}\u0000${model}`;
|
|
4013
|
+
}
|
|
3984
4014
|
/** A labeled, human-readable clock reading for error messages. */
|
|
3985
4015
|
function clockLabel(ms) {
|
|
3986
4016
|
return new Date(ms).toLocaleString();
|
|
@@ -4051,6 +4081,11 @@ var CommandCodeAccountPool = class CommandCodeAccountPool {
|
|
|
4051
4081
|
states = /* @__PURE__ */ new Map();
|
|
4052
4082
|
/** 当前网关与密钥的显式探测时间,限制失败探测的频率。 */
|
|
4053
4083
|
explicitProbes = /* @__PURE__ */ new Map();
|
|
4084
|
+
/**
|
|
4085
|
+
* 模型级 `unavailable` 软标记:`key\0model` → 过期时刻(毫秒)。
|
|
4086
|
+
* 只影响选择顺序,从不参与 {@link accountUsable},所以免费模型仍可用。
|
|
4087
|
+
*/
|
|
4088
|
+
modelUnavailable = /* @__PURE__ */ new Map();
|
|
4054
4089
|
scopedStates = /* @__PURE__ */ new Map();
|
|
4055
4090
|
constructor(deps) {
|
|
4056
4091
|
this.deps = deps;
|
|
@@ -4062,7 +4097,8 @@ var CommandCodeAccountPool = class CommandCodeAccountPool {
|
|
|
4062
4097
|
if (shared === void 0) {
|
|
4063
4098
|
shared = {
|
|
4064
4099
|
states: /* @__PURE__ */ new Map(),
|
|
4065
|
-
probes: /* @__PURE__ */ new Map()
|
|
4100
|
+
probes: /* @__PURE__ */ new Map(),
|
|
4101
|
+
modelUnavailable: /* @__PURE__ */ new Map()
|
|
4066
4102
|
};
|
|
4067
4103
|
this.scopedStates.set(context.apiBase, shared);
|
|
4068
4104
|
}
|
|
@@ -4075,6 +4111,7 @@ var CommandCodeAccountPool = class CommandCodeAccountPool {
|
|
|
4075
4111
|
});
|
|
4076
4112
|
scoped.states = shared.states;
|
|
4077
4113
|
scoped.explicitProbes = shared.probes;
|
|
4114
|
+
scoped.modelUnavailable = shared.modelUnavailable;
|
|
4078
4115
|
scoped.scopedStates = this.scopedStates;
|
|
4079
4116
|
return scoped;
|
|
4080
4117
|
}
|
|
@@ -4141,11 +4178,13 @@ var CommandCodeAccountPool = class CommandCodeAccountPool {
|
|
|
4141
4178
|
if (selectActiveAccount(accounts, preferred) !== void 0) return void 0;
|
|
4142
4179
|
throw allAccountsUnusable(accounts);
|
|
4143
4180
|
}
|
|
4144
|
-
const
|
|
4181
|
+
const model = options?.model ?? "";
|
|
4182
|
+
const serveable = this.withoutModelUnavailable(available, model);
|
|
4183
|
+
const routed = selectAccountForModel(serveable, model, this.deps.modelAccountRules?.());
|
|
4145
4184
|
if (routed !== void 0) return this.pick(routed);
|
|
4146
|
-
const chosen = selectActiveAccount(
|
|
4185
|
+
const chosen = selectActiveAccount(serveable, preferred);
|
|
4147
4186
|
if (chosen !== void 0) {
|
|
4148
|
-
const revived = await this.reviveExplicit(available,
|
|
4187
|
+
const revived = await this.reviveExplicit(available, model, preferred, chosen);
|
|
4149
4188
|
return this.pick(revived ?? chosen);
|
|
4150
4189
|
}
|
|
4151
4190
|
await this.probeAllMarked(available);
|
|
@@ -4198,13 +4237,15 @@ var CommandCodeAccountPool = class CommandCodeAccountPool {
|
|
|
4198
4237
|
* keeps its window unknown on purpose, so a probe can still find out.
|
|
4199
4238
|
*/
|
|
4200
4239
|
markRejected(apiKey, rejection, resetAtMs) {
|
|
4201
|
-
if (rejection === "invalid-credential")
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4240
|
+
if (rejection === "invalid-credential") {
|
|
4241
|
+
this.states.set(apiKey, {
|
|
4242
|
+
kind: "disabled",
|
|
4243
|
+
cause: "auth",
|
|
4244
|
+
reason: "invalid API key (401)",
|
|
4245
|
+
until: 0
|
|
4246
|
+
});
|
|
4247
|
+
this.clearModelUnavailable(apiKey);
|
|
4248
|
+
} else if (rejection === "throttled") this.states.set(apiKey, {
|
|
4208
4249
|
kind: "unknown",
|
|
4209
4250
|
cause: "throttle",
|
|
4210
4251
|
reason: "rate limited (429)",
|
|
@@ -4224,6 +4265,46 @@ var CommandCodeAccountPool = class CommandCodeAccountPool {
|
|
|
4224
4265
|
});
|
|
4225
4266
|
}
|
|
4226
4267
|
/**
|
|
4268
|
+
* Record a model-scoped `unavailable` rejection (no credits, or a model
|
|
4269
|
+
* outside this account's plan) as a SHORT-LIVED PREFERENCE demotion.
|
|
4270
|
+
*
|
|
4271
|
+
* 职责:让同一模型的后续请求先跳过这个账号,而不是每一步都重撞一次
|
|
4272
|
+
* `400 insufficient credits`;同时刻意不写进 `states`,所以
|
|
4273
|
+
* {@link accountUsable} 与 {@link activeAccount} 都不受影响——余额为空的
|
|
4274
|
+
* 账号仍然能服务 `:free` 模型,账号卡片也不会把它标成"不可用"。
|
|
4275
|
+
*
|
|
4276
|
+
* 为什么按时限而非永久:余额/套餐是可以不换密钥就改变的事实(充值、升级),
|
|
4277
|
+
* 而进程内没有任何事件能通知这改变,所以只能靠时钟自行恢复。
|
|
4278
|
+
*/
|
|
4279
|
+
markModelUnavailable(apiKey, model) {
|
|
4280
|
+
if (model === "") return;
|
|
4281
|
+
const now = this.now();
|
|
4282
|
+
this.modelUnavailable.set(modelUnavailableKey(apiKey, model), now + MODEL_UNAVAILABLE_TTL_MS);
|
|
4283
|
+
for (const [key, until] of this.modelUnavailable) if (until <= now) this.modelUnavailable.delete(key);
|
|
4284
|
+
}
|
|
4285
|
+
/** Drop every model-scoped mark for one key (a new credential invalidates them). */
|
|
4286
|
+
clearModelUnavailable(apiKey) {
|
|
4287
|
+
const prefix = `${apiKey}\u0000`;
|
|
4288
|
+
for (const key of this.modelUnavailable.keys()) if (key.startsWith(prefix)) this.modelUnavailable.delete(key);
|
|
4289
|
+
}
|
|
4290
|
+
/**
|
|
4291
|
+
* The candidates for THIS model, in preference order: accounts without a live
|
|
4292
|
+
* model-scoped mark first, everything else after them.
|
|
4293
|
+
*
|
|
4294
|
+
* 为什么要"排后"而不是"剔除":软标记只是偏好(见
|
|
4295
|
+
* {@link markModelUnavailable})。全部候选都被标记时必须退回全量,否则唯一
|
|
4296
|
+
* 账号的池子会变成"没有可用账号",把一次余额不足误报成额度耗尽。
|
|
4297
|
+
*/
|
|
4298
|
+
withoutModelUnavailable(accounts, model) {
|
|
4299
|
+
if (model === "") return accounts;
|
|
4300
|
+
const now = this.now();
|
|
4301
|
+
const healthy = accounts.filter((account) => {
|
|
4302
|
+
const until = this.modelUnavailable.get(modelUnavailableKey(account.key, model));
|
|
4303
|
+
return until === void 0 || until <= now;
|
|
4304
|
+
});
|
|
4305
|
+
return healthy.length > 0 ? healthy : accounts;
|
|
4306
|
+
}
|
|
4307
|
+
/**
|
|
4227
4308
|
* One account's key: literal → credential seam → auth file (default slot).
|
|
4228
4309
|
*
|
|
4229
4310
|
* Every source is normalized here, at the single point where a slot's key
|
|
@@ -4495,6 +4576,18 @@ function parseOutputCeiling(detail) {
|
|
|
4495
4576
|
return Number.isSafeInteger(ceiling) && ceiling > 0 ? ceiling : void 0;
|
|
4496
4577
|
}
|
|
4497
4578
|
/**
|
|
4579
|
+
* The gateway's cap on the request's JSON STRUCTURE: `JSON node count
|
|
4580
|
+
* cannot exceed 32768` (issue #96). A structural limit, not a token one —
|
|
4581
|
+
* a session heavy in messages, tool calls and images trips it while well
|
|
4582
|
+
* inside the context window, so it needs its own wording: reported as the
|
|
4583
|
+
* generic refusal, the one sentence naming the actual cap was dropped and
|
|
4584
|
+
* the user had no lever at all; reported as a context overflow, the
|
|
4585
|
+
* harness would compact tokens and resend a structure that still exceeds
|
|
4586
|
+
* the cap. The stated limit is captured because the gateway owns the
|
|
4587
|
+
* number and it can move.
|
|
4588
|
+
*/
|
|
4589
|
+
const JSON_NODE_LIMIT_REJECTION = /JSON node count cannot exceed\s+(\d+)/i;
|
|
4590
|
+
/**
|
|
4498
4591
|
* Whether one rejection says "ZDR requested, no ZDR-capable upstream": the
|
|
4499
4592
|
* provider spells the code `cmd_zdr_no_providers` in JSON error bodies and
|
|
4500
4593
|
* `CMD_ZDR_NO_PROVIDERS` in plain-text ones (the official CLI's own
|
|
@@ -4635,6 +4728,8 @@ function generateHttpError(status, errText, retryAfterMs, parsed = parseProvider
|
|
|
4635
4728
|
return bilingual("PROVIDER_HTTP_ERROR", `Command Code API error ${status}: this request's max_tokens is not valid for this model` + (stated ? ` (${stated})` : "") + " — the session is not oversized, so compacting it will not help; lower \"Max output tokens\" or pick a model with a larger output ceiling", `Command Code API 返回 ${status}:本次请求的 max_tokens 对该模型不合法` + (stated ? `(${stated})` : "") + "——会话内容并未超长,压缩上下文无效;请调低「最大输出 token」或改用输出上限更大的模型", { status });
|
|
4636
4729
|
}
|
|
4637
4730
|
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 });
|
|
4731
|
+
const nodeLimit = status < 500 ? JSON_NODE_LIMIT_REJECTION.exec(overflowDetail)?.[1] : void 0;
|
|
4732
|
+
if (nodeLimit !== void 0) return bilingual("PROVIDER_HTTP_ERROR", `Command Code API error ${status}: the request exceeds the gateway's JSON node limit (node count cannot exceed ${nodeLimit}) — the cap is on the request's structure, not its token length, so compacting the session usually will not be enough; start a new session, or reduce the messages and images in this one`, `Command Code API 返回 ${status}:请求超过网关的 JSON 节点数上限(节点数不能超过 ${nodeLimit})——限制的是请求结构,不是 token 长度,压缩上下文通常不足以降到该上限以内;请新建会话,或减少本会话中的消息与图片`, { status });
|
|
4638
4733
|
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 });
|
|
4639
4734
|
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 });
|
|
4640
4735
|
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 });
|
|
@@ -6393,7 +6488,7 @@ var GatewayFacts = class {
|
|
|
6393
6488
|
* per-request options thunk and an API-key resolver from the plugin entry
|
|
6394
6489
|
* (src/index.ts), so a settings change reaches the very next request.
|
|
6395
6490
|
*/
|
|
6396
|
-
const COMMAND_CODE_CLI_VERSION = "1.
|
|
6491
|
+
const COMMAND_CODE_CLI_VERSION = "1.87.1";
|
|
6397
6492
|
const DEFAULT_API_BASE = "https://api.commandcode.ai";
|
|
6398
6493
|
/**
|
|
6399
6494
|
* The output budget this bundle asks for, and the ceiling it will never exceed.
|
|
@@ -8996,7 +9091,7 @@ const USAGE_REPORT_ENDPOINT = "commandcode/report";
|
|
|
8996
9091
|
* The shared read/validate helpers for the usage report endpoint, prefixed
|
|
8997
9092
|
* so rejection messages name the offending boundary.
|
|
8998
9093
|
*/
|
|
8999
|
-
const { reject: reject$
|
|
9094
|
+
const { reject: reject$2, record: record$2, stringField: stringField$2, numberField: numberField$1, booleanField } = makeBoundaryValidator("commandcode/report result:");
|
|
9000
9095
|
/**
|
|
9001
9096
|
* Parse one quota window, or undefined when the frame carries no such window.
|
|
9002
9097
|
*
|
|
@@ -9007,12 +9102,12 @@ const { reject: reject$1, record: record$1, stringField: stringField$1, numberFi
|
|
|
9007
9102
|
*/
|
|
9008
9103
|
function windowLimit(value, field) {
|
|
9009
9104
|
if (value === void 0) return void 0;
|
|
9010
|
-
const source = record$
|
|
9105
|
+
const source = record$2(value, field);
|
|
9011
9106
|
return {
|
|
9012
|
-
used: numberField(source, "used", `${field}.used`),
|
|
9013
|
-
cap: numberField(source, "cap", `${field}.cap`),
|
|
9107
|
+
used: numberField$1(source, "used", `${field}.used`),
|
|
9108
|
+
cap: numberField$1(source, "cap", `${field}.cap`),
|
|
9014
9109
|
exceeded: booleanField(source, "exceeded", `${field}.exceeded`),
|
|
9015
|
-
resetAt: numberField(source, "resetAt", `${field}.resetAt`)
|
|
9110
|
+
resetAt: numberField$1(source, "resetAt", `${field}.resetAt`)
|
|
9016
9111
|
};
|
|
9017
9112
|
}
|
|
9018
9113
|
/**
|
|
@@ -9021,44 +9116,44 @@ function windowLimit(value, field) {
|
|
|
9021
9116
|
* malformed frame fails the boundary instead of rendering garbage.
|
|
9022
9117
|
*/
|
|
9023
9118
|
function parseUsageReport(value) {
|
|
9024
|
-
const source = record$
|
|
9119
|
+
const source = record$2(value, "report");
|
|
9025
9120
|
const failures = source.failures;
|
|
9026
|
-
if (!Array.isArray(failures) || failures.some((entry) => typeof entry !== "string")) reject$
|
|
9121
|
+
if (!Array.isArray(failures) || failures.some((entry) => typeof entry !== "string")) reject$2("failures");
|
|
9027
9122
|
const report = { failures };
|
|
9028
9123
|
if (source.blocked !== void 0) {
|
|
9029
9124
|
const blocked = source.blocked;
|
|
9030
9125
|
if (blocked === "invalid-key" || blocked === "service-unavailable" || blocked === "invalid-response" || blocked === "network") report.blocked = blocked;
|
|
9031
|
-
else reject$
|
|
9126
|
+
else reject$2("blocked");
|
|
9032
9127
|
}
|
|
9033
9128
|
if (source.account !== void 0) {
|
|
9034
|
-
const account = record$
|
|
9129
|
+
const account = record$2(source.account, "account");
|
|
9035
9130
|
report.account = {
|
|
9036
|
-
id: stringField$
|
|
9037
|
-
name: stringField$
|
|
9038
|
-
userName: stringField$
|
|
9131
|
+
id: stringField$2(account, "id", "account.id"),
|
|
9132
|
+
name: stringField$2(account, "name", "account.name"),
|
|
9133
|
+
userName: stringField$2(account, "userName", "account.userName")
|
|
9039
9134
|
};
|
|
9040
9135
|
}
|
|
9041
9136
|
if (source.usage !== void 0) {
|
|
9042
|
-
const usage = record$
|
|
9137
|
+
const usage = record$2(source.usage, "usage");
|
|
9043
9138
|
report.usage = {
|
|
9044
|
-
totalCount: numberField(usage, "totalCount", "usage.totalCount"),
|
|
9045
|
-
totalCost: numberField(usage, "totalCost", "usage.totalCost"),
|
|
9046
|
-
successRate: numberField(usage, "successRate", "usage.successRate"),
|
|
9047
|
-
completedCount: numberField(usage, "completedCount", "usage.completedCount"),
|
|
9048
|
-
failedCount: numberField(usage, "failedCount", "usage.failedCount"),
|
|
9049
|
-
totalTokensIn: numberField(usage, "totalTokensIn", "usage.totalTokensIn"),
|
|
9050
|
-
totalTokensOut: numberField(usage, "totalTokensOut", "usage.totalTokensOut"),
|
|
9051
|
-
totalCredits: numberField(usage, "totalCredits", "usage.totalCredits"),
|
|
9052
|
-
periodBasis: stringField$
|
|
9139
|
+
totalCount: numberField$1(usage, "totalCount", "usage.totalCount"),
|
|
9140
|
+
totalCost: numberField$1(usage, "totalCost", "usage.totalCost"),
|
|
9141
|
+
successRate: numberField$1(usage, "successRate", "usage.successRate"),
|
|
9142
|
+
completedCount: numberField$1(usage, "completedCount", "usage.completedCount"),
|
|
9143
|
+
failedCount: numberField$1(usage, "failedCount", "usage.failedCount"),
|
|
9144
|
+
totalTokensIn: numberField$1(usage, "totalTokensIn", "usage.totalTokensIn"),
|
|
9145
|
+
totalTokensOut: numberField$1(usage, "totalTokensOut", "usage.totalTokensOut"),
|
|
9146
|
+
totalCredits: numberField$1(usage, "totalCredits", "usage.totalCredits"),
|
|
9147
|
+
periodBasis: stringField$2(usage, "periodBasis", "usage.periodBasis")
|
|
9053
9148
|
};
|
|
9054
|
-
if (usage.periodStart !== void 0) report.usage.periodStart = numberField(usage, "periodStart", "usage.periodStart");
|
|
9149
|
+
if (usage.periodStart !== void 0) report.usage.periodStart = numberField$1(usage, "periodStart", "usage.periodStart");
|
|
9055
9150
|
}
|
|
9056
9151
|
if (source.credits !== void 0) {
|
|
9057
|
-
const credits = record$
|
|
9152
|
+
const credits = record$2(source.credits, "credits");
|
|
9058
9153
|
const parsed = {
|
|
9059
|
-
monthlyCredits: numberField(credits, "monthlyCredits", "credits.monthlyCredits"),
|
|
9060
|
-
purchasedCredits: numberField(credits, "purchasedCredits", "credits.purchasedCredits"),
|
|
9061
|
-
freeCredits: numberField(credits, "freeCredits", "credits.freeCredits")
|
|
9154
|
+
monthlyCredits: numberField$1(credits, "monthlyCredits", "credits.monthlyCredits"),
|
|
9155
|
+
purchasedCredits: numberField$1(credits, "purchasedCredits", "credits.purchasedCredits"),
|
|
9156
|
+
freeCredits: numberField$1(credits, "freeCredits", "credits.freeCredits")
|
|
9062
9157
|
};
|
|
9063
9158
|
if (credits.monthlyReported !== void 0) parsed.monthlyReported = booleanField(credits, "monthlyReported", "credits.monthlyReported");
|
|
9064
9159
|
for (const flag of ["purchasedReported", "freeReported"]) if (credits[flag] !== void 0) parsed[flag] = booleanField(credits, flag, `credits.${flag}`);
|
|
@@ -9069,38 +9164,38 @@ function parseUsageReport(value) {
|
|
|
9069
9164
|
report.credits = parsed;
|
|
9070
9165
|
}
|
|
9071
9166
|
if (source.plan !== void 0) {
|
|
9072
|
-
const plan = record$
|
|
9167
|
+
const plan = record$2(source.plan, "plan");
|
|
9073
9168
|
const monthly = plan.monthlyCredits;
|
|
9074
|
-
if (monthly !== null && (typeof monthly !== "number" || !Number.isFinite(monthly))) reject$
|
|
9169
|
+
if (monthly !== null && (typeof monthly !== "number" || !Number.isFinite(monthly))) reject$2("plan.monthlyCredits");
|
|
9075
9170
|
report.plan = {
|
|
9076
|
-
planId: stringField$
|
|
9077
|
-
name: stringField$
|
|
9078
|
-
status: stringField$
|
|
9171
|
+
planId: stringField$2(plan, "planId", "plan.planId"),
|
|
9172
|
+
name: stringField$2(plan, "name", "plan.name"),
|
|
9173
|
+
status: stringField$2(plan, "status", "plan.status"),
|
|
9079
9174
|
monthlyCredits: monthly,
|
|
9080
|
-
currentPeriodEnd: numberField(plan, "currentPeriodEnd", "plan.currentPeriodEnd")
|
|
9175
|
+
currentPeriodEnd: numberField$1(plan, "currentPeriodEnd", "plan.currentPeriodEnd")
|
|
9081
9176
|
};
|
|
9082
|
-
if (plan.currentPeriodStart !== void 0) report.plan.currentPeriodStart = numberField(plan, "currentPeriodStart", "plan.currentPeriodStart");
|
|
9177
|
+
if (plan.currentPeriodStart !== void 0) report.plan.currentPeriodStart = numberField$1(plan, "currentPeriodStart", "plan.currentPeriodStart");
|
|
9083
9178
|
}
|
|
9084
9179
|
return report;
|
|
9085
9180
|
}
|
|
9086
9181
|
/** Parse one untrusted boundary value into a {@link CommandCodeAccountUsage}. */
|
|
9087
9182
|
function parseAccountUsage(value) {
|
|
9088
|
-
const source = record$
|
|
9183
|
+
const source = record$2(value, "account");
|
|
9089
9184
|
return {
|
|
9090
|
-
id: stringField$
|
|
9091
|
-
label: stringField$
|
|
9185
|
+
id: stringField$2(source, "id", "account.id"),
|
|
9186
|
+
label: stringField$2(source, "label", "account.label"),
|
|
9092
9187
|
configured: booleanField(source, "configured", "account.configured"),
|
|
9093
9188
|
active: booleanField(source, "active", "account.active"),
|
|
9094
|
-
mark: stringField$
|
|
9095
|
-
cooldownUntil: numberField(source, "cooldownUntil", "account.cooldownUntil"),
|
|
9189
|
+
mark: stringField$2(source, "mark", "account.mark"),
|
|
9190
|
+
cooldownUntil: numberField$1(source, "cooldownUntil", "account.cooldownUntil"),
|
|
9096
9191
|
report: parseUsageReport(source.report)
|
|
9097
9192
|
};
|
|
9098
9193
|
}
|
|
9099
9194
|
/** Parse the wire result into a {@link CommandCodeAccountsReport}. */
|
|
9100
9195
|
function parseAccountsReport(value) {
|
|
9101
|
-
const accounts = record$
|
|
9196
|
+
const accounts = record$2(value, "result").accounts;
|
|
9102
9197
|
if (Array.isArray(accounts)) return { accounts: accounts.map(parseAccountUsage) };
|
|
9103
|
-
return reject$
|
|
9198
|
+
return reject$2("accounts");
|
|
9104
9199
|
}
|
|
9105
9200
|
/**
|
|
9106
9201
|
* The strict result codec both halves attach to the descriptor. Hand-rolled:
|
|
@@ -9233,36 +9328,36 @@ const REASONS = [
|
|
|
9233
9328
|
];
|
|
9234
9329
|
/** The shared read/validate helpers, prefixed with the login endpoint so
|
|
9235
9330
|
* rejection messages name the offending boundary. */
|
|
9236
|
-
const { reject, record, stringField } = makeBoundaryValidator("commandcode/login result:");
|
|
9331
|
+
const { reject: reject$1, record: record$1, stringField: stringField$1 } = makeBoundaryValidator("commandcode/login result:");
|
|
9237
9332
|
/**
|
|
9238
9333
|
* Parse one untrusted boundary value into a {@link CommandCodeLoginStatus}.
|
|
9239
9334
|
* Every field is shape-checked so a malformed frame fails the boundary
|
|
9240
9335
|
* instead of leaking into the page.
|
|
9241
9336
|
*/
|
|
9242
9337
|
function parseLoginStatus(value) {
|
|
9243
|
-
const source = record(value, "status");
|
|
9338
|
+
const source = record$1(value, "status");
|
|
9244
9339
|
const state = source.state;
|
|
9245
9340
|
if (state === "idle" || state === "waiting" || state === "success" || state === "failed") {
|
|
9246
9341
|
const status = { state };
|
|
9247
|
-
if (source.authUrl !== void 0) status.authUrl = stringField(source, "authUrl", "authUrl");
|
|
9248
|
-
if (source.userName !== void 0) status.userName = stringField(source, "userName", "userName");
|
|
9249
|
-
if (source.keyName !== void 0) status.keyName = stringField(source, "keyName", "keyName");
|
|
9342
|
+
if (source.authUrl !== void 0) status.authUrl = stringField$1(source, "authUrl", "authUrl");
|
|
9343
|
+
if (source.userName !== void 0) status.userName = stringField$1(source, "userName", "userName");
|
|
9344
|
+
if (source.keyName !== void 0) status.keyName = stringField$1(source, "keyName", "keyName");
|
|
9250
9345
|
if (source.reason !== void 0) {
|
|
9251
9346
|
const reason = source.reason;
|
|
9252
9347
|
if (typeof reason === "string" && REASONS.includes(reason)) status.reason = reason;
|
|
9253
|
-
else reject("reason");
|
|
9348
|
+
else reject$1("reason");
|
|
9254
9349
|
}
|
|
9255
|
-
if (source.message !== void 0) status.message = stringField(source, "message", "message");
|
|
9350
|
+
if (source.message !== void 0) status.message = stringField$1(source, "message", "message");
|
|
9256
9351
|
return status;
|
|
9257
9352
|
}
|
|
9258
|
-
return reject("state");
|
|
9353
|
+
return reject$1("state");
|
|
9259
9354
|
}
|
|
9260
9355
|
/** The strict result codec shared by all three login endpoints. */
|
|
9261
9356
|
const loginStatusSchema = { parse: parseLoginStatus };
|
|
9262
9357
|
/** Optional account target; the Host still checks it against saved slots. */
|
|
9263
9358
|
const loginTargetSchema = { parse(value) {
|
|
9264
9359
|
if (value === void 0 || typeof value === "string") return value;
|
|
9265
|
-
return reject("targetRef");
|
|
9360
|
+
return reject$1("targetRef");
|
|
9266
9361
|
} };
|
|
9267
9362
|
/** Build one login invocation descriptor. Only begin accepts an account ref. */
|
|
9268
9363
|
function loginDescriptor(endpoint, method) {
|
|
@@ -9421,6 +9516,31 @@ const ENROLLMENT_DESCRIPTORS = [
|
|
|
9421
9516
|
}
|
|
9422
9517
|
];
|
|
9423
9518
|
//#endregion
|
|
9519
|
+
//#region src/service-probe-wire.ts
|
|
9520
|
+
/** Canonical `<namespace>/<method>` endpoint of the probe Remote. */
|
|
9521
|
+
const PROBE_ENDPOINT = "commandcode/probe";
|
|
9522
|
+
const OUTCOMES = [
|
|
9523
|
+
"ok",
|
|
9524
|
+
"http-error",
|
|
9525
|
+
"timeout",
|
|
9526
|
+
"unreachable"
|
|
9527
|
+
];
|
|
9528
|
+
const { reject, record, stringField, numberField } = makeBoundaryValidator("commandcode/probe result:");
|
|
9529
|
+
/** Strict result codec, shared verbatim by the Host registration and Client mount. */
|
|
9530
|
+
function parseProbeResult(value) {
|
|
9531
|
+
const source = record(value, "result");
|
|
9532
|
+
const outcome = stringField(source, "outcome", "outcome");
|
|
9533
|
+
if (!OUTCOMES.includes(outcome)) reject("outcome");
|
|
9534
|
+
return {
|
|
9535
|
+
outcome,
|
|
9536
|
+
status: numberField(source, "status", "status"),
|
|
9537
|
+
latencyMs: numberField(source, "latencyMs", "latencyMs"),
|
|
9538
|
+
checkedAt: numberField(source, "checkedAt", "checkedAt")
|
|
9539
|
+
};
|
|
9540
|
+
}
|
|
9541
|
+
/** The one invocation descriptor, shared by the Host registration and the Client mount. */
|
|
9542
|
+
const PROBE_DESCRIPTOR = makeRemoteDescriptor(PROBE_ENDPOINT, "probe", `${REMOTE_PACKAGE}#CommandCodeProbeResult`, { parse: parseProbeResult });
|
|
9543
|
+
//#endregion
|
|
9424
9544
|
//#region src/usage-remote.ts
|
|
9425
9545
|
/**
|
|
9426
9546
|
* The Remote receiver: a Cordis service the Gateway resolves by key
|
|
@@ -9471,6 +9591,17 @@ var CommandCodeUsageService = class extends TypertRemoteService {
|
|
|
9471
9591
|
async prices() {
|
|
9472
9592
|
return (this.deps.prices ?? modelPriceTable)();
|
|
9473
9593
|
}
|
|
9594
|
+
/**
|
|
9595
|
+
* One live connectivity probe of the configured API base, run on this Host.
|
|
9596
|
+
* The status page's own data lags by design (Upptime publishes on change or
|
|
9597
|
+
* once a day), so this is the only real-time half of the status row. The
|
|
9598
|
+
* plugin entry wires the probe; a programmatic setup without it gets an
|
|
9599
|
+
* explicit failure, which the row renders as unknown.
|
|
9600
|
+
*/
|
|
9601
|
+
async probe() {
|
|
9602
|
+
if (this.deps.probe === void 0) throw new Error("宿主未提供连通性探测");
|
|
9603
|
+
return this.deps.probe();
|
|
9604
|
+
}
|
|
9474
9605
|
async analytics(query) {
|
|
9475
9606
|
if (!this.deps.analytics) throw new Error("宿主不支持模型分析,请更新插件");
|
|
9476
9607
|
return this.deps.analytics(query, this.ctx.invocation?.signal);
|
|
@@ -9565,6 +9696,7 @@ function applyUsageRemote(ctx, deps) {
|
|
|
9565
9696
|
MODELS_DESCRIPTOR,
|
|
9566
9697
|
PRICES_DESCRIPTOR,
|
|
9567
9698
|
ANALYTICS_DESCRIPTOR,
|
|
9699
|
+
PROBE_DESCRIPTOR,
|
|
9568
9700
|
...LOGIN_HOST_CONTRIBUTION.invocations,
|
|
9569
9701
|
...ENROLLMENT_DESCRIPTORS
|
|
9570
9702
|
]
|
|
@@ -9572,6 +9704,93 @@ function applyUsageRemote(ctx, deps) {
|
|
|
9572
9704
|
remoteCtx.effect(() => () => void unregister(), "dsh-commandcode-provider: usage remote");
|
|
9573
9705
|
});
|
|
9574
9706
|
}
|
|
9707
|
+
/**
|
|
9708
|
+
* HTTP statuses Upptime's default monitor accepts as `up` (from
|
|
9709
|
+
* `uptime-monitor`'s `expectedStatusCodes` fallback). Kept in sync with the
|
|
9710
|
+
* page's monitor rather than invented here: a 401 or 404 means the endpoint
|
|
9711
|
+
* answered but is not the surface the official check calls healthy.
|
|
9712
|
+
*/
|
|
9713
|
+
const UP_STATUSES = /* @__PURE__ */ new Set([
|
|
9714
|
+
200,
|
|
9715
|
+
201,
|
|
9716
|
+
202,
|
|
9717
|
+
203,
|
|
9718
|
+
204,
|
|
9719
|
+
205,
|
|
9720
|
+
206,
|
|
9721
|
+
207,
|
|
9722
|
+
208,
|
|
9723
|
+
226,
|
|
9724
|
+
300,
|
|
9725
|
+
301,
|
|
9726
|
+
302,
|
|
9727
|
+
303,
|
|
9728
|
+
304,
|
|
9729
|
+
305,
|
|
9730
|
+
306,
|
|
9731
|
+
307,
|
|
9732
|
+
308
|
|
9733
|
+
]);
|
|
9734
|
+
/** Turn the response status into the outcome the row renders. */
|
|
9735
|
+
function probeOutcomeForStatus(status) {
|
|
9736
|
+
return UP_STATUSES.has(status) ? "ok" : "http-error";
|
|
9737
|
+
}
|
|
9738
|
+
/**
|
|
9739
|
+
* Probe the configured API base once. Never rejects: an invalid base, a
|
|
9740
|
+
* timeout and a transport failure all resolve to an outcome.
|
|
9741
|
+
*
|
|
9742
|
+
* `redirect: 'manual'` keeps the classification on the FIRST response, exactly
|
|
9743
|
+
* like the monitor's HTTP client; following a redirect would report the final
|
|
9744
|
+
* destination's status as if it were the API's own.
|
|
9745
|
+
*/
|
|
9746
|
+
async function probeCommandCode(options) {
|
|
9747
|
+
const now = options.now ?? Date.now;
|
|
9748
|
+
let url;
|
|
9749
|
+
try {
|
|
9750
|
+
url = new URL("/", options.apiBase).toString();
|
|
9751
|
+
} catch {
|
|
9752
|
+
return {
|
|
9753
|
+
outcome: "unreachable",
|
|
9754
|
+
status: 0,
|
|
9755
|
+
latencyMs: 0,
|
|
9756
|
+
checkedAt: now()
|
|
9757
|
+
};
|
|
9758
|
+
}
|
|
9759
|
+
const controller = new AbortController();
|
|
9760
|
+
let timedOut = false;
|
|
9761
|
+
const timer = setTimeout(() => {
|
|
9762
|
+
timedOut = true;
|
|
9763
|
+
controller.abort();
|
|
9764
|
+
}, options.timeoutMs ?? 5e3);
|
|
9765
|
+
const started = Date.now();
|
|
9766
|
+
try {
|
|
9767
|
+
const response = await (options.fetchImpl ?? fetch)(url, {
|
|
9768
|
+
method: "GET",
|
|
9769
|
+
redirect: "manual",
|
|
9770
|
+
signal: controller.signal,
|
|
9771
|
+
headers: { accept: "*/*" }
|
|
9772
|
+
});
|
|
9773
|
+
const latencyMs = Math.max(0, Date.now() - started);
|
|
9774
|
+
try {
|
|
9775
|
+
await response.body?.cancel();
|
|
9776
|
+
} catch {}
|
|
9777
|
+
return {
|
|
9778
|
+
outcome: probeOutcomeForStatus(response.status),
|
|
9779
|
+
status: response.status,
|
|
9780
|
+
latencyMs,
|
|
9781
|
+
checkedAt: now()
|
|
9782
|
+
};
|
|
9783
|
+
} catch {
|
|
9784
|
+
return {
|
|
9785
|
+
outcome: timedOut ? "timeout" : "unreachable",
|
|
9786
|
+
status: 0,
|
|
9787
|
+
latencyMs: 0,
|
|
9788
|
+
checkedAt: now()
|
|
9789
|
+
};
|
|
9790
|
+
} finally {
|
|
9791
|
+
clearTimeout(timer);
|
|
9792
|
+
}
|
|
9793
|
+
}
|
|
9575
9794
|
//#endregion
|
|
9576
9795
|
//#region src/login.ts
|
|
9577
9796
|
/**
|
|
@@ -11694,6 +11913,7 @@ function apply(ctx, config) {
|
|
|
11694
11913
|
rotateApiKey: async (rejectedKey, rejection, connection, model, rotation) => {
|
|
11695
11914
|
const accounts = scopedPool(connection);
|
|
11696
11915
|
if (rejection !== "unavailable") accounts.markRejected(rejectedKey, rejection, rotation?.resetAtMs);
|
|
11916
|
+
else if (model !== void 0) accounts.markModelUnavailable(rejectedKey, model);
|
|
11697
11917
|
const tried = rotation?.tried?.length ? rotation.tried : [rejectedKey];
|
|
11698
11918
|
const resolved = await accounts.resolveKey(model === void 0 ? { tried } : {
|
|
11699
11919
|
tried,
|
|
@@ -11848,7 +12068,8 @@ function apply(ctx, config) {
|
|
|
11848
12068
|
login: loginFlow,
|
|
11849
12069
|
enrollment,
|
|
11850
12070
|
listModels: catalogForEditors,
|
|
11851
|
-
analytics: createAnalyticsReader(ctx, adapter)
|
|
12071
|
+
analytics: createAnalyticsReader(ctx, adapter),
|
|
12072
|
+
probe: () => probeCommandCode({ apiBase: options().apiBase })
|
|
11852
12073
|
});
|
|
11853
12074
|
const selectionLogger = {
|
|
11854
12075
|
warn: (message) => {
|