@mars-sea/dsh-commandcode-provider 0.13.4 → 0.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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;
@@ -505,6 +512,66 @@ window.__ModuleLoader__.load({
505
512
  return entries.length === 0 ? void 0 : Object.fromEntries(entries);
506
513
  }
507
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
+ }
574
+ //#endregion
508
575
  //#region src/client/settings-write.ts
509
576
  /** 设置写入过程:同一控制器的意图按顺序执行,配置确认与凭据确认分别保留。
510
577
  * 生产使用现有远端设置/凭据,测试替换这些依赖;不提供跨服务事务。 */
@@ -959,17 +1026,26 @@ window.__ModuleLoader__.load({
959
1026
  * The compact token-count shorthand (`32K`, `1M`) in BINARY units, the way
960
1027
  * context windows are quoted: 32K → 32 768 (32 × 1024), 128K → 131 072,
961
1028
  * 1M → 1 048 576 (1024 × 1024), so the common power-of-two windows land on
962
- * their exact values instead of 1000-based rounding.
963
- *
964
- * Plain text uses the {@link numberField} parse plus an integer check;
965
- * shorthand converts to whole tokens first. Empty-clears and bounds
966
- * errors stay identical, and the converted value is bounds-checked against the same
967
- * 1 – 10,000,000 range (the Host schema would reject an overshoot at save
968
- * time with only a generic failure; naming `tooLarge` here says which).
969
- * `Math.round` keeps fractional shorthand like `1.5K` on whole tokens.
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.
970
1031
  */
971
1032
  const COMPACT_TOKENS = /^(\d+(?:\.\d+)?)\s*([kKmM])$/;
972
- function contextWindowField(field, bounds) {
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) {
973
1049
  const plain = numberField$1(field, bounds);
974
1050
  return {
975
1051
  field,
@@ -978,7 +1054,7 @@ window.__ModuleLoader__.load({
978
1054
  const compact = COMPACT_TOKENS.exec(text.trim());
979
1055
  if (compact === null) {
980
1056
  const parsed = plain.parse(text);
981
- return parsed.kind === "set" && !isValidContextWindow(parsed.value) ? {
1057
+ return parsed.kind === "set" && !isValid(parsed.value) ? {
982
1058
  kind: "invalid",
983
1059
  reason: "format"
984
1060
  } : parsed;
@@ -1027,6 +1103,23 @@ window.__ModuleLoader__.load({
1027
1103
  };
1028
1104
  }
1029
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
+ /**
1030
1123
  * 秒制输入保留毫秒精度,兼容已有 1500ms、1ms 等配置。
1031
1124
  *
1032
1125
  * Milliseconds are the wrong unit to show: the defaults are 300000 and the
@@ -1078,10 +1171,11 @@ window.__ModuleLoader__.load({
1078
1171
  min: 0,
1079
1172
  max: 50
1080
1173
  }),
1081
- contextWindowField("contextWindow", {
1174
+ tokenCountField("contextWindow", {
1082
1175
  min: 1,
1083
1176
  max: CONTEXT_WINDOW_MAX
1084
- }),
1177
+ }, isValidContextWindow),
1178
+ maxOutputTokensField("maxOutputTokens"),
1085
1179
  booleanField$1("offloadSeenImagesForCache"),
1086
1180
  booleanField$1("filterModelsByPlan"),
1087
1181
  booleanField$1("webSearch"),
@@ -1201,6 +1295,7 @@ window.__ModuleLoader__.load({
1201
1295
  visibleModels: this.effectiveVisibleModels(),
1202
1296
  visibleModelsAll: this.visibleModelsDraft === void 0 ? this.storedShowsAll() : this.visibleModelsDraft.length === 0,
1203
1297
  contextWindow: this.field("contextWindow"),
1298
+ maxOutputTokens: this.field("maxOutputTokens"),
1204
1299
  contextWindowOverrides: this.effectiveContextWindowOverrides(),
1205
1300
  catalogModels: this.catalogModels,
1206
1301
  catalogFailed: this.catalogFailed,
@@ -2618,6 +2713,30 @@ window.__ModuleLoader__.load({
2618
2713
  };
2619
2714
  //#endregion
2620
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
+ ];
2621
2740
  const guard = makeBoundaryValidator("commandcode/enrollment");
2622
2741
  const phases = [
2623
2742
  "creating",
@@ -2634,11 +2753,13 @@ window.__ModuleLoader__.load({
2634
2753
  const source = guard.record(value, "state");
2635
2754
  const phase = source.phase;
2636
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");
2637
2758
  const state = {
2638
2759
  id: guard.stringField(source, "id", "id"),
2639
2760
  ref: guard.stringField(source, "ref", "ref"),
2640
2761
  phase,
2641
- message: guard.stringField(source, "message", "message")
2762
+ messageKey
2642
2763
  };
2643
2764
  for (const field of ["authUrl", "suggestedName"]) if (source[field] !== void 0) state[field] = guard.stringField(source, field, field);
2644
2765
  return state;
@@ -2793,7 +2914,7 @@ window.__ModuleLoader__.load({
2793
2914
  id,
2794
2915
  ref: "",
2795
2916
  phase: "creating",
2796
- message: "正在创建账号"
2917
+ messageKey: "enrollmentCreating"
2797
2918
  }
2798
2919
  });
2799
2920
  try {
@@ -2814,7 +2935,7 @@ window.__ModuleLoader__.load({
2814
2935
  this.receive(id, result);
2815
2936
  } catch {
2816
2937
  if (this.id === id) this.cancel();
2817
- this.failure("账号开通请求失败,请检查连接后重试");
2938
+ this.failure("enrollmentBeginFailed");
2818
2939
  }
2819
2940
  }
2820
2941
  /** 页面可立即离开;宿主仍等待写入和补偿收尾。 */
@@ -2833,7 +2954,7 @@ window.__ModuleLoader__.load({
2833
2954
  })).then(() => {
2834
2955
  this.changed();
2835
2956
  this.refresh();
2836
- }, () => this.failure("取消请求未确认,重新连接后请检查未完成记录"));
2957
+ }, () => this.failure("enrollmentCancelUnconfirmed"));
2837
2958
  }
2838
2959
  async name(name) {
2839
2960
  const id = this.id;
@@ -2845,7 +2966,7 @@ window.__ModuleLoader__.load({
2845
2966
  ...name ? { name } : {}
2846
2967
  }));
2847
2968
  } catch {
2848
- this.failure("名称保存失败,请重试或接受已有名称");
2969
+ this.failure("enrollmentNameRequestFailed");
2849
2970
  }
2850
2971
  }
2851
2972
  async recover(record, name) {
@@ -2855,7 +2976,7 @@ window.__ModuleLoader__.load({
2855
2976
  pageId: this.pageId,
2856
2977
  ...name ? { name } : {}
2857
2978
  });
2858
- if (!result.ok) this.failure("收尾未完成,请重试");
2979
+ if (!result.ok) this.failure("enrollmentCleanupPending");
2859
2980
  else if (!this.disposed && this.store.getSnapshot().active?.id === record.id) this.store.set({
2860
2981
  ...this.store.getSnapshot(),
2861
2982
  active: result.value,
@@ -2864,7 +2985,7 @@ window.__ModuleLoader__.load({
2864
2985
  this.changed();
2865
2986
  await this.refresh();
2866
2987
  } catch {
2867
- this.failure("收尾请求失败,请检查连接后重试");
2988
+ this.failure("enrollmentCleanupRequestFailed");
2868
2989
  }
2869
2990
  }
2870
2991
  dispose() {
@@ -2889,7 +3010,7 @@ window.__ModuleLoader__.load({
2889
3010
  this.watch = void 0;
2890
3011
  this.ready = void 0;
2891
3012
  this.cancel();
2892
- this.failure("开通连接已断开,宿主正在收尾;重连后请检查未完成记录");
3013
+ this.failure("enrollmentDisconnected");
2893
3014
  }
2894
3015
  }
2895
3016
  })();
@@ -2906,7 +3027,7 @@ window.__ModuleLoader__.load({
2906
3027
  receive(id, result) {
2907
3028
  if (this.disposed || this.id !== id) return;
2908
3029
  if (!result.ok) {
2909
- this.failure("宿主无法完成账号开通,请检查连接与写入权限");
3030
+ this.failure("enrollmentHostUnavailable");
2910
3031
  return;
2911
3032
  }
2912
3033
  const active = result.value;
@@ -2938,16 +3059,17 @@ window.__ModuleLoader__.load({
2938
3059
  pageId: this.pageId
2939
3060
  }));
2940
3061
  } catch {
2941
- this.failure("开通状态暂不可用,正在等待重新连接");
3062
+ this.failure("enrollmentStatusUnavailable");
2942
3063
  if (!this.disposed && this.id === id) this.timer = setTimeout(() => {
2943
3064
  this.poll(id);
2944
3065
  }, this.pollMs);
2945
3066
  }
2946
3067
  }
2947
- failure(message) {
3068
+ /** 记下要显示的语义键;文案在渲染处按界面语言取,本控制器不接触成品文本。 */
3069
+ failure(key) {
2948
3070
  if (!this.disposed) this.store.set({
2949
3071
  ...this.store.getSnapshot(),
2950
- error: message
3072
+ error: key
2951
3073
  });
2952
3074
  }
2953
3075
  stopTimer() {
@@ -2985,7 +3107,7 @@ window.__ModuleLoader__.load({
2985
3107
  filterModelsByPlan: "隐藏套餐外模型",
2986
3108
  filterModelsByPlanHint: "只显示套餐内模型;按需余额账户仍显示全部。",
2987
3109
  webSearch: "用 Command Code 承载联网搜索",
2988
- webSearchHint: "使用同一密钥承载 dsh 的 web_search;关闭后恢复原搜索后端。",
3110
+ webSearchHint: "使用同一密钥承载 dsh 的 web_search;关闭后恢复原搜索后端。若「网页搜索」设置里已显式选择其他后端,本开关不生效。",
2989
3111
  showSidebarQuota: "在侧边栏显示额度卡片",
2990
3112
  showSidebarQuotaHint: "在侧边栏底部显示套餐与额度卡片,点击可打开完整仪表盘。默认关闭,关闭时不会为它后台刷新用量。",
2991
3113
  zdr: "零数据保留(ZDR)",
@@ -3018,6 +3140,11 @@ window.__ModuleLoader__.load({
3018
3140
  contextWindowTooSmall: "上下文长度不能小于 1",
3019
3141
  contextWindowTooLarge: "上下文长度不能大于 10,000,000",
3020
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 数",
3021
3148
  overridden: "已覆盖",
3022
3149
  reset: "重置",
3023
3150
  invalidNumber: "无效数字",
@@ -3044,6 +3171,7 @@ window.__ModuleLoader__.load({
3044
3171
  writeRefreshFailed: "密钥状态刷新失败;已获确认的写入仍有效。",
3045
3172
  writeRefreshRetry: "仅刷新密钥状态",
3046
3173
  writeStateUnknown: "密钥状态尚未确认",
3174
+ writeCredentialRefNote: "(原引用:{ref})",
3047
3175
  unsavedChanges: "有未保存的更改",
3048
3176
  saveInvalid: "有字段填写不正确,修正后才能保存",
3049
3177
  discard: "放弃",
@@ -3131,12 +3259,19 @@ window.__ModuleLoader__.load({
3131
3259
  accountAddLogin: "网页登录",
3132
3260
  accountAddPaste: "粘贴密钥",
3133
3261
  accountAddConfirm: "添加",
3262
+ accountAddCancel: "取消开通",
3263
+ accountAddLinkHint: "若登录页面未自动打开,请点击下方链接。",
3264
+ accountAddOpenLink: "打开浏览器登录",
3134
3265
  accountNamePlaceholder: "备注名(可选,网页登录时默认使用账户名)",
3135
3266
  accountNameN: "账户 {n}",
3267
+ accountNameSaveRetry: "重试保存名称",
3268
+ accountNameAcceptExisting: "接受已有名称",
3136
3269
  accountKeyPlaceholder: "粘贴 API 密钥",
3137
3270
  accountOpFailed: "账户操作未能完成,请重试。",
3138
3271
  accountCleanupPending: "账户已移除,但有密钥尚未清理。可重试,不影响其他账户。",
3139
3272
  accountCleanupRetry: "重试清理密钥",
3273
+ accountEnrollmentCleanupRetry: "重试清理",
3274
+ accountRefSeparator: ":",
3140
3275
  accountModels: "专用模型",
3141
3276
  accountModelsHint: "选中的模型优先由此账户处理,此账户不可用时仍会自动切换。一个模型只属于一个账户,选中会把它从其他账户移过来。",
3142
3277
  accountModelOwner: "属于 {name}",
@@ -3145,7 +3280,29 @@ window.__ModuleLoader__.load({
3145
3280
  modelCatalogFailed: "模型目录获取失败,暂时无法选择模型;已保存的设置仍会生效。",
3146
3281
  modelsTitle: "模型与上下文",
3147
3282
  privacyTitle: "隐私与安全",
3148
- 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: "开通状态暂不可用,正在等待重新连接"
3149
3306
  };
3150
3307
  const en = {
3151
3308
  openAnalytics: "View model usage, cost and performance",
@@ -3175,7 +3332,7 @@ window.__ModuleLoader__.load({
3175
3332
  filterModelsByPlan: "Hide out-of-plan models",
3176
3333
  filterModelsByPlanHint: "Show only models in your plan; on-demand credit accounts still see the full catalog.",
3177
3334
  webSearch: "Serve dsh web search with Command Code",
3178
- 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.",
3179
3336
  showSidebarQuota: "Show the quota card in the sidebar",
3180
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.",
3181
3338
  zdr: "Zero data retention (ZDR)",
@@ -3208,6 +3365,11 @@ window.__ModuleLoader__.load({
3208
3365
  contextWindowTooSmall: "Context length must be at least 1",
3209
3366
  contextWindowTooLarge: "Context length must not exceed 10,000,000",
3210
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",
3211
3373
  overridden: "Overridden",
3212
3374
  reset: "Reset",
3213
3375
  invalidNumber: "Invalid number",
@@ -3221,19 +3383,20 @@ window.__ModuleLoader__.load({
3221
3383
  saving: "Saving",
3222
3384
  saved: "Settings saved",
3223
3385
  saveFailed: "Save failed, please retry.",
3224
- writeConflict: "待提交字段已被其他操作修改,配置未写入;请核对后再保存。",
3225
- writeConfigUnconfirmed: "配置提交未获确认;密钥未写入,请核对宿主状态后重试。",
3226
- writeConfigSaved: "配置已保存。",
3227
- writeKeyNotIssued: "密钥操作尚未发出;请核对后重试。",
3228
- writeKeyUnconfirmed: "密钥操作未获确认;请核对后手动重试。",
3229
- writeKeyConfirmed: "原引用的密钥操作已获确认。",
3230
- writeTargetBefore: "默认密钥引用已变化,本次密钥操作未发出;请核对当前目标。",
3231
- writeTargetAfter: "原引用的密钥操作已发出,默认引用随后变化;请核对两个引用。",
3232
- writeClosed: "客户端已销毁,未发出的操作已停止;已确认的修改保留。",
3233
- writeCleanupPending: "账号修改已确认,清理记录尚未收束,请重试清理。",
3234
- writeRefreshFailed: "密钥状态刷新失败;已获确认的写入仍有效。",
3235
- writeRefreshRetry: "仅刷新密钥状态",
3236
- 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})",
3237
3400
  unsavedChanges: "You have unsaved changes",
3238
3401
  saveInvalid: "Fix the highlighted field before saving",
3239
3402
  discard: "Discard",
@@ -3250,7 +3413,7 @@ window.__ModuleLoader__.load({
3250
3413
  usageCost: "Spend",
3251
3414
  usageCompleted: "Completed",
3252
3415
  usageBillingPeriod: "Official billing-period usage",
3253
- usagePeriodUnknown: "官方用量汇总,统计周期未确认",
3416
+ usagePeriodUnknown: "Official usage summary; the billing period is unconfirmed",
3254
3417
  usageTokens: "Tokens",
3255
3418
  usageTokensIn: "in",
3256
3419
  usageTokensOut: "out",
@@ -3321,12 +3484,19 @@ window.__ModuleLoader__.load({
3321
3484
  accountAddLogin: "Sign in",
3322
3485
  accountAddPaste: "Paste key",
3323
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",
3324
3490
  accountNamePlaceholder: "Name (optional; sign-in uses the account name)",
3325
3491
  accountNameN: "Account {n}",
3492
+ accountNameSaveRetry: "Retry saving the name",
3493
+ accountNameAcceptExisting: "Accept the existing name",
3326
3494
  accountKeyPlaceholder: "Paste an API key",
3327
3495
  accountOpFailed: "The account change did not complete. Try again.",
3328
3496
  accountCleanupPending: "The account was removed, but credential cleanup is still pending. Retry without affecting other accounts.",
3329
3497
  accountCleanupRetry: "Retry credential cleanup",
3498
+ accountEnrollmentCleanupRetry: "Retry cleanup",
3499
+ accountRefSeparator: ": ",
3330
3500
  accountModels: "Dedicated models",
3331
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.",
3332
3502
  accountModelOwner: "on {name}",
@@ -3335,7 +3505,29 @@ window.__ModuleLoader__.load({
3335
3505
  modelCatalogFailed: "The model catalog could not be loaded; saved settings still apply.",
3336
3506
  modelsTitle: "Models & context",
3337
3507
  privacyTitle: "Privacy & security",
3338
- 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."
3339
3531
  };
3340
3532
  /** 同一确认结果在设置页和模型卡使用相同措辞,不从 configured 推断写入结果。 */
3341
3533
  function settingsWriteNotice(result, t) {
@@ -3351,7 +3543,7 @@ window.__ModuleLoader__.load({
3351
3543
  const messages = key ? [t(key)] : [];
3352
3544
  if (result.issue === "target-changed" && result.credentialRef) {
3353
3545
  messages.push(t(result.credential === "confirmed" ? "writeKeyConfirmed" : "writeKeyUnconfirmed"));
3354
- messages.push(`(原引用:${result.credentialRef})`);
3546
+ messages.push(t("writeCredentialRefNote").replace("{ref}", result.credentialRef));
3355
3547
  }
3356
3548
  if (key && result.config === "confirmed" && result.issue !== "cleanup-pending") messages.unshift(t("writeConfigSaved"));
3357
3549
  if (result.refreshFailed) messages.push(t("writeRefreshFailed"));
@@ -3510,7 +3702,7 @@ window.__ModuleLoader__.load({
3510
3702
  }
3511
3703
  //#endregion
3512
3704
  //#region package.json
3513
- var version = "0.13.4";
3705
+ var version = "0.14.1";
3514
3706
  var repository = {
3515
3707
  "type": "git",
3516
3708
  "url": "git+https://github.com/Mars-Sea/dsh-commandcode-provider.git"
@@ -3769,7 +3961,7 @@ window.__ModuleLoader__.load({
3769
3961
  }) : null;
3770
3962
  }
3771
3963
  /** One text/number field row. */
3772
- function Field({ id, label, hint, state, disabled, numeric, decimal, context, wide, placeholder, onEdit, onReset, t }) {
3964
+ function Field({ id, label, hint, state, disabled, numeric, decimal, context, output, wide, placeholder, onEdit, onReset, t }) {
3773
3965
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingRow, {
3774
3966
  title: label,
3775
3967
  titleFor: id,
@@ -3778,7 +3970,7 @@ window.__ModuleLoader__.load({
3778
3970
  t
3779
3971
  }),
3780
3972
  description: hint,
3781
- error: state.invalid ? invalidCopy(state.invalidReason, t, context === true ? "context" : decimal === true ? "duration" : "number") : void 0,
3973
+ error: state.invalid ? invalidCopy(state.invalidReason, t, output === true ? "output" : context === true ? "context" : decimal === true ? "duration" : "number") : void 0,
3782
3974
  control: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldOverride, {
3783
3975
  label,
3784
3976
  state,
@@ -3810,6 +4002,11 @@ window.__ModuleLoader__.load({
3810
4002
  if (reason === "tooLarge") return t("contextWindowTooLarge");
3811
4003
  return t("contextWindowInvalid");
3812
4004
  }
4005
+ if (mode === "output") {
4006
+ if (reason === "tooSmall") return t("maxOutputTokensTooSmall");
4007
+ if (reason === "tooLarge") return t("maxOutputTokensTooLarge");
4008
+ return t("maxOutputTokensInvalid");
4009
+ }
3813
4010
  if (reason === "tooSmall") return t("numberTooSmall");
3814
4011
  if (reason === "tooLarge") return t("numberTooLarge");
3815
4012
  return t("invalidNumber");
@@ -4360,6 +4557,18 @@ window.__ModuleLoader__.load({
4360
4557
  onReset: () => onReset("contextWindow"),
4361
4558
  t
4362
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,
4565
+ disabled,
4566
+ numeric: true,
4567
+ output: true,
4568
+ onEdit: (text) => onEdit("maxOutputTokens", text),
4569
+ onReset: () => onReset("maxOutputTokens"),
4570
+ t
4571
+ }),
4363
4572
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingRow, {
4364
4573
  title: t("contextOverridesTitle"),
4365
4574
  description: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("contextOverridesHint") }),
@@ -5175,23 +5384,23 @@ window.__ModuleLoader__.load({
5175
5384
  active ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5176
5385
  className: "cc-hint",
5177
5386
  role: "status",
5178
- children: active.message
5387
+ children: t(active.messageKey)
5179
5388
  }) : null,
5180
5389
  active?.authUrl && active.phase === "waiting" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5181
5390
  className: "cc-hint",
5182
- children: "若登录页面未自动打开,请点击下方链接。"
5391
+ children: t("accountAddLinkHint")
5183
5392
  }) : null,
5184
5393
  active?.authUrl && active.phase === "waiting" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
5185
5394
  className: "cc-linkButton",
5186
5395
  href: active.authUrl,
5187
5396
  target: "_blank",
5188
5397
  rel: "noreferrer",
5189
- children: "打开浏览器登录"
5398
+ children: t("accountAddOpenLink")
5190
5399
  }) : null,
5191
5400
  snapshot.error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5192
5401
  className: "cc-loginError",
5193
5402
  role: "status",
5194
- children: snapshot.error
5403
+ children: t(snapshot.error)
5195
5404
  }) : null,
5196
5405
  active?.phase === "naming" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5197
5406
  className: "cc-inlineActions",
@@ -5201,14 +5410,14 @@ window.__ModuleLoader__.load({
5201
5410
  size: "sm",
5202
5411
  disabled,
5203
5412
  onClick: () => void enrollment.name(active.suggestedName || label || fallbackName),
5204
- children: "重试保存名称"
5413
+ children: t("accountNameSaveRetry")
5205
5414
  }),
5206
5415
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5207
5416
  variant: "ghost",
5208
5417
  size: "sm",
5209
5418
  disabled,
5210
5419
  onClick: () => void enrollment.name(),
5211
- children: "接受已有名称"
5420
+ children: t("accountNameAcceptExisting")
5212
5421
  }),
5213
5422
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5214
5423
  variant: "ghost",
@@ -5259,7 +5468,7 @@ window.__ModuleLoader__.load({
5259
5468
  variant: "ghost",
5260
5469
  size: "sm",
5261
5470
  onClick: close,
5262
- children: "取消开通"
5471
+ children: t("accountAddCancel")
5263
5472
  }) : null
5264
5473
  ]
5265
5474
  });
@@ -5346,7 +5555,7 @@ window.__ModuleLoader__.load({
5346
5555
  !adding && snapshot.error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5347
5556
  className: "cc-rowError",
5348
5557
  role: "status",
5349
- children: snapshot.error
5558
+ children: t(snapshot.error)
5350
5559
  }) : null,
5351
5560
  state.pendingCredentialCleanup.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5352
5561
  className: "cc-rowError",
@@ -5367,34 +5576,34 @@ window.__ModuleLoader__.load({
5367
5576
  children: [
5368
5577
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", { children: [
5369
5578
  record.ref,
5370
- ":",
5371
- record.message
5579
+ t("accountRefSeparator"),
5580
+ t(record.messageKey)
5372
5581
  ] }),
5373
5582
  record.phase === "naming" && record.suggestedName ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5374
5583
  variant: "ghost",
5375
5584
  size: "sm",
5376
5585
  disabled,
5377
5586
  onClick: () => void enrollment.recover(record, record.suggestedName),
5378
- children: "重试保存名称"
5587
+ children: t("accountNameSaveRetry")
5379
5588
  }) : null,
5380
5589
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5381
5590
  variant: "ghost",
5382
5591
  size: "sm",
5383
5592
  disabled,
5384
5593
  onClick: () => void enrollment.recover(record),
5385
- children: record.phase === "naming" ? "接受已有名称" : "重试清理"
5594
+ children: record.phase === "naming" ? t("accountNameAcceptExisting") : t("accountEnrollmentCleanupRetry")
5386
5595
  })
5387
5596
  ]
5388
5597
  }, record.id)),
5389
5598
  snapshot.active?.phase === "cleanup-needed" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5390
5599
  className: "cc-rowError",
5391
5600
  role: "status",
5392
- 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, {
5393
5602
  variant: "ghost",
5394
5603
  size: "sm",
5395
5604
  disabled,
5396
5605
  onClick: () => void enrollment.recover(snapshot.active),
5397
- children: "重试清理"
5606
+ children: t("accountEnrollmentCleanupRetry")
5398
5607
  })]
5399
5608
  }) : null,
5400
5609
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -6269,7 +6478,7 @@ window.__ModuleLoader__.load({
6269
6478
  usage: "Usage",
6270
6479
  completed: "completed",
6271
6480
  usageBillingPeriod: "Official billing-period usage",
6272
- usagePeriodUnknown: "官方用量汇总,统计周期未确认",
6481
+ usagePeriodUnknown: "Official usage summary; the billing period is unconfirmed",
6273
6482
  monthly: "Monthly",
6274
6483
  monthlyLimit: "Monthly limit",
6275
6484
  monthlyUsed: "Monthly used",