@mars-sea/dsh-commandcode-provider 0.13.3 → 0.14.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/lib/client.js CHANGED
@@ -36,13 +36,15 @@ window.__ModuleLoader__.load({
36
36
  };
37
37
  }
38
38
  //#endregion
39
- //#region src/client/error-text.ts
39
+ //#region src/readable-error.ts
40
40
  /**
41
- * Turn a caught value into readable text for the settings page's error UI.
41
+ * Readable error text for the settings page AND the Host-side failure paths.
42
42
  *
43
- * @module dsh-commandcode-provider/client/error-text
43
+ * @module dsh-commandcode-provider/readable-error
44
44
  */
45
45
  /**
46
+ * Turn a caught value into readable text.
47
+ *
46
48
  * `error instanceof Error ? error.message : String(error)` degrades to the
47
49
  * literal string `[object Object]` for a plain object with no `Error`
48
50
  * prototype — which is exactly what a Remote call across the webview/host
@@ -52,6 +54,11 @@ window.__ModuleLoader__.load({
52
54
  * off the plain object first recovers the original text in that case; only a
53
55
  * value with neither an `Error` prototype nor a usable `message` field falls
54
56
  * through to a JSON dump.
57
+ *
58
+ * Shared by both halves on purpose. The browser client needs it for rejections
59
+ * that crossed the RPC boundary, and the Host needs it for values that reached
60
+ * it from a transport, a JSON body, or a user-thrown non-Error — the same
61
+ * degradation, one owner.
55
62
  */
