@mars-sea/dsh-commandcode-provider 0.8.3 → 0.9.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
@@ -8,13 +8,114 @@ window.__ModuleLoader__.load({
8
8
  let react = require("react");
9
9
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
10
10
  let react_jsx_runtime = require("react/jsx-runtime");
11
+ //#region src/command-locales.ts
12
+ const commandcodeCommand = {
13
+ zh: {
14
+ title: "📊 Command Code 用量{account}",
15
+ accountTitle: "📊 {label}{badges}",
16
+ accountSeparator: "────────────────────",
17
+ activeBadge: " ✅ 当前使用",
18
+ invalidCredentialBadge: " ⛔ 密钥无效",
19
+ cooldownBadge: " ⏳ 限额冷却中,重置 {when}",
20
+ rateLimitBadge: " ⏳ 已达限额(等待窗口探测)",
21
+ unconfigured: " (未配置 API 密钥)",
22
+ blockedInvalidKey: "⛔ API 密钥无效或已过期 — 服务端拒绝了全部请求(401),请检查该账户的密钥配置",
23
+ blockedServiceUnavailable: "⚠️ Command Code 服务暂时不可用(5xx),稍后重试",
24
+ blockedNetwork: "⚠️ 无法连接 Command Code 服务 — 请检查网络或 API 地址",
25
+ planLine: " 📦 套餐 {name}{status}{period}",
26
+ planPeriodSuffix: " · 账期截止 {date}",
27
+ usageHeader: "── 请求 ──────────────────────────────",
28
+ requestsLine: " 💬 请求 {n} 次 / 失败 {f} 成功率 {r}%",
29
+ costLine: " 💰 花费 {money} ({credits} credits)",
30
+ tokensLine: " 🔤 Token {in} 入 / {out} 出",
31
+ creditsHeader: "── 信用 ──────────────────────────────",
32
+ monthlyLine: " 💳 月额度 {monthly} (已购 {purchased} / 赠送 {free})",
33
+ barLine: " └ {bar} {pct}%",
34
+ windowsHeader: "── 窗口用量 ──────────────────────────",
35
+ fiveHourLine: " ⏱ 5 小时 {used} / {cap}{warn}",
36
+ weeklyLine: " 📅 每周 {used} / {cap}{warn}",
37
+ windowBarLine: " └ {bar} 重置 {when}",
38
+ exceededWarning: " ⚠️ 超限!",
39
+ resetSuffix: "重置 {when}",
40
+ partialFailures: "⚠️ 部分端点失败: {list}",
41
+ noData: "(no data — check your API key)",
42
+ errorText: "Could not fetch Command Code usage: {message}",
43
+ imageGate: "当前会话已包含图片,而模型 {model} 不支持图片输入;请选择支持图片的模型,或先移除会话中的图片。"
44
+ },
45
+ en: {
46
+ title: "📊 Command Code usage{account}",
47
+ accountTitle: "📊 {label}{badges}",
48
+ accountSeparator: "────────────────────",
49
+ activeBadge: " ✅ active",
50
+ invalidCredentialBadge: " ⛔ invalid key",
51
+ cooldownBadge: " ⏳ cooling down, resets {when}",
52
+ rateLimitBadge: " ⏳ rate-limited (waiting for window probe)",
53
+ unconfigured: " (no API key configured)",
54
+ blockedInvalidKey: "⛔ API key invalid or expired — the server rejected every request (401); check the key configured for this account",
55
+ blockedServiceUnavailable: "⚠️ Command Code service temporarily unavailable (5xx); try again later",
56
+ blockedNetwork: "⚠️ could not reach the Command Code service — check your network or the API base setting",
57
+ planLine: " 📦 Plan {name}{status}{period}",
58
+ planPeriodSuffix: " · period ends {date}",
59
+ usageHeader: "── Requests ──────────────────────────",
60
+ requestsLine: " 💬 Requests {n} / failed {f} success rate {r}%",
61
+ costLine: " 💰 Spend {money} ({credits} credits)",
62
+ tokensLine: " 🔤 Tokens {in} in / {out} out",
63
+ creditsHeader: "── Credits ───────────────────────────",
64
+ monthlyLine: " 💳 Monthly {monthly} (purchased {purchased} / free {free})",
65
+ barLine: " └ {bar} {pct}%",
66
+ windowsHeader: "── Window usage ──────────────────────",
67
+ fiveHourLine: " ⏱ 5-hour {used} / {cap}{warn}",
68
+ weeklyLine: " 📅 Weekly {used} / {cap}{warn}",
69
+ windowBarLine: " └ {bar} resets {when}",
70
+ exceededWarning: " ⚠️ exceeded!",
71
+ resetSuffix: "resets {when}",
72
+ partialFailures: "⚠️ some endpoints failed: {list}",
73
+ noData: "(no data — check your API key)",
74
+ errorText: "Could not fetch Command Code usage: {message}",
75
+ imageGate: "This session already contains images, and model {model} does not accept image input; please select an image-capable model, or remove the images from the session first."
76
+ }
77
+ };
78
+ //#endregion
11
79
  //#region src/client/sessions.ts
80
+ /**
81
+ * Friendly-error wrapper for the harness's image-session gate.
82
+ *
83
+ * The host rejects switching to a text-only model while the session already
84
+ * contains images with a `model-unavailable` error
85
+ * (`dsh-host-apiproxy`'s `session.selectModel` handler). That rejection is
86
+ * intentional and cannot be relaxed from the plugin side — the adapter's
87
+ * `inputModalities` is exactly what makes the guard work. What we CAN do is
88
+ * make the error message friendlier: wrap the shared
89
+ * `connection.api.sessions.selectModel` face so a `model-unavailable`
90
+ * rejection shows a clear, actionable hint (with the requested model name)
91
+ * instead of the raw English harness message.
92
+ *
93
+ * The wrapper is deliberately narrow: only the `model-unavailable` code is
94
+ * rewritten, only when the message matches the image-session gate, and only
95
+ * the message text changes — the error code and details pass through
96
+ * untouched so any caller that switches on `error.code` keeps working.
97
+ *
98
+ * The wire types are spelled structurally here (not imported from
99
+ * `@deepseek-ai/dsh-host-apiproxy`) so this client bundle does not drag an
100
+ * extra peer dependency into the package; the shapes are stable and the
101
+ * client build inlines them anyway.
102
+ *
103
+ * The wrapper takes a `getLocale` thunk because it is reached from a
104
+ * non-React path that has no `t` in scope; the supplied thunk reads the
105
+ * active locale at call time (typically `() => ctx.locale.getLocale().active`
106
+ * in the client entry), and the message template lives in the shared
107
+ * `commandcodeCommand` dictionary used by the Host-side `/commandcode`
108
+ * command — the same bilingual surface serves both.
109
+ *
110
+ * This module is deliberately free of React and other client-platform
111
+ * imports so the node test runner can exercise it directly.
112
+ */
12
113
  /** Whether a selectModel rejection is the harness's image-session gate. */
13
114
  function isImageSessionRejection(result) {
14
115
  return !result.result.ok && result.result.error.code === "model-unavailable" && result.result.error.message.includes("does not accept image input");
15
116
  }
16
117
  /** Wrap the shared sessions API so selectModel failures read friendlier. */
17
- function withFriendlyImageError(sessions) {
118
+ function withFriendlyImageError(sessions, getLocale) {
18
119
  const selectModel = sessions.selectModel.bind(sessions);
19
120
  return {
20
121
  ...sessions,
@@ -22,13 +123,14 @@ window.__ModuleLoader__.load({
22
123
  const result = await selectModel(payload, signal);
23
124
  if (!isImageSessionRejection(result)) return result;
24
125
  const model = result.result.error.details?.model ?? payload.model;
126
+ const template = commandcodeCommand[getLocale()].imageGate ?? commandcodeCommand.en.imageGate;
25
127
  return {
26
128
  ...result,
27
129
  result: {
28
130
  ...result.result,
29
131
  error: {
30
132
  ...result.result.error,
31
- message: `当前会话已包含图片,而模型 ${model} 不支持图片输入;请选择支持图片的模型,或先移除会话中的图片。`
133
+ message: template.replace("{model}", model)
32
134
  }
33
135
  }
34
136
  };
@@ -36,8 +138,8 @@ window.__ModuleLoader__.load({
36
138
  };
37
139
  }
38
140
  /** Install the wrapper on a connection's shared sessions face. */
39
- function installFriendlyImageError(connection) {
40
- connection.api.sessions = withFriendlyImageError(connection.api.sessions);
141
+ function installFriendlyImageError(connection, getLocale) {
142
+ connection.api.sessions = withFriendlyImageError(connection.api.sessions, getLocale);
41
143
  }
42
144
  //#endregion
43
145
  //#region src/client/settings.ts
@@ -1197,7 +1299,7 @@ window.__ModuleLoader__.load({
1197
1299
  };
1198
1300
  //#endregion
1199
1301
  //#region package.json
1200
- var version = "0.8.3";
1302
+ var version = "0.9.1";
1201
1303
  var repository = {
1202
1304
  "type": "git",
1203
1305
  "url": "git+https://github.com/Mars-Sea/dsh-commandcode-provider.git"
@@ -1377,9 +1479,19 @@ window.__ModuleLoader__.load({
1377
1479
  *
1378
1480
  * The layout mirrors the harness's settings pages: a max-width content
1379
1481
  * column, labelled fields with hints, a reset affordance, and a
1380
- * save/discard footer. Styles are injected once by the client entry
1482
+ * save/discard footer. Rarely touched connection facts (API base, working
1483
+ * directory, timeouts, plan filter) fold into a collapsed Advanced card
1484
+ * (`AdvancedSection`) so the page leads with the key and the usage facts.
1485
+ * Styles are injected once by the client entry
1381
1486
  * (see src/client/index.ts) and class-prefixed `cc-` to stay local.
1382
1487
  */
1488
+ const ADVANCED_FIELDS = [
1489
+ "apiBase",
1490
+ "workingDir",
1491
+ "requestTimeoutMs",
1492
+ "streamIdleTimeoutMs",
1493
+ "filterModelsByPlan"
1494
+ ];
1383
1495
  /** One labelled field row in the page body. */
1384
1496
  function Field({ id, label, hint, state, disabled, numeric, placeholder, onEdit, onReset, t }) {
1385
1497
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -1429,6 +1541,114 @@ window.__ModuleLoader__.load({
1429
1541
  return t("invalidNumber");
1430
1542
  }
1431
1543
  /**
1544
+ * The collapsed "Advanced" card: API base, working dir, both timeouts, and
1545
+ * the plan filter live here so the page leads with the facts a user actually
1546
+ * touches. Starts collapsed on every visit; expands on demand. While
1547
+ * collapsed, a badge names the customized count so a nonzero override stays
1548
+ * visible (a number field's error blocks save and must be reachable).
1549
+ */
1550
+ function AdvancedSection({ state, disabled, t, onEdit, onReset }) {
1551
+ const [expanded, setExpanded] = (0, react.useState)(false);
1552
+ const overridden = ADVANCED_FIELDS.filter((field) => state[field].overridden).length;
1553
+ const invalid = ADVANCED_FIELDS.some((field) => state[field].invalid);
1554
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1555
+ className: "cc-card",
1556
+ children: [
1557
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1558
+ type: "button",
1559
+ className: "cc-advancedHead",
1560
+ "aria-expanded": expanded,
1561
+ "aria-controls": "cc-advanced-body",
1562
+ onClick: () => setExpanded((value) => !value),
1563
+ children: [
1564
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1565
+ className: "cc-advancedTitle",
1566
+ children: t("advancedSettings")
1567
+ }),
1568
+ overridden > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1569
+ className: "cc-badge",
1570
+ children: overridden === 1 ? t("advancedOverriddenOne") : t("advancedOverriddenMany").replace("{count}", String(overridden))
1571
+ }) : null,
1572
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "cc-advancedSpacer" }),
1573
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1574
+ className: expanded ? "cc-chevron cc-chevronUp" : "cc-chevron",
1575
+ "aria-hidden": "true"
1576
+ })
1577
+ ]
1578
+ }),
1579
+ expanded ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1580
+ id: "cc-advanced-body",
1581
+ className: "cc-advancedBody",
1582
+ children: [
1583
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1584
+ className: "cc-hint",
1585
+ children: t("advancedSettingsHint")
1586
+ }),
1587
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
1588
+ id: "cc-api-base",
1589
+ label: t("apiBase"),
1590
+ hint: t("apiBaseHint"),
1591
+ state: state.apiBase,
1592
+ disabled,
1593
+ onEdit: (text) => onEdit("apiBase", text),
1594
+ onReset: () => onReset("apiBase"),
1595
+ t
1596
+ }),
1597
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
1598
+ id: "cc-working-dir",
1599
+ label: t("workingDir"),
1600
+ hint: t("workingDirHint"),
1601
+ state: state.workingDir,
1602
+ disabled,
1603
+ placeholder: state.defaultWorkingDir,
1604
+ onEdit: (text) => onEdit("workingDir", text),
1605
+ onReset: () => onReset("workingDir"),
1606
+ t
1607
+ }),
1608
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
1609
+ id: "cc-request-timeout",
1610
+ label: t("requestTimeoutMs"),
1611
+ hint: t("requestTimeoutMsHint"),
1612
+ state: state.requestTimeoutMs,
1613
+ disabled,
1614
+ numeric: true,
1615
+ onEdit: (text) => onEdit("requestTimeoutMs", text),
1616
+ onReset: () => onReset("requestTimeoutMs"),
1617
+ t
1618
+ }),
1619
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
1620
+ id: "cc-stream-idle-timeout",
1621
+ label: t("streamIdleTimeoutMs"),
1622
+ hint: t("streamIdleTimeoutMsHint"),
1623
+ state: state.streamIdleTimeoutMs,
1624
+ disabled,
1625
+ numeric: true,
1626
+ onEdit: (text) => onEdit("streamIdleTimeoutMs", text),
1627
+ onReset: () => onReset("streamIdleTimeoutMs"),
1628
+ t
1629
+ }),
1630
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleField, {
1631
+ id: "cc-filter-models-by-plan",
1632
+ label: t("filterModelsByPlan"),
1633
+ hint: t("filterModelsByPlanHint"),
1634
+ state: state.filterModelsByPlan,
1635
+ disabled,
1636
+ defaultChecked: true,
1637
+ onEdit: (text) => onEdit("filterModelsByPlan", text),
1638
+ onReset: () => onReset("filterModelsByPlan"),
1639
+ t
1640
+ })
1641
+ ]
1642
+ }) : null,
1643
+ expanded && invalid ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1644
+ className: "cc-invalid",
1645
+ role: "status",
1646
+ children: t("advancedInvalid")
1647
+ }) : null
1648
+ ]
1649
+ });
1650
+ }
1651
+ /**
1432
1652
  * One boolean field row rendered as a toggle. The staged text is `'true'` /
1433
1653
  * `'false'` / `''` (unset → `defaultChecked`); toggling stages the string the
1434
1654
  * boolean field spec parses back into a real boolean on save.
@@ -1536,7 +1756,7 @@ window.__ModuleLoader__.load({
1536
1756
  });
1537
1757
  }
1538
1758
  /** The per-reason copy for a failed login attempt. */
