@mars-sea/dsh-commandcode-provider 0.11.17 → 0.12.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
@@ -526,22 +526,62 @@ window.__ModuleLoader__.load({
526
526
  }
527
527
  };
528
528
  }
529
- const MAX_TIMEOUT_MS = 2147483647;
529
+ const MAX_TIMEOUT_SECONDS = 3600;
530
+ /**
531
+ * A duration field the user edits in WHOLE SECONDS.
532
+ *
533
+ * Milliseconds are the wrong unit to show: the defaults are 300000 and the
534
+ * ceiling is 2147483647, so the page asked users to do arithmetic to answer
535
+ * "how long is a reasonable wait". `format` divides, `parse` multiplies, and
536
+ * the stored value never leaves milliseconds.
537
+ *
538
+ * Decimals are refused rather than rounded. "1.5" is a format the control does
539
+ * not accept, and silently storing 1000 or 2000 for it would make the saved
540
+ * profile disagree with the box it was typed in.
541
+ */
542
+ function secondsField(field, bounds) {
543
+ return {
544
+ field,
545
+ format: (value) => typeof value === "number" ? String(Math.round(value / 1e3)) : "",
546
+ parse: (text) => {
547
+ const trimmed = text.trim();
548
+ if (trimmed === "") return { kind: "clear" };
549
+ const parsed = Number(trimmed);
550
+ if (!Number.isFinite(parsed) || !Number.isInteger(parsed)) return {
551
+ kind: "invalid",
552
+ reason: "format"
553
+ };
554
+ if (parsed < bounds.min) return {
555
+ kind: "invalid",
556
+ reason: "tooSmall"
557
+ };
558
+ if (parsed > bounds.max) return {
559
+ kind: "invalid",
560
+ reason: "tooLarge"
561
+ };
562
+ return {
563
+ kind: "set",
564
+ value: parsed * 1e3
565
+ };
566
+ }
567
+ };
568
+ }
530
569
  /** The fields this page edits inside the `llm-commandcode` namespace. */
