@mars-sea/dsh-commandcode-provider 0.8.4 โ†’ 0.10.0-alpha.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
@@ -4,17 +4,146 @@ window.__ModuleLoader__.load({
4
4
  var module = { exports: {} };
5
5
  var exports = module.exports;
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
- let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
8
7
  let react = require("react");
9
8
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
10
9
  let react_jsx_runtime = require("react/jsx-runtime");
10
+ //#region src/client/snapshot-store.ts
11
+ /** Notify every subscriber without letting one faulty UI consumer suppress the rest. */
12
+ function notifyListeners(listeners) {
13
+ for (const listener of listeners) try {
14
+ listener();
15
+ } catch (error) {
16
+ console.error("[dsh-commandcode-provider] snapshot subscriber failed:", error);
17
+ }
18
+ }
19
+ /** Create one observable snapshot store. */
20
+ function createSnapshotStore(initial) {
21
+ let snapshot = initial;
22
+ const listeners = /* @__PURE__ */ new Set();
23
+ return {
24
+ getSnapshot: () => snapshot,
25
+ subscribe(listener) {
26
+ listeners.add(listener);
27
+ return () => {
28
+ listeners.delete(listener);
29
+ };
30
+ },
31
+ set(value) {
32
+ if (Object.is(value, snapshot)) return;
33
+ snapshot = value;
34
+ notifyListeners(listeners);
35
+ }
36
+ };
37
+ }
38
+ //#endregion
39
+ //#region src/command-locales.ts
40
+ const commandcodeCommand = {
41
+ zh: {
42
+ title: "๐Ÿ“Š Command Code ็”จ้‡{account}",
43
+ accountTitle: "๐Ÿ“Š {label}{badges}",
44
+ accountSeparator: "โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€",
45
+ activeBadge: " โœ… ๅฝ“ๅ‰ไฝฟ็”จ",
46
+ invalidCredentialBadge: " โ›” ๅฏ†้’ฅๆ— ๆ•ˆ",
47
+ cooldownBadge: " โณ ้™้ขๅ†ทๅดไธญ๏ผŒ้‡็ฝฎ {when}",
48
+ rateLimitBadge: " โณ ๅทฒ่พพ้™้ข๏ผˆ็ญ‰ๅพ…็ช—ๅฃๆŽขๆต‹๏ผ‰",
49
+ unconfigured: " (ๆœช้…็ฝฎ API ๅฏ†้’ฅ)",
50
+ blockedInvalidKey: "โ›” API ๅฏ†้’ฅๆ— ๆ•ˆๆˆ–ๅทฒ่ฟ‡ๆœŸ โ€” ๆœๅŠก็ซฏๆ‹’็ปไบ†ๅ…จ้ƒจ่ฏทๆฑ‚๏ผˆ401๏ผ‰๏ผŒ่ฏทๆฃ€ๆŸฅ่ฏฅ่ดฆๆˆท็š„ๅฏ†้’ฅ้…็ฝฎ",
51
+ blockedServiceUnavailable: "โš ๏ธ Command Code ๆœๅŠกๆš‚ๆ—ถไธๅฏ็”จ๏ผˆ5xx๏ผ‰๏ผŒ็จๅŽ้‡่ฏ•",
52
+ blockedNetwork: "โš ๏ธ ๆ— ๆณ•่ฟžๆŽฅ Command Code ๆœๅŠก โ€” ่ฏทๆฃ€ๆŸฅ็ฝ‘็ปœๆˆ– API ๅœฐๅ€",
53
+ planLine: " ๐Ÿ“ฆ ๅฅ—้ค {name}{status}{period}",
54
+ planPeriodSuffix: " ยท ่ดฆๆœŸๆˆชๆญข {date}",
55
+ usageHeader: "โ”€โ”€ ่ฏทๆฑ‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€",
56
+ requestsLine: " ๐Ÿ’ฌ ่ฏทๆฑ‚ {n} ๆฌก / ๅคฑ่ดฅ {f} ๆˆๅŠŸ็އ {r}%",
57
+ costLine: " ๐Ÿ’ฐ ่Šฑ่ดน {money} ({credits} credits)",
58
+ tokensLine: " ๐Ÿ”ค Token {in} ๅ…ฅ / {out} ๅ‡บ",
59
+ creditsHeader: "โ”€โ”€ ไฟก็”จ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€",
60
+ monthlyLine: " ๐Ÿ’ณ ๆœˆ้ขๅบฆ {monthly} (ๅทฒ่ดญ {purchased} / ่ต ้€ {free})",
61
+ barLine: " โ”” {bar} {pct}%",
62
+ windowsHeader: "โ”€โ”€ ็ช—ๅฃ็”จ้‡ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€",
63
+ fiveHourLine: " โฑ 5 ๅฐๆ—ถ {used} / {cap}{warn}",
64
+ weeklyLine: " ๐Ÿ“… ๆฏๅ‘จ {used} / {cap}{warn}",
65
+ windowBarLine: " โ”” {bar} ้‡็ฝฎ {when}",
66
+ exceededWarning: " โš ๏ธ ่ถ…้™!",
67
+ resetSuffix: "้‡็ฝฎ {when}",
68
+ partialFailures: "โš ๏ธ ้ƒจๅˆ†็ซฏ็‚นๅคฑ่ดฅ: {list}",
69
+ noData: "(no data โ€” check your API key)",
70
+ errorText: "Could not fetch Command Code usage: {message}",
71
+ imageGate: "ๅฝ“ๅ‰ไผš่ฏๅทฒๅŒ…ๅซๅ›พ็‰‡๏ผŒ่€Œๆจกๅž‹ {model} ไธๆ”ฏๆŒๅ›พ็‰‡่พ“ๅ…ฅ๏ผ›่ฏท้€‰ๆ‹ฉๆ”ฏๆŒๅ›พ็‰‡็š„ๆจกๅž‹๏ผŒๆˆ–ๅ…ˆ็งป้™คไผš่ฏไธญ็š„ๅ›พ็‰‡ใ€‚"
72
+ },
73
+ en: {
74
+ title: "๐Ÿ“Š Command Code usage{account}",
75
+ accountTitle: "๐Ÿ“Š {label}{badges}",
76
+ accountSeparator: "โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€",
77
+ activeBadge: " โœ… active",
78
+ invalidCredentialBadge: " โ›” invalid key",
79
+ cooldownBadge: " โณ cooling down, resets {when}",
80
+ rateLimitBadge: " โณ rate-limited (waiting for window probe)",
81
+ unconfigured: " (no API key configured)",
82
+ blockedInvalidKey: "โ›” API key invalid or expired โ€” the server rejected every request (401); check the key configured for this account",
83
+ blockedServiceUnavailable: "โš ๏ธ Command Code service temporarily unavailable (5xx); try again later",
84
+ blockedNetwork: "โš ๏ธ could not reach the Command Code service โ€” check your network or the API base setting",
85
+ planLine: " ๐Ÿ“ฆ Plan {name}{status}{period}",
86
+ planPeriodSuffix: " ยท period ends {date}",
87
+ usageHeader: "โ”€โ”€ Requests โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€",
88
+ requestsLine: " ๐Ÿ’ฌ Requests {n} / failed {f} success rate {r}%",
89
+ costLine: " ๐Ÿ’ฐ Spend {money} ({credits} credits)",
90
+ tokensLine: " ๐Ÿ”ค Tokens {in} in / {out} out",
91
+ creditsHeader: "โ”€โ”€ Credits โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€",
92
+ monthlyLine: " ๐Ÿ’ณ Monthly {monthly} (purchased {purchased} / free {free})",
93
+ barLine: " โ”” {bar} {pct}%",
94
+ windowsHeader: "โ”€โ”€ Window usage โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€",
95
+ fiveHourLine: " โฑ 5-hour {used} / {cap}{warn}",
96
+ weeklyLine: " ๐Ÿ“… Weekly {used} / {cap}{warn}",
97
+ windowBarLine: " โ”” {bar} resets {when}",
98
+ exceededWarning: " โš ๏ธ exceeded!",
99
+ resetSuffix: "resets {when}",
100
+ partialFailures: "โš ๏ธ some endpoints failed: {list}",
101
+ noData: "(no data โ€” check your API key)",
102
+ errorText: "Could not fetch Command Code usage: {message}",
103
+ 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."
104
+ }
105
+ };
106
+ //#endregion
11
107
  //#region src/client/sessions.ts
108
+ /**
109
+ * Friendly-error wrapper for the harness's image-session gate.
110
+ *
111
+ * The host rejects switching to a text-only model while the session already
112
+ * contains images with a `model-unavailable` error
113
+ * (`dsh-host-apiproxy`'s `session.selectModel` handler). That rejection is
114
+ * intentional and cannot be relaxed from the plugin side โ€” the adapter's
115
+ * `inputModalities` is exactly what makes the guard work. What we CAN do is
116
+ * make the error message friendlier: wrap the shared
117
+ * `connection.api.sessions.selectModel` face so a `model-unavailable`
118
+ * rejection shows a clear, actionable hint (with the requested model name)
119
+ * instead of the raw English harness message.
120
+ *
121
+ * The wrapper is deliberately narrow: only the `model-unavailable` code is
122
+ * rewritten, only when the message matches the image-session gate, and only
123
+ * the message text changes โ€” the error code and details pass through
124
+ * untouched so any caller that switches on `error.code` keeps working.
125
+ *
126
+ * The wire types are spelled structurally here (not imported from
127
+ * `@deepseek-ai/dsh-host-apiproxy`) so this client bundle does not drag an
128
+ * extra peer dependency into the package; the shapes are stable and the
129
+ * client build inlines them anyway.
130
+ *
131
+ * The wrapper takes a `getLocale` thunk because it is reached from a
132
+ * non-React path that has no `t` in scope; the supplied thunk reads the
133
+ * active locale at call time (typically `() => ctx.locale.getLocale().active`
134
+ * in the client entry), and the message template lives in the shared
135
+ * `commandcodeCommand` dictionary used by the Host-side `/commandcode`
136
+ * command โ€” the same bilingual surface serves both.
137
+ *
138
+ * This module is deliberately free of React and other client-platform
139
+ * imports so the node test runner can exercise it directly.
140
+ */
12
141
  /** Whether a selectModel rejection is the harness's image-session gate. */
13
142
  function isImageSessionRejection(result) {
14
143
  return !result.result.ok && result.result.error.code === "model-unavailable" && result.result.error.message.includes("does not accept image input");
15
144
  }
16
145
  /** Wrap the shared sessions API so selectModel failures read friendlier. */
17
- function withFriendlyImageError(sessions) {
146
+ function withFriendlyImageError(sessions, getLocale) {
18
147
  const selectModel = sessions.selectModel.bind(sessions);
19
148
  return {
20
149
  ...sessions,
@@ -22,25 +151,63 @@ window.__ModuleLoader__.load({
22
151
  const result = await selectModel(payload, signal);
23
152
  if (!isImageSessionRejection(result)) return result;
24
153
  const model = result.result.error.details?.model ?? payload.model;
154
+ const template = commandcodeCommand[getLocale()].imageGate ?? commandcodeCommand.en.imageGate;
25
155
  return {
26
156
  ...result,
27
157
  result: {
28
158
  ...result.result,
29
159
  error: {
30
160
  ...result.result.error,
31
- message: `ๅฝ“ๅ‰ไผš่ฏๅทฒๅŒ…ๅซๅ›พ็‰‡๏ผŒ่€Œๆจกๅž‹ ${model} ไธๆ”ฏๆŒๅ›พ็‰‡่พ“ๅ…ฅ๏ผ›่ฏท้€‰ๆ‹ฉๆ”ฏๆŒๅ›พ็‰‡็š„ๆจกๅž‹๏ผŒๆˆ–ๅ…ˆ็งป้™คไผš่ฏไธญ็š„ๅ›พ็‰‡ใ€‚`
161
+ message: template.replace("{model}", model)
32
162
  }
33
163
  }
34
164
  };
35
165
  }
36
166
  };
37
167
  }
38
- /** Install the wrapper on a connection's shared sessions face. */
39
- function installFriendlyImageError(connection) {
40
- connection.api.sessions = withFriendlyImageError(connection.api.sessions);
168
+ /**
169
+ * Install the friendly-error wrapper when a legacy sessions face is present.
170
+ *
171
+ * @returns whether the wrapper was installed. The rewrite is only UX polish;
172
+ * a 0.1.2 connection has no `api.sessions`, and its absence must never block
173
+ * the plugin from mounting its settings, credential, or usage surfaces.
174
+ */
175
+ function installFriendlyImageError(connection, getLocale) {
176
+ const api = connection.api;
177
+ const sessions = api?.sessions;
178
+ if (api === void 0 || sessions === void 0 || typeof sessions.selectModel !== "function") return false;
179
+ api.sessions = withFriendlyImageError(sessions, getLocale);
180
+ return true;
41
181
  }
42
182
  //#endregion
43
183
  //#region src/client/settings.ts
184
+ /**
185
+ * Browser controller for the "Command Code" settings page.
186
+ *
187
+ * The page lives at the same settings-nav level as General / Models / Plugins
188
+ * (a `settings.section` entry, id `commandcode`). It exists because the
189
+ * Models page renders an unknown-adapter-family card for the `commandcode`
190
+ * provider and deliberately disables its submit โ€” the API key cannot be
191
+ * configured there. This page owns the connection facts the plugin resolves
192
+ * per request:
193
+ *
194
+ * - API key -> written through the credentials domain under the reference
195
+ * the plugin resolves (`apiKeyEnv`, default
196
+ * `COMMANDCODE_API_KEY`). The literal never rides a response,
197
+ * so the control only reports whether one is configured.
198
+ * - API base -> the `llm-commandcode` settings namespace (`apiBase`), same
199
+ * namespace the Models page card addresses.
200
+ * - Working dir, request/stream timeouts -> the same namespace.
201
+ *
202
+ * The controller mirrors the plugin-card pattern from the harness's own
203
+ * settings UI: it binds the `llm-commandcode` namespace through the
204
+ * `settingsScope` service, keeps a staged draft of edits, and writes them on
205
+ * save through `scope.set` / the credentials domain. The Host stays the
206
+ * single fact source; the snapshot is republished after each accepted write.
207
+ *
208
+ * This module is deliberately free of JSX โ€” it only produces the state face
209
+ * the React component renders.
210
+ */
44
211
  /** The settings namespace the plugin registers (host half, src/index.ts). */
45
212
  const COMMANDCODE_NS = "llm-commandcode";
46
213
  /** Default credential reference the plugin resolves when none is named. */
@@ -505,10 +672,7 @@ window.__ModuleLoader__.load({
505
672
  /** Write one account's key, then re-read the Host's credential states. */
506
673
  async writeKeyTo(ref, value) {
507
674
  try {
508
- if (!(await this.api.credentials.set({
509
- ref,
510
- value
511
- })).result.ok) return false;
675
+ if (!(await this.api.credentials.set(ref, value)).ok) return false;
512
676
  } catch {
513
677
  return false;
514
678
  }
@@ -524,14 +688,14 @@ window.__ModuleLoader__.load({
524
688
  ];
525
689
  let response;
526
690
  try {
527
- response = await this.api.credentials.describe({ refs });
691
+ response = await this.api.credentials.describe(refs);
528
692
  } catch {
529
693
  return;
530
694
  }
531
- if (!response.result.ok) return;
695
+ if (!response.ok) return;
532
696
  let changed = false;
533
697
  for (const ref of refs) {
534
- const view = response.result.value.credentials[ref];
698
+ const view = response.value?.[ref];
535
699
  const next = {
536
700
  configured: view?.configured ?? false,
537
701
  writable: view?.writable ?? true
@@ -609,7 +773,7 @@ window.__ModuleLoader__.load({
609
773
  /** Unset one stored credential, then re-read the Host's credential states. */
610
774
  async unsetKey(ref) {
611
775
  try {
612
- if (!(await this.api.credentials.unset({ ref })).result.ok) return false;
776
+ if (!(await this.api.credentials.unset(ref)).ok) return false;
613
777
  } catch {
614
778
  return false;
615
779
  }
@@ -649,6 +813,47 @@ window.__ModuleLoader__.load({
649
813
  }
650
814
  };
651
815
  //#endregion
816
+ //#region src/client/legacy-credentials.ts
817
+ /** Convert a legacy credentials ApiProxy into the current settings-page face. */
818
+ function adaptLegacyCredentials(legacy) {
819
+ if (legacy === void 0) return void 0;
820
+ return { credentials: {
821
+ describe: async (refs) => {
822
+ const response = await legacy.describe({ refs });
823
+ return response.result.ok ? {
824
+ ok: true,
825
+ value: response.result.value.credentials
826
+ } : {
827
+ ok: false,
828
+ error: response.result.error
829
+ };
830
+ },
831
+ set: async (ref, value) => {
832
+ const response = await legacy.set({
833
+ ref,
834
+ value
835
+ });
836
+ return response.result.ok ? {
837
+ ok: true,
838
+ value: void 0
839
+ } : {
840
+ ok: false,
841
+ error: response.result.error
842
+ };
843
+ },
844
+ unset: async (ref) => {
845
+ const response = await legacy.unset({ ref });
846
+ return response.result.ok ? {
847
+ ok: true,
848
+ value: void 0
849
+ } : {
850
+ ok: false,
851
+ error: response.result.error
852
+ };
853
+ }
854
+ } };
855
+ }
856
+ //#endregion
652
857
  //#region src/client/usage.ts
653
858
  const IDLE = {
654
859
  status: "idle",
@@ -1197,7 +1402,7 @@ window.__ModuleLoader__.load({
1197
1402
  };
1198
1403
  //#endregion
1199
1404
  //#region package.json
1200
- var version = "0.8.4";
1405
+ var version = "0.10.0-alpha.1";
1201
1406
  var repository = {
1202
1407
  "type": "git",
1203
1408
  "url": "git+https://github.com/Mars-Sea/dsh-commandcode-provider.git"
@@ -1377,9 +1582,19 @@ window.__ModuleLoader__.load({
1377
1582
  *
1378
1583
  * The layout mirrors the harness's settings pages: a max-width content
1379
1584
  * column, labelled fields with hints, a reset affordance, and a
1380
- * save/discard footer. Styles are injected once by the client entry
1585
+ * save/discard footer. Rarely touched connection facts (API base, working
1586
+ * directory, timeouts, plan filter) fold into a collapsed Advanced card
1587
+ * (`AdvancedSection`) so the page leads with the key and the usage facts.
1588
+ * Styles are injected once by the client entry
1381
1589
  * (see src/client/index.ts) and class-prefixed `cc-` to stay local.
1382
1590
  */
1591
+ const ADVANCED_FIELDS = [
1592
+ "apiBase",
1593
+ "workingDir",
1594
+ "requestTimeoutMs",
1595
+ "streamIdleTimeoutMs",
1596
+ "filterModelsByPlan"
1597
+ ];
1383
1598
  /** One labelled field row in the page body. */
1384
1599
  function Field({ id, label, hint, state, disabled, numeric, placeholder, onEdit, onReset, t }) {
1385
1600
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -1429,6 +1644,114 @@ window.__ModuleLoader__.load({
1429
1644
  return t("invalidNumber");
1430
1645
  }
1431
1646
  /**
1647
+ * The collapsed "Advanced" card: API base, working dir, both timeouts, and
1648
+ * the plan filter live here so the page leads with the facts a user actually
1649
+ * touches. Starts collapsed on every visit; expands on demand. While
1650
+ * collapsed, a badge names the customized count so a nonzero override stays
1651
+ * visible (a number field's error blocks save and must be reachable).
1652
+ */
1653
+ function AdvancedSection({ state, disabled, t, onEdit, onReset }) {
1654
+ const [expanded, setExpanded] = (0, react.useState)(false);
1655
+ const overridden = ADVANCED_FIELDS.filter((field) => state[field].overridden).length;
1656
+ const invalid = ADVANCED_FIELDS.some((field) => state[field].invalid);
1657
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1658
+ className: "cc-card",
1659
+ children: [
1660
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1661
+ type: "button",
1662
+ className: "cc-advancedHead",
1663
+ "aria-expanded": expanded,
1664
+ "aria-controls": "cc-advanced-body",
1665
+ onClick: () => setExpanded((value) => !value),
1666
+ children: [
1667
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1668
+ className: "cc-advancedTitle",
1669
+ children: t("advancedSettings")
1670
+ }),
1671
+ overridden > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1672
+ className: "cc-badge",
1673
+ children: overridden === 1 ? t("advancedOverriddenOne") : t("advancedOverriddenMany").replace("{count}", String(overridden))
1674
+ }) : null,
1675
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "cc-advancedSpacer" }),
1676
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1677
+ className: expanded ? "cc-chevron cc-chevronUp" : "cc-chevron",
1678
+ "aria-hidden": "true"
1679
+ })
1680
+ ]
1681
+ }),
1682
+ expanded ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1683
+ id: "cc-advanced-body",
1684
+ className: "cc-advancedBody",
1685
+ children: [
1686
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1687
+ className: "cc-hint",
1688
+ children: t("advancedSettingsHint")
1689
+ }),
1690
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
1691
+ id: "cc-api-base",
1692
+ label: t("apiBase"),
1693
+ hint: t("apiBaseHint"),
1694
+ state: state.apiBase,
1695
+ disabled,
1696
+ onEdit: (text) => onEdit("apiBase", text),
1697
+ onReset: () => onReset("apiBase"),
1698
+ t
1699
+ }),
1700
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
1701
+ id: "cc-working-dir",
1702
+ label: t("workingDir"),
1703
+ hint: t("workingDirHint"),
1704
+ state: state.workingDir,
1705
+ disabled,
1706
+ placeholder: state.defaultWorkingDir,
1707
+ onEdit: (text) => onEdit("workingDir", text),
1708
+ onReset: () => onReset("workingDir"),
1709
+ t
1710
+ }),
1711
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
1712
+ id: "cc-request-timeout",
1713
+ label: t("requestTimeoutMs"),
1714
+ hint: t("requestTimeoutMsHint"),
1715
+ state: state.requestTimeoutMs,
1716
+ disabled,
1717
+ numeric: true,
1718
+ onEdit: (text) => onEdit("requestTimeoutMs", text),
1719
+ onReset: () => onReset("requestTimeoutMs"),
1720
+ t
1721
+ }),
1722
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
1723
+ id: "cc-stream-idle-timeout",
1724
+ label: t("streamIdleTimeoutMs"),
1725
+ hint: t("streamIdleTimeoutMsHint"),
1726
+ state: state.streamIdleTimeoutMs,
1727
+ disabled,
1728
+ numeric: true,
1729
+ onEdit: (text) => onEdit("streamIdleTimeoutMs", text),
1730
+ onReset: () => onReset("streamIdleTimeoutMs"),
1731
+ t
1732
+ }),
1733
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleField, {
1734
+ id: "cc-filter-models-by-plan",
1735
+ label: t("filterModelsByPlan"),
1736
+ hint: t("filterModelsByPlanHint"),
1737
+ state: state.filterModelsByPlan,
1738
+ disabled,
1739
+ defaultChecked: true,
1740
+ onEdit: (text) => onEdit("filterModelsByPlan", text),
1741
+ onReset: () => onReset("filterModelsByPlan"),
1742
+ t
1743
+ })
1744
+ ]
1745
+ }) : null,
1746
+ expanded && invalid ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1747
+ className: "cc-invalid",
1748
+ role: "status",
1749
+ children: t("advancedInvalid")
1750
+ }) : null
1751
+ ]
1752
+ });
1753
+ }
1754
+ /**
1432
1755
  * One boolean field row rendered as a toggle. The staged text is `'true'` /
1433
1756
  * `'false'` / `''` (unset โ†’ `defaultChecked`); toggling stages the string the
1434
1757
  * boolean field spec parses back into a real boolean on save.
@@ -1536,7 +1859,7 @@ window.__ModuleLoader__.load({
1536
1859
  });
1537
1860
  }
1538
1861
  /** The per-reason copy for a failed login attempt. */