1539
- function loginFailureCopy(reason, t) {
1759
+ function loginFailureCopy$1(reason, t) {
1540
1760
  if (reason === "denied") return t("loginDenied");
1541
1761
  if (reason === "timeout") return t("loginTimeout");
1542
1762
  if (reason === "invalid-key") return t("loginInvalidKey");
@@ -1562,7 +1782,7 @@ window.__ModuleLoader__.load({
1562
1782
  hint = `${t("loginSuccess")} ${state.userName ?? ""}${keyName}`.trim();
1563
1783
  hintClass = "cc-loginDone";
1564
1784
  } else if (state.phase === "failed") {
1565
- hint = loginFailureCopy(state.reason, t);
1785
+ hint = loginFailureCopy$1(state.reason, t);
1566
1786
  hintClass = "cc-loginError";
1567
1787
  if (state.message !== void 0) hintTitle = state.message;
1568
1788
  } else if (state.phase === "unavailable") {
@@ -2230,86 +2450,36 @@ window.__ModuleLoader__.load({
2230
2450
  }),
2231
2451
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2232
2452
  className: "cc-card",
2233
- children: [
2234
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SecretKeyField, {
2235
- label: t("apiKey"),
2236
- hint: keyLocked ? t("apiKeyLocked") : t("apiKeyHint"),
2237
- state: state.apiKey,
2238
- disabled: disabled || keyLocked,
2239
- configured: state.apiKeyConfigured,
2240
- configuredLabel: t("apiKeySet"),
2241
- unconfiguredLabel: t("apiKeyUnset"),
2242
- clearStaged: state.apiKeyClearStaged,
2243
- showLabel: t("show"),
2244
- hideLabel: t("hide"),
2245
- clearLabel: t("usageKeyClear"),
2246
- clearStagedLabel: t("usageKeyClearStaged"),
2247
- undoClearLabel: t("usageUndoKeyClear"),
2248
- onEdit: (text) => props.edit("apiKey", text),
2249
- onToggleClear: () => props.toggleKeyClear("default")
2250
- }),
2251
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LoginPanel, {
2252
- state: login,
2253
- disabled: disabled || keyLocked,
2254
- t,
2255
- onBegin: props.beginLogin,
2256
- onCancel: props.cancelLogin
2257
- }),
2258
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2259
- id: "cc-api-base",
2260
- label: t("apiBase"),
2261
- hint: t("apiBaseHint"),
2262
- state: state.apiBase,
2263
- disabled,
2264
- onEdit: (text) => props.edit("apiBase", text),
2265
- onReset: () => props.resetField("apiBase"),
2266
- t
2267
- }),
2268
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2269
- id: "cc-working-dir",
2270
- label: t("workingDir"),
2271
- hint: t("workingDirHint"),
2272
- state: state.workingDir,
2273
- disabled,
2274
- placeholder: state.defaultWorkingDir,
2275
- onEdit: (text) => props.edit("workingDir", text),
2276
- onReset: () => props.resetField("workingDir"),
2277
- t
2278
- }),
2279
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2280
- id: "cc-request-timeout",
2281
- label: t("requestTimeoutMs"),
2282
- hint: t("requestTimeoutMsHint"),
2283
- state: state.requestTimeoutMs,
2284
- disabled,
2285
- numeric: true,
2286
- onEdit: (text) => props.edit("requestTimeoutMs", text),
2287
- onReset: () => props.resetField("requestTimeoutMs"),
2288
- t
2289
- }),
2290
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2291
- id: "cc-stream-idle-timeout",
2292
- label: t("streamIdleTimeoutMs"),
2293
- hint: t("streamIdleTimeoutMsHint"),
2294
- state: state.streamIdleTimeoutMs,
2295
- disabled,
2296
- numeric: true,
2297
- onEdit: (text) => props.edit("streamIdleTimeoutMs", text),
2298
- onReset: () => props.resetField("streamIdleTimeoutMs"),
2299
- t
2300
- }),
2301
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleField, {
2302
- id: "cc-filter-models-by-plan",
2303
- label: t("filterModelsByPlan"),
2304
- hint: t("filterModelsByPlanHint"),
2305
- state: state.filterModelsByPlan,
2306
- disabled,
2307
- defaultChecked: true,
2308
- onEdit: (text) => props.edit("filterModelsByPlan", text),
2309
- onReset: () => props.resetField("filterModelsByPlan"),
2310
- t
2311
- })
2312
- ]
2453
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SecretKeyField, {
2454
+ label: t("apiKey"),
2455
+ hint: keyLocked ? t("apiKeyLocked") : t("apiKeyHint"),
2456
+ state: state.apiKey,
2457
+ disabled: disabled || keyLocked,
2458
+ configured: state.apiKeyConfigured,
2459
+ configuredLabel: t("apiKeySet"),
2460
+ unconfiguredLabel: t("apiKeyUnset"),
2461
+ clearStaged: state.apiKeyClearStaged,
2462
+ showLabel: t("show"),
2463
+ hideLabel: t("hide"),
2464
+ clearLabel: t("usageKeyClear"),
2465
+ clearStagedLabel: t("usageKeyClearStaged"),
2466
+ undoClearLabel: t("usageUndoKeyClear"),
2467
+ onEdit: (text) => props.edit("apiKey", text),
2468
+ onToggleClear: () => props.toggleKeyClear("default")
2469
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LoginPanel, {
2470
+ state: login,
2471
+ disabled: disabled || keyLocked,
2472
+ t,
2473
+ onBegin: props.beginLogin,
2474
+ onCancel: props.cancelLogin
2475
+ })]
2476
+ }),
2477
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AdvancedSection, {
2478
+ state,
2479
+ disabled,
2480
+ t,
2481
+ onEdit: props.edit,
2482
+ onReset: props.resetField
2313
2483
  }),
