@mars-sea/dsh-commandcode-provider 0.15.4 → 0.16.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 +53 -0
- package/README.md +16 -3
- package/README.zh-CN.md +16 -3
- package/lib/client.js +409 -144
- package/lib/index.d.ts +73 -6
- package/lib/index.js +344 -78
- package/package.json +50 -43
package/lib/index.js
CHANGED
|
@@ -123,7 +123,7 @@ 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.88.
|
|
126
|
+
* bundle's model table (`dist/cli.mjs`, re-verified at command-code@1.88.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
|
|
@@ -139,7 +139,13 @@ const PLAN_ORDER = Object.fromEntries(PLAN_TIER_ORDER.map((tier, index) => [tier
|
|
|
139
139
|
* efforts `low/high/max`, Go tier, `$2/$10/$0.2`). The 1.88.0 artifact does not
|
|
140
140
|
* name that row yet; the default catalog, `?view=client`, the models docs page
|
|
141
141
|
* and the pricing page all do, so the capability fields come from the catalog
|
|
142
|
-
* as usual.
|
|
142
|
+
* as usual. The 2026-10-27 check finds the CLI still at 1.88.0 and one more
|
|
143
|
+
* catalog-only addition, GPT-6.1 Terra (`gpt-6.1-terra`): 1.05M context, text +
|
|
144
|
+
* image, the same five-level effort set, Pro tier, `$1/$5` up to a 272K context
|
|
145
|
+
* step and `$2/$7.5` beyond. The 2026-10-28 check moves the constant to 1.88.1,
|
|
146
|
+
* whose registry now names both catalog-only rows (Kimi K3.1 Pro and GPT-6.1
|
|
147
|
+
* Terra) with efforts and context identical to this snapshot, so no table
|
|
148
|
+
* changes. Keeping the snapshot in its own module
|
|
143
149
|
* confines those frequent sync diffs here: src/adapter.ts holds only the stable
|
|
144
150
|
* wire/runtime logic. The read helpers live here too, for the same reason.
|
|
145
151
|
*
|
|
@@ -499,6 +505,13 @@ const KNOWN_EFFORTS = {
|
|
|
499
505
|
"xhigh",
|
|
500
506
|
"max"
|
|
501
507
|
],
|
|
508
|
+
"gpt-6.1-terra": [
|
|
509
|
+
"low",
|
|
510
|
+
"medium",
|
|
511
|
+
"high",
|
|
512
|
+
"xhigh",
|
|
513
|
+
"max"
|
|
514
|
+
],
|
|
502
515
|
"inclusionai/ling-3.1-flash": [
|
|
503
516
|
"low",
|
|
504
517
|
"medium",
|
|
@@ -581,6 +594,7 @@ const KNOWN_IMAGE_MODELS = /* @__PURE__ */ new Set([
|
|
|
581
594
|
"gpt-6-luna",
|
|
582
595
|
"gpt-6-sol",
|
|
583
596
|
"gpt-6.1-sol",
|
|
597
|
+
"gpt-6.1-terra",
|
|
584
598
|
"meta/muse-spark-1.1",
|
|
585
599
|
"meta/muse-spark-1.2",
|
|
586
600
|
"meta/muse-spark-1.2-contributor",
|
|
@@ -934,10 +948,9 @@ const MODEL_OUTPUT_TOKEN_LIMITS = /* @__PURE__ */ new Map([
|
|
|
934
948
|
* `/docs/plans/max` and `/docs/resources/pricing-limits`). Each plan's model
|
|
935
949
|
* list is a superset of the one below it: Go ⊂ GOAT ⊂ Pro ⊂ Provider/Max.
|
|
936
950
|
* Models absent from every plan list (Claude Opus/Fable, Fugu Ultra) are
|
|
937
|
-
* Provider-tier. Re-verified at command-code@1.88.
|
|
938
|
-
* live `?view=client` catalog:
|
|
939
|
-
* Large 4
|
|
940
|
-
* (1.75.0) was the only addition at the 1.77.0 check; the 1.78.0/1.79.0 pair
|
|
951
|
+
* Provider-tier. Re-verified at command-code@1.88.1 (2026-10-28) against the
|
|
952
|
+
* live `?view=client` catalog: 106 ids, still a strict superset chain. Mistral
|
|
953
|
+
* Large 4 (1.75.0) was the only addition at the 1.77.0 check; the 1.78.0/1.79.0 pair
|
|
941
954
|
* added Claude Haiku 5.5 at Pro and the free Glyph Cluster preview at Go.
|
|
942
955
|
* 1.79.2 lowers Haiku 5.5's minimum plan to GOAT; 1.80.0 adds Muse Wonder at
|
|
943
956
|
* Max; 1.82.0 adds Gemini 4 Argon at Pro and nothing else moves. The 1.82.4
|
|
@@ -946,7 +959,8 @@ const MODEL_OUTPUT_TOKEN_LIMITS = /* @__PURE__ */ new Map([
|
|
|
946
959
|
* which left the live catalog. Every other
|
|
947
960
|
* tier is unchanged. The 1.86.1 sync (2026-10-21) adds only the Ornith 1.5 122B
|
|
948
961
|
* launch pair at Go; the 1.87.1 check (2026-10-22) adds no model and moves no
|
|
949
|
-
* tier,
|
|
962
|
+
* tier, the 2026-10-24 check adds only Kimi K3.1 Pro at Go, and the 2026-10-27
|
|
963
|
+
* check adds only GPT-6.1 Terra at Pro.
|
|
950
964
|
* The only removals since 1.49.0 are the two retired stealth previews (Pixel
|
|
951
965
|
* Canary at 1.73.1, Space Bunny Alpha at 1.74.3), Ling 3.0 Flash Sante
|
|
952
966
|
* (2026-10-16) and the retired Cat Jam preview (2026-10-20). Per-entry tags
|
|
@@ -1043,6 +1057,7 @@ const KNOWN_PLANS = {
|
|
|
1043
1057
|
"gpt-5.5": "pro",
|
|
1044
1058
|
"gpt-5.6-terra": "pro",
|
|
1045
1059
|
"gpt-6-sol": "pro",
|
|
1060
|
+
"gpt-6.1-terra": "pro",
|
|
1046
1061
|
"meta/muse-spark-1.1": "pro",
|
|
1047
1062
|
"claude-fable-5-1": "provider",
|
|
1048
1063
|
"claude-fable-5": "provider",
|
|
@@ -2106,6 +2121,34 @@ const MODEL_PRICE_ROWS = [
|
|
|
2106
2121
|
5
|
|
2107
2122
|
] }]
|
|
2108
2123
|
},
|
|
2124
|
+
{
|
|
2125
|
+
id: "gpt-6.1-terra",
|
|
2126
|
+
rates: [
|
|
2127
|
+
1,
|
|
2128
|
+
5,
|
|
2129
|
+
.05,
|
|
2130
|
+
1.25
|
|
2131
|
+
],
|
|
2132
|
+
allowance: {
|
|
2133
|
+
go: 6,
|
|
2134
|
+
goat: 20,
|
|
2135
|
+
pro: 20
|
|
2136
|
+
},
|
|
2137
|
+
contextTiers: [{
|
|
2138
|
+
"maxContext": 272e3,
|
|
2139
|
+
"rates": [
|
|
2140
|
+
1,
|
|
2141
|
+
5,
|
|
2142
|
+
.05,
|
|
2143
|
+
1.25
|
|
2144
|
+
]
|
|
2145
|
+
}, { "rates": [
|
|
2146
|
+
2,
|
|
2147
|
+
7.5,
|
|
2148
|
+
.1,
|
|
2149
|
+
2.5
|
|
2150
|
+
] }]
|
|
2151
|
+
},
|
|
2109
2152
|
{
|
|
2110
2153
|
id: "grok-4.5",
|
|
2111
2154
|
rates: [
|
|
@@ -4224,6 +4267,29 @@ var CommandCodeAccountPool = class CommandCodeAccountPool {
|
|
|
4224
4267
|
});
|
|
4225
4268
|
}
|
|
4226
4269
|
/**
|
|
4270
|
+
* 用量报告读到的窗口结论(设置页账号卡片、`/commandcode` 拉取的
|
|
4271
|
+
* `/alpha/billing/credits`):某个窗口已超限,就按探测的同一口径给这个密钥
|
|
4272
|
+
* 打标记——有可信重置时刻记为到期自动恢复的 `cooldown`,否则为 `unknown`。
|
|
4273
|
+
*
|
|
4274
|
+
* 为什么需要(issue #105):轮转原本只认真实请求的拒绝,进程重启或冷却过早
|
|
4275
|
+
* 到期后,卡片明明显示「每周 100%」,自动模式和「当前使用」徽标仍选列表第一个
|
|
4276
|
+
* 账号,下一个请求白撞一次拒绝才换号。报告本来就要拉这个接口,把结论记进池里
|
|
4277
|
+
* 不增加任何请求。
|
|
4278
|
+
*
|
|
4279
|
+
* 只加不减:窗口未超限时不清除已有标记。限流(throttle)或消费上限这类拒绝
|
|
4280
|
+
* 在窗口数据里看不到(issue #54),据此清标记会让每次刷新卡片都换来一次注定
|
|
4281
|
+
* 被拒的请求;清除仍只走有节流的探测路径。401 禁用的密钥不改。
|
|
4282
|
+
*/
|
|
4283
|
+
noteWindowReport(apiKey, verdict) {
|
|
4284
|
+
if (verdict === void 0 || !verdict.exceeded) return;
|
|
4285
|
+
const state = this.states.get(apiKey);
|
|
4286
|
+
if (state?.kind === "disabled") return;
|
|
4287
|
+
this.stampWindowMark({
|
|
4288
|
+
key: apiKey,
|
|
4289
|
+
state
|
|
4290
|
+
}, verdict);
|
|
4291
|
+
}
|
|
4292
|
+
/**
|
|
4227
4293
|
* Record a model-scoped `unavailable` rejection (no credits, or a model
|
|
4228
4294
|
* outside this account's plan) as a SHORT-LIVED PREFERENCE demotion.
|
|
4229
4295
|
*
|
|
@@ -6369,7 +6435,8 @@ var GatewayFacts = class {
|
|
|
6369
6435
|
if (this.state?.source !== identity) this.state = {
|
|
6370
6436
|
source: identity,
|
|
6371
6437
|
rows: [],
|
|
6372
|
-
expiresAt: 0
|
|
6438
|
+
expiresAt: 0,
|
|
6439
|
+
origin: "none"
|
|
6373
6440
|
};
|
|
6374
6441
|
return this.state;
|
|
6375
6442
|
}
|
|
@@ -6378,6 +6445,20 @@ var GatewayFacts = class {
|
|
|
6378
6445
|
const row = this.forSource(source).rows.find((candidate) => candidate.id === model);
|
|
6379
6446
|
return row === void 0 ? void 0 : copyRows([row])[0];
|
|
6380
6447
|
}
|
|
6448
|
+
/**
|
|
6449
|
+
* 本进程**联网成功过**的目录;否则返回 undefined。
|
|
6450
|
+
*
|
|
6451
|
+
* 判据是来源而不是「内存里有行」:`snapshot()` 会把磁盘缓存写进同一份
|
|
6452
|
+
* `state.rows`(生成路径需要它),若只看行数,冷启动先发过一次聊天再打开
|
|
6453
|
+
* 选择器时,首屏拿到的就是没有年龄校验的磁盘副本——正是本方法要排除的东西。
|
|
6454
|
+
* 空目录同样返回 undefined:刷新失败后的空行(`expiresAt` 已进入重试窗口)
|
|
6455
|
+
* 不能让选择器把「暂时读不到」画成「没有模型」,调用方应回落到 `refresh()`。
|
|
6456
|
+
*/
|
|
6457
|
+
loaded(source) {
|
|
6458
|
+
const state = this.forSource(source);
|
|
6459
|
+
if (state.origin !== "network" || state.rows.length === 0) return void 0;
|
|
6460
|
+
return copyRows(state.rows);
|
|
6461
|
+
}
|
|
6381
6462
|
/** 生成只读取本地目录;捕获状态后不再转向实例的新来源。 */
|
|
6382
6463
|
async snapshot(source) {
|
|
6383
6464
|
const state = this.forSource(source);
|
|
@@ -6385,7 +6466,10 @@ var GatewayFacts = class {
|
|
|
6385
6466
|
state.disk ??= readCache(source).catch(() => []);
|
|
6386
6467
|
const rows = await state.disk;
|
|
6387
6468
|
if (state.rows.length > 0 || state.expiresAt > 0) return copyRows(state.rows);
|
|
6388
|
-
if (state.inflight === void 0)
|
|
6469
|
+
if (state.inflight === void 0) {
|
|
6470
|
+
state.rows = rows;
|
|
6471
|
+
if (rows.length > 0 && state.origin === "none") state.origin = "disk";
|
|
6472
|
+
}
|
|
6389
6473
|
return copyRows(rows);
|
|
6390
6474
|
}
|
|
6391
6475
|
async refresh(source, signal) {
|
|
@@ -6398,8 +6482,10 @@ var GatewayFacts = class {
|
|
|
6398
6482
|
const mayPublish = () => this.state === state && generations.get(source.apiBase) === generation;
|
|
6399
6483
|
state.inflight = (async () => {
|
|
6400
6484
|
let rows, ttl = MODEL_CATALOG_TTL_MS;
|
|
6485
|
+
let network = false;
|
|
6401
6486
|
try {
|
|
6402
6487
|
rows = parseCatalog$1(await this.fetchCatalog(source), source.apiBase);
|
|
6488
|
+
network = true;
|
|
6403
6489
|
if (mayPublish()) {
|
|
6404
6490
|
publish(source.apiBase, rows);
|
|
6405
6491
|
await writeCache(source, rows, mayPublish).catch(() => void 0);
|
|
@@ -6411,6 +6497,8 @@ var GatewayFacts = class {
|
|
|
6411
6497
|
}
|
|
6412
6498
|
state.rows = rows;
|
|
6413
6499
|
state.expiresAt = Date.now() + ttl;
|
|
6500
|
+
if (network) state.origin = "network";
|
|
6501
|
+
else if (state.origin === "none" && rows.length > 0) state.origin = "disk";
|
|
6414
6502
|
return rows;
|
|
6415
6503
|
})().finally(() => {
|
|
6416
6504
|
delete state.inflight;
|
|
@@ -6447,7 +6535,7 @@ var GatewayFacts = class {
|
|
|
6447
6535
|
* per-request options thunk and an API-key resolver from the plugin entry
|
|
6448
6536
|
* (src/index.ts), so a settings change reaches the very next request.
|
|
6449
6537
|
*/
|
|
6450
|
-
const COMMAND_CODE_CLI_VERSION = "1.88.
|
|
6538
|
+
const COMMAND_CODE_CLI_VERSION = "1.88.1";
|
|
6451
6539
|
const DEFAULT_API_BASE = "https://api.commandcode.ai";
|
|
6452
6540
|
/**
|
|
6453
6541
|
* The output budget this bundle asks for, and the ceiling it will never exceed.
|
|
@@ -6461,6 +6549,17 @@ const MODELS_TIMEOUT_MS = 1e4;
|
|
|
6461
6549
|
/** How long the picker's plan-filter billing facts stay cached before refetching. */
|
|
6462
6550
|
const BILLING_ACCESS_TTL_MS = 3e5;
|
|
6463
6551
|
/**
|
|
6552
|
+
* How long the raw account facts captured by one usage-report run stay usable
|
|
6553
|
+
* for the picker's advisory plan filter.
|
|
6554
|
+
*
|
|
6555
|
+
* This is much shorter than {@link BILLING_ACCESS_TTL_MS} because it only ever
|
|
6556
|
+
* removes a DUPLICATE read: the picker and the settings card ask the same three
|
|
6557
|
+
* endpoints about the same key, and whichever ran last already paid for them.
|
|
6558
|
+
* Anything older than this window is simply fetched again — the filter is
|
|
6559
|
+
* fail-open advisory data, so a miss costs one round trip and nothing else.
|
|
6560
|
+
*/
|
|
6561
|
+
const ACCOUNT_FACTS_TTL_MS = 3e4;
|
|
6562
|
+
/**
|
|
6464
6563
|
* How long a learned CLI-only protocol preference stays cached per account.
|
|
6465
6564
|
* After this TTL a request may probe Provider API again, so an upgraded Go
|
|
6466
6565
|
* account can recover without a restart.
|
|
@@ -6552,9 +6651,13 @@ function fingerprintValue(value) {
|
|
|
6552
6651
|
}
|
|
6553
6652
|
return {
|
|
6554
6653
|
bytes: Buffer.byteLength(serialized),
|
|
6555
|
-
sha256:
|
|
6654
|
+
sha256: sha256Hex(serialized)
|
|
6556
6655
|
};
|
|
6557
6656
|
}
|
|
6657
|
+
/** Digest of already-serialized text: no second stringify, no retained copy. */
|
|
6658
|
+
function sha256Hex(serialized) {
|
|
6659
|
+
return createHash("sha256").update(serialized).digest("hex");
|
|
6660
|
+
}
|
|
6558
6661
|
/**
|
|
6559
6662
|
* Structural request facts for issue #64's cache diagnosis.
|
|
6560
6663
|
*
|
|
@@ -6670,6 +6773,16 @@ function headersTimeoutError(input) {
|
|
|
6670
6773
|
/** How deep a root `$ref` / combinator chain is followed while normalizing. */
|
|
6671
6774
|
const SCHEMA_NORMALIZE_MAX_DEPTH = 4;
|
|
6672
6775
|
/**
|
|
6776
|
+
* Depth-0 normalization results, keyed by the caller's own `parameters` object.
|
|
6777
|
+
*
|
|
6778
|
+
* {@link toolParametersSchema} only ever reads the root, so a schema that has
|
|
6779
|
+
* already been normalized returns the identical answer however many times it is
|
|
6780
|
+
* asked. The tool list is rebuilt for EVERY attempt — a rotation or a protocol
|
|
6781
|
+
* fallback rebuilds it again — and the objects come from the harness, so they
|
|
6782
|
+
* stay identical for the life of a turn: identity is an exact key.
|
|
6783
|
+
*/
|
|
6784
|
+
const TOOL_SCHEMA_CACHE = /* @__PURE__ */ new WeakMap();
|
|
6785
|
+
/**
|
|
6673
6786
|
* Whether a type-less node is object-shaped enough to be one with the type
|
|
6674
6787
|
* declared: `properties`/`required`/`additionalProperties` only make sense on
|
|
6675
6788
|
* an object, and a schema carrying them was meant to be one.
|
|
@@ -6715,7 +6828,7 @@ function mergeCombinatorBranches(node, depth) {
|
|
|
6715
6828
|
const objectBranches = [];
|
|
6716
6829
|
for (const branch of branches) {
|
|
6717
6830
|
if (!isRecord(branch)) continue;
|
|
6718
|
-
objectBranches.push(
|
|
6831
|
+
objectBranches.push(normalizeToolParameters(branch, depth + 1));
|
|
6719
6832
|
}
|
|
6720
6833
|
if (objectBranches.length === 0) continue;
|
|
6721
6834
|
sawBranch = true;
|
|
@@ -6745,10 +6858,32 @@ function mergeCombinatorBranches(node, depth) {
|
|
|
6745
6858
|
* resolved/merged; anything else (an array/scalar root, or no schema at all)
|
|
6746
6859
|
* degrades to a permissive free-form object, because a request the provider
|
|
6747
6860
|
* refuses helps no one and the tool's own description is still in the prompt.
|
|
6748
|
-
*
|
|
6749
|
-
*
|
|
6750
|
-
|
|
6751
|
-
function
|
|
6861
|
+
*
|
|
6862
|
+
* Only the ROOT is touched. The object-root fast path deliberately returns the
|
|
6863
|
+
* caller's own object: an already-normalized schema is the common case, the
|
|
6864
|
+
* function has always answered with that same identity, and callers rely on it
|
|
6865
|
+
* to get the same frozen schema they handed in. Everything this function
|
|
6866
|
+
* CONSTRUCTS is a fresh object, so no caller can mutate another's schema.
|
|
6867
|
+
*
|
|
6868
|
+
* The ROOT is memoized per `parameters` object, because every transport rebuilds
|
|
6869
|
+
* its tool list on every attempt — on a rotating or downshifting request the
|
|
6870
|
+
* same handful of tool schemas is walked four, five, six times for an answer
|
|
6871
|
+
* that cannot have changed. The tool objects come from the harness and are
|
|
6872
|
+
* stable for the life of a turn, so a WeakMap keyed on identity is exact: a
|
|
6873
|
+
* genuinely different schema is a different object and misses. Nested
|
|
6874
|
+
* `$ref`/combinator branches are reached through {@link normalizeToolParameters}
|
|
6875
|
+
* and are not memoized; only the root is asked for on a hot path.
|
|
6876
|
+
*/
|
|
6877
|
+
function toolParametersSchema(parameters) {
|
|
6878
|
+
if (!isRecord(parameters)) return normalizeToolParameters(parameters, 0);
|
|
6879
|
+
const cached = TOOL_SCHEMA_CACHE.get(parameters);
|
|
6880
|
+
if (cached !== void 0) return cached;
|
|
6881
|
+
const normalized = normalizeToolParameters(parameters, 0);
|
|
6882
|
+
TOOL_SCHEMA_CACHE.set(parameters, normalized);
|
|
6883
|
+
return normalized;
|
|
6884
|
+
}
|
|
6885
|
+
/** Depth-0 normalization of {@link toolParametersSchema}; see its memo there. */
|
|
6886
|
+
function normalizeToolParameters(parameters, depth) {
|
|
6752
6887
|
if (!isRecord(parameters)) return {
|
|
6753
6888
|
type: "object",
|
|
6754
6889
|
properties: {},
|
|
@@ -6764,7 +6899,7 @@ function toolParametersSchema(parameters, depth = 0) {
|
|
|
6764
6899
|
const target = resolveLocalRef(parameters, parameters.$ref);
|
|
6765
6900
|
if (target !== void 0) {
|
|
6766
6901
|
const { $ref: _ref, ...rest } = parameters;
|
|
6767
|
-
return
|
|
6902
|
+
return normalizeToolParameters({
|
|
6768
6903
|
...target,
|
|
6769
6904
|
...rest
|
|
6770
6905
|
}, depth + 1);
|
|
@@ -7000,26 +7135,47 @@ function coreBudget(budget) {
|
|
|
7000
7135
|
*/
|
|
7001
7136
|
const imageVersionBytes = (block) => block.attachment.bytes;
|
|
7002
7137
|
/**
|
|
7138
|
+
* The accounting half of the image gate: project the surface's durable offload
|
|
7139
|
+
* marks and count what the byte budget still needs — WITHOUT throwing.
|
|
7140
|
+
*
|
|
7141
|
+
* The throw has to wait until the credential is known. A request that cannot
|
|
7142
|
+
* authenticate must not trigger a durable (irreversible) image offload before
|
|
7143
|
+
* the caller learns the key is missing; keeping the count pure lets
|
|
7144
|
+
* `streamAttempt()` run it before ANY I/O and report ONE error later, once the
|
|
7145
|
+
* real transport is known.
|
|
7146
|
+
*/
|
|
7147
|
+
function projectSurfaceBudget(options, policy, budget) {
|
|
7148
|
+
const marked = policy.projectOffloadedImages(options.messages, (ref) => offloadedImageText(ref));
|
|
7149
|
+
const messages = marked === options.messages ? options.messages : [...marked];
|
|
7150
|
+
return {
|
|
7151
|
+
options: messages === options.messages ? options : {
|
|
7152
|
+
...options,
|
|
7153
|
+
messages
|
|
7154
|
+
},
|
|
7155
|
+
budgetMissing: policy.requiredImageOffload(messages, coreBudget(budget), imageVersionBytes)
|
|
7156
|
+
};
|
|
7157
|
+
}
|
|
7158
|
+
/**
|
|
7003
7159
|
* Request options under one budget rung: the surface's durable marks become
|
|
7004
7160
|
* placeholder text, and an over-budget history FAILS with the count to offload
|
|
7005
7161
|
* instead of being evicted here. Throws rather than returning, so no caller can
|
|
7006
7162
|
* accidentally send a body this route already knows is too large.
|
|
7007
7163
|
*
|
|
7164
|
+
* This is the one-shot form (budget + cache in a single error) used by the 413
|
|
7165
|
+
* downshift and the protocol fallback. `streamAttempt()`'s first pass uses
|
|
7166
|
+
* {@link projectSurfaceBudget} instead, because there the throw must wait for
|
|
7167
|
+
* the credential.
|
|
7168
|
+
*
|
|
7008
7169
|
* The next rung is applied to the CURRENT projection, never to the raw history —
|
|
7009
7170
|
* otherwise the 413 retry could reintroduce images the first rung had already
|
|
7010
7171
|
* evicted.
|
|
7011
7172
|
*/
|
|
7012
7173
|
function withSurfaceOffload(options, policy, budget, cacheOffloadModel) {
|
|
7013
|
-
const
|
|
7014
|
-
const
|
|
7015
|
-
const
|
|
7016
|
-
|
|
7017
|
-
|
|
7018
|
-
if (missing > 0) throw imageOffloadRequired(missing, cacheMissing > budgetMissing ? "cache" : "budget");
|
|
7019
|
-
return messages === options.messages ? options : {
|
|
7020
|
-
...options,
|
|
7021
|
-
messages
|
|
7022
|
-
};
|
|
7174
|
+
const projected = projectSurfaceBudget(options, policy, budget);
|
|
7175
|
+
const cacheMissing = cacheOffloadModel === void 0 ? 0 : seenImageOffloadCount(projected.options.messages, cacheOffloadModel);
|
|
7176
|
+
const missing = Math.max(projected.budgetMissing, cacheMissing);
|
|
7177
|
+
if (missing > 0) throw imageOffloadRequired(missing, cacheMissing > projected.budgetMissing ? "cache" : "budget");
|
|
7178
|
+
return projected.options;
|
|
7023
7179
|
}
|
|
7024
7180
|
/**
|
|
7025
7181
|
* Count retained images already followed by an answer from this same model.
|
|
@@ -7494,6 +7650,33 @@ function parseWindowLimit(value) {
|
|
|
7494
7650
|
};
|
|
7495
7651
|
}
|
|
7496
7652
|
/**
|
|
7653
|
+
* 把已解析的用量窗口归结为账号池能用的结论:任一窗口超限即 `exceeded`,
|
|
7654
|
+
* `resetAt` 取超限窗口里最晚的重置时刻(真正卡住账号的那个)。
|
|
7655
|
+
*
|
|
7656
|
+
* 职责:`probeWindowLimits()`(池的探测)与用量报告(设置页卡片、`/commandcode`)
|
|
7657
|
+
* 读的是同一个 `/alpha/billing/credits`,两条路径必须得出同一结论,否则卡片显示
|
|
7658
|
+
* 「每周 100%」而池仍把它当可用(issue #105)。
|
|
7659
|
+
*
|
|
7660
|
+
* 一个窗口都没报告时返回 undefined:无从判断,调用方保持原标记不动。
|
|
7661
|
+
*/
|
|
7662
|
+
function windowVerdict(windows, now = Date.now()) {
|
|
7663
|
+
let reported = false;
|
|
7664
|
+
let exceeded = false;
|
|
7665
|
+
let resetAt = 0;
|
|
7666
|
+
for (const window of windows) {
|
|
7667
|
+
if (window === void 0) continue;
|
|
7668
|
+
reported = true;
|
|
7669
|
+
if (!window.exceeded) continue;
|
|
7670
|
+
exceeded = true;
|
|
7671
|
+
if (window.resetAt > 0 && window.resetAt - now <= 2592e6) resetAt = Math.max(resetAt, window.resetAt);
|
|
7672
|
+
}
|
|
7673
|
+
if (!reported) return void 0;
|
|
7674
|
+
return {
|
|
7675
|
+
exceeded,
|
|
7676
|
+
resetAt: exceeded ? resetAt : 0
|
|
7677
|
+
};
|
|
7678
|
+
}
|
|
7679
|
+
/**
|
|
7497
7680
|
* Parse one billing/credits payload into the report's credits section.
|
|
7498
7681
|
* Returns undefined when the endpoint answered nothing usable.
|
|
7499
7682
|
*/
|
|
@@ -7977,6 +8160,25 @@ var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
|
|
|
7977
8160
|
surfaceOffload;
|
|
7978
8161
|
billingAccess = /* @__PURE__ */ new Map();
|
|
7979
8162
|
billingAccessInflight = /* @__PURE__ */ new Map();
|
|
8163
|
+
/**
|
|
8164
|
+
* Successful account-endpoint responses, keyed by gateway + key + exact URL.
|
|
8165
|
+
*
|
|
8166
|
+
* The settings card and the picker's advisory plan filter ask the SAME three
|
|
8167
|
+
* endpoints about the SAME account (`/alpha/whoami`, then credits and
|
|
8168
|
+
* subscriptions in parallel). Here that is four round trips per surface for
|
|
8169
|
+
* two answers — and the card refreshes on a timer, so the picker keeps paying
|
|
8170
|
+
* for rows it already has. Sharing the raw responses removes the duplicate
|
|
8171
|
+
* without either surface having to know about the other: whoever asks second
|
|
8172
|
+
* within {@link ACCOUNT_FACTS_TTL_MS} reads the same record instead of the
|
|
8173
|
+
* wire.
|
|
8174
|
+
*
|
|
8175
|
+
* Only SUCCESSFUL records are kept. A failure must be retried rather than
|
|
8176
|
+
* replayed, or an endpoint that recovered a second ago would keep reporting
|
|
8177
|
+
* yesterday's outage — and the usage report's per-endpoint `failures` and
|
|
8178
|
+
* {@link classifyTotalFailure} verdict would describe a request this process
|
|
8179
|
+
* never made.
|
|
8180
|
+
*/
|
|
8181
|
+
accountJson = /* @__PURE__ */ new Map();
|
|
7980
8182
|
protocolCache = /* @__PURE__ */ new Map();
|
|
7981
8183
|
/** Namespace account-derived facts by gateway without exposing the key. */
|
|
7982
8184
|
accountCacheKey(apiBase, apiKey) {
|
|
@@ -8016,10 +8218,10 @@ var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
|
|
|
8016
8218
|
* @returns the error to surface: unchanged for any other failure, the
|
|
8017
8219
|
* original timeout below the threshold, and a terminal one at it.
|
|
8018
8220
|
*/
|
|
8019
|
-
gradeHeaderTimeout(error, protocol, body, timeoutMs, sessionId) {
|
|
8221
|
+
gradeHeaderTimeout(error, protocol, body, timeoutMs, sessionId, sent) {
|
|
8020
8222
|
if (!(error instanceof LlmError && error.code === "TIMEOUT")) return error;
|
|
8021
8223
|
const key = sessionId ?? GLOBAL_HEADER_TIMEOUT_KEY;
|
|
8022
|
-
const fingerprint = `${protocol}:${fingerprintValue(body).sha256}`;
|
|
8224
|
+
const fingerprint = `${protocol}:${sent === void 0 ? fingerprintValue(body).sha256 : sha256Hex(sent)}`;
|
|
8023
8225
|
const previous = this.headerTimeoutStreaks.get(key);
|
|
8024
8226
|
const count = previous?.fingerprint === fingerprint ? previous.count + 1 : 1;
|
|
8025
8227
|
if (previous === void 0) {
|
|
@@ -8122,7 +8324,9 @@ var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
|
|
|
8122
8324
|
}
|
|
8123
8325
|
async listModels(provider, opts) {
|
|
8124
8326
|
const connection = { ...this.deps.options() };
|
|
8125
|
-
const
|
|
8327
|
+
const cached = this.gatewayFacts.loaded(connection);
|
|
8328
|
+
const catalog = cached ?? await this.gatewayFacts.refresh(connection);
|
|
8329
|
+
if (cached !== void 0) this.gatewayFacts.refresh(connection).catch(() => void 0);
|
|
8126
8330
|
const toInfo = (model) => {
|
|
8127
8331
|
const vision = KNOWN_IMAGE_MODELS.has(model.id);
|
|
8128
8332
|
return {
|
|
@@ -8187,13 +8391,33 @@ var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
|
|
|
8187
8391
|
})) } } : {}
|
|
8188
8392
|
};
|
|
8189
8393
|
}
|
|
8190
|
-
/**
|
|
8191
|
-
|
|
8394
|
+
/**
|
|
8395
|
+
* Resolve and validate the key one account-endpoint call will use.
|
|
8396
|
+
*
|
|
8397
|
+
* Every account endpoint runs the chat path's own `assertUsableApiKey` check,
|
|
8398
|
+
* or a key carrying a character no HTTP header can hold throws inside `fetch`
|
|
8399
|
+
* BEFORE any I/O — once per endpoint — and the report reads as "the network is
|
|
8400
|
+
* down". The credential reference is read structurally: it belongs to the
|
|
8401
|
+
* host's resolved options, not to this adapter's connection type, and it only
|
|
8402
|
+
* names the key in the rejection message.
|
|
8403
|
+
*
|
|
8404
|
+
* Separate from {@link accountHeadersFor} so a caller that also needs to key a
|
|
8405
|
+
* cache by the same identity does not have to resolve the key twice.
|
|
8406
|
+
*/
|
|
8407
|
+
async usableAccountKey(apiKey) {
|
|
8192
8408
|
const connection = this.deps.options();
|
|
8193
8409
|
const raw = apiKey ?? await this.deps.resolveApiKey(connection);
|
|
8194
8410
|
const named = connection.apiKeyEnv;
|
|
8411
|
+
return assertUsableApiKey(raw, "llm-commandcode", typeof named === "string" && named !== "" ? named : "the stored credential");
|
|
8412
|
+
}
|
|
8413
|
+
/** The headers every authenticated account endpoint shares. */
|
|
8414
|
+
async accountHeaders(apiKey) {
|
|
8415
|
+
return this.accountHeadersFor(await this.usableAccountKey(apiKey));
|
|
8416
|
+
}
|
|
8417
|
+
/** Headers for an already-validated key; see {@link usableAccountKey}. */
|
|
8418
|
+
accountHeadersFor(key) {
|
|
8195
8419
|
return {
|
|
8196
|
-
Authorization: `Bearer ${
|
|
8420
|
+
Authorization: `Bearer ${key}`,
|
|
8197
8421
|
...IDENTITY_ENCODING_HEADER,
|
|
8198
8422
|
"x-command-code-version": COMMAND_CODE_CLI_VERSION,
|
|
8199
8423
|
"x-cli-environment": "production",
|
|
@@ -8313,6 +8537,42 @@ var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
|
|
|
8313
8537
|
return inflight;
|
|
8314
8538
|
}
|
|
8315
8539
|
/**
|
|
8540
|
+
* One account-endpoint GET, served from {@link accountJson} when `share` and
|
|
8541
|
+
* the same URL answered successfully within {@link ACCOUNT_FACTS_TTL_MS}.
|
|
8542
|
+
*
|
|
8543
|
+
* `timeoutMs` is the CALLER's budget; a cache hit spends none of it. Failures
|
|
8544
|
+
* are never cached, so this can only ever save a request that would have
|
|
8545
|
+
* re-read an answer this process already had.
|
|
8546
|
+
*
|
|
8547
|
+
* `share: false` marks a VOLATILE endpoint — one whose answer is the number
|
|
8548
|
+
* the user is actually looking at, and which must therefore never be replayed
|
|
8549
|
+
* from an earlier request. The usage card's summary is exactly that: its
|
|
8550
|
+
* headline counts and cost are the reason the card exists, and stamping a
|
|
8551
|
+
* cached copy with the current time would misreport when they were read.
|
|
8552
|
+
* Identity and billing facts are the opposite: a plan tier or an org id that
|
|
8553
|
+
* is 30 s old is still the truth, and those are what the picker duplicates.
|
|
8554
|
+
*/
|
|
8555
|
+
async accountJsonGet(apiBase, apiKey, path, timeoutMs, share = true) {
|
|
8556
|
+
const cacheKey = JSON.stringify([
|
|
8557
|
+
apiBase,
|
|
8558
|
+
apiKey,
|
|
8559
|
+
path
|
|
8560
|
+
]);
|
|
8561
|
+
if (share) {
|
|
8562
|
+
const hit = this.accountJson.get(cacheKey);
|
|
8563
|
+
if (hit !== void 0 && Date.now() - hit.at < ACCOUNT_FACTS_TTL_MS) return {
|
|
8564
|
+
status: 200,
|
|
8565
|
+
record: hit.record
|
|
8566
|
+
};
|
|
8567
|
+
}
|
|
8568
|
+
const answer = await this.fetchJson(`${apiBase}${path}`, this.accountHeadersFor(apiKey), timeoutMs);
|
|
8569
|
+
if (share && answer.record !== void 0) this.accountJson.set(cacheKey, {
|
|
8570
|
+
record: answer.record,
|
|
8571
|
+
at: Date.now()
|
|
8572
|
+
});
|
|
8573
|
+
return answer;
|
|
8574
|
+
}
|
|
8575
|
+
/**
|
|
8316
8576
|
* The billing facts behind the picker's plan filter, mirroring the CLI's
|
|
8317
8577
|
* `createBilling` flow: whoami yields the org id, then the subscriptions
|
|
8318
8578
|
* and credits endpoints answer in parallel. The plan id is honored only
|
|
@@ -8323,8 +8583,8 @@ var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
|
|
|
8323
8583
|
*/
|
|
8324
8584
|
async fetchBillingAccess(apiBase, apiKey) {
|
|
8325
8585
|
try {
|
|
8326
|
-
const
|
|
8327
|
-
const getJson = async (path) => (await this.
|
|
8586
|
+
const key = await this.usableAccountKey(apiKey);
|
|
8587
|
+
const getJson = async (path) => (await this.accountJsonGet(apiBase, key, path, MODELS_TIMEOUT_MS)).record;
|
|
8328
8588
|
const whoami = await getJson("/alpha/whoami");
|
|
8329
8589
|
const orgData = whoami && isRecord(whoami.org) ? whoami.org : void 0;
|
|
8330
8590
|
const orgId = orgData === void 0 ? void 0 : stringValue(orgData.id);
|
|
@@ -8431,9 +8691,9 @@ var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
|
|
|
8431
8691
|
async getUsage(apiKey, capturedConnection) {
|
|
8432
8692
|
const connection = capturedConnection ?? { ...this.deps.options() };
|
|
8433
8693
|
const base = connection.apiBase;
|
|
8434
|
-
let
|
|
8694
|
+
let key;
|
|
8435
8695
|
try {
|
|
8436
|
-
|
|
8696
|
+
key = await this.usableAccountKey(apiKey);
|
|
8437
8697
|
} catch (error) {
|
|
8438
8698
|
if (error instanceof LlmError && error.code === "INVALID_CREDENTIAL") return {
|
|
8439
8699
|
failures: [error.message],
|
|
@@ -8443,9 +8703,9 @@ var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
|
|
|
8443
8703
|
}
|
|
8444
8704
|
const failures = [];
|
|
8445
8705
|
const failed = [];
|
|
8446
|
-
const getJson = async (path) => {
|
|
8706
|
+
const getJson = async (path, share = true) => {
|
|
8447
8707
|
try {
|
|
8448
|
-
const { status, record, responseFailure } = await this.
|
|
8708
|
+
const { status, record, responseFailure } = await this.accountJsonGet(base, key, path, connection.requestTimeoutMs, share);
|
|
8449
8709
|
if (record === void 0) {
|
|
8450
8710
|
failures.push(`${path}: HTTP ${status}${responseFailure ? `;${responseFailure}` : ""}`);
|
|
8451
8711
|
failed.push(responseFailure ? {
|
|
@@ -8479,7 +8739,7 @@ var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
|
|
|
8479
8739
|
const rawStart = subData?.currentPeriodStart;
|
|
8480
8740
|
const periodStart = periodTimestampValue(rawStart);
|
|
8481
8741
|
const validStart = periodStart > 0 && Number.isFinite(new Date(periodStart).getTime());
|
|
8482
|
-
const totals = parseUsageTotals(await getJson(scopedPath("/alpha/usage/summary", validStart ? String(rawStart) : void 0)));
|
|
8742
|
+
const totals = parseUsageTotals(await getJson(scopedPath("/alpha/usage/summary", validStart ? String(rawStart) : void 0), false));
|
|
8483
8743
|
if (totals !== void 0) report.usage = totals;
|
|
8484
8744
|
const limits = parseCreditLimits(credits);
|
|
8485
8745
|
if (limits !== void 0) report.credits = limits;
|
|
@@ -8528,22 +8788,7 @@ var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
|
|
|
8528
8788
|
if (parsed === void 0) return void 0;
|
|
8529
8789
|
const windowLimits = isRecord(parsed.windowLimits) ? parsed.windowLimits : void 0;
|
|
8530
8790
|
if (windowLimits === void 0) return void 0;
|
|
8531
|
-
|
|
8532
|
-
let exceeded = false;
|
|
8533
|
-
let resetAt = 0;
|
|
8534
|
-
for (const raw of [windowLimits.fiveHour, windowLimits.weekly]) {
|
|
8535
|
-
const window = isRecord(raw) ? parseWindowLimit(raw) : void 0;
|
|
8536
|
-
if (window === void 0) continue;
|
|
8537
|
-
reported = true;
|
|
8538
|
-
if (!window.exceeded) continue;
|
|
8539
|
-
exceeded = true;
|
|
8540
|
-
if (window.resetAt > 0 && window.resetAt - Date.now() <= 2592e6) resetAt = Math.max(resetAt, window.resetAt);
|
|
8541
|
-
}
|
|
8542
|
-
if (!reported) return void 0;
|
|
8543
|
-
return {
|
|
8544
|
-
exceeded,
|
|
8545
|
-
resetAt: exceeded ? resetAt : 0
|
|
8546
|
-
};
|
|
8791
|
+
return windowVerdict([windowLimits.fiveHour, windowLimits.weekly].map((raw) => isRecord(raw) ? parseWindowLimit(raw) : void 0));
|
|
8547
8792
|
} catch {
|
|
8548
8793
|
return;
|
|
8549
8794
|
}
|
|
@@ -8634,23 +8879,15 @@ var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
|
|
|
8634
8879
|
if (attachments === void 0) throw new LlmError("Command Code image input requires the durable attachment service", "UNSUPPORTED_CONTENT");
|
|
8635
8880
|
readImage = requestImages(attachments, options.signal, timing);
|
|
8636
8881
|
}
|
|
8637
|
-
const finishCredentials = timing.phase("credentials");
|
|
8638
|
-
let apiKey;
|
|
8639
|
-
try {
|
|
8640
|
-
apiKey = await this.deps.resolveApiKey(connection, options.model);
|
|
8641
|
-
} finally {
|
|
8642
|
-
finishCredentials();
|
|
8643
|
-
}
|
|
8644
|
-
options.signal?.throwIfAborted();
|
|
8645
8882
|
let maxTokens = Math.min(budget.max, learnedOutputLimit(connection.apiBase, options.model));
|
|
8883
|
+
const builtMaxTokens = maxTokens;
|
|
8646
8884
|
const supported = KNOWN_EFFORTS[options.model];
|
|
8647
8885
|
const effort = options.purpose === "session-title" && supported?.includes("off") ? "off" : options.reasoningEffort;
|
|
8648
8886
|
const declared = effort !== void 0 && supported?.includes(effort) ? effort : void 0;
|
|
8649
8887
|
const reasoningEffort = effort === "off" && declared === void 0 ? void 0 : declared;
|
|
8650
8888
|
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");
|
|
8651
|
-
|
|
8652
|
-
let
|
|
8653
|
-
let requestOptions = withSurfaceOffload(options, this.surfaceOffload, REQUEST_IMAGE_BUDGETS[0], connection.offloadSeenImagesForCache === true && protocol === "cli" ? options.model : void 0);
|
|
8889
|
+
const projected = projectSurfaceBudget(options, this.surfaceOffload, REQUEST_IMAGE_BUDGETS[0]);
|
|
8890
|
+
let requestOptions = projected.options;
|
|
8654
8891
|
const buildBody = async (target) => {
|
|
8655
8892
|
const finishBody = timing.phase("body");
|
|
8656
8893
|
try {
|
|
@@ -8668,7 +8905,30 @@ var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
|
|
|
8668
8905
|
finishBody();
|
|
8669
8906
|
}
|
|
8670
8907
|
};
|
|
8671
|
-
|
|
8908
|
+
const provisionalProtocol = connection.protocol === "cli" ? "cli" : this.routesToMessages(options.model, modelEntry) ? "messages" : "openai";
|
|
8909
|
+
const finishCredentials = timing.phase("credentials");
|
|
8910
|
+
const credentials = this.deps.resolveApiKey(connection, options.model);
|
|
8911
|
+
credentials.catch(() => void 0);
|
|
8912
|
+
const provisionalBody = connection.offloadSeenImagesForCache === true || projected.budgetMissing > 0 ? void 0 : buildBody(provisionalProtocol);
|
|
8913
|
+
provisionalBody?.catch(() => void 0);
|
|
8914
|
+
let apiKey;
|
|
8915
|
+
try {
|
|
8916
|
+
apiKey = await credentials;
|
|
8917
|
+
} finally {
|
|
8918
|
+
finishCredentials();
|
|
8919
|
+
}
|
|
8920
|
+
options.signal?.throwIfAborted();
|
|
8921
|
+
maxTokens = Math.min(budget.max, learnedOutputLimit(connection.apiBase, options.model));
|
|
8922
|
+
let protocol = this.resolveProtocol(connection, apiKey, options.model, modelEntry);
|
|
8923
|
+
const cacheMissing = protocol === "cli" && connection.offloadSeenImagesForCache === true ? seenImageOffloadCount(requestOptions.messages, options.model) : 0;
|
|
8924
|
+
const missing = Math.max(projected.budgetMissing, cacheMissing);
|
|
8925
|
+
if (missing > 0) throw imageOffloadRequired(missing, cacheMissing > projected.budgetMissing ? "cache" : "budget");
|
|
8926
|
+
let messagesPermissionRejection;
|
|
8927
|
+
let body = provisionalBody !== void 0 && protocol === provisionalProtocol ? await provisionalBody : await buildBody(protocol);
|
|
8928
|
+
if (maxTokens !== builtMaxTokens) {
|
|
8929
|
+
budget.max = Math.min(budget.max, maxTokens);
|
|
8930
|
+
if (!omitOutputTokens(connection.maxOutputTokens, protocol, options.maxTokens)) assignMaxTokens(body, protocol, maxTokens);
|
|
8931
|
+
}
|
|
8672
8932
|
const serializedBodies = /* @__PURE__ */ new WeakMap();
|
|
8673
8933
|
const trace = openStreamTrace(`${options.provider}/${options.model}`);
|
|
8674
8934
|
const tried = /* @__PURE__ */ new Set();
|
|
@@ -8790,7 +9050,7 @@ var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
|
|
|
8790
9050
|
});
|
|
8791
9051
|
trace.close();
|
|
8792
9052
|
if (options.signal?.aborted) throw error;
|
|
8793
|
-
throw failureWithRequestId(this.gradeHeaderTimeout(error, protocol, body, connection.requestTimeoutMs, options.sessionId), responseMetadata);
|
|
9053
|
+
throw failureWithRequestId(this.gradeHeaderTimeout(error, protocol, body, connection.requestTimeoutMs, options.sessionId, serializedBodies.get(body)), responseMetadata);
|
|
8794
9054
|
}
|
|
8795
9055
|
if (connected === void 0) throw new LlmError("Command Code API connection failed without a response", "TRANSPORT");
|
|
8796
9056
|
const { response, cleanup } = connected;
|
|
@@ -11888,18 +12148,24 @@ function apply(ctx, config) {
|
|
|
11888
12148
|
const usageReports = async () => {
|
|
11889
12149
|
const connection = options();
|
|
11890
12150
|
const accounts = scopedPool(connection);
|
|
12151
|
+
const keyed = new Map((await accounts.describeAccounts()).map((account) => [account.slot.id, account.key]));
|
|
12152
|
+
const reports = await Promise.all(connection.accountSelection.slots.map(async (slot) => {
|
|
12153
|
+
const key = keyed.get(slot.id);
|
|
12154
|
+
if (key === void 0) return { failures: [] };
|
|
12155
|
+
try {
|
|
12156
|
+
const report = await adapter.getUsage(key, connection);
|
|
12157
|
+
if (report.credits !== void 0) accounts.noteWindowReport(key, windowVerdict([report.credits.fiveHour, report.credits.weekly]));
|
|
12158
|
+
return report;
|
|
12159
|
+
} catch (error) {
|
|
12160
|
+
return { failures: [readableErrorText(error)] };
|
|
12161
|
+
}
|
|
12162
|
+
}));
|
|
11891
12163
|
const active = await accounts.activeAccount();
|
|
11892
12164
|
const described = await accounts.describeAccounts();
|
|
11893
12165
|
const byId = new Map(described.map((account) => [account.slot.id, account]));
|
|
11894
|
-
return { accounts:
|
|
12166
|
+
return { accounts: connection.accountSelection.slots.map((slot, index) => {
|
|
11895
12167
|
const account = byId.get(slot.id);
|
|
11896
|
-
|
|
11897
|
-
if (account === void 0) report = { failures: [] };
|
|
11898
|
-
else try {
|
|
11899
|
-
report = await adapter.getUsage(account.key, connection);
|
|
11900
|
-
} catch (error) {
|
|
11901
|
-
report = { failures: [readableErrorText(error)] };
|
|
11902
|
-
}
|
|
12168
|
+
const report = reports[index] ?? { failures: [] };
|
|
11903
12169
|
const state = account?.state;
|
|
11904
12170
|
const usable = accountUsable(state);
|
|
11905
12171
|
return {
|
|
@@ -11911,7 +12177,7 @@ function apply(ctx, config) {
|
|
|
11911
12177
|
cooldownUntil: !usable && state?.kind === "cooldown" ? state.until : 0,
|
|
11912
12178
|
report
|
|
11913
12179
|
};
|
|
11914
|
-
})
|
|
12180
|
+
}) };
|
|
11915
12181
|
};
|
|
11916
12182
|
const commandLocale = () => pickCommandLocale(current().lang);
|
|
11917
12183
|
ctx.inject(["commands"], (commandCtx) => {
|