1539
- function loginFailureCopy(reason, t) {
1862
+ function loginFailureCopy$1(reason, t) {
1540
1863
  if (reason === "denied") return t("loginDenied");
1541
1864
  if (reason === "timeout") return t("loginTimeout");
1542
1865
  if (reason === "invalid-key") return t("loginInvalidKey");
@@ -1562,7 +1885,7 @@ window.__ModuleLoader__.load({
1562
1885
  hint = `${t("loginSuccess")} ${state.userName ?? ""}${keyName}`.trim();
1563
1886
  hintClass = "cc-loginDone";
1564
1887
  } else if (state.phase === "failed") {
1565
- hint = loginFailureCopy(state.reason, t);
1888
+ hint = loginFailureCopy$1(state.reason, t);
1566
1889
  hintClass = "cc-loginError";
1567
1890
  if (state.message !== void 0) hintTitle = state.message;
1568
1891
  } else if (state.phase === "unavailable") {
@@ -2230,86 +2553,36 @@ window.__ModuleLoader__.load({
2230
2553
  }),
2231
2554
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2232
2555
  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
- ]
2556
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SecretKeyField, {
2557
+ label: t("apiKey"),
2558
+ hint: keyLocked ? t("apiKeyLocked") : t("apiKeyHint"),
2559
+ state: state.apiKey,
2560
+ disabled: disabled || keyLocked,
2561
+ configured: state.apiKeyConfigured,
2562
+ configuredLabel: t("apiKeySet"),
2563
+ unconfiguredLabel: t("apiKeyUnset"),
2564
+ clearStaged: state.apiKeyClearStaged,
2565
+ showLabel: t("show"),
2566
+ hideLabel: t("hide"),
2567
+ clearLabel: t("usageKeyClear"),
2568
+ clearStagedLabel: t("usageKeyClearStaged"),
2569
+ undoClearLabel: t("usageUndoKeyClear"),
2570
+ onEdit: (text) => props.edit("apiKey", text),
2571
+ onToggleClear: () => props.toggleKeyClear("default")
2572
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LoginPanel, {
2573
+ state: login,
2574
+ disabled: disabled || keyLocked,
2575
+ t,
2576
+ onBegin: props.beginLogin,
2577
+ onCancel: props.cancelLogin
2578
+ })]
2579
+ }),
2580
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AdvancedSection, {
2581
+ state,
2582
+ disabled,
2583
+ t,
2584
+ onEdit: props.edit,
2585
+ onReset: props.resetField
2313
2586
  }),