56
63
  function readableErrorText(error) {
57
64
  if (error instanceof Error) return error.message;
@@ -482,6 +489,88 @@ window.__ModuleLoader__.load({
482
489
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return {};
483
490
  return Object.fromEntries(Object.entries(raw).filter(([id, value]) => id !== "" && typeof value === "boolean"));
484
491
  }
492
+ /** 自定义上下文长度接受的上界(token);覆盖任何已知模型窗口仍留有余量。 */
493
+ const CONTEXT_WINDOW_MAX = 1e7;
494
+ /**
495
+ * 判定一个值能否作为自定义上下文长度:边界内的安全整数,符合宿主模型元数据约束。
496
+ *
497
+ * 程序化构造的连接可能绕过 schemastery 归一化,所以 `resolveAdapterOptions`
498
+ * 也要重新判定——越界或非数值直接丢弃,回落到目录公布值,而不是让请求带着
499
+ * 一个网关会拒绝的窗口上线。
500
+ */
501
+ function isValidContextWindow(value) {
502
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 1 && value <= 1e7;
503
+ }
504
+ /**
505
+ * 只接收合法条目的按模型覆盖表;空表、非对象与非法数值一律视为未设置。
506
+ * 配置校验会拒绝非整数或越界值;本函数额外保护绕过配置校验的程序化调用,
507
+ * 丢弃坏条目,不把非法窗口交给宿主。返回新对象;未设置时为 `undefined`。
508
+ */
509
+ function readContextWindowOverrides(raw) {
510
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return void 0;
511
+ const entries = Object.entries(raw).filter((entry) => entry[0] !== "" && isValidContextWindow(entry[1]));
512
+ return entries.length === 0 ? void 0 : Object.fromEntries(entries);
513
+ }
514
+ //#endregion
515
+ //#region src/output-budget.ts
516
+ /**
517
+ * Output-budget bounds shared by the Host, the browser settings page and the
518
+ * adapter (issue #74).
519
+ *
520
+ * The module stays pure (no node imports) so the client bundle can inline it,
521
+ * exactly like `context-window.ts`. The report behind issue #74 asked for a
522
+ * knob that stops the adapter from writing `max_tokens` at all; that is the
523
+ * `0` sentinel here rather than a separate `null` spelling, because the value
524
+ * travels through one numeric settings field on both surfaces.
525
+ */
526
+ /**
527
+ * The output budget this bundle asks for, and the ceiling it will never exceed.
528
+ *
529
+ * 131 072, chosen against a measured provider limit rather than a round guess.
530
+ * Probing `/alpha/generate` directly on 2026-09-28: 64 000 → 200, 131 072 → 200,
531
+ * 196 608 → 200, **200 000 → 200, 200 704 → 400**, 1 000 000 → 400. The server
532
+ * therefore caps `max_tokens` at 200 000, and it does so identically for a
533
+ * 262 144-window model (`stealth/pixel-canary`, retired by Command Code on
534
+ * 2026-10-01 and named here only as the probe's subject) and a 1 000 000-window
535
+ * one (`stealth/space-bunny-alpha`) — the cap is global, not per model. 131 072
536
+ * keeps a 35 % margin under it while covering the real output ceiling of the
537
+ * models that have one (Claude 64 K, Gemini 64 K, several at 128 K).
538
+ *
539
+ * The old 64 000 was never a provider requirement: it cost nothing in latency
540
+ * (a 64 000 request answered headers in 1.28 s against 1.57 s for 131 072) but
541
+ * it did cap what a model could ever say in one turn.
542
+ *
543
+ * No client-side clamp is applied on top of this: an earlier version pre-shrank
544
+ * `max_tokens` from an estimate of the prompt already sent (`requestContextBudget()`,
545
+ * issue #67), but that estimate over-corrected on ordinary long conversations —
546
+ * exactly the "output gets cut short as the conversation grows" symptom issue #74
547
+ * reported — while a live A/B probe (2026-09-30, `stealth/space-bunny-alpha`,
548
+ * ~19.6% over its 1,000,000-token window with no client-side shrink) showed the
549
+ * endpoint answers a genuine overflow with an unambiguous `400 … exceeds the
550
+ * model's context window`, which `isContextOverflowDetail()` classifies as
551
+ * `CONTEXT_WINDOW_EXCEEDED` independently of this constant.
552
+ *
553
+ * A user-set positive budget can only TIGHTEN this, never raise it — a figure
554
+ * above the cap would be refused by the gateway anyway, so the settings field
555
+ * refuses it at the input instead of spending a round trip to learn that.
556
+ *
557
+ * Lives here rather than in `adapter.ts` so the browser settings page can bound
558
+ * its input by the same figure the Host enforces; `adapter.ts` re-exports it
559
+ * under its historical name.
560
+ */
561
+ const MAX_OUTPUT_TOKEN_LIMIT = 131072;
562
+ /**
563
+ * Whether a value can serve as a user output budget: the omit sentinel, or a
564
+ * whole token count inside the gateway's own ceiling.
565
+ *
566
+ * Programmatically built connections can bypass schemastery normalization, so
567
+ * the resolve step re-judges every value here and drops an out-of-range one
568
+ * back to "unset" instead of letting the request carry a budget the gateway
569
+ * refuses.
570
+ */
571
+ function isValidMaxOutputTokens(value) {
572
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 && value <= 131072;
573
+ }
485
574
  //#endregion
486
575
  //#region src/client/settings-write.ts
487
576
  /** 设置写入过程:同一控制器的意图按顺序执行,配置确认与凭据确认分别保留。
@@ -579,6 +668,11 @@ window.__ModuleLoader__.load({
579
668
  value: Object.fromEntries([...keys].map((id) => [id, list.length === 0 || list.includes(id)]))
580
669
  });
581
670
  }
671
+ if (owned.kind === "save" && owned.contextWindowOverrides !== void 0) ops.push({
672
+ op: "set",
673
+ path: ["contextWindowOverrides"],
674
+ value: owned.contextWindowOverrides
675
+ });
582
676
  const baseline = new Map(ops.map((op) => [op.path[0], structuredClone(fieldBaseline(snapshot, op.path[0]))]));
583
677
  const defaultRef = owned.kind === "save" && owned.key !== void 0 || owned.kind === "key" && owned.target === "default" ? this.defaultRef() : void 0;
584
678
  return new Promise((resolve) => {
@@ -929,6 +1023,59 @@ window.__ModuleLoader__.load({
929
1023
  };
930
1024
  }
931
1025
  /**
1026
+ * The compact token-count shorthand (`32K`, `1M`) in BINARY units, the way
1027
+ * context windows are quoted: 32K → 32 768 (32 × 1024), 128K → 131 072,
1028
+ * 1M → 1 048 576 (1024 × 1024), so the common power-of-two windows land on
1029
+ * their exact values instead of 1000-based rounding. A leading sign is not part
1030
+ * of the grammar, so this path can only produce values ≥ 0.
1031
+ */
1032
+ const COMPACT_TOKENS = /^(\d+(?:\.\d+)?)\s*([kKmM])$/;
1033
+ /**
1034
+ * A whole-token count field with optional compact shorthand, shared by the
1035
+ * context-length and output-budget inputs (issues #86, #74).
1036
+ *
1037
+ * Plain text goes through the {@link numberField} parse plus the caller's
1038
+ * whole-token validator; shorthand converts to whole tokens first
1039
+ * (`Math.round` keeps fractional shorthand like `1.5K` on whole tokens) and is
1040
+ * then bounds-checked against the SAME `bounds`, so an overshoot names
1041
+ * `tooLarge` here instead of reaching the Host schema, which would reject it at
1042
+ * save time with only a generic failure. Empty clears in both paths.
1043
+ *
1044
+ * `isValid` is what tells the two apart: context lengths start at 1, while the
1045
+ * output budget's lower bound IS its omit sentinel (`0` means "send no
1046
+ * `max_tokens`"), so no single numeric range can serve both.
1047
+ */
1048
+ function tokenCountField(field, bounds, isValid) {
1049
+ const plain = numberField$1(field, bounds);
1050
+ return {
1051
+ field,
1052
+ format: plain.format,
1053
+ parse: (text) => {
1054
+ const compact = COMPACT_TOKENS.exec(text.trim());
1055
+ if (compact === null) {
1056
+ const parsed = plain.parse(text);
1057
+ return parsed.kind === "set" && !isValid(parsed.value) ? {
1058
+ kind: "invalid",
1059
+ reason: "format"
1060
+ } : parsed;
1061
+ }
1062
+ const value = Math.round(Number(compact[1]) * (compact[2].toLowerCase() === "k" ? 1024 : 1048576));
1063
+ if (value < bounds.min) return {
1064
+ kind: "invalid",
1065
+ reason: "tooSmall"
1066
+ };
1067
+ if (value > bounds.max) return {
1068
+ kind: "invalid",
1069
+ reason: "tooLarge"
1070
+ };
1071
+ return {
1072
+ kind: "set",
1073
+ value
1074
+ };
1075
+ }
1076
+ };
1077
+ }
1078
+ /**
932
1079
  * A boolean field, staged as the strings `'true'`/`'false'` (an empty draft
933
1080
  * clears it). The component renders a toggle and only ever stages these two
934
1081
  * strings; anything else blocks save.
@@ -956,6 +1103,23 @@ window.__ModuleLoader__.load({
956
1103
  };
957
1104
  }
958
1105
  /**
1106
+ * The single-turn output budget (issue #74).
1107
+ *
1108
+ * The range INCLUDES `0`, and `0` is not a budget: it is the sentinel asking
1109
+ * the adapter to send no `max_tokens` field at all, leaving the per-turn output
1110
+ * size to the platform. The upper bound is the gateway's own cap
1111
+ * ({@link MAX_OUTPUT_TOKEN_LIMIT}) rather than an arbitrary one — a larger
1112
+ * figure would simply be refused, so the input is where that is worth saying. A
1113
+ * blank draft clears the setting back to the derived default, which is the
1114
+ * behaviour every existing profile has.
1115
+ */
1116
+ function maxOutputTokensField(field) {
1117
+ return tokenCountField(field, {
1118
+ min: 0,
1119
+ max: MAX_OUTPUT_TOKEN_LIMIT
1120
+ }, isValidMaxOutputTokens);
1121
+ }
1122
+ /**
959
1123
  * 秒制输入保留毫秒精度,兼容已有 1500ms、1ms 等配置。
960
1124
  *
961
1125
  * Milliseconds are the wrong unit to show: the defaults are 300000 and the
@@ -1007,6 +1171,11 @@ window.__ModuleLoader__.load({
1007
1171
  min: 0,
1008
1172
  max: 50
1009
1173
  }),
1174
+ tokenCountField("contextWindow", {
1175
+ min: 1,
1176
+ max: CONTEXT_WINDOW_MAX
1177
+ }, isValidContextWindow),
1178
+ maxOutputTokensField("maxOutputTokens"),
1010
1179
  booleanField$1("offloadSeenImagesForCache"),
1011
1180
  booleanField$1("filterModelsByPlan"),
1012
1181
  booleanField$1("webSearch"),
@@ -1034,6 +1203,8 @@ window.__ModuleLoader__.load({
1034
1203
  accountResult;
1035
1204
  /** Staged visible-model allowlist (undefined = no draft). */
1036
1205
  visibleModelsDraft = void 0;
1206
+ /** Staged per-model context-length overrides as raw text (undefined = no draft, issue #86). */
1207
+ contextWindowOverridesDraft = void 0;
1037
1208
  catalogModels = [];
1038
1209
  catalogFailed = false;
1039
1210
  saving = false;
@@ -1061,6 +1232,7 @@ window.__ModuleLoader__.load({
1061
1232
  this.writer.dispose();
1062
1233
  this.staged.clear();
1063
1234
  this.visibleModelsDraft = void 0;
1235
+ this.contextWindowOverridesDraft = void 0;
1064
1236
  for (const dispose of this.disposers) dispose();
1065
1237
  this.disposers.length = 0;
1066
1238
  this.listeners.clear();
@@ -1122,10 +1294,13 @@ window.__ModuleLoader__.load({
1122
1294
  accountEnrollmentRefs: this.enrollmentRefs(false),
1123
1295
  visibleModels: this.effectiveVisibleModels(),
1124
1296
  visibleModelsAll: this.visibleModelsDraft === void 0 ? this.storedShowsAll() : this.visibleModelsDraft.length === 0,
1297
+ contextWindow: this.field("contextWindow"),
1298
+ maxOutputTokens: this.field("maxOutputTokens"),
1299
+ contextWindowOverrides: this.effectiveContextWindowOverrides(),
1125
1300
  catalogModels: this.catalogModels,
1126
1301
  catalogFailed: this.catalogFailed,
1127
- dirty: plan.length > 0 || this.visibleModelsDirty(),
1128
- invalid: plan.some((item) => item.change === void 0),
1302
+ dirty: plan.length > 0 || this.visibleModelsDirty() || this.contextWindowOverridesDirty(),
1303
+ invalid: plan.some((item) => item.change === void 0) || this.effectiveContextWindowOverrides().some((row) => row.invalid),
1129
1304
  saving: this.saving,
1130
1305
  failed: this.failed,
1131
1306
  savedCount: this.savedCount,
@@ -1173,9 +1348,10 @@ window.__ModuleLoader__.load({
1173
1348
  }
1174
1349
  /** Discard every staged edit. */
1175
1350
  discard() {
1176
- if (this.staged.size === 0 && this.visibleModelsDraft === void 0 && !this.failed) return;
1351
+ if (this.staged.size === 0 && this.visibleModelsDraft === void 0 && this.contextWindowOverridesDraft === void 0 && !this.failed) return;
1177
1352
  this.staged.clear();
1178
1353
  this.visibleModelsDraft = void 0;
1354
+ this.contextWindowOverridesDraft = void 0;
1179
1355
  this.failed = false;
1180
1356
  this.saveResult = void 0;
1181
1357
  this.publish();
@@ -1202,9 +1378,12 @@ window.__ModuleLoader__.load({
1202
1378
  if (this.disposed || this.saving) return;
1203
1379
  const submitted = new Map(this.staged);
1204
1380
  const submittedVisible = this.visibleModelsDraft;
1381
+ const submittedContextOverrides = this.contextWindowOverridesDraft;
1205
1382
  const plan = this.plan();
1206
1383
  const visibleDirty = this.visibleModelsDirty();
1207
- if (plan.length === 0 && !visibleDirty || plan.some((item) => item.change === void 0)) return;
1384
+ const contextOverrides = this.parsedContextWindowOverrides();
1385
+ const contextOverridesDirty = this.contextWindowOverridesDirty();
1386
+ if (plan.length === 0 && !visibleDirty && !contextOverridesDirty || plan.some((item) => item.change === void 0) || contextOverrides.invalid) return;
1208
1387
  const ops = plan.flatMap((item) => typeof item.change === "object" ? [item.change] : []);
1209
1388
  const key = plan.find((item) => item.field === "apiKey")?.change;
1210
1389
  this.saving = true;
@@ -1214,6 +1393,7 @@ window.__ModuleLoader__.load({
1214
1393
  kind: "save",
1215
1394
  ops,
1216
1395
  ...visibleDirty ? { visibleModels: submittedVisible ?? [] } : {},
1396
+ ...contextOverridesDirty ? { contextWindowOverrides: contextOverrides.values } : {},
1217
1397
  ...typeof key === "string" ? { key } : {}
1218
1398
  });
1219
1399
  this.publish();
@@ -1225,6 +1405,7 @@ window.__ModuleLoader__.load({
1225
1405
  if (!this.failed) this.savedCount += 1;
1226
1406
  for (const [field, draft] of submitted) if ((field === "apiKey" ? outcome.credential === "confirmed" && outcome.issue === void 0 : outcome.config === "confirmed" || writeCompleted(outcome)) && this.staged.get(field) === draft) this.staged.delete(field);
1227
1407
  if ((outcome.config === "confirmed" || writeCompleted(outcome)) && this.visibleModelsDraft === submittedVisible) this.visibleModelsDraft = void 0;
1408
+ if ((outcome.config === "confirmed" || writeCompleted(outcome)) && this.contextWindowOverridesDraft === submittedContextOverrides) this.contextWindowOverridesDraft = void 0;
1228
1409
  this.publish();
1229
1410
  return outcome;
1230
1411
  }
@@ -1242,6 +1423,63 @@ window.__ModuleLoader__.load({
1242
1423
  this.saveResult = void 0;
1243
1424
  this.publish();
1244
1425
  }
1426
+ /**
1427
+ * Stage the whole per-model context-length map (issue #86): one entry per
1428
+ * row, the value the row's input shows verbatim (blank = drop the override
1429
+ * on save). The component rebuilds the map from its projected rows, so a
1430
+ * model reselect moves the value to its new key in the same edit.
1431
+ */
1432
+ editContextWindowOverrides(next) {
1433
+ this.contextWindowOverridesDraft = { ...next };
1434
+ this.failed = false;
1435
+ this.saveResult = void 0;
1436
+ this.publish();
1437
+ }
1438
+ /** The stored per-model overrides, already cleaned by the shared validity rule. */
1439
+ storedContextWindowOverrides() {
1440
+ return { ...readContextWindowOverrides(this.sectionValue("contextWindowOverrides")) };
1441
+ }
1442
+ /** Effective rows: the staged draft verbatim, or the stored values formatted. */
1443
+ effectiveContextWindowOverrides() {
1444
+ const draft = this.contextWindowOverridesDraft;
1445
+ const source = draft !== void 0 ? draft : Object.fromEntries(Object.entries(this.storedContextWindowOverrides()).map(([model, value]) => [model, String(value)]));
1446
+ return Object.entries(source).filter(([model]) => model !== "").map(([model, text]) => {
1447
+ const parsed = this.spec("contextWindow").parse(text);
1448
+ return {
1449
+ model,
1450
+ text,
1451
+ invalid: parsed.kind === "invalid",
1452
+ invalidReason: parsed.kind === "invalid" ? parsed.reason : void 0
1453
+ };
1454
+ });
1455
+ }
1456
+ /**
1457
+ * The draft parsed for the write: blank rows and unparseable rows drop out
1458
+ * (an invalid row blocks save on its own), everything else is the value the
1459
+ * writer puts at `contextWindowOverrides`.
1460
+ */
1461
+ parsedContextWindowOverrides() {
1462
+ const values = {};
1463
+ let invalid = false;
1464
+ for (const row of this.effectiveContextWindowOverrides()) {
1465
+ if (row.invalid) {
1466
+ invalid = true;
1467
+ continue;
1468
+ }
1469
+ const parsed = this.spec("contextWindow").parse(row.text);
1470
+ if (parsed.kind === "set" && typeof parsed.value === "number") values[row.model] = parsed.value;
1471
+ }
1472
+ return {
1473
+ values,
1474
+ invalid
1475
+ };
1476
+ }
1477
+ contextWindowOverridesDirty() {
1478
+ if (this.contextWindowOverridesDraft === void 0) return false;
1479
+ const values = this.parsedContextWindowOverrides().values;
1480
+ const stored = this.storedContextWindowOverrides();
1481
+ return [.../* @__PURE__ */ new Set([...Object.keys(values), ...Object.keys(stored)])].some((model) => values[model] !== stored[model]);
1482
+ }
1245
1483
  renameAccount(ref, label) {
1246
1484
  return this.runAccountOp("rename", {
1247
1485
  kind: "rename",
@@ -1519,12 +1757,51 @@ window.__ModuleLoader__.load({
1519
1757
  function formatMoneyExact(value) {
1520
1758
  return `$${value.toFixed(4)}`;
1521
1759
  }
1522
- function formatTokensCompact(value) {
1523
- if (value >= 1e9) return `${(value / 1e9).toFixed(1)}B`;
1524
- if (value >= 1e6) return `${(value / 1e6).toFixed(1)}M`;
1525
- if (value >= 1e3) return `${(value / 1e3).toFixed(1)}K`;
1760
+ /**
1761
+ * 紧凑数字:K/M/B 三级,一切档位决策的唯一实现。
1762
+ *
1763
+ * 档位必须**回头判断**:先除以档位再四舍五入会算出 `999_999 → "1000.0K"`、
1764
+ * `999_999_999 → "1000.0M"` 这类既超长、又落在并不存在的档位上的结果,
1765
+ * 因此每个档位算完后若已经够到下一档,就改用下一档重算一次。
1766
+ *
1767
+ * @param value - 非负计数或词元数。
1768
+ * @param keepZeroDecimal - 整数值是否保留一位小数。词元显示沿用 `3.0B` 的旧样式;
1769
+ * 计数显示去掉多余的 `.0`,见 {@link formatCompactCount}。
1770
+ * @returns 紧凑文本;不足 1000 时原样返回该整数。
1771
+ */
1772
+ function compact(value, keepZeroDecimal) {
1773
+ const rounded = (scaled) => Math.round(scaled * 10) / 10;
1774
+ const text = (scaled) => {
1775
+ const fixed = rounded(scaled);
1776
+ return keepZeroDecimal ? fixed.toFixed(1) : String(fixed);
1777
+ };
1778
+ if (value >= 1e9) return `${text(value / 1e9)}B`;
1779
+ if (value >= 1e6) return rounded(value / 1e6) >= 1e3 ? `${text(value / 1e9)}B` : `${text(value / 1e6)}M`;
1780
+ if (value >= 1e3) return rounded(value / 1e3) >= 1e3 ? `${text(value / 1e6)}M` : `${text(value / 1e3)}K`;
1526
1781
  return String(value);
1527
1782
  }
1783
+ /**
1784
+ * 紧凑计数:请求数、成功/失败数这类会被窄卡片截断的整数。
1785
+ * `1234` → `1.2K`、`12000` → `12K`、`1500000` → `1.5M`,整数不带多余的 `.0`。
1786
+ */
1787
+ function formatCompactCount(value) {
1788
+ return compact(value, false);
1789
+ }
1790
+ /** 紧凑词元数:与 {@link formatCompactCount} 共用档位,但整数值保留一位小数(`3.0B`)。 */
1791
+ function formatTokensCompact(value) {
1792
+ return compact(value, true);
1793
+ }
1794
+ /**
1795
+ * 精确值悬停提示:只在紧凑显示确实丢了信息时才返回文本。
1796
+ * 未换算时悬停弹出的文字与卡片上写的一模一样,没有意义,因此返回 `undefined` 让调用方
1797
+ * 整个省略 `title`,而不是挂一个内容重复的提示。
1798
+ *
1799
+ * @param parts - 标签与精确数值,按卡片上的显示顺序。
1800
+ * @returns 形如 `请求数 1234567 · 成功 1234567 · 失败 123` 的提示,或 `undefined`。
1801
+ */
1802
+ function compactCountTitle(parts) {
1803
+ return parts.some(([, value]) => formatCompactCount(value) !== String(value)) ? parts.map(([label, value]) => `${label} ${value}`).join(" · ") : void 0;
1804
+ }
1528
1805
  /** The input is already in percent units; callers add the percent sign. */
1529
1806
  function formatSuccessRate(value) {
1530
1807
  return String(Number(value.toFixed(2)));
@@ -2436,6 +2713,30 @@ window.__ModuleLoader__.load({
2436
2713
  };
2437
2714
  //#endregion
2438
2715
  //#region src/enrollment-wire.ts
2716
+ const ENROLLMENT_MESSAGE_KEYS = [
2717
+ "enrollmentCreating",
2718
+ "enrollmentWaiting",
2719
+ "enrollmentWriting",
2720
+ "enrollmentNaming",
2721
+ "enrollmentNameFailed",
2722
+ "enrollmentFinished",
2723
+ "enrollmentCancelling",
2724
+ "enrollmentCancelled",
2725
+ "enrollmentCancelledCleaned",
2726
+ "enrollmentFailed",
2727
+ "enrollmentFailedCleaned",
2728
+ "enrollmentCleanupPending",
2729
+ "enrollmentCleanupFailed",
2730
+ "enrollmentRecordNaming",
2731
+ "enrollmentRecordCleanup",
2732
+ "enrollmentBeginFailed",
2733
+ "enrollmentCancelUnconfirmed",
2734
+ "enrollmentNameRequestFailed",
2735
+ "enrollmentCleanupRequestFailed",
2736
+ "enrollmentDisconnected",
2737
+ "enrollmentHostUnavailable",
2738
+ "enrollmentStatusUnavailable"
2739
+ ];
2439
2740
  const guard = makeBoundaryValidator("commandcode/enrollment");
2440
2741
  const phases = [
2441
2742
  "creating",
@@ -2452,11 +2753,13 @@ window.__ModuleLoader__.load({
2452
2753
  const source = guard.record(value, "state");
2453
2754
  const phase = source.phase;
2454
2755
  if (!phases.includes(phase)) return guard.reject("phase");
2756
+ const messageKey = source.messageKey;
2757
+ if (!ENROLLMENT_MESSAGE_KEYS.includes(messageKey)) return guard.reject("messageKey");
2455
2758
  const state = {
2456
2759
  id: guard.stringField(source, "id", "id"),
2457
2760
  ref: guard.stringField(source, "ref", "ref"),
2458
2761
  phase,
2459
- message: guard.stringField(source, "message", "message")
2762
+ messageKey
2460
2763
  };
2461
2764
  for (const field of ["authUrl", "suggestedName"]) if (source[field] !== void 0) state[field] = guard.stringField(source, field, field);
2462
2765
  return state;
@@ -2611,7 +2914,7 @@ window.__ModuleLoader__.load({
2611
2914
  id,
2612
2915
  ref: "",
2613
2916
  phase: "creating",
2614
- message: "正在创建账号"
2917
+ messageKey: "enrollmentCreating"
2615
2918
  }
2616
2919
  });
2617
2920
  try {
@@ -2632,7 +2935,7 @@ window.__ModuleLoader__.load({
2632
2935
  this.receive(id, result);
2633
2936
  } catch {
2634
2937
  if (this.id === id) this.cancel();
2635
- this.failure("账号开通请求失败,请检查连接后重试");
2938
+ this.failure("enrollmentBeginFailed");
2636
2939
  }
2637
2940
  }
2638
2941
  /** 页面可立即离开;宿主仍等待写入和补偿收尾。 */
@@ -2651,7 +2954,7 @@ window.__ModuleLoader__.load({
2651
2954
  })).then(() => {
2652
2955
  this.changed();
2653
2956
  this.refresh();
2654
- }, () => this.failure("取消请求未确认,重新连接后请检查未完成记录"));
2957
+ }, () => this.failure("enrollmentCancelUnconfirmed"));
2655
2958
  }
2656
2959
  async name(name) {
2657
2960
  const id = this.id;
@@ -2663,7 +2966,7 @@ window.__ModuleLoader__.load({
2663
2966
  ...name ? { name } : {}
2664
2967
  }));
2665
2968
  } catch {
2666
- this.failure("名称保存失败,请重试或接受已有名称");
2969
+ this.failure("enrollmentNameRequestFailed");
2667
2970
  }
2668
2971
  }
2669
2972
  async recover(record, name) {
@@ -2673,7 +2976,7 @@ window.__ModuleLoader__.load({
2673
2976
  pageId: this.pageId,
2674
2977
  ...name ? { name } : {}
2675
2978
  });
2676
- if (!result.ok) this.failure("收尾未完成,请重试");
2979
+ if (!result.ok) this.failure("enrollmentCleanupPending");
2677
2980
  else if (!this.disposed && this.store.getSnapshot().active?.id === record.id) this.store.set({
2678
2981
  ...this.store.getSnapshot(),
2679
2982
  active: result.value,
@@ -2682,7 +2985,7 @@ window.__ModuleLoader__.load({
2682
2985
  this.changed();
2683
2986
  await this.refresh();
2684
2987
  } catch {
2685
- this.failure("收尾请求失败,请检查连接后重试");
2988
+ this.failure("enrollmentCleanupRequestFailed");
2686
2989
  }
2687
2990
  }
2688
2991
  dispose() {
@@ -2707,7 +3010,7 @@ window.__ModuleLoader__.load({
2707
3010
  this.watch = void 0;
2708
3011
  this.ready = void 0;
2709
3012
  this.cancel();
2710
- this.failure("开通连接已断开,宿主正在收尾;重连后请检查未完成记录");
3013
+ this.failure("enrollmentDisconnected");
2711
3014
  }
2712
3015
  }
2713
3016
  })();
@@ -2724,7 +3027,7 @@ window.__ModuleLoader__.load({
2724
3027
  receive(id, result) {
2725
3028
  if (this.disposed || this.id !== id) return;
2726
3029
  if (!result.ok) {
2727
- this.failure("宿主无法完成账号开通,请检查连接与写入权限");
3030
+ this.failure("enrollmentHostUnavailable");
2728
3031
  return;
2729
3032
  }
2730
3033
  const active = result.value;
@@ -2756,16 +3059,17 @@ window.__ModuleLoader__.load({
2756
3059
  pageId: this.pageId
2757
3060
  }));
2758
3061
  } catch {
2759
- this.failure("开通状态暂不可用,正在等待重新连接");
3062
+ this.failure("enrollmentStatusUnavailable");
2760
3063
  if (!this.disposed && this.id === id) this.timer = setTimeout(() => {
2761
3064
  this.poll(id);
2762
3065
  }, this.pollMs);
2763
3066
  }
2764
3067
  }
2765
- failure(message) {
3068
+ /** 记下要显示的语义键;文案在渲染处按界面语言取,本控制器不接触成品文本。 */
3069
+ failure(key) {
2766
3070
  if (!this.disposed) this.store.set({
2767
3071
  ...this.store.getSnapshot(),
2768
- error: message
3072
+ error: key
2769
3073
  });
2770
3074
  }
2771
3075
  stopTimer() {
@@ -2803,7 +3107,7 @@ window.__ModuleLoader__.load({
2803
3107
  filterModelsByPlan: "隐藏套餐外模型",
2804
3108
  filterModelsByPlanHint: "只显示套餐内模型;按需余额账户仍显示全部。",
2805
3109
  webSearch: "用 Command Code 承载联网搜索",
2806
- webSearchHint: "使用同一密钥承载 dsh 的 web_search;关闭后恢复原搜索后端。",
3110
+ webSearchHint: "使用同一密钥承载 dsh 的 web_search;关闭后恢复原搜索后端。若「网页搜索」设置里已显式选择其他后端,本开关不生效。",
2807
3111
  showSidebarQuota: "在侧边栏显示额度卡片",
2808
3112
  showSidebarQuotaHint: "在侧边栏底部显示套餐与额度卡片,点击可打开完整仪表盘。默认关闭,关闭时不会为它后台刷新用量。",
2809
3113
  zdr: "零数据保留(ZDR)",
@@ -2824,6 +3128,23 @@ window.__ModuleLoader__.load({
2824
3128
  visibleModelsShowAll: "显示全部",
2825
3129
  visibleModelsStaleHint: "有 {count} 个已选模型在目录中找不到了(可能已下架),不影响其他模型;可清理或保留。",
2826
3130
  visibleModelsCleanStale: "清理失效({count})",
3131
+ contextWindow: "自定义上下文长度",
3132
+ contextWindowHint: "覆盖模型目录公布的上下文窗口(token):上下文计量、自动压缩时机与模型选择器的上下文标注都按此值计算,对未单独设置的模型生效。留空则跟随目录;支持 32K、1M 这样的简写(二进制换算:32K = 32 768,1M = 1 048 576)。",
3133
+ contextOverridesTitle: "按模型覆盖",
3134
+ contextOverridesHint: "为单个模型单独设置上下文长度,优先于上面的全局值;模型下拉只列出模型白名单中的可见模型(显示全部时即整个目录)。",
3135
+ contextOverrideAdd: "添加模型",
3136
+ contextOverrideRemove: "移除该模型的上下文覆盖",
3137
+ contextOverrideModel: "模型",
3138
+ contextOverrideValue: "上下文长度",
3139
+ contextOverrideNoCatalog: "模型目录尚未加载,暂时无法添加按模型覆盖。",
3140
+ contextWindowTooSmall: "上下文长度不能小于 1",
3141
+ contextWindowTooLarge: "上下文长度不能大于 10,000,000",
3142
+ contextWindowInvalid: "请输入有效的整数 token 数",
3143
+ maxOutputTokens: "单轮输出上限",
3144
+ maxOutputTokensHint: "每次请求发送的输出预算(token)。留空=按模型自身输出上限推导(默认,行为不变);填正整数=与推导值取较小者,永远不会超过模型上限或网关上限 131072。填 `0`=请求中不发送该字段,把单轮输出规模交回平台自行限制——这是「长回答被硬切」的解法,但仅 CLI 与 OpenAI 两条传输支持;Claude 走的 Messages 端点必填该字段,因此那里仍按推导值发送,填 0 对它无效。会话标题这类由宿主明确限定预算的辅助调用也不受此设置影响。支持 64K 这样的简写。",
3145
+ maxOutputTokensTooSmall: "输出上限不能小于 0(0 表示不发送该字段)",
3146
+ maxOutputTokensTooLarge: "输出上限不能大于 131072(网关上限)",
3147
+ maxOutputTokensInvalid: "请输入 0 或有效的整数 token 数",
2827
3148
  overridden: "已覆盖",
2828
3149
  reset: "重置",
2829
3150
  invalidNumber: "无效数字",
@@ -2850,6 +3171,7 @@ window.__ModuleLoader__.load({
2850
3171
  writeRefreshFailed: "密钥状态刷新失败;已获确认的写入仍有效。",
2851
3172
  writeRefreshRetry: "仅刷新密钥状态",
2852
3173
  writeStateUnknown: "密钥状态尚未确认",
3174
+ writeCredentialRefNote: "(原引用:{ref})",
2853
3175
  unsavedChanges: "有未保存的更改",
2854
3176
  saveInvalid: "有字段填写不正确,修正后才能保存",
2855
3177
  discard: "放弃",
@@ -2937,21 +3259,50 @@ window.__ModuleLoader__.load({
2937
3259
  accountAddLogin: "网页登录",
2938
3260
  accountAddPaste: "粘贴密钥",
2939
3261
  accountAddConfirm: "添加",
3262
+ accountAddCancel: "取消开通",
3263
+ accountAddLinkHint: "若登录页面未自动打开,请点击下方链接。",
3264
+ accountAddOpenLink: "打开浏览器登录",
2940
3265
  accountNamePlaceholder: "备注名(可选,网页登录时默认使用账户名)",
2941
3266
  accountNameN: "账户 {n}",
3267
+ accountNameSaveRetry: "重试保存名称",
3268
+ accountNameAcceptExisting: "接受已有名称",
2942
3269
  accountKeyPlaceholder: "粘贴 API 密钥",
2943
3270
  accountOpFailed: "账户操作未能完成,请重试。",
2944
3271
  accountCleanupPending: "账户已移除,但有密钥尚未清理。可重试,不影响其他账户。",
2945
3272
  accountCleanupRetry: "重试清理密钥",
3273
+ accountEnrollmentCleanupRetry: "重试清理",
3274
+ accountRefSeparator: ":",
2946
3275
  accountModels: "专用模型",
2947
3276
  accountModelsHint: "选中的模型优先由此账户处理,此账户不可用时仍会自动切换。一个模型只属于一个账户,选中会把它从其他账户移过来。",
2948
3277
  accountModelOwner: "属于 {name}",
2949
3278
  modelPick: "选择模型…",
2950
3279
  modelCount: "已选 {count} 个模型",
2951
3280
  modelCatalogFailed: "模型目录获取失败,暂时无法选择模型;已保存的设置仍会生效。",
2952
- modelsTitle: "模型",
3281
+ modelsTitle: "模型与上下文",
2953
3282
  privacyTitle: "隐私与安全",
2954
- integrationsTitle: "集成与显示"
3283
+ integrationsTitle: "集成与显示",
3284
+ enrollmentCreating: "正在创建账号",
3285
+ enrollmentWaiting: "等待浏览器登录",
3286
+ enrollmentWriting: "正在保存凭据",
3287
+ enrollmentNaming: "账号已登录,正在保存名称",
3288
+ enrollmentNameFailed: "账号已登录,名称保存失败,请重试或接受已有名称",
3289
+ enrollmentFinished: "账号开通完成",
3290
+ enrollmentCancelling: "正在等待写入结束并清理",
3291
+ enrollmentCancelled: "已取消",
3292
+ enrollmentCancelledCleaned: "已取消并清理",
3293
+ enrollmentFailed: "账号开通失败,未创建账号",
3294
+ enrollmentFailedCleaned: "开通失败,已清理临时账号",
3295
+ enrollmentCleanupPending: "收尾未完成,请重试",
3296
+ enrollmentCleanupFailed: "清理未完成,请重试",
3297
+ enrollmentRecordNaming: "已登录账号等待确认名称",
3298
+ enrollmentRecordCleanup: "账号开通收尾未完成,请重试",
3299
+ enrollmentBeginFailed: "账号开通请求失败,请检查连接后重试",
3300
+ enrollmentCancelUnconfirmed: "取消请求未确认,重新连接后请检查未完成记录",
3301
+ enrollmentNameRequestFailed: "名称保存失败,请重试或接受已有名称",
3302
+ enrollmentCleanupRequestFailed: "收尾请求失败,请检查连接后重试",
3303
+ enrollmentDisconnected: "开通连接已断开,宿主正在收尾;重连后请检查未完成记录",
3304
+ enrollmentHostUnavailable: "宿主无法完成账号开通,请检查连接与写入权限",
3305
+ enrollmentStatusUnavailable: "开通状态暂不可用,正在等待重新连接"
2955
3306
  };
2956
3307
  const en = {
2957
3308
  openAnalytics: "View model usage, cost and performance",
@@ -2981,7 +3332,7 @@ window.__ModuleLoader__.load({
2981
3332
  filterModelsByPlan: "Hide out-of-plan models",
2982
3333
  filterModelsByPlanHint: "Show only models in your plan; on-demand credit accounts still see the full catalog.",
2983
3334
  webSearch: "Serve dsh web search with Command Code",
2984
- webSearchHint: "Use the same key for dsh web search; turning it off restores the previous backend.",
3335
+ webSearchHint: "Use the same key for dsh web search; turning it off restores the previous backend. When the Web search settings already pin another backend, this switch has no effect.",
2985
3336
  showSidebarQuota: "Show the quota card in the sidebar",
2986
3337
  showSidebarQuotaHint: "Show a plan and quota card at the bottom of the sidebar that opens the full dashboard. Off by default; while off, no background usage refresh runs for it.",
2987
3338
  zdr: "Zero data retention (ZDR)",
@@ -3002,6 +3353,23 @@ window.__ModuleLoader__.load({
3002
3353
  visibleModelsShowAll: "Show all",
3003
3354
  visibleModelsStaleHint: "{count} selected model(s) are no longer in the catalog (possibly retired); other models are unaffected. Clean them up or keep them.",
3004
3355
  visibleModelsCleanStale: "Clean stale ({count})",
3356
+ contextWindow: "Custom context length",
3357
+ contextWindowHint: "Overrides the context window published by the model catalog (in tokens): the context meter, auto-compaction timing and the model-picker annotation all follow it, for every model without a per-model override below. Empty follows the catalog; shorthand like 32K or 1M is accepted (binary units: 32K is 32,768, 1M is 1,048,576).",
3358
+ contextOverridesTitle: "Per-model overrides",
3359
+ contextOverridesHint: "Set a context length for one model; it wins over the global value above. The picker lists only the models kept by the visible-models allowlist (the whole catalog while showing all).",
3360
+ contextOverrideAdd: "Add model",
3361
+ contextOverrideRemove: "Remove this model’s context override",
3362
+ contextOverrideModel: "Model",
3363
+ contextOverrideValue: "Context length",
3364
+ contextOverrideNoCatalog: "The model catalog has not loaded yet; per-model overrides cannot be added right now.",
3365
+ contextWindowTooSmall: "Context length must be at least 1",
3366
+ contextWindowTooLarge: "Context length must not exceed 10,000,000",
3367
+ contextWindowInvalid: "Enter a valid whole-token count",
3368
+ maxOutputTokens: "Output limit per turn",
3369
+ maxOutputTokensHint: "The output budget sent with each request, in tokens. Empty derives it from the model’s own ceiling (the default; nothing changes). A positive number is taken as a cap alongside that derived value, so it can never exceed the model ceiling or the gateway’s 131,072. `0` sends no such field at all and leaves the per-turn output size to the platform — the fix for answers being cut short — but only the CLI and OpenAI transports support it: the Messages endpoint Claude uses requires the field, so Claude keeps its derived budget and `0` has no effect there. Auxiliary calls whose budget the host fixes itself, such as the session title, are unaffected too. Shorthand like 64K is accepted.",
3370
+ maxOutputTokensTooSmall: "Output limit must be at least 0 (0 omits the field)",
3371
+ maxOutputTokensTooLarge: "Output limit must not exceed 131,072 (the gateway cap)",
3372
+ maxOutputTokensInvalid: "Enter 0 or a valid whole-token count",
3005
3373
  overridden: "Overridden",
3006
3374
  reset: "Reset",
3007
3375
  invalidNumber: "Invalid number",
@@ -3015,19 +3383,20 @@ window.__ModuleLoader__.load({
3015
3383
  saving: "Saving",
3016
3384
  saved: "Settings saved",
3017
3385
  saveFailed: "Save failed, please retry.",
3018
- writeConflict: "待提交字段已被其他操作修改,配置未写入;请核对后再保存。",
3019
- writeConfigUnconfirmed: "配置提交未获确认;密钥未写入,请核对宿主状态后重试。",
3020
- writeConfigSaved: "配置已保存。",
3021
- writeKeyNotIssued: "密钥操作尚未发出;请核对后重试。",
3022
- writeKeyUnconfirmed: "密钥操作未获确认;请核对后手动重试。",
3023
- writeKeyConfirmed: "原引用的密钥操作已获确认。",
3024
- writeTargetBefore: "默认密钥引用已变化,本次密钥操作未发出;请核对当前目标。",
3025
- writeTargetAfter: "原引用的密钥操作已发出,默认引用随后变化;请核对两个引用。",
3026
- writeClosed: "客户端已销毁,未发出的操作已停止;已确认的修改保留。",
3027
- writeCleanupPending: "账号修改已确认,清理记录尚未收束,请重试清理。",
3028
- writeRefreshFailed: "密钥状态刷新失败;已获确认的写入仍有效。",
3029
- writeRefreshRetry: "仅刷新密钥状态",
3030
- writeStateUnknown: "密钥状态尚未确认",
3386
+ writeConflict: "Pending fields were modified by another operation; the configuration was not written. Please review and save again.",
3387
+ writeConfigUnconfirmed: "The configuration submission was not confirmed; the key was not written. Please check the host state and retry.",
3388
+ writeConfigSaved: "Configuration saved.",
3389
+ writeKeyNotIssued: "The key operation has not been issued; please review and retry.",
3390
+ writeKeyUnconfirmed: "The key operation was not confirmed; please review and retry manually.",
3391
+ writeKeyConfirmed: "The key operation on the original reference has been confirmed.",
3392
+ writeTargetBefore: "The default key reference has changed; this key operation was not issued. Please check the current target.",
3393
+ writeTargetAfter: "The key operation on the original reference was issued, then the default reference changed. Please check both references.",
3394
+ writeClosed: "The client has been destroyed; unissued operations were stopped. Confirmed changes are kept.",
3395
+ writeCleanupPending: "The account change was confirmed, but cleanup records are still pending. Please retry cleanup.",
3396
+ writeRefreshFailed: "Key status refresh failed; confirmed writes remain valid.",
3397
+ writeRefreshRetry: "Refresh key status only",
3398
+ writeStateUnknown: "Key status not yet confirmed",
3399
+ writeCredentialRefNote: "(original ref: {ref})",
3031
3400
  unsavedChanges: "You have unsaved changes",
3032
3401
  saveInvalid: "Fix the highlighted field before saving",
3033
3402
  discard: "Discard",
@@ -3044,7 +3413,7 @@ window.__ModuleLoader__.load({
3044
3413
  usageCost: "Spend",
3045
3414
  usageCompleted: "Completed",
3046
3415
  usageBillingPeriod: "Official billing-period usage",
3047
- usagePeriodUnknown: "官方用量汇总,统计周期未确认",
3416
+ usagePeriodUnknown: "Official usage summary; the billing period is unconfirmed",
3048
3417
  usageTokens: "Tokens",
3049
3418
  usageTokensIn: "in",
3050
3419
  usageTokensOut: "out",
@@ -3115,21 +3484,50 @@ window.__ModuleLoader__.load({
3115
3484
  accountAddLogin: "Sign in",
3116
3485
  accountAddPaste: "Paste key",
3117
3486
  accountAddConfirm: "Add",
3487
+ accountAddCancel: "Cancel setup",
3488
+ accountAddLinkHint: "If the sign-in page did not open automatically, use the link below.",
3489
+ accountAddOpenLink: "Open the sign-in page",
3118
3490
  accountNamePlaceholder: "Name (optional; sign-in uses the account name)",
3119
3491
  accountNameN: "Account {n}",
3492
+ accountNameSaveRetry: "Retry saving the name",
3493
+ accountNameAcceptExisting: "Accept the existing name",
3120
3494
  accountKeyPlaceholder: "Paste an API key",
3121
3495
  accountOpFailed: "The account change did not complete. Try again.",
3122
3496
  accountCleanupPending: "The account was removed, but credential cleanup is still pending. Retry without affecting other accounts.",
3123
3497
  accountCleanupRetry: "Retry credential cleanup",
3498
+ accountEnrollmentCleanupRetry: "Retry cleanup",
3499
+ accountRefSeparator: ": ",
3124
3500
  accountModels: "Dedicated models",
3125
3501
  accountModelsHint: "These models prefer this account and still switch when it is unavailable. A model belongs to one account; selecting it here moves it from any other.",
3126
3502
  accountModelOwner: "on {name}",
3127
3503
  modelPick: "Choose models…",
3128
3504
  modelCount: "{count} models selected",
3129
3505
  modelCatalogFailed: "The model catalog could not be loaded; saved settings still apply.",
3130
- modelsTitle: "Models",
3506
+ modelsTitle: "Models & context",
3131
3507
  privacyTitle: "Privacy & security",
3132
- integrationsTitle: "Integrations & display"
3508
+ integrationsTitle: "Integrations & display",
3509
+ enrollmentCreating: "Creating the account",
3510
+ enrollmentWaiting: "Waiting for the browser sign-in",
3511
+ enrollmentWriting: "Saving the credential",
3512
+ enrollmentNaming: "Signed in. Saving the name",
3513
+ enrollmentNameFailed: "Signed in, but saving the name failed. Retry, or accept the existing name.",
3514
+ enrollmentFinished: "Account setup complete",
3515
+ enrollmentCancelling: "Waiting for the write to finish, then cleaning up",
3516
+ enrollmentCancelled: "Cancelled",
3517
+ enrollmentCancelledCleaned: "Cancelled and cleaned up",
3518
+ enrollmentFailed: "Account setup failed. No account was created.",
3519
+ enrollmentFailedCleaned: "Setup failed; the temporary account was cleaned up.",
3520
+ enrollmentCleanupPending: "Cleanup is not finished. Please retry.",
3521
+ enrollmentCleanupFailed: "Cleanup failed. Please retry.",
3522
+ enrollmentRecordNaming: "Signed-in account waiting for its name to be confirmed",
3523
+ enrollmentRecordCleanup: "Account setup was not finished. Please retry.",
3524
+ enrollmentBeginFailed: "The account setup request failed. Check the connection and retry.",
3525
+ enrollmentCancelUnconfirmed: "The cancel request was not confirmed. After reconnecting, check for unfinished records.",
3526
+ enrollmentNameRequestFailed: "Saving the name failed. Retry, or accept the existing name.",
3527
+ enrollmentCleanupRequestFailed: "The cleanup request failed. Check the connection and retry.",
3528
+ enrollmentDisconnected: "The setup connection dropped; the host is finishing up. After reconnecting, check for unfinished records.",
3529
+ enrollmentHostUnavailable: "The host could not complete the account setup. Check the connection and write permissions.",
3530
+ enrollmentStatusUnavailable: "Setup status is unavailable; waiting to reconnect."
3133
3531
  };
3134
3532
  /** 同一确认结果在设置页和模型卡使用相同措辞,不从 configured 推断写入结果。 */
3135
3533
  function settingsWriteNotice(result, t) {
@@ -3145,7 +3543,7 @@ window.__ModuleLoader__.load({
3145
3543
  const messages = key ? [t(key)] : [];
3146
3544
  if (result.issue === "target-changed" && result.credentialRef) {
3147
3545
  messages.push(t(result.credential === "confirmed" ? "writeKeyConfirmed" : "writeKeyUnconfirmed"));
3148
- messages.push(`(原引用:${result.credentialRef})`);
3546
+ messages.push(t("writeCredentialRefNote").replace("{ref}", result.credentialRef));
3149
3547
  }
3150
3548
  if (key && result.config === "confirmed" && result.issue !== "cleanup-pending") messages.unshift(t("writeConfigSaved"));
3151
3549
  if (result.refreshFailed) messages.push(t("writeRefreshFailed"));
@@ -3270,6 +3668,19 @@ window.__ModuleLoader__.load({
3270
3668
  return selected.includes(modelId) ? selected.filter((value) => value !== modelId) : [...selected, modelId];
3271
3669
  }
3272
3670
  /**
3671
+ * The catalog rows a picker may offer under the visible-models allowlist, in
3672
+ * catalog order: the whole catalog while "show all" is active (the empty
3673
+ * allowlist means exactly that), otherwise only the allowlisted ids. The
3674
+ * per-model context override editor uses this so its dropdown agrees with the
3675
+ * whitelist on the same card stack — a staged whitelist edit narrows it
3676
+ * immediately, since both read the projected page state.
3677
+ */
3678
+ function allowlistedModels(catalog, allowlist, showAll) {
3679
+ if (showAll) return [...catalog];
3680
+ const keep = new Set(allowlist);
3681
+ return catalog.filter((model) => keep.has(model.id));
3682
+ }
3683
+ /**
3273
3684
  * Whether the catalog is trustworthy enough to call an unlisted selection
3274
3685
  * "retired". FALSE while the first fetch is in flight, after a failure, and
3275
3686
  * for a loaded-but-empty catalog — an empty list makes every selected id look
@@ -3291,7 +3702,7 @@ window.__ModuleLoader__.load({
3291
3702
  }
3292
3703
  //#endregion
3293
3704
  //#region package.json
3294
- var version = "0.13.3";
3705
+ var version = "0.14.0";
3295
3706
  var repository = {
3296
3707
  "type": "git",
3297
3708
  "url": "git+https://github.com/Mars-Sea/dsh-commandcode-provider.git"
@@ -3474,11 +3885,12 @@ window.__ModuleLoader__.load({
3474
3885
  *
3475
3886
  * Layout, top to bottom: Accounts (one card per account, status, quota,
3476
3887
  * actions and dedicated models — every operation commits immediately), Models
3477
- * (plan filter + visible-model allowlist), Privacy & security (ZDR),
3478
- * Integrations & display (web search, sidebar quota card), and a collapsed
3479
- * Advanced card (API base + network limits). Everything below Accounts is a
3480
- * flat group of rows (title/description left, control right) inside the staged
3481
- * form the floating save bar writes.
3888
+ * & context (plan filter + visible-model allowlist, then the context-length
3889
+ * rows: the global custom window and the per-model overrides, issue #86),
3890
+ * Privacy & security (ZDR), Integrations & display (web search, sidebar quota
3891
+ * card), and a collapsed Advanced card (API base + network limits). Everything
3892
+ * below Accounts is a flat group of rows (title/description left, control
3893
+ * right) inside the staged form the floating save bar writes.
3482
3894
  *
3483
3895
  * Styles are injected once by the client entry (see src/client/index.ts) and
3484
3896
  * class-prefixed `cc-` to stay local.
@@ -3549,7 +3961,7 @@ window.__ModuleLoader__.load({
3549
3961
  }) : null;
3550
3962
  }
3551
3963
  /** One text/number field row. */
3552
- function Field({ id, label, hint, state, disabled, numeric, decimal, wide, placeholder, onEdit, onReset, t }) {
3964
+ function Field({ id, label, hint, state, disabled, numeric, decimal, context, output, wide, placeholder, onEdit, onReset, t }) {
3553
3965
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingRow, {
3554
3966
  title: label,
3555
3967
  titleFor: id,
@@ -3558,7 +3970,7 @@ window.__ModuleLoader__.load({
3558
3970
  t
3559
3971
  }),
3560
3972
  description: hint,
3561
- error: state.invalid ? invalidCopy(state.invalidReason, t, decimal) : void 0,
3973
+ error: state.invalid ? invalidCopy(state.invalidReason, t, output === true ? "output" : context === true ? "context" : decimal === true ? "duration" : "number") : void 0,
3562
3974
  control: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldOverride, {
3563
3975
  label,
3564
3976
  state,
@@ -3579,12 +3991,22 @@ window.__ModuleLoader__.load({
3579
3991
  });
3580
3992
  }
3581
3993
  /** The per-field error copy for a staged draft's failure reason. */
3582
- function invalidCopy(reason, t, duration = false) {
3583
- if (duration) {
3994
+ function invalidCopy(reason, t, mode = "number") {
3995
+ if (mode === "duration") {
3584
3996
  if (reason === "tooSmall") return t("durationTooSmall");
3585
3997
  if (reason === "tooLarge") return t("durationTooLarge");
3586
3998
  return t("durationInvalid");
3587
3999
  }
4000
+ if (mode === "context") {
4001
+ if (reason === "tooSmall") return t("contextWindowTooSmall");
4002
+ if (reason === "tooLarge") return t("contextWindowTooLarge");
4003
+ return t("contextWindowInvalid");
4004
+ }
4005
+ if (mode === "output") {
4006
+ if (reason === "tooSmall") return t("maxOutputTokensTooSmall");
4007
+ if (reason === "tooLarge") return t("maxOutputTokensTooLarge");
4008
+ return t("maxOutputTokensInvalid");
4009
+ }
3588
4010
  if (reason === "tooSmall") return t("numberTooSmall");
3589
4011
  if (reason === "tooLarge") return t("numberTooLarge");
3590
4012
  return t("invalidNumber");
@@ -3622,9 +4044,10 @@ window.__ModuleLoader__.load({
3622
4044
  });
3623
4045
  }
3624
4046
  /** One stat tile in the account card's summary grid. */
3625
- function UsageStat({ label, value, sub }) {
4047
+ function UsageStat({ label, value, sub, title }) {
3626
4048
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3627
4049
  className: "cc-usageStat",
4050
+ title,
3628
4051
  children: [
3629
4052
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3630
4053
  className: "cc-usageStatLabel",
@@ -3757,8 +4180,13 @@ window.__ModuleLoader__.load({
3757
4180
  children: [
3758
4181
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageStat, {
3759
4182
  label: t("usageRequests"),
3760
- value: String(report.usage.totalCount),
3761
- sub: `${t("usageCompleted")} ${report.usage.completedCount} / ${t("usageFailed")} ${report.usage.failedCount}`
4183
+ value: formatCompactCount(report.usage.totalCount),
4184
+ sub: `${t("usageCompleted")} ${formatCompactCount(report.usage.completedCount)} / ${t("usageFailed")} ${formatCompactCount(report.usage.failedCount)}`,
4185
+ title: compactCountTitle([
4186
+ [t("usageRequests"), report.usage.totalCount],
4187
+ [t("usageCompleted"), report.usage.completedCount],
4188
+ [t("usageFailed"), report.usage.failedCount]
4189
+ ])
3762
4190
  }),
3763
4191
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageStat, {
3764
4192
  label: t("usageSuccessRate"),
@@ -4025,31 +4453,201 @@ window.__ModuleLoader__.load({
4025
4453
  });
4026
4454
  }
4027
4455
  /**
4028
- * Models: the plan filter and the allowlist sit together because both decide
4029
- * what the model picker lists (the allowlist applies after the plan filter).
4456
+ * Models & context: the plan filter and the allowlist sit together because both
4457
+ * decide what the model picker lists (the allowlist applies after the plan
4458
+ * filter), and the context-length fields follow them because they are the same
4459
+ * subject seen from the other side — the window each listed model advertises.
4460
+ * Those fields carry no heading or divider of their own: they are rows of this
4461
+ * group, not a section inside it.
4030
4462
  */
4031
- function ModelsCard({ state, disabled, t, onEdit, onReset, onSelect, onClear }) {
4463
+ function ModelsCard({ state, disabled, t, onEdit, onReset, onSelect, onClear, onOverrides }) {
4032
4464
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(SettingsGroup, {
4033
4465
  title: t("modelsTitle"),
4034
4466
  divided: true,
4035
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleField, {
4036
- id: "cc-filter-models-by-plan",
4037
- label: t("filterModelsByPlan"),
4038
- hint: t("filterModelsByPlanHint"),
4039
- state: state.filterModelsByPlan,
4467
+ children: [
4468
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleField, {
4469
+ id: "cc-filter-models-by-plan",
4470
+ label: t("filterModelsByPlan"),
4471
+ hint: t("filterModelsByPlanHint"),
4472
+ state: state.filterModelsByPlan,
4473
+ disabled,
4474
+ defaultChecked: true,
4475
+ onEdit: (text) => onEdit("filterModelsByPlan", text),
4476
+ onReset: () => onReset("filterModelsByPlan"),
4477
+ t
4478
+ }),
4479
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(VisibleModelsRow, {
4480
+ t,
4481
+ state,
4482
+ disabled,
4483
+ onSelect,
4484
+ onClear
4485
+ }),
4486
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ContextCard, {
4487
+ state,
4488
+ disabled,
4489
+ t,
4490
+ onEdit,
4491
+ onReset,
4492
+ onOverrides
4493
+ })
4494
+ ]
4495
+ });
4496
+ }
4497
+ /**
4498
+ * Context length (issue #86): the global custom window plus one row per model
4499
+ * override, both staged into the same save as the other form fields. Rendered
4500
+ * as plain rows of the Models & context group — no heading, no divider of its
4501
+ * own: the setting belongs to the models that group already governs, and on the
4502
+ * real page a section inside the group read as visual noise. The advertised
4503
+ * window is what the harness meters context against and when it compacts, so a
4504
+ * lowered value compacts earlier and a raised one follows the model's real
4505
+ * capacity when the catalog under-reports it. Precedence: a per-model row, then
4506
+ * the global value, then the catalog.
4507
+ *
4508
+ * The rows live as raw text drafts: blank drops the override on save, an
4509
+ * unparseable one blocks the save with its own bounds copy, and the value
4510
+ * accepts the compact shorthand the picker prints (`256K`, `1M`). The model
4511
+ * select offers only allowlisted models the list does not already carry —
4512
+ * one override per model, matching what the Models card lets through — and
4513
+ * keeps a stored id the catalog no longer carries (or the whitelist just
4514
+ * dropped) as itself so an existing override never silently disappears.
4515
+ */
4516
+ function ContextCard({ state, disabled, t, onEdit, onReset, onOverrides }) {
4517
+ const rows = state.contextWindowOverrides;
4518
+ const used = new Set(rows.map((row) => row.model));
4519
+ const available = allowlistedModels(state.catalogModels, state.visibleModels, state.visibleModelsAll).filter((model) => !used.has(model.id));
4520
+ const asMap = () => Object.fromEntries(rows.map((row) => [row.model, row.text]));
4521
+ const setText = (model, text) => onOverrides({
4522
+ ...asMap(),
4523
+ [model]: text
4524
+ });
4525
+ const setModel = (from, to) => {
4526
+ if (to === "" || to === from || used.has(to)) return;
4527
+ const next = {};
4528
+ for (const row of rows) next[row.model === from ? to : row.model] = row.text;
4529
+ onOverrides(next);
4530
+ };
4531
+ const removeRow = (model) => {
4532
+ const next = asMap();
4533
+ delete next[model];
4534
+ onOverrides(next);
4535
+ };
4536
+ const addRow = () => {
4537
+ const model = available[0];
4538
+ if (model === void 0) return;
4539
+ onOverrides({
4540
+ ...asMap(),
4541
+ [model.id]: ""
4542
+ });
4543
+ };
4544
+ const labelOf = (model) => {
4545
+ const entry = state.catalogModels.find((candidate) => candidate.id === model);
4546
+ return entry === void 0 ? `${model} · ${t("modelStale")}` : `${entry.name} (${model})`;
4547
+ };
4548
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
4549
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
4550
+ id: "cc-context-window",
4551
+ label: t("contextWindow"),
4552
+ hint: t("contextWindowHint"),
4553
+ state: state.contextWindow,
4040
4554
  disabled,
4041
- defaultChecked: true,
4042
- onEdit: (text) => onEdit("filterModelsByPlan", text),
4043
- onReset: () => onReset("filterModelsByPlan"),
4555
+ context: true,
4556
+ onEdit: (text) => onEdit("contextWindow", text),
4557
+ onReset: () => onReset("contextWindow"),
4044
4558
  t
4045
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(VisibleModelsRow, {
4046
- t,
4047
- state,
4559
+ }),
4560
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
4561
+ id: "cc-max-output-tokens",
4562
+ label: t("maxOutputTokens"),
4563
+ hint: t("maxOutputTokensHint"),
4564
+ state: state.maxOutputTokens,
4048
4565
  disabled,
4049
- onSelect,
4050
- onClear
4051
- })]
4052
- });
4566
+ numeric: true,
4567
+ output: true,
4568
+ onEdit: (text) => onEdit("maxOutputTokens", text),
4569
+ onReset: () => onReset("maxOutputTokens"),
4570
+ t
4571
+ }),
4572
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingRow, {
4573
+ title: t("contextOverridesTitle"),
4574
+ description: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("contextOverridesHint") }),
4575
+ control: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4576
+ type: "button",
4577
+ className: "cc-linkButton",
4578
+ disabled: disabled || available.length === 0,
4579
+ onClick: addRow,
4580
+ children: t("contextOverrideAdd")
4581
+ })
4582
+ }),
4583
+ state.catalogFailed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4584
+ className: "cc-groupDesc",
4585
+ children: t("contextOverrideNoCatalog")
4586
+ }) : null,
4587
+ rows.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4588
+ className: "cc-contextHead",
4589
+ children: [
4590
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4591
+ className: "cc-contextHeadModel",
4592
+ children: t("contextOverrideModel")
4593
+ }),
4594
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4595
+ className: "cc-contextHeadValue",
4596
+ children: t("contextOverrideValue")
4597
+ }),
4598
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4599
+ className: "cc-contextHeadGap",
4600
+ "aria-hidden": "true"
4601
+ })
4602
+ ]
4603
+ }) : null,
4604
+ rows.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4605
+ className: "cc-contextItem",
4606
+ children: [
4607
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4608
+ className: "cc-contextSelect",
4609
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
4610
+ className: "cc-input cc-contextModel",
4611
+ value: row.model,
4612
+ "aria-label": t("contextOverrideModel"),
4613
+ disabled,
4614
+ onChange: (event) => setModel(row.model, event.target.value),
4615
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
4616
+ value: row.model,
4617
+ children: labelOf(row.model)
4618
+ }), available.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
4619
+ value: model.id,
4620
+ children: labelOf(model.id)
4621
+ }, model.id))]
4622
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4623
+ className: "cc-contextCaret",
4624
+ "aria-hidden": "true"
4625
+ })]
4626
+ }),
4627
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4628
+ className: `cc-input cc-contextValue${row.invalid ? " cc-inputInvalid" : ""}`,
4629
+ type: "text",
4630
+ "aria-label": t("contextOverrideValue"),
4631
+ "aria-invalid": row.invalid || void 0,
4632
+ value: row.text,
4633
+ disabled,
4634
+ onChange: (event) => setText(row.model, event.target.value)
4635
+ }),
4636
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4637
+ type: "button",
4638
+ className: "cc-iconButton",
4639
+ "aria-label": t("contextOverrideRemove"),
4640
+ disabled,
4641
+ onClick: () => removeRow(row.model),
4642
+ children: "×"
4643
+ }),
4644
+ row.invalid ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4645
+ className: "cc-rowError cc-contextError",
4646
+ children: invalidCopy(row.invalidReason, t, "context")
4647
+ }) : null
4648
+ ]
4649
+ }, row.model))
4650
+ ] });
4053
4651
  }
4054
4652
  /**
4055
4653
  * Privacy & security: the opt-in that changes where data goes. ZDR restricts
@@ -4786,23 +5384,23 @@ window.__ModuleLoader__.load({
4786
5384
  active ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4787
5385
  className: "cc-hint",
4788
5386
  role: "status",
4789
- children: active.message
5387
+ children: t(active.messageKey)
4790
5388
  }) : null,
4791
5389
  active?.authUrl && active.phase === "waiting" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4792
5390
  className: "cc-hint",
4793
- children: "若登录页面未自动打开,请点击下方链接。"
5391
+ children: t("accountAddLinkHint")
4794
5392
  }) : null,
4795
5393
  active?.authUrl && active.phase === "waiting" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
4796
5394
  className: "cc-linkButton",
4797
5395
  href: active.authUrl,
4798
5396
  target: "_blank",
4799
5397
  rel: "noreferrer",
4800
- children: "打开浏览器登录"
5398
+ children: t("accountAddOpenLink")
4801
5399
  }) : null,
4802
5400
  snapshot.error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4803
5401
  className: "cc-loginError",
4804
5402
  role: "status",
4805
- children: snapshot.error
5403
+ children: t(snapshot.error)
4806
5404
  }) : null,
4807
5405
  active?.phase === "naming" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4808
5406
  className: "cc-inlineActions",
@@ -4812,14 +5410,14 @@ window.__ModuleLoader__.load({
4812
5410
  size: "sm",
4813
5411
  disabled,
4814
5412
  onClick: () => void enrollment.name(active.suggestedName || label || fallbackName),
4815
- children: "重试保存名称"
5413
+ children: t("accountNameSaveRetry")
4816
5414
  }),
4817
5415
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4818
5416
  variant: "ghost",
4819
5417
  size: "sm",
4820
5418
  disabled,
4821
5419
  onClick: () => void enrollment.name(),
4822
- children: "接受已有名称"
5420
+ children: t("accountNameAcceptExisting")
4823
5421
  }),
4824
5422
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4825
5423
  variant: "ghost",
@@ -4870,7 +5468,7 @@ window.__ModuleLoader__.load({
4870
5468
  variant: "ghost",
4871
5469
  size: "sm",
4872
5470
  onClick: close,
4873
- children: "取消开通"
5471
+ children: t("accountAddCancel")
4874
5472
  }) : null
4875
5473
  ]
4876
5474
  });
@@ -4957,7 +5555,7 @@ window.__ModuleLoader__.load({
4957
5555
  !adding && snapshot.error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4958
5556
  className: "cc-rowError",
4959
5557
  role: "status",
4960
- children: snapshot.error
5558
+ children: t(snapshot.error)
4961
5559
  }) : null,
4962
5560
  state.pendingCredentialCleanup.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4963
5561
  className: "cc-rowError",
@@ -4978,34 +5576,34 @@ window.__ModuleLoader__.load({
4978
5576
  children: [
4979
5577
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", { children: [
4980
5578
  record.ref,
4981
- ":",
4982
- record.message
5579
+ t("accountRefSeparator"),
5580
+ t(record.messageKey)
4983
5581
  ] }),
4984
5582
  record.phase === "naming" && record.suggestedName ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4985
5583
  variant: "ghost",
4986
5584
  size: "sm",
4987
5585
  disabled,
4988
5586
  onClick: () => void enrollment.recover(record, record.suggestedName),
4989
- children: "重试保存名称"
5587
+ children: t("accountNameSaveRetry")
4990
5588
  }) : null,
4991
5589
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4992
5590
  variant: "ghost",
4993
5591
  size: "sm",
4994
5592
  disabled,
4995
5593
  onClick: () => void enrollment.recover(record),
4996
- children: record.phase === "naming" ? "接受已有名称" : "重试清理"
5594
+ children: record.phase === "naming" ? t("accountNameAcceptExisting") : t("accountEnrollmentCleanupRetry")
4997
5595
  })
4998
5596
  ]
4999
5597
  }, record.id)),
5000
5598
  snapshot.active?.phase === "cleanup-needed" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5001
5599
  className: "cc-rowError",
5002
5600
  role: "status",
5003
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: snapshot.active.message }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5601
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t(snapshot.active.messageKey) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5004
5602
  variant: "ghost",
5005
5603
  size: "sm",
5006
5604
  disabled,
5007
5605
  onClick: () => void enrollment.recover(snapshot.active),
5008
- children: "重试清理"
5606
+ children: t("accountEnrollmentCleanupRetry")
5009
5607
  })]
5010
5608
  }) : null,
5011
5609
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -5279,7 +5877,8 @@ window.__ModuleLoader__.load({
5279
5877
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelsCard, {
5280
5878
  ...form,
5281
5879
  onSelect: props.editVisibleModels,
5282
- onClear: props.clearVisibleModels
5880
+ onClear: props.clearVisibleModels,
5881
+ onOverrides: props.editContextWindowOverrides
5283
5882
  }),
5284
5883
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PrivacyCard, { ...form }),
5285
5884
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IntegrationsCard, { ...form }),
@@ -5608,11 +6207,22 @@ window.__ModuleLoader__.load({
5608
6207
  analysisNoShare: "无占比",
5609
6208
  analysisShareHint: "占全部已知成本小计",
5610
6209
  analysisSimulationHeading: "相同工作量价格试算",
5611
- analysisSimulationHint: "计价覆盖不同的金额不能直接比较。",
6210
+ analysisSimulationHint: "差额以所选历史全部请求的当前价估算为基准;负数表示成本更低,仅比较完整覆盖的相同工作量。",
5612
6211
  analysisSimulationCost: "试算成本",
5613
6212
  analysisCoverage: "计价覆盖",
5614
6213
  analysisUsd: "美元",
5615
6214
  analysisTokenUnit: "词元",
6215
+ analysisScenarioMonthly: "折算 30 天",
6216
+ analysisDifference: "较历史基准",
6217
+ analysisBaseline: "历史成本基准",
6218
+ analysisZeroBaseline: "基准为零,不计算比例",
6219
+ analysisNeedRange: "请选择日期范围",
6220
+ analysisShortRange: "观察范围不足一天",
6221
+ analysisIncomplete: "历史读取不完整",
6222
+ analysisNoRequests: "没有请求记录",
6223
+ analysisScenarioExcluded: "存在超上下文请求",
6224
+ analysisScenarioUnpriced: "存在未知用量或价格",
6225
+ analysisBaselineUnpriced: "历史基准未全部计价",
5616
6226
  analysisCost: "已知成本 · 美元",
5617
6227
  analysisRequests: "模型请求",
5618
6228
  analysisSessionCount: "会话数",
@@ -5650,6 +6260,7 @@ window.__ModuleLoader__.load({
5650
6260
  analysisAssumption: "沿用原请求的词元与时段,对比不同模型的价格。换模型后的生成长度、缓存命中、重试和质量可能变化;不会调用模型或自动推荐。",
5651
6261
  analysisContext: "上下文词元",
5652
6262
  analysisCapabilities: "模型能力",
6263
+ analysisInputType: "输入类型",
5653
6264
  analysisEfforts: "推理力度",
5654
6265
  analysisExcluded: "超出上下文",
5655
6266
  analysisUnknownPrice: "未知用量或价格",
@@ -5701,11 +6312,22 @@ window.__ModuleLoader__.load({
5701
6312
  analysisNoShare: ANALYTICS_COPY_ZH.analysisNoShare,
5702
6313
  analysisShareHint: "Share of the known cost subtotal",
5703
6314
  analysisSimulationHeading: "Price simulation for the same workload",
5704
- analysisSimulationHint: "Amounts with different priced coverage are not directly comparable.",
6315
+ analysisSimulationHint: "Differences use all selected recorded requests repriced at current rates as the baseline. Negative means lower cost; only complete coverage is compared.",
5705
6316
  analysisSimulationCost: "Simulated cost",
5706
6317
  analysisCoverage: "Priced coverage",
5707
6318
  analysisUsd: "USD",
5708
6319
  analysisTokenUnit: "tokens",
6320
+ analysisScenarioMonthly: "30-day estimate",
6321
+ analysisDifference: "vs. recorded baseline",
6322
+ analysisBaseline: "Recorded cost baseline",
6323
+ analysisZeroBaseline: ANALYTICS_COPY_ZH.analysisZeroBaseline,
6324
+ analysisNeedRange: ANALYTICS_COPY_ZH.analysisNeedRange,
6325
+ analysisShortRange: ANALYTICS_COPY_ZH.analysisShortRange,
6326
+ analysisIncomplete: ANALYTICS_COPY_ZH.analysisIncomplete,
6327
+ analysisNoRequests: ANALYTICS_COPY_ZH.analysisNoRequests,
6328
+ analysisScenarioExcluded: ANALYTICS_COPY_ZH.analysisScenarioExcluded,
6329
+ analysisScenarioUnpriced: ANALYTICS_COPY_ZH.analysisScenarioUnpriced,
6330
+ analysisBaselineUnpriced: ANALYTICS_COPY_ZH.analysisBaselineUnpriced,
5709
6331
  analysisCost: "Known cost · USD",
5710
6332
  analysisRequests: "Model requests",
5711
6333
  analysisSessionCount: "Sessions",
@@ -5743,6 +6365,7 @@ window.__ModuleLoader__.load({
5743
6365
  analysisAssumption: "Compares model prices using original tokens and request times. Output length, cache hits, retries and quality may change with another model. Makes no model calls or automatic recommendations.",
5744
6366
  analysisContext: "Context tokens",
5745
6367
  analysisCapabilities: "Model capabilities",
6368
+ analysisInputType: "Input type",
5746
6369
  analysisEfforts: "Reasoning efforts",
5747
6370
  analysisExcluded: "Over context limit",
5748
6371
  analysisUnknownPrice: "Unknown usage or price",
@@ -5855,7 +6478,7 @@ window.__ModuleLoader__.load({
5855
6478
  usage: "Usage",
5856
6479
  completed: "completed",
5857
6480
  usageBillingPeriod: "Official billing-period usage",
5858
- usagePeriodUnknown: "官方用量汇总,统计周期未确认",
6481
+ usagePeriodUnknown: "Official usage summary; the billing period is unconfirmed",
5859
6482
  monthly: "Monthly",
5860
6483
  monthlyLimit: "Monthly limit",
5861
6484
  monthlyUsed: "Monthly used",
@@ -6010,8 +6633,13 @@ window.__ModuleLoader__.load({
6010
6633
  if (usage !== void 0) {
6011
6634
  stats.push({
6012
6635
  label: "requests",
6013
- value: String(usage.totalCount),
6014
- sub: `${usage.completedCount} ${t("completed")} / ${usage.failedCount} ${t("failed")}`
6636
+ value: formatCompactCount(usage.totalCount),
6637
+ sub: `${formatCompactCount(usage.completedCount)} ${t("completed")} / ${formatCompactCount(usage.failedCount)} ${t("failed")}`,
6638
+ title: compactCountTitle([
6639
+ [t("requests"), usage.totalCount],
6640
+ [t("completed"), usage.completedCount],
6641
+ [t("failed"), usage.failedCount]
6642
+ ])
6015
6643
  });
6016
6644
  stats.push({
6017
6645
  label: "successRate",
@@ -6337,6 +6965,44 @@ window.__ModuleLoader__.load({
6337
6965
  }]
6338
6966
  };
6339
6967
  //#endregion
6968
+ //#region src/client/analytics-comparison.ts
6969
+ function coverageReason(report, scenario) {
6970
+ if (report.status !== "ready") return "unavailable";
6971
+ if (report.truncated || report.unreadableSessions > 0 || report.skippedSessions > 0) return "partial";
6972
+ if (report.totals.requests === 0) return "empty";
6973
+ if (scenario.excludedRequests > 0) return "excluded";
6974
+ if (scenario.unknownRequests > 0 || scenario.pricedRequests !== report.totals.requests) return "coverage";
6975
+ }
6976
+ function compareScenario(report, scenario) {
6977
+ const reason = coverageReason(report, scenario);
6978
+ let monthly;
6979
+ if (reason) monthly = {
6980
+ value: null,
6981
+ reason
6982
+ };
6983
+ else if (report.query.mode !== "range") monthly = {
6984
+ value: null,
6985
+ reason: "recent"
6986
+ };
6987
+ else {
6988
+ const elapsed = Math.min(report.query.to, report.generatedAt) - report.query.from;
6989
+ monthly = elapsed >= 864e5 ? { value: scenario.cost * (2592e6 / elapsed) } : {
6990
+ value: null,
6991
+ reason: "short-range"
6992
+ };
6993
+ }
6994
+ const comparisonReason = reason ?? (report.totals.pricedRequests !== report.totals.requests ? "baseline" : void 0);
6995
+ const difference = comparisonReason ? {
6996
+ value: null,
6997
+ reason: comparisonReason
6998
+ } : { value: scenario.cost - report.totals.cost };
6999
+ return {
7000
+ monthly,
7001
+ difference,
7002
+ differencePercent: difference.value !== null && report.totals.cost > 0 ? difference.value / report.totals.cost * 100 : null
7003
+ };
7004
+ }
7005
+ //#endregion
6340
7006
  //#region src/client/analytics-view.tsx
6341
7007
  /** 只展示数字快照;筛选提交后才查询,页签、展开与模型筛选不触发历史读取。 */
6342
7008
  function localDate(time) {
@@ -6587,7 +7253,19 @@ window.__ModuleLoader__.load({
6587
7253
  })]
6588
7254
  });
6589
7255
  }
6590
- function ScenarioRow({ m, text }) {
7256
+ const estimateReasonKeys = {
7257
+ unavailable: "analysisUnavailable",
7258
+ empty: "analysisNoRequests",
7259
+ partial: "analysisIncomplete",
7260
+ excluded: "analysisScenarioExcluded",
7261
+ coverage: "analysisScenarioUnpriced",
7262
+ baseline: "analysisBaselineUnpriced",
7263
+ recent: "analysisNeedRange",
7264
+ "short-range": "analysisShortRange"
7265
+ };
7266
+ const signedMoney = (n) => `${n > 0 ? "+" : n < 0 ? "-" : ""}${money(Math.abs(n))}`;
7267
+ function ScenarioRow({ m, report, text }) {
7268
+ const { monthly, difference, differencePercent } = compareScenario(report, m);
6591
7269
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
6592
7270
  className: "ccp-analysisModelRow",
6593
7271
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("summary", {
@@ -6606,17 +7284,17 @@ window.__ModuleLoader__.load({
6606
7284
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
6607
7285
  label: text("analysisSimulationCost"),
6608
7286
  value: m.pricedRequests ? money(m.cost) : text("analysisMissing"),
6609
- sub: text("analysisUsd")
7287
+ sub: `${text("analysisCoverage")} ${m.pricedRequests}/${report.totals.requests}`
6610
7288
  }),
6611
7289
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
6612
- label: text("analysisCoverage"),
6613
- value: `${m.pricedRequests}/${m.pricedRequests + m.excludedRequests + m.unknownRequests}`,
6614
- sub: `${text("analysisExcluded")} ${digits(m.excludedRequests)} · ${text("analysisUnknownPrice")} ${digits(m.unknownRequests)}`
7290
+ label: text("analysisScenarioMonthly"),
7291
+ value: monthly.value === null ? text("analysisMissing") : money(monthly.value),
7292
+ sub: monthly.value === null ? text(estimateReasonKeys[monthly.reason]) : text("analysisUsd")
6615
7293
  }),
6616
7294
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
6617
- label: text("analysisCapabilities"),
6618
- value: m.contextWindow === null ? text("analysisMissing") : `${digits(m.contextWindow)} ${text("analysisTokenUnit")}`,
6619
- sub: text(m.images ? "analysisImages" : "analysisTextOnly")
7295
+ label: text("analysisDifference"),
7296
+ value: difference.value === null ? text("analysisMissing") : signedMoney(difference.value),
7297
+ sub: difference.value === null ? text(estimateReasonKeys[difference.reason]) : differencePercent === null ? text("analysisZeroBaseline") : `${differencePercent > 0 ? "+" : ""}${differencePercent.toFixed(1)}%`
6620
7298
  }),
6621
7299
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Chevron, {})
6622
7300
  ]
@@ -6633,7 +7311,11 @@ window.__ModuleLoader__.load({
6633
7311
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DetailGroup, {
6634
7312
  title: "analysisCapabilities",
6635
7313
  text,
6636
- values: [["analysisEfforts", m.efforts.length ? m.efforts.map((e) => effortName(e, text)).join(", ") : text("analysisNoEfforts")]]
7314
+ values: [
7315
+ ["analysisContext", m.contextWindow === null ? text("analysisMissing") : `${digits(m.contextWindow)} ${text("analysisTokenUnit")}`],
7316
+ ["analysisInputType", text(m.images ? "analysisImages" : "analysisTextOnly")],
7317
+ ["analysisEfforts", m.efforts.length ? m.efforts.map((e) => effortName(e, text)).join(", ") : text("analysisNoEfforts")]
7318
+ ]
6637
7319
  })]
6638
7320
  })]