2314
2484
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2315
2485
  className: "cc-footer",
@@ -2364,6 +2534,260 @@ window.__ModuleLoader__.load({
2364
2534
  });
2365
2535
  }
2366
2536
  //#endregion
2537
+ //#region src/client/card.tsx
2538
+ /**
2539
+ * The Command Code provider card inside the harness Models settings page
2540
+ * (browser half). Rendered through the `settings.models.provider-card` keyed
2541
+ * slot introduced in dsh 0.1.2-alpha.1, registered with
2542
+ * `entryKey = 'llm-commandcode'` (the plugin's settings namespace, the key the
2543
+ * Models page dispatches for every Command Code provider row).
2544
+ *
2545
+ * The slot's owner props (`configured`, `keyConfigured`) mirror what the
2546
+ * Models page already knows; the authoritative credential facts still come
2547
+ * from this plugin's `CommandCodeSettingsController` shared with the dedicated
2548
+ * settings page, so the two surfaces can never disagree about whether a key
2549
+ * is stored.
2550
+ *
2551
+ * Not configured: an "Unconfigured" badge, a paste-a-key field, and the
2552
+ * official sign-in button — the whole flow a first-run user needs, inline.
2553
+ * Configured: a green "Configured" badge plus a pointer to the full
2554
+ * Command Code settings page for rotation, usage, and connection facts.
2555
+ *
2556
+ * The card renders nothing until the shared controller's first snapshot is
2557
+ * ready (`available`), which also keeps the stale-facts window of the older
2558
+ * join from ever being visible. A controller-less render (card mounted before
2559
+ * the section registered its inject face — the composition runs one apply)
2560
+ * degrades to the stateless registration-notice form.
2561
+ *
2562
+ * Styles ride the page stylesheet the client entry injects once (`cc-`
2563
+ * prefixed classes); the card adds no CSS of its own.
2564
+ */
2565
+ /** Decide the card's posture from the injected face and the owner facts. */
2566
+ function cardMode(props) {
2567
+ if (props.useCommandCodeSettings === void 0) return { kind: "registration" };
2568
+ const snapshot = props.useCommandCodeSettings((state) => state);
2569
+ return {
2570
+ kind: "live",
2571
+ ready: snapshot.available,
2572
+ controllerConfigured: snapshot.apiKeyConfigured,
2573
+ writable: snapshot.writable,
2574
+ apiKeyWritable: snapshot.apiKeyWritable
2575
+ };
2576
+ }
2577
+ /** Status badge for the credential state (green when configured). */
2578
+ function StatusBadge({ ok, okLabel, pendingLabel }) {
2579
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2580
+ className: ok ? "cc-badge" : "cc-badgeMuted",
2581
+ children: ok ? okLabel : pendingLabel
2582
+ });
2583
+ }
2584
+ /**
2585
+ * The per-reason copy for a failed login attempt (mirrors the settings
2586
+ * page's LoginPanel; the same reasons can surface from this card).
2587
+ */
2588
+ function loginFailureCopy(reason, t) {
2589
+ if (reason === "denied") return t("loginDenied");
2590
+ if (reason === "timeout") return t("loginTimeout");
2591
+ if (reason === "invalid-key") return t("loginInvalidKey");
2592
+ if (reason === "network") return t("loginNetwork");
2593
+ if (reason === "unavailable") return t("loginStoreFailed");
2594
+ if (reason === "cancelled") return t("loginCancelled");
2595
+ return t("loginFailedGeneric");
2596
+ }
2597
+ /** The sign-in affordance row for the not-configured card (see LoginPanel). */
2598
+ function CardLoginRow({ state, disabled, t, onBegin, onCancel }) {
2599
+ const busy = state.phase === "starting" || state.phase === "waiting";
2600
+ let hint = t("loginHintIdle");
2601
+ let hintClass = "cc-hint";
2602
+ if (state.phase === "starting" || state.phase === "waiting") hint = t(state.phase === "starting" ? "loginStarting" : "loginWaiting");
2603
+ else if (state.phase === "success") {
2604
+ const keyName = state.keyName !== void 0 && state.keyName !== "" ? ` · ${state.keyName}` : "";
2605
+ hint = `${t("loginSuccess")} ${state.userName ?? ""}${keyName}`.trim();
2606
+ hintClass = "cc-loginDone";
2607
+ } else if (state.phase === "failed") {
2608
+ hint = loginFailureCopy(state.reason, t);
2609
+ hintClass = "cc-loginError";
2610
+ } else if (state.phase === "unavailable") {
2611
+ hint = `${t("loginUnavailable")} ${state.message ?? ""}`.trim();
2612
+ hintClass = "cc-loginError";
2613
+ }
2614
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2615
+ className: "cc-field",
2616
+ children: [
2617
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2618
+ className: "cc-fieldHead",
2619
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2620
+ className: "cc-label",
2621
+ children: t("loginTitle")
2622
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2623
+ className: "cc-badges",
2624
+ children: busy ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2625
+ type: "button",
2626
+ className: "cc-reset",
2627
+ onClick: onCancel,
2628
+ children: t("loginCancel")
2629
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2630
+ type: "button",
2631
+ className: "cc-reset",
2632
+ disabled,
2633
+ onClick: onBegin,
2634
+ children: t("loginButton")
2635
+ })
2636
+ })]
2637
+ }),
2638
+ state.authUrl !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2639
+ className: "cc-hint",
2640
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
2641
+ className: "cc-loginLink",
2642
+ href: state.authUrl,
2643
+ target: "_blank",
2644
+ rel: "noreferrer",
2645
+ children: t("loginOpenLink")
2646
+ })
2647
+ }) : null,
2648
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2649
+ className: hintClass,
2650
+ children: hint
2651
+ })
2652
+ ]
2653
+ });
2654
+ }
2655
+ /** Compact key field for the not-configured card. */
2656
+ function CardKeyField({ state, disabled, t, onEdit }) {
2657
+ const [visible, setVisible] = (0, react.useState)(false);
2658
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2659
+ className: "cc-field",
2660
+ children: [
2661
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2662
+ className: "cc-fieldHead",
2663
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
2664
+ className: "cc-label",
2665
+ htmlFor: "cc-card-api-key",
2666
+ children: t("apiKey")
2667
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2668
+ className: "cc-badges",
2669
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2670
+ type: "button",
2671
+ className: "cc-reset",
2672
+ disabled,
2673
+ onClick: () => setVisible((value) => !value),
2674
+ children: visible ? t("hide") : t("show")
2675
+ })
2676
+ })]
2677
+ }),
2678
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2679
+ id: "cc-card-api-key",
2680
+ className: "cc-input",
2681
+ type: visible ? "text" : "password",
2682
+ autoComplete: "off",
2683
+ spellCheck: false,
2684
+ value: state.text,
2685
+ disabled,
2686
+ onChange: (event) => onEdit(event.target.value)
2687
+ }),
2688
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2689
+ className: "cc-hint",
2690
+ children: t("apiKeyHint")
2691
+ })
2692
+ ]
2693
+ });
2694
+ }
2695
+ /** One "Configured" badge + pointer to the full settings page (footer area). */
2696
+ function CardConfiguredBody({ t }) {
2697
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2698
+ className: "cc-field",
2699
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2700
+ className: "cc-hint",
2701
+ children: t("cardConfiguredHint")
2702
+ })
2703
+ });
2704
+ }
2705
+ /**
2706
+ * The slot component body. Dispatched on every Command Code provider card of
2707
+ * the Models page (saved row, first-run setup posture, and add-provider
2708
+ * draft).
2709
+ */
2710
+ function CommandCodeProviderCard(props) {
2711
+ const { t } = props;
2712
+ const mode = cardMode(props);
2713
+ const state = props.useCommandCodeSettings !== void 0 ? props.useCommandCodeSettings((snapshot) => snapshot) : void 0;
2714
+ const login = props.useCommandCodeLogin !== void 0 ? props.useCommandCodeLogin((snapshot) => snapshot) : void 0;
2715
+ const dirty = state?.dirty ?? false;
2716
+ const saving = state?.saving ?? false;
2717
+ const invalid = state?.invalid ?? false;
2718
+ const failed = state?.failed ?? false;
2719
+ const savingBlocked = !dirty || invalid;
2720
+ const configured = mode.kind === "live" && mode.ready ? mode.controllerConfigured : props.keyConfigured;
2721
+ const disabled = mode.kind === "live" && (!mode.writable || state !== void 0 && !mode.apiKeyWritable);
2722
+ const showBody = mode.kind === "live" && mode.ready && state !== void 0;
2723
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2724
+ className: "cc-providerCard",
2725
+ "data-cc-models-card": "true",
2726
+ children: [
2727
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2728
+ className: "cc-field",
2729
+ children: [
2730
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2731
+ className: "cc-fieldHead",
2732
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2733
+ className: "cc-label",
2734
+ children: t("cardTitle")
2735
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2736
+ className: "cc-badges",
2737
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusBadge, {
2738
+ ok: configured,
2739
+ okLabel: t("apiKeySet"),
2740
+ pendingLabel: t("apiKeyUnset")
2741
+ }), props.provider.active ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2742
+ className: "cc-badge",
2743
+ children: t("cardRouteActive")
2744
+ }) : null]
2745
+ })]
2746
+ }),
2747
+ mode.kind === "registration" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2748
+ className: "cc-hint",
2749
+ children: t("cardRegistrationHint")
2750
+ }) : null,
2751
+ mode.kind === "live" && !mode.ready ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2752
+ className: "cc-hint",
2753
+ children: t("cardLoadingHint")
2754
+ }) : null
2755
+ ]
2756
+ }),
2757
+ showBody && !configured ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2758
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CardKeyField, {
2759
+ state: state.apiKey,
2760
+ disabled,
2761
+ t,
2762
+ onEdit: (text) => props.edit("apiKey", text)
2763
+ }),
2764
+ login !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CardLoginRow, {
2765
+ state: login,
2766
+ disabled,
2767
+ t,
2768
+ onBegin: props.beginLogin,
2769
+ onCancel: props.cancelLogin
2770
+ }) : null,
2771
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2772
+ className: "cc-footer",
2773
+ children: [failed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2774
+ className: "cc-failed",
2775
+ role: "status",
2776
+ children: t("saveFailed")
2777
+ }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2778
+ type: "button",
2779
+ className: "cc-reset",
2780
+ disabled: savingBlocked || saving,
2781
+ onClick: props.save,
2782
+ children: t(saving ? "saving" : "save")
2783
+ })]
2784
+ })
2785
+ ] }) : null,
2786
+ showBody && configured ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CardConfiguredBody, { t }) : null
2787
+ ]
2788
+ });
2789
+ }
2790
+ //#endregion
2367
2791
  //#region src/client/locales.ts