2314
2587
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2315
2588
  className: "cc-footer",
@@ -2364,6 +2637,260 @@ window.__ModuleLoader__.load({
2364
2637
  });
2365
2638
  }
2366
2639
  //#endregion
2640
+ //#region src/client/card.tsx
2641
+ /**
2642
+ * The Command Code provider card inside the harness Models settings page
2643
+ * (browser half). Rendered through the `settings.models.provider-card` keyed
2644
+ * slot available in dsh 0.1.2-alpha.2, registered with
2645
+ * `entryKey = 'llm-commandcode'` (the plugin's settings namespace, the key the
2646
+ * Models page dispatches for every Command Code provider row).
2647
+ *
2648
+ * The slot's owner props (`configured`, `keyConfigured`) mirror what the
2649
+ * Models page already knows; the authoritative credential facts still come
2650
+ * from this plugin's `CommandCodeSettingsController` shared with the dedicated
2651
+ * settings page, so the two surfaces can never disagree about whether a key
2652
+ * is stored.
2653
+ *
2654
+ * Not configured: an "Unconfigured" badge, a paste-a-key field, and the
2655
+ * official sign-in button โ€” the whole flow a first-run user needs, inline.
2656
+ * Configured: a green "Configured" badge plus a pointer to the full
2657
+ * Command Code settings page for rotation, usage, and connection facts.
2658
+ *
2659
+ * The card renders nothing until the shared controller's first snapshot is
2660
+ * ready (`available`), which also keeps the stale-facts window of the older
2661
+ * join from ever being visible. A controller-less render (card mounted before
2662
+ * the section registered its inject face โ€” the composition runs one apply)
2663
+ * degrades to the stateless registration-notice form.
2664
+ *
2665
+ * Styles ride the page stylesheet the client entry injects once (`cc-`
2666
+ * prefixed classes); the card adds no CSS of its own.
2667
+ */
2668
+ /** Decide the card's posture from the injected face and the owner facts. */
2669
+ function cardMode(props) {
2670
+ if (props.useCommandCodeSettings === void 0) return { kind: "registration" };
2671
+ const snapshot = props.useCommandCodeSettings((state) => state);
2672
+ return {
2673
+ kind: "live",
2674
+ ready: snapshot.available,
2675
+ controllerConfigured: snapshot.apiKeyConfigured,
2676
+ writable: snapshot.writable,
2677
+ apiKeyWritable: snapshot.apiKeyWritable
2678
+ };
2679
+ }
2680
+ /** Status badge for the credential state (green when configured). */
2681
+ function StatusBadge({ ok, okLabel, pendingLabel }) {
2682
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2683
+ className: ok ? "cc-badge" : "cc-badgeMuted",
2684
+ children: ok ? okLabel : pendingLabel
2685
+ });
2686
+ }
2687
+ /**
2688
+ * The per-reason copy for a failed login attempt (mirrors the settings
2689
+ * page's LoginPanel; the same reasons can surface from this card).
2690
+ */
2691
+ function loginFailureCopy(reason, t) {
2692
+ if (reason === "denied") return t("loginDenied");
2693
+ if (reason === "timeout") return t("loginTimeout");
2694
+ if (reason === "invalid-key") return t("loginInvalidKey");
2695
+ if (reason === "network") return t("loginNetwork");
2696
+ if (reason === "unavailable") return t("loginStoreFailed");
2697
+ if (reason === "cancelled") return t("loginCancelled");
2698
+ return t("loginFailedGeneric");
2699
+ }
2700
+ /** The sign-in affordance row for the not-configured card (see LoginPanel). */
2701
+ function CardLoginRow({ state, disabled, t, onBegin, onCancel }) {
2702
+ const busy = state.phase === "starting" || state.phase === "waiting";
2703
+ let hint = t("loginHintIdle");
2704
+ let hintClass = "cc-hint";
2705
+ if (state.phase === "starting" || state.phase === "waiting") hint = t(state.phase === "starting" ? "loginStarting" : "loginWaiting");
2706
+ else if (state.phase === "success") {
2707
+ const keyName = state.keyName !== void 0 && state.keyName !== "" ? ` ยท ${state.keyName}` : "";
2708
+ hint = `${t("loginSuccess")} ${state.userName ?? ""}${keyName}`.trim();
2709
+ hintClass = "cc-loginDone";
2710
+ } else if (state.phase === "failed") {
2711
+ hint = loginFailureCopy(state.reason, t);
2712
+ hintClass = "cc-loginError";
2713
+ } else if (state.phase === "unavailable") {
2714
+ hint = `${t("loginUnavailable")} ${state.message ?? ""}`.trim();
2715
+ hintClass = "cc-loginError";
2716
+ }
2717
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2718
+ className: "cc-field",
2719
+ children: [
2720
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2721
+ className: "cc-fieldHead",
2722
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2723
+ className: "cc-label",
2724
+ children: t("loginTitle")
2725
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2726
+ className: "cc-badges",
2727
+ children: busy ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2728
+ type: "button",
2729
+ className: "cc-reset",
2730
+ onClick: onCancel,
2731
+ children: t("loginCancel")
2732
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2733
+ type: "button",
2734
+ className: "cc-reset",
2735
+ disabled,
2736
+ onClick: onBegin,
2737
+ children: t("loginButton")
2738
+ })
2739
+ })]
2740
+ }),
2741
+ state.authUrl !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2742
+ className: "cc-hint",
2743
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
2744
+ className: "cc-loginLink",
2745
+ href: state.authUrl,
2746
+ target: "_blank",
2747
+ rel: "noreferrer",
2748
+ children: t("loginOpenLink")
2749
+ })
2750
+ }) : null,
2751
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2752
+ className: hintClass,
2753
+ children: hint
2754
+ })
2755
+ ]
2756
+ });
2757
+ }
2758
+ /** Compact key field for the not-configured card. */
2759
+ function CardKeyField({ state, disabled, t, onEdit }) {
2760
+ const [visible, setVisible] = (0, react.useState)(false);
2761
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2762
+ className: "cc-field",
2763
+ children: [
2764
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2765
+ className: "cc-fieldHead",
2766
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
2767
+ className: "cc-label",
2768
+ htmlFor: "cc-card-api-key",
2769
+ children: t("apiKey")
2770
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2771
+ className: "cc-badges",
2772
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2773
+ type: "button",
2774
+ className: "cc-reset",
2775
+ disabled,
2776
+ onClick: () => setVisible((value) => !value),
2777
+ children: visible ? t("hide") : t("show")
2778
+ })
2779
+ })]
2780
+ }),
2781
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2782
+ id: "cc-card-api-key",
2783
+ className: "cc-input",
2784
+ type: visible ? "text" : "password",
2785
+ autoComplete: "off",
2786
+ spellCheck: false,
2787
+ value: state.text,
2788
+ disabled,
2789
+ onChange: (event) => onEdit(event.target.value)
2790
+ }),
2791
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2792
+ className: "cc-hint",
2793
+ children: t("apiKeyHint")
2794
+ })
2795
+ ]
2796
+ });
2797
+ }
2798
+ /** One "Configured" badge + pointer to the full settings page (footer area). */
2799
+ function CardConfiguredBody({ t }) {
2800
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2801
+ className: "cc-field",
2802
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2803
+ className: "cc-hint",
2804
+ children: t("cardConfiguredHint")
2805
+ })
2806
+ });
2807
+ }
2808
+ /**
2809
+ * The slot component body. Dispatched on every Command Code provider card of
2810
+ * the Models page (saved row, first-run setup posture, and add-provider
2811
+ * draft).
2812
+ */
2813
+ function CommandCodeProviderCard(props) {
2814
+ const { t } = props;
2815
+ const mode = cardMode(props);
2816
+ const state = props.useCommandCodeSettings !== void 0 ? props.useCommandCodeSettings((snapshot) => snapshot) : void 0;
2817
+ const login = props.useCommandCodeLogin !== void 0 ? props.useCommandCodeLogin((snapshot) => snapshot) : void 0;
2818
+ const dirty = state?.dirty ?? false;
2819
+ const saving = state?.saving ?? false;
2820
+ const invalid = state?.invalid ?? false;
2821
+ const failed = state?.failed ?? false;
2822
+ const savingBlocked = !dirty || invalid;
2823
+ const configured = mode.kind === "live" && mode.ready ? mode.controllerConfigured : props.keyConfigured;
2824
+ const disabled = mode.kind === "live" && (!mode.writable || state !== void 0 && !mode.apiKeyWritable);
2825
+ const showBody = mode.kind === "live" && mode.ready && state !== void 0;
2826
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2827
+ className: "cc-providerCard",
2828
+ "data-cc-models-card": "true",
2829
+ children: [
2830
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2831
+ className: "cc-field",
2832
+ children: [
2833
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2834
+ className: "cc-fieldHead",
2835
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2836
+ className: "cc-label",
2837
+ children: t("cardTitle")
2838
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2839
+ className: "cc-badges",
2840
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusBadge, {
2841
+ ok: configured,
2842
+ okLabel: t("apiKeySet"),
2843
+ pendingLabel: t("apiKeyUnset")
2844
+ }), props.provider.active ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2845
+ className: "cc-badge",
2846
+ children: t("cardRouteActive")
2847
+ }) : null]
2848
+ })]
2849
+ }),
2850
+ mode.kind === "registration" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2851
+ className: "cc-hint",
2852
+ children: t("cardRegistrationHint")
2853
+ }) : null,
2854
+ mode.kind === "live" && !mode.ready ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2855
+ className: "cc-hint",
2856
+ children: t("cardLoadingHint")
2857
+ }) : null
2858
+ ]
2859
+ }),
2860
+ showBody && !configured ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2861
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CardKeyField, {
2862
+ state: state.apiKey,
2863
+ disabled,
2864
+ t,
2865
+ onEdit: (text) => props.edit("apiKey", text)
2866
+ }),
2867
+ login !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CardLoginRow, {
2868
+ state: login,
2869
+ disabled,
2870
+ t,
2871
+ onBegin: props.beginLogin,
2872
+ onCancel: props.cancelLogin
2873
+ }) : null,
2874
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2875
+ className: "cc-footer",
2876
+ children: [failed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2877
+ className: "cc-failed",
2878
+ role: "status",
2879
+ children: t("saveFailed")
2880
+ }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2881
+ type: "button",
2882
+ className: "cc-reset",
2883
+ disabled: savingBlocked || saving,
2884
+ onClick: props.save,
2885
+ children: t(saving ? "saving" : "save")
2886
+ })]
2887
+ })
2888
+ ] }) : null,
2889
+ showBody && configured ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CardConfiguredBody, { t }) : null
2890
+ ]
2891
+ });
2892
+ }
2893
+ //#endregion
2367
2894
  //#region src/client/locales.ts