531
570
  const SECTION_FIELDS = [
532
571
  textField("apiBase"),
533
- numberField$1("requestTimeoutMs", {
572
+ secondsField("requestTimeoutMs", {
534
573
  min: 1,
535
- max: MAX_TIMEOUT_MS
574
+ max: MAX_TIMEOUT_SECONDS
536
575
  }),
537
- numberField$1("streamIdleTimeoutMs", {
576
+ secondsField("streamIdleTimeoutMs", {
538
577
  min: 1,
539
- max: MAX_TIMEOUT_MS
578
+ max: MAX_TIMEOUT_SECONDS
540
579
  }),
541
580
  numberField$1("transportMaxRetries", {
542
581
  min: 0,
543
582
  max: 50
544
583
  }),
584
+ booleanField$1("offloadSeenImagesForCache"),
545
585
  booleanField$1("filterModelsByPlan"),
546
586
  booleanField$1("webSearch"),
547
587
  booleanField$1("showSidebarQuota"),
@@ -675,6 +715,7 @@ window.__ModuleLoader__.load({
675
715
  requestTimeoutMs: this.field("requestTimeoutMs"),
676
716
  streamIdleTimeoutMs: this.field("streamIdleTimeoutMs"),
677
717
  transportMaxRetries: this.field("transportMaxRetries"),
718
+ offloadSeenImagesForCache: this.field("offloadSeenImagesForCache"),
678
719
  filterModelsByPlan: this.field("filterModelsByPlan"),
679
720
  webSearch: this.field("webSearch"),
680
721
  showSidebarQuota: this.field("showSidebarQuota"),
@@ -1082,7 +1123,8 @@ window.__ModuleLoader__.load({
1082
1123
  shaped.push({
1083
1124
  id: entry.id,
1084
1125
  name: entry.name,
1085
- ...typeof entry.tier === "string" ? { tier: entry.tier } : {}
1126
+ ...typeof entry.tier === "string" ? { tier: entry.tier } : {},
1127
+ ...typeof entry.allowance === "number" && Number.isFinite(entry.allowance) ? { allowance: entry.allowance } : {}
1086
1128
  });
1087
1129
  }
1088
1130
  this.catalogModels = shaped;
@@ -1184,6 +1226,34 @@ window.__ModuleLoader__.load({
1184
1226
  }
1185
1227
  };
1186
1228
  //#endregion
1229
+ //#region src/display-format.ts
1230
+ /** Shared, dependency-free formatting for Host commands and browser surfaces. */
1231
+ function formatMoney(value) {
1232
+ return `$${value.toFixed(2)}`;
1233
+ }
1234
+ function formatMoneyExact(value) {
1235
+ return `$${value.toFixed(4)}`;
1236
+ }
1237
+ function formatTokensCompact(value) {
1238
+ if (value >= 1e9) return `${(value / 1e9).toFixed(1)}B`;
1239
+ if (value >= 1e6) return `${(value / 1e6).toFixed(1)}M`;
1240
+ if (value >= 1e3) return `${(value / 1e3).toFixed(1)}K`;
1241
+ return String(value);
1242
+ }
1243
+ /** The input is already in percent units; callers add the percent sign. */
1244
+ function formatSuccessRate(value) {
1245
+ return String(Number(value.toFixed(2)));
1246
+ }
1247
+ /** One window's fill ratio in [0, 1]; zero when uncapped. */
1248
+ function windowRatio(used, cap) {
1249
+ if (cap <= 0) return 0;
1250
+ return Math.max(0, Math.min(1, used / cap));
1251
+ }
1252
+ /** Local date-time, with the empty state chosen by the caller. */
1253
+ function formatResetAt(ms) {
1254
+ return ms <= 0 ? "" : new Date(ms).toLocaleString();
1255
+ }
1256
+ //#endregion
1187
1257
  //#region src/client/usage.ts
1188
1258
  const IDLE$1 = {
1189
1259
  status: "idle",
@@ -1266,39 +1336,6 @@ window.__ModuleLoader__.load({
1266
1336
  for (const listener of this.listeners) listener();
1267
1337
  }
1268
1338
  };
1269
- /** Format a dollar amount compactly (2 decimals). */
1270
- function formatMoney(value) {
1271
- return `$${value.toFixed(2)}`;
1272
- }
1273
- /** Format a dollar amount precisely (4 decimals) for small totals. */
1274
- function formatMoneyExact(value) {
1275
- return `$${value.toFixed(4)}`;
1276
- }
1277
- /** Format a large token count compactly (1.9M style). */
1278
- function formatTokensCompact(value) {
1279
- if (value >= 1e9) return `${(value / 1e9).toFixed(1)}B`;
1280
- if (value >= 1e6) return `${(value / 1e6).toFixed(1)}M`;
1281
- if (value >= 1e3) return `${(value / 1e3).toFixed(1)}K`;
1282
- return String(value);
1283
- }
1284
- /**
1285
- * Success-rate percentage (already in percent units, e.g. 99.96): at most two
1286
- * decimals, trailing zeros trimmed — `100` stays `100`, not `100.00`, and
1287
- * `99.965552876334` becomes `99.97`. The `%` suffix is the caller's job.
1288
- */
1289
- function formatSuccessRate(value) {
1290
- return String(Number(value.toFixed(2)));
1291
- }
1292
- /** One window's fill ratio in [0, 1]; 0 when uncapped. */
1293
- function windowRatio(used, cap) {
1294
- if (cap <= 0) return 0;
1295
- return Math.max(0, Math.min(1, used / cap));
1296
- }
1297
- /** Format a millis timestamp as a local short date-time; empty when unset. */
1298
- function formatResetAt(ms) {
1299
- if (ms <= 0) return "";
1300
- return new Date(ms).toLocaleString();
1301
- }
1302
1339
  /** Host facts drive the usage card, including keys invisible to browser credentials. */
1303
1340
  function usageCardState(usage) {
1304
1341
  const accounts = usage.report?.accounts ?? [];
@@ -1925,7 +1962,7 @@ window.__ModuleLoader__.load({
1925
1962
  * separate instance so catalog boundary errors name `commandcode/models`,
1926
1963
  * not the report endpoint.
1927
1964
  */
1928
- const { record: catalogRecord, stringField: catalogString } = makeBoundaryValidator("commandcode/models result:");
1965
+ const { record: catalogRecord, stringField: catalogString, numberField: catalogNumber } = makeBoundaryValidator("commandcode/models result:");
1929
1966
  /** Parse one untrusted boundary value into a {@link CommandCodeCatalogModel}. */
1930
1967
  function parseCatalogModel(value) {
1931
1968
  const source = catalogRecord(value, "model");
@@ -1934,6 +1971,7 @@ window.__ModuleLoader__.load({
1934
1971
  name: catalogString(source, "name", "model.name")
1935
1972
  };
1936
1973
  if (source.tier !== void 0) model.tier = catalogString(source, "tier", "model.tier");
1974
+ if (source.allowance !== void 0) model.allowance = catalogNumber(source, "allowance", "model.allowance");
1937
1975
  return model;
1938
1976
  }
1939
1977
  /** Parse the wire result into a {@link CommandCodeCatalog}. */
@@ -2085,6 +2123,24 @@ window.__ModuleLoader__.load({
2085
2123
  ]
2086
2124
  };
2087
2125
  //#endregion
2126
+ //#region src/plan-tiers.ts
2127
+ /** Shared plan-tier presentation facts; safe to inline into the browser bundle. */
2128
+ const PLAN_TIER_ORDER = [
2129
+ "go",
2130
+ "goat",
2131
+ "pro",
2132
+ "provider",
2133
+ "max"
2134
+ ];
2135
+ const PLAN_LABELS = {
2136
+ go: "Go",
2137
+ goat: "GOAT",
2138
+ pro: "Pro",
2139
+ provider: "Provider",
2140
+ max: "Max"
2141
+ };
2142
+ Object.fromEntries(PLAN_TIER_ORDER.map((tier, index) => [tier, index]));
2143
+ //#endregion
2088
2144
  //#region src/client/model-select.ts
2089
2145
  /**
2090
2146
  * Model-select helpers for the settings page's model editors (browser half).
@@ -2095,26 +2151,12 @@ window.__ModuleLoader__.load({
2095
2151
  * detection, tier grouping — lives here, React-free, so node tests can drive
2096
2152
  * it directly.
2097
2153
  *
2098
- * Dependency-free by design: the client bundle may only import platform/seed
2099
- * modules, so the plan snapshot below is a deliberately small vendored copy
2100
- * (tier key → heading label) rather than an import of src/capabilities.ts.
2101
- * When upstream adds a plan tier, extend BOTH tables.
2154
+ * The shared tier module is dependency-free and inlined into the client bundle;
2155
+ * importing the Host's full capability snapshot here would bloat that bundle.
2102
2156
  *
2103
2157
  * @module dsh-commandcode-provider/model-select
2104
2158
  */
2105
2159
  /**
2106
- * Minimum plan tier → dropdown section heading. Mirrors the Host-side
2107
- * `KNOWN_PLANS` tiers + `PLAN_LABELS` in src/capabilities.ts; an unknown tier
2108
- * key falls back to the raw key rather than vanishing the row.
2109
- */
2110
- const TIER_HEADINGS = {
2111
- go: "Go",
2112
- goat: "GOAT",
2113
- pro: "Pro",
2114
- provider: "Provider",
2115
- max: "Max"
2116
- };
2117
- /**
2118
2160
  * The dropdown section heading for a catalog model id, or undefined for
2119
2161
  * models outside the known plan tiers (unmapped models and stale ids render
2120
2162
  * unheaded). `knownPlans` is the Host-side `KNOWN_PLANS` table, threaded in
@@ -2123,7 +2165,7 @@ window.__ModuleLoader__.load({
2123
2165
  function tierHeadingFor(modelId, knownPlans) {
2124
2166
  const tier = knownPlans[modelId];
2125
2167
  if (tier === void 0) return void 0;
2126
- return TIER_HEADINGS[tier] ?? tier;
2168
+ return PLAN_LABELS[tier] ?? tier;
2127
2169
  }
2128
2170
  /**
2129
2171
  * Whether `text` matches `query` as a case-insensitive substring over the
@@ -2146,7 +2188,8 @@ window.__ModuleLoader__.load({
2146
2188
  const options = catalog.filter((model) => matchesModelQuery(model, query)).map((model) => ({
2147
2189
  value: model.id,
2148
2190
  label: model.name,
2149
- stale: false
2191
+ stale: false,
2192
+ ...model.allowance === void 0 ? {} : { allowance: model.allowance }
2150
2193
  }));
2151
2194
  const seen = new Set(catalogIds);
2152
2195
  for (const id of selected) {
@@ -2219,7 +2262,7 @@ window.__ModuleLoader__.load({
2219
2262
  }
2220
2263
  //#endregion
2221
2264
  //#region package.json
2222
- var version = "0.11.17";
2265
+ var version = "0.12.1";
2223
2266
  var repository = {
2224
2267
  "type": "git",
2225
2268
  "url": "git+https://github.com/Mars-Sea/dsh-commandcode-provider.git"
@@ -2415,7 +2458,8 @@ window.__ModuleLoader__.load({
2415
2458
  "apiBase",
2416
2459
  "requestTimeoutMs",
2417
2460
  "streamIdleTimeoutMs",
2418
- "transportMaxRetries"
2461
+ "transportMaxRetries",
2462
+ "offloadSeenImagesForCache"
2419
2463
  ];
2420
2464
  /** The "customized" tag and reset link a staged field shows once it differs from the default. */
2421
2465
  function FieldOverride({ label, state, disabled, t, onReset }) {
@@ -2612,7 +2656,7 @@ window.__ModuleLoader__.load({
2612
2656
  });
2613
2657
  }
2614
2658
  /** One pool account's facts (identity, totals, credits, window limits). */
2615
- function AccountReport({ entry, fetchedAt, t, summaryOnly = false, headless = false }) {
2659
+ function AccountReport({ entry, fetchedAt, t, headless = false }) {
2616
2660
  const report = entry.report;
2617
2661
  const account = report.account;
2618
2662
  const accountName = account === void 0 ? "" : account.userName || account.name;
@@ -2669,7 +2713,7 @@ window.__ModuleLoader__.load({
2669
2713
  }) : null
2670
2714
  ]
2671
2715
  }) : null,
2672
- !summaryOnly && report.usage !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2716
+ report.usage !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2673
2717
  className: "cc-usageStats cc-usageStatsActivity",
2674
2718
  children: [
2675
2719
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageStat, {
@@ -2693,7 +2737,7 @@ window.__ModuleLoader__.load({
2693
2737
  })
2694
2738
  ]
2695
2739
  }) : null,
2696
- !summaryOnly && credits !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2740
+ credits !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2697
2741
  className: "cc-usageStats cc-usageStatsBalance",
2698
2742
  children: [
2699
2743
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageStat, {
@@ -2819,6 +2863,11 @@ window.__ModuleLoader__.load({
2819
2863
  className: "cc-badge",
2820
2864
  children: t("modelStale")
2821
2865
  }) : null,
2866
+ option.allowance !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2867
+ className: "cc-badgeMuted",
2868
+ title: t("modelAllowanceTitle"),
2869
+ children: t("modelAllowance", { amount: option.allowance })
2870
+ }) : null,
2822
2871
  !selected.includes(option.value) && ownerOf?.(option.value) !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2823
2872
  className: "cc-badgeMuted",
2824
2873
  children: t("accountModelOwner", { name: ownerOf(option.value) })
@@ -2917,11 +2966,13 @@ window.__ModuleLoader__.load({
2917
2966
  }
2918
2967
  /**
2919
2968
  * One titled group of rows. No card surface: like the harness's own settings
2920
- * pages, a group is a heading over hairline-separated rows.
2969
+ * pages, a group is a heading over its rows. The hairline between sections is
2970
+ * `cc-groupDivided`, so it belongs to every group but the first one on the page
2971
+ * (accounts opens it) and never to the rows inside a group.
2921
2972
  */
2922
- function SettingsGroup({ title, action, children }) {
2973
+ function SettingsGroup({ title, action, divided = false, children }) {
2923
2974
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2924
- className: "cc-group",
2975
+ className: divided ? "cc-group cc-groupDivided" : "cc-group",
2925
2976
  "aria-label": title,
2926
2977
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2927
2978
  className: "cc-groupHead",
@@ -2942,6 +2993,7 @@ window.__ModuleLoader__.load({
2942
2993
  function ModelsCard({ state, disabled, t, onEdit, onReset, onSelect, onClear }) {
2943
2994
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(SettingsGroup, {
2944
2995
  title: t("modelsTitle"),
2996
+ divided: true,
2945
2997
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleField, {
2946
2998
  id: "cc-filter-models-by-plan",
2947
2999
  label: t("filterModelsByPlan"),
@@ -2968,6 +3020,7 @@ window.__ModuleLoader__.load({
2968
3020
  function PrivacyCard({ state, disabled, t, onEdit, onReset }) {
2969
3021
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsGroup, {
2970
3022
  title: t("privacyTitle"),
3023
+ divided: true,
2971
3024
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleField, {
2972
3025
  id: "cc-zdr",
2973
3026
  label: t("zdr"),
@@ -2985,6 +3038,7 @@ window.__ModuleLoader__.load({
2985
3038
  function IntegrationsCard({ state, disabled, t, onEdit, onReset }) {
2986
3039
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(SettingsGroup, {
2987
3040
  title: t("integrationsTitle"),
3041
+ divided: true,
2988
3042
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleField, {
2989
3043
  id: "cc-web-search",
2990
3044
  label: t("webSearch"),
@@ -3018,7 +3072,7 @@ window.__ModuleLoader__.load({
3018
3072
  const overridden = ADVANCED_FIELDS.filter((field) => state[field].overridden).length;
3019
3073
  const invalid = ADVANCED_FIELDS.some((field) => state[field].invalid);
3020
3074
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
3021
- className: "cc-group",
3075
+ className: "cc-group cc-groupDivided",
3022
3076
  "aria-label": t("advancedSettings"),
3023
3077
  children: [
3024
3078
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
@@ -3098,6 +3152,17 @@ window.__ModuleLoader__.load({
3098
3152
  onEdit: (text) => onEdit("transportMaxRetries", text),
3099
3153
  onReset: () => onReset("transportMaxRetries"),
3100
3154
  t
3155
+ }),
3156
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleField, {
3157
+ id: "cc-offload-seen-images-for-cache",
3158
+ label: t("offloadSeenImagesForCache"),
3159
+ hint: t("offloadSeenImagesForCacheHint"),
3160
+ state: state.offloadSeenImagesForCache,
3161
+ disabled,
3162
+ defaultChecked: false,
3163
+ onEdit: (text) => onEdit("offloadSeenImagesForCache", text),
3164
+ onReset: () => onReset("offloadSeenImagesForCache"),
3165
+ t
3101
3166
  })
3102
3167
  ]
3103
3168
  }) : null,
@@ -3161,12 +3226,33 @@ window.__ModuleLoader__.load({
3161
3226
  }
3162
3227
  return rows;
3163
3228
  }
3164
- /** The row's status dot: error for a rejected key, warning while cooling down. */
3229
+ /**
3230
+ * The row's status dot. Read top-down, so a problem always outranks "serving":
3231
+ * red for a key the Host rejected, amber while a rate limit is cooling down,
3232
+ * green for the account that is actually serving requests. A standby (or not
3233
+ * yet configured) account draws NOTHING — `cc-tabDotBlank` is an unfilled,
3234
+ * borderless dot that only holds the column so the account names stay aligned
3235
+ * with the rows that do have a verdict. Green therefore means "in use", not
3236
+ * "healthy": a wall of green dots would say nothing.
3237
+ */
3165
3238
  function statusDotClass(row) {
3166
3239
  const entry = row.usage;
3167
- if (!row.configured || entry?.mark === "invalid-credential") return "cc-tabDot cc-tabDotError";
3240
+ if (entry?.mark === "invalid-credential") return "cc-tabDot cc-tabDotError";
3168
3241
  if (entry !== void 0 && (entry.mark !== "" || entry.cooldownUntil > 0)) return "cc-tabDot cc-tabDotWarn";
3169
- return "cc-tabDot cc-tabDotOk";
3242
+ if (row.configured && entry?.active === true) return "cc-tabDot cc-tabDotOk";
3243
+ return "cc-tabDot cc-tabDotBlank";
3244
+ }
3245
+ /**
3246
+ * The same verdict as a sentence, for the dot's tooltip. Undefined on a blank
3247
+ * dot: there is no verdict to read out, and a hover hotspot on an invisible
3248
+ * 8px mark would only invite the question it cannot usefully answer.
3249
+ */
3250
+ function statusDotTitle(row, t) {
3251
+ const entry = row.usage;
3252
+ if (entry?.mark === "invalid-credential") return t("usageInvalidKey");
3253
+ if (entry !== void 0 && (entry.mark !== "" || entry.cooldownUntil > 0)) return t("usageCooldown");
3254
+ if (!row.configured) return t("apiKeyUnset");
3255
+ return entry?.active === true ? t("usageActive") : void 0;
3170
3256
  }
3171
3257
  /** One compact quota meter for a row's summary line. */
3172
3258
  function MiniMeter({ label, limit }) {
@@ -3390,6 +3476,7 @@ window.__ModuleLoader__.load({
3390
3476
  children: [
3391
3477
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3392
3478
  className: statusDotClass(row),
3479
+ title: statusDotTitle(row, t),
3393
3480
  "aria-hidden": "true"
3394
3481
  }),
3395
3482
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -4467,8 +4554,7 @@ window.__ModuleLoader__.load({
4467
4554
  const UNREPORTED = "—";
4468
4555
  /** Local reset time; empty when the endpoint reported none. */
4469
4556
  function resetText(ms) {
4470
- if (ms <= 0) return "";
4471
- return new Date(ms).toLocaleString();
4557
+ return formatResetAt(ms);
4472
4558
  }
4473
4559
  /** Local short date; empty when unset. */
4474
4560
  function dateText(ms) {
@@ -4901,14 +4987,6 @@ window.__ModuleLoader__.load({
4901
4987
  ]
4902
4988
  });
4903
4989
  }
4904
- /** One usage tile. */
4905
- function StatTile({ stat, label }) {
4906
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Tile, {
4907
- label,
4908
- value: stat.value,
4909
- sub: stat.sub
4910
- });
4911
- }
4912
4990
  /** The rotation/credential badge line for one account. */
4913
4991
  function markText(account, text) {
4914
4992
  if (account.mark === void 0) return "";
@@ -5055,9 +5133,10 @@ window.__ModuleLoader__.load({
5055
5133
  children: text("usage")
5056
5134
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5057
5135
  className: "ccp-tiles",
5058
- children: account.stats.map((stat) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatTile, {
5059
- stat,
5060
- label: text(stat.label)
5136
+ children: account.stats.map((stat) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Tile, {
5137
+ label: text(stat.label),
5138
+ value: stat.value,
5139
+ sub: stat.sub
5061
5140
  }, stat.label))
5062
5141
  })]
5063
5142
  }) : null
@@ -6134,10 +6213,12 @@ window.__ModuleLoader__.load({
6134
6213
  * The settings-page stylesheet.
6135
6214
  *
6136
6215
  * The metrics are the harness's own settings pages', read out of the 0.1.7
6137
- * bundles so the page sits beside General and Models without looking foreign: a
6138
- * row is 16px of padding over a 0.5px border-l2 hairline, its title 14/22
6139
- * label-primary and its description 12/18 label-tertiary, the control on the
6140
- * right. Selectors and buttons are capsules (36px / 28px compact), inputs are
6216
+ * bundles so the page sits beside General and Models without looking foreign:
6217
+ * a row is 12px of padding, its title 14/22 label-primary and its description
6218
+ * 12/18 label-tertiary, the control on the right. The hairline rhythm is
6219
+ * section-level: a 0.5px border-l2 rule separates the GROUPS (see
6220
+ * `cc-groupDivided`), never the rows inside one, so a group's rows read as one
6221
+ * list. Selectors and buttons are capsules (36px / 28px compact), inputs are
6141
6222
  * 32px with an 8px radius and a 0.5px border-l4 edge, a card is a 0.5px
6142
6223
  * border-l4 outline with a 16px radius, and a nested panel is a
6143
6224
  * bg-module-platform fill with a 12px radius. Errors use state-error-primary.
@@ -6149,6 +6230,7 @@ window.__ModuleLoader__.load({
6149
6230
  .cc-readOnly{margin:8px 0 0;color:var(--dsw-alias-state-warn-label,var(--dsw-alias-label-tertiary));font-size:12px;line-height:18px}
6150
6231
  .cc-spacer{flex:1}
6151
6232
  .cc-group{flex-direction:column;display:flex;margin-top:28px}
6233
+ .cc-groupDivided{margin-top:24px;padding-top:20px;border-top:.5px solid var(--dsw-alias-border-l2)}
6152
6234
  .cc-groupHead{align-items:center;gap:8px;display:flex;min-height:28px;padding-bottom:4px}
6153
6235
  .cc-groupTitle{margin:0;color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:22px}
6154
6236
  .cc-groupDesc{margin:0 0 12px;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
@@ -6156,9 +6238,8 @@ window.__ModuleLoader__.load({
6156
6238
  .cc-disclosure{width:100%;padding:0 0 4px;border:0;background:0 0;font:inherit;text-align:left;cursor:pointer;border-radius:6px}
6157
6239
  .cc-disclosure:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}
6158
6240
  .cc-rows{flex-direction:column;display:flex}
6159
- .cc-row{align-items:center;gap:8px;display:flex;padding:16px 0;border-bottom:.5px solid var(--dsw-alias-border-l2)}
6160
- .cc-rows>.cc-row:last-child{border-bottom:0}
6161
- .cc-rowFlush{padding:0;border-bottom:0}
6241
+ .cc-row{align-items:center;gap:8px;display:flex;padding:12px 0}
6242
+ .cc-rowFlush{padding:0}
6162
6243
  .cc-rowText{flex-direction:column;flex:1;gap:4px;min-width:0;padding-right:32px;display:flex}
6163
6244
  .cc-rowTitleLine{align-items:center;gap:8px;display:flex;min-width:0}
6164
6245
  .cc-rowTitle{min-width:0;color:var(--dsw-alias-label-primary);font-size:14px;font-weight:400;line-height:22px}
@@ -6174,7 +6255,7 @@ window.__ModuleLoader__.load({
6174
6255
  .cc-linkButton:disabled{cursor:default;opacity:.4}
6175
6256
  .cc-linkButton:focus-visible{outline:none;box-shadow:0 0 0 2px var(--dsw-alias-border-l3)}
6176
6257
  .cc-badges{align-items:center;gap:8px;display:inline-flex}
6177
- .cc-badge,.cc-badgeMuted{flex:none;align-items:center;display:inline-flex;white-space:nowrap;border-radius:999px;corner-shape:round;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
6258
+ .cc-badge,.cc-badgeMuted{flex:none;align-items:center;display:inline-flex;white-space:nowrap;border-radius:999px;corner-shape:round;padding:1px 8px;font-size:12px;font-weight:500;line-height:17px}
6178
6259
  .cc-badge{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary)}
6179
6260
  .cc-badgeMuted{border:.5px solid var(--dsw-alias-border-l4);color:var(--dsw-alias-label-tertiary)}
6180
6261
  .cc-badgeWarn{background:var(--dsw-alias-state-warn-tertiary,var(--dsw-alias-bg-module-platform));color:var(--dsw-alias-state-warn-label,var(--dsw-alias-label-secondary))}
@@ -6248,6 +6329,7 @@ window.__ModuleLoader__.load({
6248
6329
  .cc-kebab{width:3px;height:3px;border-radius:50%;corner-shape:round;background:currentColor;box-shadow:0 -5px 0 currentColor,0 5px 0 currentColor}
6249
6330
  .cc-tabDot{flex-shrink:0;width:8px;height:8px;border-radius:50%;corner-shape:round}
6250
6331
  .cc-tabDotOk{background:var(--dsw-alias-state-success-primary)}
6332
+ .cc-tabDotBlank{background:0 0;border:0}
6251
6333
  .cc-tabDotWarn{background:var(--dsw-alias-state-warn-primary,#d97706)}
6252
6334
  .cc-tabDotError{background:var(--dsw-alias-state-error-primary)}
6253
6335
  .cc-accountMeters{flex-wrap:wrap;gap:6px 20px;display:flex;padding-left:16px}
@@ -6513,12 +6595,14 @@ window.__ModuleLoader__.load({
6513
6595
  apiKeyLocked: "密钥由只读来源提供",
6514
6596
  apiBase: "API 地址",
6515
6597
  apiBaseHint: "默认 https://api.commandcode.ai,一般无需修改。",
6516
- requestTimeoutMs: "请求超时(毫秒)",
6517
- requestTimeoutMsHint: "等待响应首个字节的超时;默认 60000。",
6518
- streamIdleTimeoutMs: "流空闲超时(毫秒)",
6519
- streamIdleTimeoutMsHint: "生成流停滞多久视为断连;默认 300000(长思考模型可静默数分钟,默认值刻意放宽)。",
6598
+ requestTimeoutMs: "请求超时(秒)",
6599
+ requestTimeoutMsHint: "等待响应首个字节的超时;默认 300 秒(与官方 CLI 一致)。Provider API 路由要等上游模型吐出第一个 token 才返回响应头,上游慢时仍可能不够;报超时时可按提示调高。",
6600
+ streamIdleTimeoutMs: "流空闲超时(秒)",
6601
+ streamIdleTimeoutMsHint: "生成流停滞多久视为断连;默认 300 秒(长思考模型可静默数分钟,默认值刻意放宽)。",
6520
6602
  transportMaxRetries: "网络失败重试次数",
6521
6603
  transportMaxRetriesHint: "连接失败时自动重试几次,默认 5(各次等待 0.5+1+2+4+8 秒,合计约 15 秒)。超过后直接报错,不再按指数退避长时间等待;填 0 表示不重试。限流、5xx 等由服务端要求重试的失败不受此项影响。",
6604
+ offloadSeenImagesForCache: "已看过的图片不再重复发送",
6605
+ offloadSeenImagesForCacheHint: "仅 CLI 传输。模型看过图片并回复后,永久用文字占位替代旧图片,以减少新增图片时的缓存回退。模型如需重看旧图,必须重新读取文件或让用户重新附加。默认关闭。",
6522
6606
  advancedSettings: "高级设置",
6523
6607
  advancedSettingsHint: "API 地址和网络参数,一般无需修改。",
6524
6608
  advancedOverriddenOne: "已自定义 1 项",
@@ -6539,6 +6623,8 @@ window.__ModuleLoader__.load({
6539
6623
  modelSearchPlaceholder: "搜索模型…",
6540
6624
  modelSearchEmpty: "没有匹配的模型。",
6541
6625
  modelStale: "已下架",
6626
+ modelAllowance: "${amount}/月",
6627
+ modelAllowanceTitle: "该模型每月可用的额度(美元)。套餐内所有模型共用同一个月度额度池,这不是额外赠送的额度。",
6542
6628
  visibleModelsTitle: "模型白名单",
6543
6629
  visibleModelsHint: "勾选要保留的模型,模型选择器就只列出这些;一个都不勾选时则显示全部模型。保存后,下次打开模型选择器生效。",
6544
6630
  visibleModelsPick: "选择要保留的模型…",
@@ -6665,12 +6751,14 @@ window.__ModuleLoader__.load({
6665
6751
  apiKeyLocked: "Key provided by a read-only source",
6666
6752
  apiBase: "API base URL",
6667
6753
  apiBaseHint: "Defaults to https://api.commandcode.ai; usually leave as-is.",
6668
- requestTimeoutMs: "Request timeout (ms)",
6669
- requestTimeoutMsHint: "Time to wait for the first response byte; default 60000.",
6670
- streamIdleTimeoutMs: "Stream idle timeout (ms)",
6671
- streamIdleTimeoutMsHint: "How long a stalled stream is treated as dead; default 300000 (deliberately generous — long-thinking models can stay silent for minutes).",
6754
+ requestTimeoutMs: "Request timeout (seconds)",
6755
+ requestTimeoutMsHint: "Time to wait for the first response byte; default 300 s (the official CLI’s). The Provider API route withholds headers until the upstream model emits its first token, so a slow upstream can still outlast the default; raise it when a timeout message suggests it.",
6756
+ streamIdleTimeoutMs: "Stream idle timeout (seconds)",
6757
+ streamIdleTimeoutMsHint: "How long a stalled stream is treated as dead; default 300 s (deliberately generous — long-thinking models can stay silent for minutes).",
6672
6758
  transportMaxRetries: "Transport retries",
6673
6759
  transportMaxRetriesHint: "How many times a failed connection is retried automatically; default 5 (waits of 0.5+1+2+4+8s, ~15s in total). After that the failure is reported instead of waiting on the exponential backoff. 0 disables the retries. Rate limits and 5xx answers keep their own, longer retry window.",
6760
+ offloadSeenImagesForCache: "Stop replaying images already seen",
6761
+ offloadSeenImagesForCacheHint: "CLI transport only. After this model responds to an image, durably replace older pixels with text to avoid cache rollback when another image arrives. To inspect an old image again, the model must reread its file or the user must attach it again. Off by default.",
6674
6762
  advancedSettings: "Advanced",
6675
6763
  advancedSettingsHint: "API base URL and network limits; usually leave as-is.",
6676
6764
  advancedOverriddenOne: "1 customized",
@@ -6691,6 +6779,8 @@ window.__ModuleLoader__.load({
6691
6779
  modelSearchPlaceholder: "Search models…",
6692
6780
  modelSearchEmpty: "No matching models.",
6693
6781
  modelStale: "Retired",
6782
+ modelAllowance: "${amount}/mo",
6783
+ modelAllowanceTitle: "Monthly usage this model can draw, in USD. Every model shares one monthly pool — this is not extra credit on top of the plan.",
6694
6784
  visibleModelsTitle: "Model allowlist",
6695
6785
  visibleModelsHint: "Check the models you want to keep, and model pickers will list only those. If nothing is checked, every model is shown. After saving, the change applies the next time you open a model picker.",
6696
6786
  visibleModelsPick: "Select models to keep…",
@@ -6808,32 +6898,16 @@ window.__ModuleLoader__.load({
6808
6898
  };
6809
6899
  //#endregion
6810
6900
  //#region src/client/index.ts
6811
- /** Inject the page stylesheet once (idempotent per tag). */
6812
- function injectPageCss() {
6813
- if (typeof document === "undefined") return;
6814
- if (document.querySelector(`style[data-plugin-css="@mars-sea/dsh-commandcode-provider/CommandCodeSettingsPage.module.css"]`) !== null) return;
6815
- const tag = document.createElement("style");
6816
- tag.dataset.plugin = "@mars-sea/dsh-commandcode-provider";
6817
- tag.dataset.pluginCss = PAGE_CSS_ID;
6818
- tag.textContent = PAGE_CSS;
6819
- document.head.appendChild(tag);
6820
- }
6821
- /**
6822
- * Install the plans & quota panel's stylesheet and return its disposer, for
6823
- * `ctx.effect` to own. Keyed by its own `data-plugin-css` id, so a second
6824
- * surface asking for it later is a no-op.
6825
- */
6826
- function injectPanelCss() {
6901
+ /** Install one stylesheet by id and return a disposer for its owning effect. */
6902
+ function injectCss(id, css) {
6827
6903
  if (typeof document === "undefined") return () => {};
6828
- if (document.querySelector(`style[data-plugin-css="@mars-sea/dsh-commandcode-provider/CommandCodePanel.module.css"]`) !== null) return () => {};
6904
+ if (document.querySelector(`style[data-plugin-css="${id}"]`) !== null) return () => {};
6829
6905
  const tag = document.createElement("style");
6830
6906
  tag.dataset.plugin = "@mars-sea/dsh-commandcode-provider";
6831
- tag.dataset.pluginCss = PANEL_CSS_ID;
6832
- tag.textContent = PANEL_CSS;
6907
+ tag.dataset.pluginCss = id;
6908
+ tag.textContent = css;
6833
6909
  document.head.appendChild(tag);
6834
- return () => {
6835
- tag.remove();
6836
- };
6910
+ return () => tag.remove();
6837
6911
  }
6838
6912
  /**
6839
6913
  * Plans & quota panel id. It is the layout's `MainPanelId`: one string shared
@@ -6859,7 +6933,7 @@ window.__ModuleLoader__.load({
6859
6933
  * list at the bottom of this file.
6860
6934
  */
6861
6935
  function apply(ctx) {
6862
- injectPageCss();
6936
+ injectCss(PAGE_CSS_ID, PAGE_CSS);
6863
6937
  ctx.effect(() => ctx.locale.register("settings.commandcode", {
6864
6938
  zh,
6865
6939
  en
@@ -7108,7 +7182,7 @@ window.__ModuleLoader__.load({
7108
7182
  ctx.get("layout")?.selectPanel(null);
7109
7183
  }
7110
7184
  });
7111
- ctx.effect(() => injectPanelCss(), "dsh-commandcode-provider: panel styles");
7185
+ ctx.effect(() => injectCss(PANEL_CSS_ID, PANEL_CSS), "dsh-commandcode-provider: panel styles");
7112
7186
  try {
7113
7187
  ctx.slots.inject("main", () => ctx.slots.register({
7114
7188
  name: "main",