6639
7321
  });
@@ -6935,6 +7617,16 @@ window.__ModuleLoader__.load({
6935
7617
  className: "ccp-analysisAssumption",
6936
7618
  children: text("analysisAssumption")
6937
7619
  }),
7620
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
7621
+ className: "ccp-analysisNote ccp-analysisBaseline",
7622
+ children: [
7623
+ text("analysisBaseline"),
7624
+ ": ",
7625
+ m.pricedRequests === m.requests && m.requests > 0 && !report.truncated && !report.unreadableSessions && !report.skippedSessions ? money(m.cost) : text("analysisMissing"),
7626
+ " · ",
7627
+ text("analysisMonthlyHint")
7628
+ ]
7629
+ }),
6938
7630
  report.catalogUnavailable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6939
7631
  className: "ccp-analysisMessage ccp-analysisWarning",
6940
7632
  role: "status",
@@ -6976,12 +7668,13 @@ window.__ModuleLoader__.load({
6976
7668
  children: [
6977
7669
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: text("analysisModel") }),
6978
7670
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: text("analysisSimulationCost") }),
6979
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: text("analysisCoverage") }),
6980
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: text("analysisCapabilities") }),
7671
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: text("analysisScenarioMonthly") }),
7672
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: text("analysisDifference") }),
6981
7673
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {})
6982
7674
  ]