2368
2895
  const zh = {
2369
2896
  nav: "Command Code",
@@ -2382,6 +2909,11 @@ window.__ModuleLoader__.load({
2382
2909
  requestTimeoutMsHint: "็ญ‰ๅพ…ๅ“ๅบ”้ฆ–ไธชๅญ—่Š‚็š„่ถ…ๆ—ถ๏ผ›้ป˜่ฎค 60000ใ€‚",
2383
2910
  streamIdleTimeoutMs: "ๆต็ฉบ้—ฒ่ถ…ๆ—ถ๏ผˆๆฏซ็ง’๏ผ‰",
2384
2911
  streamIdleTimeoutMsHint: "็”Ÿๆˆๆตๅœๆปžๅคšไน…่ง†ไธบๆ–ญ่ฟž๏ผ›้ป˜่ฎค 300000๏ผˆ้•ฟๆ€่€ƒๆจกๅž‹ๅฏ้™้ป˜ๆ•ฐๅˆ†้’Ÿ๏ผŒ้ป˜่ฎคๅ€ผๅˆปๆ„ๆ”พๅฎฝ๏ผ‰ใ€‚",
2912
+ advancedSettings: "้ซ˜็บง่ฎพ็ฝฎ",
2913
+ advancedSettingsHint: "API ๅœฐๅ€ใ€ๅทฅไฝœ็›ฎๅฝ•ใ€่ถ…ๆ—ถไธŽๆจกๅž‹่ฟ‡ๆปค็ญ‰ไธๅธธไฟฎๆ”น็š„้€‰้กนใ€‚",
2914
+ advancedOverriddenOne: "ๅทฒ่‡ชๅฎšไน‰ 1 ้กน",
2915
+ advancedOverriddenMany: "ๅทฒ่‡ชๅฎšไน‰ {count} ้กน",
2916
+ advancedInvalid: "้ซ˜็บง่ฎพ็ฝฎไธญๆœ‰ๆœชๅกซๅฅฝ็š„ๆ•ฐๅญ—๏ผŒ่ฏทๅฑ•ๅผ€ไฟฎๆญฃๅŽๅ†ไฟๅญ˜ใ€‚",
2385
2917
  filterModelsByPlan: "้š่—ๅฅ—้คๅค–ๆจกๅž‹",
2386
2918
  filterModelsByPlanHint: "ๅผ€ๅฏๅŽ๏ผŒๆจกๅž‹้€‰ๆ‹ฉๅ™จๅชๅˆ—ๅ‡บๅฝ“ๅ‰ๅฅ—้คๅฏ็”จ็š„ๆจกๅž‹๏ผ›่ดฆๆˆทๆŒๆœ‰ๆŒ‰้œ€ไฝ™้ขๆ—ถไผšๆ˜พ็คบๅ…จ้ƒจใ€‚",
2387
2919
  accountsTitle: "ๅคš่ดฆๆˆท่ฝฎๆข",
@@ -2463,7 +2995,12 @@ window.__ModuleLoader__.load({
2463
2995
  loginNetwork: "ๆ— ๆณ•่ฟžๆŽฅ Command Code ๆœๅŠกๆ ก้ชŒๅฏ†้’ฅ๏ผŒ่ฏทๆฃ€ๆŸฅ็ฝ‘็ปœๅŽ้‡่ฏ•ใ€‚",
2464
2996
  loginStoreFailed: "ๅฏ†้’ฅๆ— ๆณ•ๅ†™ๅ…ฅๆœฌๆœบๅ‡ญๆฎๆœๅŠก๏ผŒ่ฏทๆ‰‹ๅŠจ็ฒ˜่ดดใ€‚",
2465
2997
  loginCancelled: "็™ปๅฝ•ๅทฒๅ–ๆถˆใ€‚",
2466
- loginFailedGeneric: "็™ปๅฝ•ๅคฑ่ดฅ๏ผŒ่ฏท้‡่ฏ•ๆˆ–ๆ‰‹ๅŠจ็ฒ˜่ดดๅฏ†้’ฅใ€‚"
2998
+ loginFailedGeneric: "็™ปๅฝ•ๅคฑ่ดฅ๏ผŒ่ฏท้‡่ฏ•ๆˆ–ๆ‰‹ๅŠจ็ฒ˜่ดดๅฏ†้’ฅใ€‚",
2999
+ cardTitle: "Command Code ่ฟžๆŽฅ",
3000
+ cardRouteActive: "ๅทฒๅฏ็”จ",
3001
+ cardLoadingHint: "ๆญฃๅœจ่ฏปๅ– Command Code ้…็ฝฎโ€ฆ",
3002
+ cardRegistrationHint: "ๆญคๅก็‰‡้š Command Code ๆ’ไปถๆณจๅ†Œ๏ผŒ้œ€่ฆ่พƒๆ–ฐ็‰ˆๆœฌ็š„ DeepSeek Harness ๆ‰ไผšๆ˜พ็คบๅฎŒๆ•ดๅ†…ๅฎนใ€‚",
3003
+ cardConfiguredHint: "API ๅฏ†้’ฅๅทฒๅฐฑ็ปชใ€‚ๅฆ‚้œ€ๆ›ดๆขๅฏ†้’ฅใ€ๆทปๅŠ ๅคš่ดฆๆˆท่ฝฎๆขใ€ๆŸฅ็œ‹็”จ้‡ๆˆ–ไฟฎๆ”น API ๅœฐๅ€๏ผŒ่ฏทๅ‰ๅพ€่ฎพ็ฝฎไธญ็š„ใ€ŒCommand Codeใ€้กตใ€‚"
2467
3004
  };
2468
3005
  const en = {
2469
3006
  nav: "Command Code",
@@ -2482,6 +3019,11 @@ window.__ModuleLoader__.load({
2482
3019
  requestTimeoutMsHint: "Time to wait for the first response byte; default 60000.",
2483
3020
  streamIdleTimeoutMs: "Stream idle timeout (ms)",
2484
3021
  streamIdleTimeoutMsHint: "How long a stalled stream is treated as dead; default 300000 (deliberately generous โ€” long-thinking models can stay silent for minutes).",
3022
+ advancedSettings: "Advanced",
3023
+ advancedSettingsHint: "Rarely touched options: API base URL, working directory, timeouts, and model filtering.",
3024
+ advancedOverriddenOne: "1 customized",
3025
+ advancedOverriddenMany: "{count} customized",
3026
+ advancedInvalid: "A number in Advanced settings is not ready to save; expand to fix it.",
2485
3027
  filterModelsByPlan: "Hide out-of-plan models",
2486
3028
  filterModelsByPlanHint: "When on, the model picker lists only models your subscription includes; any on-demand credit balance shows the full catalog.",
2487
3029
  accountsTitle: "Account rotation",
@@ -2563,7 +3105,12 @@ window.__ModuleLoader__.load({
2563
3105
  loginNetwork: "Could not reach the Command Code service to validate the key; check your network and retry.",
2564
3106
  loginStoreFailed: "The key could not be stored in the local credential service; paste it manually.",
2565
3107
  loginCancelled: "Sign-in cancelled.",
2566
- loginFailedGeneric: "Sign-in failed; try again or paste the key manually."
3108
+ loginFailedGeneric: "Sign-in failed; try again or paste the key manually.",
3109
+ cardTitle: "Command Code connection",
3110
+ cardRouteActive: "Active",
3111
+ cardLoadingHint: "Loading the Command Code configurationโ€ฆ",
3112
+ cardRegistrationHint: "This card is contributed by the Command Code plugin; a newer DeepSeek Harness is needed to show the full controls.",
3113
+ 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
3114
  };
2568
3115
  //#endregion
2569
3116
  //#region src/client/index.ts
@@ -2596,6 +3143,16 @@ window.__ModuleLoader__.load({
2596
3143
  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
3144
  .cc-inputInvalid{border-color:var(--dsw-alias-label-error)}
2598
3145
  .cc-invalid{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}
3146
+ /* The Advanced card: its header row is one full-width toggle button; the
3147
+ * chevron is two borders on a rotated square (no icon dependency). */
3148
+ .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}
3149
+ .cc-advancedHead:hover .cc-advancedTitle{color:var(--dsw-alias-label-primary)}
3150
+ .cc-advancedTitle{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500;line-height:1.5}
3151
+ .cc-advancedSpacer{flex:1}
3152
+ .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}
3153
+ .cc-chevronUp{transform:rotate(-135deg);margin-bottom:-3px}
3154
+ .cc-advancedBody{flex-direction:column;display:flex}
3155
+ .cc-advancedBody>.cc-field:first-of-type{border-top:1px solid var(--dsw-alias-border-l2)}
2599
3156
  .cc-hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}
2600
3157
  .cc-footer{justify-content:flex-end;align-items:center;gap:8px;display:flex}
2601
3158
  .cc-toggleRow{align-items:center;gap:8px;cursor:pointer;display:flex}
@@ -2674,25 +3231,38 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
2674
3231
  document.head.appendChild(tag);
2675
3232
  }
2676
3233
  /**
2677
- * Client plugin body. Gates on the services the settings page needs
2678
- * (`slots`, `locale`, `connection`, `remote`, `settingsScope`) plus the
2679
- * `connection` used by the friendly-error wrapper โ€” the same inject list the
2680
- * harness's own settings-surface plugins declare.
3234
+ * Client plugin body. Gates on the services shared by both client generations
3235
+ * (`slots`, `locale`, `connection`, `remote`, `settingsScope`). Current clients
3236
+ * mount the page after `remote.credentials` appears; legacy clients mount it
3237
+ * from the connection ApiProxy credential face.
2681
3238
  */
2682
3239
  function apply(ctx) {
2683
3240
  injectPageCss();
2684
3241
  const connection = ctx.get("connection");
2685
- if (connection !== void 0) installFriendlyImageError(connection);
3242
+ if (connection !== void 0) installFriendlyImageError(connection, () => ctx.locale.getLocale().active === "zh" ? "zh" : "en");
2686
3243
  ctx.effect(() => ctx.locale.register("settings.commandcode", {
2687
3244
  zh,
2688
3245
  en
2689
3246
  }), "dsh-commandcode-provider: page copy");
2690
- const api = ctx.get("connection").api;
2691
- const hostDescription = ctx.get("connection").hostDescription;
2692
- const controller = new CommandCodeSettingsController(ctx.settingsScope.bind({ namespace: COMMANDCODE_NS }), { credentials: api.credentials }, hostDescription);
3247
+ const legacyApi = adaptLegacyCredentials(connection?.api?.credentials);
3248
+ if (legacyApi !== void 0) {
3249
+ applyClientSurfaces(ctx, legacyApi, connection?.hostDescription);
3250
+ return;
3251
+ }
3252
+ ctx.inject(["remote.credentials"], (remoteCtx) => {
3253
+ const credentials = remoteCtx.remote.credentials;
3254
+ applyClientSurfaces(remoteCtx, { credentials });
3255
+ });
3256
+ }
3257
+ /** Mount the one shared UI implementation over either credential transport. */
3258
+ function applyClientSurfaces(ctx, api, hostDescription) {
3259
+ const controller = new CommandCodeSettingsController(ctx.settingsScope.bind({ namespace: COMMANDCODE_NS }), api, hostDescription);
2693
3260
  ctx.effect(() => () => controller.dispose(), "dsh-commandcode-provider: settings controller");
2694
- const store = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)(controller.state());
3261
+ const store = createSnapshotStore(controller.state());
2695
3262
  controller.subscribe(() => store.set(controller.state()));
3263
+ ctx.effect(() => ctx.remote.$on("credentials/reference-updated", () => {
3264
+ controller.refreshCredentials();
3265
+ }), "dsh-commandcode-provider: credential invalidations");
2696
3266
  let usageNamespace;
2697
3267
  let usageMountError;
2698
3268
  const contribution = {
@@ -2732,7 +3302,7 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
2732
3302
  return namespace.report();
2733
3303
  } });
2734
3304
  ctx.effect(() => () => usageController.dispose(), "dsh-commandcode-provider: usage controller");
2735
- const usageStore = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)(usageController.state());
3305
+ const usageStore = createSnapshotStore(usageController.state());
2736
3306
  usageController.subscribe(() => usageStore.set(usageController.state()));
2737
3307
  const loginRemote = {
2738
3308
  loginBegin: async () => {
@@ -2759,7 +3329,7 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
2759
3329
  };
2760
3330
  const loginController = new CommandCodeLoginController(() => loginRemote);
2761
3331
  ctx.effect(() => () => loginController.dispose(), "dsh-commandcode-provider: login controller");
2762
- const loginStore = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)(loginController.state());
3332
+ const loginStore = createSnapshotStore(loginController.state());
2763
3333
  let lastLoginPhase = loginController.state().phase;
2764
3334
  loginController.subscribe(() => {
2765
3335
  const phase = loginController.state().phase;
@@ -2800,6 +3370,24 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
2800
3370
  locale: "settings.commandcode",
2801
3371
  inject: injected
2802
3372
  }, CommandCodeSettingsPage));
3373
+ ctx.slots.inject("settings.models.provider-card", () => ctx.slots.register({
3374
+ name: "settings.models.provider-card",
3375
+ key: "llm-commandcode",
3376
+ locale: "settings.commandcode",
3377
+ inject: () => ({
3378
+ hooks: {
3379
+ commandCodeSettings: store,
3380
+ commandCodeLogin: loginStore
3381
+ },
3382
+ edit: (field, text) => controller.edit(field, text),
3383
+ save: () => void controller.save().then(() => {
3384
+ const settled = controller.state();
3385
+ if (!settled.failed && settled.anyAccountConfigured) usageController.refresh();
3386
+ }),
3387
+ beginLogin: () => void loginController.begin(),
3388
+ cancelLogin: () => void loginController.cancel()
3389
+ })
3390
+ }, CommandCodeProviderCard));
2803
3391
  }
2804
3392
  const inject = [
2805
3393
  "slots",