2368
2792
  const zh = {
2369
2793
  nav: "Command Code",
@@ -2382,6 +2806,11 @@ window.__ModuleLoader__.load({
2382
2806
  requestTimeoutMsHint: "等待响应首个字节的超时;默认 60000。",
2383
2807
  streamIdleTimeoutMs: "流空闲超时(毫秒)",
2384
2808
  streamIdleTimeoutMsHint: "生成流停滞多久视为断连;默认 300000(长思考模型可静默数分钟,默认值刻意放宽)。",
2809
+ advancedSettings: "高级设置",
2810
+ advancedSettingsHint: "API 地址、工作目录、超时与模型过滤等不常修改的选项。",
2811
+ advancedOverriddenOne: "已自定义 1 项",
2812
+ advancedOverriddenMany: "已自定义 {count} 项",
2813
+ advancedInvalid: "高级设置中有未填好的数字,请展开修正后再保存。",
2385
2814
  filterModelsByPlan: "隐藏套餐外模型",
2386
2815
  filterModelsByPlanHint: "开启后,模型选择器只列出当前套餐可用的模型;账户持有按需余额时会显示全部。",
2387
2816
  accountsTitle: "多账户轮换",
@@ -2463,7 +2892,12 @@ window.__ModuleLoader__.load({
2463
2892
  loginNetwork: "无法连接 Command Code 服务校验密钥,请检查网络后重试。",
2464
2893
  loginStoreFailed: "密钥无法写入本机凭据服务,请手动粘贴。",
2465
2894
  loginCancelled: "登录已取消。",
2466
- loginFailedGeneric: "登录失败,请重试或手动粘贴密钥。"
2895
+ loginFailedGeneric: "登录失败,请重试或手动粘贴密钥。",
2896
+ cardTitle: "Command Code 连接",
2897
+ cardRouteActive: "已启用",
2898
+ cardLoadingHint: "正在读取 Command Code 配置…",
2899
+ cardRegistrationHint: "此卡片随 Command Code 插件注册,需要较新版本的 DeepSeek Harness 才会显示完整内容。",
2900
+ cardConfiguredHint: "API 密钥已就绪。如需更换密钥、添加多账户轮换、查看用量或修改 API 地址,请前往设置中的「Command Code」页。"
2467
2901
  };
2468
2902
  const en = {
2469
2903
  nav: "Command Code",
@@ -2482,6 +2916,11 @@ window.__ModuleLoader__.load({
2482
2916
  requestTimeoutMsHint: "Time to wait for the first response byte; default 60000.",
2483
2917
  streamIdleTimeoutMs: "Stream idle timeout (ms)",
2484
2918
  streamIdleTimeoutMsHint: "How long a stalled stream is treated as dead; default 300000 (deliberately generous — long-thinking models can stay silent for minutes).",
2919
+ advancedSettings: "Advanced",
2920
+ advancedSettingsHint: "Rarely touched options: API base URL, working directory, timeouts, and model filtering.",
2921
+ advancedOverriddenOne: "1 customized",
2922
+ advancedOverriddenMany: "{count} customized",
2923
+ advancedInvalid: "A number in Advanced settings is not ready to save; expand to fix it.",
2485
2924
  filterModelsByPlan: "Hide out-of-plan models",
2486
2925
  filterModelsByPlanHint: "When on, the model picker lists only models your subscription includes; any on-demand credit balance shows the full catalog.",
2487
2926
  accountsTitle: "Account rotation",
@@ -2563,7 +3002,12 @@ window.__ModuleLoader__.load({
2563
3002
  loginNetwork: "Could not reach the Command Code service to validate the key; check your network and retry.",
2564
3003
  loginStoreFailed: "The key could not be stored in the local credential service; paste it manually.",
2565
3004
  loginCancelled: "Sign-in cancelled.",
2566
- loginFailedGeneric: "Sign-in failed; try again or paste the key manually."
3005
+ loginFailedGeneric: "Sign-in failed; try again or paste the key manually.",
3006
+ cardTitle: "Command Code connection",
3007
+ cardRouteActive: "Active",
3008
+ cardLoadingHint: "Loading the Command Code configuration…",
3009
+ cardRegistrationHint: "This card is contributed by the Command Code plugin; a newer DeepSeek Harness is needed to show the full controls.",
3010
+ cardConfiguredHint: "The API key is ready. To replace it, add account rotation, review usage, or change the API base, open the \"Command Code\" page in Settings."
2567
3011
  };
2568
3012
  //#endregion
2569
3013
  //#region src/client/index.ts
@@ -2596,6 +3040,16 @@ window.__ModuleLoader__.load({
2596
3040
  select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box-sizing:content-box;padding-right:32px;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='7' viewBox='0 0 12 7'%3E%3Cpath d='M1 1l5 5 5-5' fill='none' stroke='%23888f98' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 11px center}
2597
3041
  .cc-inputInvalid{border-color:var(--dsw-alias-label-error)}
2598
3042
  .cc-invalid{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}
3043
+ /* The Advanced card: its header row is one full-width toggle button; the
3044
+ * chevron is two borders on a rotated square (no icon dependency). */
3045
+ .cc-advancedHead{align-items:center;gap:8px;display:flex;width:100%;padding:12px 0;background:0 0;border:none;cursor:pointer;font:inherit;text-align:left}
3046
+ .cc-advancedHead:hover .cc-advancedTitle{color:var(--dsw-alias-label-primary)}
3047
+ .cc-advancedTitle{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500;line-height:1.5}
3048
+ .cc-advancedSpacer{flex:1}
3049
+ .cc-chevron{flex-shrink:0;border-right:1.5px solid var(--dsw-alias-label-tertiary);border-bottom:1.5px solid var(--dsw-alias-label-tertiary);width:8px;height:8px;margin-right:4px;margin-bottom:2px;transform:rotate(45deg);transition:transform .15s ease}
3050
+ .cc-chevronUp{transform:rotate(-135deg);margin-bottom:-3px}
3051
+ .cc-advancedBody{flex-direction:column;display:flex}
3052
+ .cc-advancedBody>.cc-field:first-of-type{border-top:1px solid var(--dsw-alias-border-l2)}
2599
3053
  .cc-hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}
2600
3054
  .cc-footer{justify-content:flex-end;align-items:center;gap:8px;display:flex}
2601
3055
  .cc-toggleRow{align-items:center;gap:8px;cursor:pointer;display:flex}
@@ -2682,7 +3136,7 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
2682
3136
  function apply(ctx) {
2683
3137
  injectPageCss();
2684
3138
  const connection = ctx.get("connection");
2685
- if (connection !== void 0) installFriendlyImageError(connection);
3139
+ if (connection !== void 0) installFriendlyImageError(connection, () => ctx.locale.getLocale().active);
2686
3140
  ctx.effect(() => ctx.locale.register("settings.commandcode", {
2687
3141
  zh,
2688
3142
  en
@@ -2800,6 +3254,24 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
2800
3254
  locale: "settings.commandcode",
2801
3255
  inject: injected
2802
3256
  }, CommandCodeSettingsPage));
3257
+ ctx.slots.inject("settings.models.provider-card", () => ctx.slots.register({
3258
+ name: "settings.models.provider-card",
3259
+ key: "llm-commandcode",
3260
+ locale: "settings.commandcode",
3261
+ inject: () => ({
3262
+ hooks: {
3263
+ commandCodeSettings: store,
3264
+ commandCodeLogin: loginStore
3265
+ },
3266
+ edit: (field, text) => controller.edit(field, text),
3267
+ save: () => void controller.save().then(() => {
3268
+ const settled = controller.state();
3269
+ if (!settled.failed && settled.anyAccountConfigured) usageController.refresh();
3270
+ }),
3271
+ beginLogin: () => void loginController.begin(),
3272
+ cancelLogin: () => void loginController.cancel()
3273
+ })
3274
+ }, CommandCodeProviderCard));
2803
3275
  }
2804
3276
  const inject = [
2805
3277
  "slots",