6983
7675
  }), scenarios.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ScenarioRow, {
6984
7676
  m: model,
7677
+ report,
6985
7678
  text
6986
7679
  }, model.id))]
6987
7680
  }) : !report.catalogUnavailable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
@@ -7125,9 +7818,10 @@ window.__ModuleLoader__.load({
7125
7818
  });
7126
7819
  }
7127
7820
  /** A labelled figure. */
7128
- function Tile({ label, value, sub }) {
7821
+ function Tile({ label, value, sub, title }) {
7129
7822
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7130
7823
  className: "ccp-tile",
7824
+ title,
7131
7825
  children: [
7132
7826
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7133
7827
  className: "ccp-tileLabel",
@@ -7299,7 +7993,8 @@ window.__ModuleLoader__.load({
7299
7993
  children: account.stats.map((stat) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Tile, {
7300
7994
  label: text(stat.label),
7301
7995
  value: stat.value,
7302
- sub: stat.sub
7996
+ sub: stat.sub,
7997
+ title: stat.title
7303
7998
  }, stat.label))
7304
7999
  })
7305
8000
  ]
@@ -8430,7 +9125,8 @@ window.__ModuleLoader__.load({
8430
9125
  * 12/18 label-tertiary, the control on the right. The hairline rhythm is
8431
9126
  * section-level: a 0.5px border-l2 rule separates the GROUPS (see
8432
9127
  * `cc-groupDivided`), never the rows inside one, so a group's rows read as one
8433
- * list. Selectors and buttons are capsules (36px / 28px compact), inputs are
9128
+ * list. Selectors and buttons are
9129
+ * capsules (36px / 28px compact), inputs are
8434
9130
  * 32px with an 8px radius and a 0.5px border-l4 edge, a card is a 0.5px
8435
9131
  * border-l4 outline with a 16px radius, and a nested panel is a
8436
9132
  * bg-module-platform fill with a 12px radius. Errors use state-error-primary.
@@ -8537,6 +9233,25 @@ window.__ModuleLoader__.load({
8537
9233
  .cc-iconButton{flex-shrink:0;align-items:center;justify-content:center;display:inline-flex;width:28px;height:28px;padding:0;background:0 0;border:0;border-radius:6px;color:var(--dsw-alias-label-tertiary);cursor:pointer}
8538
9234
  .cc-iconButton:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
8539
9235
  .cc-iconButton:focus-visible{outline:none;box-shadow:0 0 0 2px var(--dsw-alias-border-l3)}
9236
+ /* Per-model context-length override rows (issue #86): the error wraps onto
9237
+ its own line beneath the select/input/remove trio. The three cells form a
9238
+ fixed column contract — flexible model, 112px value, 28px remove — and the
9239
+ cc-contextHead labels repeat the first two (plus the remove cell's width
9240
+ as a spacer) so they sit exactly over their columns. */
9241
+ .cc-contextItem{align-items:center;gap:8px;display:flex;flex-wrap:wrap;padding:8px 0}
9242
+ .cc-contextHead{align-items:center;gap:8px;display:flex;flex-wrap:wrap;padding:4px 0 0;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
9243
+ .cc-contextHeadModel{flex:1;min-width:200px}
9244
+ .cc-contextHeadValue{flex:none;width:112px}
9245
+ .cc-contextHeadGap{flex:none;width:28px}
9246
+ .cc-contextSelect{position:relative;display:block;flex:1;min-width:200px}
9247
+ /* appearance:none swaps the platform arrow for the cc-contextCaret, shaped
9248
+ like cc-selectorCaret so every dropdown in the plugin opens with the same
9249
+ hand-drawn chevron. */
9250
+ .cc-contextModel{box-sizing:border-box;appearance:none;-webkit-appearance:none;width:100%;padding-inline-end:26px;text-overflow:ellipsis;cursor:pointer}
9251
+ .cc-contextCaret{position:absolute;inset-inline-end:12px;top:50%;width:6px;height:6px;border-right:1.5px solid var(--dsw-alias-label-tertiary);border-bottom:1.5px solid var(--dsw-alias-label-tertiary);transform:translateY(-50%) rotate(45deg);pointer-events:none}
9252
+ .cc-contextSelect:has(.cc-contextModel:disabled) .cc-contextCaret{opacity:.6}
9253
+ .cc-contextValue{flex:none;width:112px}
9254
+ .cc-contextError{flex-basis:100%}
8540
9255
  /* Three dots from one element: the dot plus two box-shadow copies. */
8541
9256
  .cc-kebab{width:3px;height:3px;border-radius:50%;corner-shape:round;background:currentColor;box-shadow:0 -5px 0 currentColor,0 5px 0 currentColor}
8542
9257
  .cc-tabDot{flex-shrink:0;width:8px;height:8px;border-radius:50%;corner-shape:round}
@@ -8848,7 +9563,6 @@ window.__ModuleLoader__.load({
8848
9563
  .ccp-analysisTableHead{padding-top:11px;padding-bottom:11px;background:var(--dsw-alias-interactive-bg-hover);border-bottom:.5px solid var(--dsw-alias-border-l2);font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary)}
8849
9564
  .ccp-analysisTableHead > span:not(:first-child){text-align:right}
8850
9565
  .ccp-analysisScenarioHead,.ccp-analysisScenarioSummary{grid-template-columns:minmax(190px,2fr) minmax(118px,1.1fr) minmax(155px,1.35fr) minmax(142px,1.15fr) 16px}
8851
- .ccp-analysisScenarioHead > span:nth-child(3),.ccp-analysisScenarioHead > span:nth-child(4){text-align:left}
8852
9566
  .ccp-analysisModelSummary,.ccp-analysisScenarioSummary{cursor:pointer;list-style:none;min-height:60px}
8853
9567
  .ccp-analysisModelSummary::-webkit-details-marker,.ccp-analysisScenarioSummary::-webkit-details-marker,.ccp-analysisDisclosure::-webkit-details-marker{display:none}
8854
9568
  .ccp-analysisModelSummary:hover,.ccp-analysisScenarioSummary:hover{background:var(--dsw-alias-interactive-bg-hover)}
@@ -8856,7 +9570,6 @@ window.__ModuleLoader__.load({
8856
9570
  .ccp-analysisModelName{font-size:14px;line-height:22px;font-weight:500;overflow-wrap:anywhere}
8857
9571
  .ccp-analysisModelCount{margin-top:3px}
8858
9572
  .ccp-analysisModelSummary > .ccp-analysisMetric,.ccp-analysisScenarioSummary > .ccp-analysisMetric{align-items:flex-end}
8859
- .ccp-analysisScenarioSummary > .ccp-analysisMetric:nth-child(3),.ccp-analysisScenarioSummary > .ccp-analysisMetric:nth-child(4){align-items:flex-start}
8860
9573
  /* 宽表使用集中表头,但保留每个指标的无障碍标签;窄面板恢复可见标签。 */
8861
9574
  .ccp-analysisModelSummary .ccp-analysisMetricLabel,.ccp-analysisScenarioSummary .ccp-analysisMetricLabel,.ccp-analysisSearchLabel{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
8862
9575
  .ccp-analysisShare{display:flex;align-items:center;gap:8px;width:100%;max-width:148px;margin-top:3px}
@@ -8880,6 +9593,7 @@ window.__ModuleLoader__.load({
8880
9593
  .ccp-analysisMonthlyValue{font-size:14px;line-height:22px;font-weight:500;color:var(--dsw-alias-label-primary);font-variant-numeric:tabular-nums}
8881
9594
  .ccp-analysisMonthly .ccp-analysisNote{width:100%;margin:0}
8882
9595
  .ccp-analysisAssumption{font-size:14px;line-height:22px;color:var(--dsw-alias-label-tertiary);margin:0 0 18px;max-width:76ch}
9596
+ .ccp-analysisBaseline{margin:0 0 16px}
8883
9597
  .ccp-analysisSearch{flex:0 1 270px}
8884
9598
  .ccp-analysisSearchShell{position:relative;display:block}
8885
9599
  .ccp-analysisSearchShell > .ccp-analysisIcon{position:absolute;top:10px;left:11px;color:var(--dsw-alias-label-tertiary);pointer-events:none}
@@ -9265,7 +9979,8 @@ window.__ModuleLoader__.load({
9265
9979
  setActiveAccount: (id) => controller.setActiveAccount(id).then(refreshUsageOn),
9266
9980
  setAccountModels: (target, ids) => controller.setAccountModels(target, ids),
9267
9981
  editVisibleModels: (ids) => controller.editVisibleModels(ids),
9268
- clearVisibleModels: () => controller.clearVisibleModels()
9982
+ clearVisibleModels: () => controller.clearVisibleModels(),
9983
+ editContextWindowOverrides: (next) => controller.editContextWindowOverrides(next)
9269
9984
  });
9270
9985
  ctx.slots.inject("settings.section", () => ctx.slots.register({
9271
9986
  name: "settings.section",