@mars-sea/dsh-commandcode-provider 0.16.2 → 0.16.4

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/CHANGELOG.md CHANGED
@@ -4,6 +4,27 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.16.4] - 2026-11-02
8
+
9
+ ### Added
10
+
11
+ - **新增免费模型 Ling 3.1 Flash 1019(`inclusionai/ling-3.1-flash-1019:free`,服务端目录先行)。** `?view=client` 的 `catalogVersion` 由 `7f405e24…` 变为 `f3a51355…`,默认目录 94 → 95、client 视图 106 → 107,唯一新增行是 Ling 3.1 Flash 1019:1,000,000 上下文、262,144 输出上限、纯文本输入、推理档位 `low`/`medium`/`high`、Go 档、ZDR 覆盖、`/chat/completions` 端点;100% 免费促销自 `2026-10-29T16:00Z` 起至 `2026-11-03T16:00Z`(北京时间 11-04 00:00 到期),每账号每天 100 次请求。官方 models、pricing 与 go/goat/pro/max 计划页均已上架;`1.88.2` 产物注册表尚未收录该行(审计报 `absent from CLI registry`,与 `gpt-6.1-terra` 的目录先行形态一致)。快照 +1:`KNOWN_PLANS` 94 → 95、`KNOWN_EFFORTS` 67 → 68、`KNOWN_DEALS` 4 → 5(带 `expiresAt`,到期后 FREE 徽标自动消失,不跟随下架);`KNOWN_IMAGE_MODELS`(73)、`KNOWN_THINKING_MODELS`(16)、`KNOWN_NON_ZDR_MODELS`(18)、订阅表(9)、峰值定价(5)、内置价格行(94)与输出上限表(79)均不变——免费行照旧不进价格表,`MODEL_OUTPUT_TOKEN_LIMITS` 也不含 Ling 行(走 chat/completions)。`COMMAND_CODE_CLI_VERSION` 保持 `1.88.2`。**验证**:`typecheck` 退出码 0,`npm test` 1156/1156(扩展既有快照断言,未新增用例),`build` 两段 `Build complete`;本轮仅公开 GET 采集(14 个源全部 HTTP 200),未做真实账号登录、付费生成与服务端协议验证。
12
+
13
+ ### Fixed
14
+
15
+ - **账户余额为 0 时提示指向错误方向(2026-10-31 实测)。** 上游对 `POST /provider/v1/chat/completions` 返回 `400`,正文为 `You have insufficient credits to make this request…`、结构化代码只有 `BAD_REQUEST`;插件已能据此把账号轮换掉,但单账号场景没有可换对象,最终只显示通用文案「服务端拒绝了本次请求,请检查模型、套餐和请求设置」——按这三条查一条也查不到点上。现在该形状在通用分支之前被识别,提示为「账户额度不足,无法完成本次请求——请充值额度,或切换到其他账号」。判定复用账号轮换的同一组 pattern(新增 `isInsufficientCreditsText()` 与 `INSUFFICIENT_CREDITS`/`PREMIUM_CREDITS_EXHAUSTED` 代码集),两处不会漂移;失败码仍取 `httpErrorCode(status)`,重试预算与轮换行为不变。**验证**:`typecheck` 退出码 0,`npm test` 1156/1156(新增 3 条:实测正文、仅结构化代码、普通 400 不被吞)。
16
+
17
+ ## [0.16.3] - 2026-10-31
18
+
19
+ ### Changed
20
+
21
+ - **同步 Command Code CLI `1.88.1` → `1.88.2`(纯请求头同步)。** npm `latest` 于 `2026-10-29T02:05Z` 前进到 `1.88.2`。CLI 的变更日志在 npm 包内 `CHANGELOG.md`(官网 `/changelog` 只覆盖 Desktop 应用),该版本只有一条 patch:「修复全新安装或更新后启动崩溃」。产物级比对:包内 80 个文件只有 `CHANGELOG.md`、`dist/cli.mjs`、`package.json`(仅版本号)与 VSIX 变化,依赖零变化;请求路径(193)、自定义请求头(13)、环境变量(48)集合相同,`/alpha/generate` 调用点在屏蔽混淆标识符后相似度 1.0,`promptCache`(5)/`reasoning_effort`(7)/`max_tokens`(12)/`cache_control`(1)计数相同,审计 `cliDiff`(模型、档位、订阅、ZDR 例外集)为空。服务端目录未动(`?view=client` 的 `catalogVersion` 仍 `7f405e24…`,默认 94、client 106 行),所有能力快照零变化,`COMMAND_CODE_CLI_VERSION` 只随请求头更新,运行时行为不变。
22
+ - **同步定价页两条模型额度(`planAllowanceUsd`)。** 官网把 DeepSeek V4.1 Flash 与 GLM-5.3 Flash 的加成月度额度从 GOAT `$80`/Pro `$90` 上调到 GOAT `$100`/Pro `$110`(Go `$10` 不变),per-token 价格、Peak 时段与套餐可用性均未变;设置页「模型额度」随之显示新值。价格表由 `node scripts/sync-model-prices.mjs` 重新生成,全表只有这两行变化。GLM-5.3 Flash 仍标注 `while capacity lasts`;V4.1 Flash 的文案从「with no end date」改为「comes with bigger monthly credits」,官方未给结束时间,若之后回调按同一脚本再同步。**验证**:`typecheck` 退出码 0,`npm test` 1142/1142,`build` 两段 `Build complete`;未做真实账号登录与付费生成验证。
23
+
24
+ ### Fixed
25
+
26
+ - **探活丢路径前缀、断连与保存失败提示不可诊断(#111、#110)。** 探活原先用 `new URL('/', apiBase)` 探 API base 根,会丢掉带路径前缀的反向代理前缀(如 `http://127.0.0.1:18797/bili/https://api.commandcode.ai`),状态行必然误报「本机无法连接」;现在改探真实端点 `${apiBase}/alpha/whoami` 并用与真实请求相同的字符串拼接保留前缀,未认证的 401/403 计为「可达」(探活刻意不带凭据),404/429/5xx 仍判异常,超时与传输失败继续分开。用量报告的每条端点失败在原文后追加中文原因与目标 `scheme://host:port`(映射 fetch `error.cause` 的 `ECONNREFUSED`/`ENOTFOUND`/连接超时等,原始错误码入括号),反代换端口、代理未启动与 API 故障因此可区分。设置保存的「配置提交未获确认」不再固定说「密钥未写入」:只有本次提交带密钥时才提密钥,否则只报配置未写入;宿主拒绝的原文(如 profile 写锁超时)以「宿主返回:…」附在提示后,折叠为一行并限长 240 字符。**验证**:`typecheck` 退出码 0,`npm test` 1153/1153,`build` 两段 `Build complete`;另在隔离 `DSH_HOME` + 真实宿主(`dsh 0.2.2-alpha.2`)+ 带路径前缀反代 + 无头 Chrome 下端到端复现:反代在线时状态行「本机 连通正常 · 781 ms」(旧探测目标反代根 `/` 返回 404),停反代后「本机无法连接」且失败详情带目标与 `ECONNREFUSED`,写锁超时保存提示带宿主原文;再用真实密钥复验:只读接口全部 200、状态行「连通正常」、真实生成 `POST /alpha/generate` 返回 200(会话费用 `≈$0.0015` 与上游扣减 `0.001471284` 一致);最后复验浏览器登录链路:隔离宿主用凭据库中不存在的引用起步(界面「未配置」),点「网页登录」后按 Studio 回调契约提交真实密钥,回调 `200 {"success":true}`、校验请求经反代 `/alpha/whoami` 200、凭据落库且界面转为「已登录为 mars-sea」。Studio 页面本身的账号登录与 Cloudflare 挑战需真人完成,不在自动验证范围内。
27
+
7
28
  ## [0.16.2] - 2026-10-30
8
29
 
9
30
  ### Changed
package/lib/client.js CHANGED
@@ -76,6 +76,13 @@ window.__ModuleLoader__.load({
76
76
  }
77
77
  //#endregion
78
78
  //#region src/client/settings-scope.ts
79
+ /** 组装报告;没有原文时不带 `message` 键(`exactOptionalPropertyTypes`)。 */
80
+ function mutationReport(result, message) {
81
+ return message === void 0 ? { result } : {
82
+ result,
83
+ message
84
+ };
85
+ }
79
86
  /**
80
87
  * Backoff before re-reading a directory whose FIRST read failed, in millis.
81
88
  *
@@ -328,19 +335,20 @@ window.__ModuleLoader__.load({
328
335
  const response = await this.mutateRemote(ownedOps, revision);
329
336
  if (!response.ok) {
330
337
  await this.recover(generation);
331
- return response.error?.message?.includes("SETTINGS_CONFLICT") ? "conflict" : "failed";
338
+ const message = response.error?.message;
339
+ return message?.includes("SETTINGS_CONFLICT") ? mutationReport("conflict", message) : mutationReport("failed", message);
332
340
  }
333
341
  const row = response.value;
334
342
  if (row?.ns !== this.namespace || decodeRow(row.value) === void 0 || typeof row.revision !== "number") {
335
343
  await this.recover(generation);
336
- return "failed";
344
+ return mutationReport("failed");
337
345
  }
338
- if (this.disposed) return "accepted";
346
+ if (this.disposed) return mutationReport("accepted");
339
347
  if (generation === this.writeGeneration) {
340
348
  this.pendingRevision = void 0;
341
349
  this.mirror.acceptView(row);
342
350
  } else this.pendingRevision = typeof row.revision === "number" ? row.revision : void 0;
343
- return "accepted";
351
+ return mutationReport("accepted");
344
352
  });
345
353
  }
346
354
  async mutateRemote(ops, revision) {
@@ -406,9 +414,9 @@ window.__ModuleLoader__.load({
406
414
  }
407
415
  /** Queue operations one at a time; disposal makes the queue inert. */
408
416
  enqueue(operation) {
409
- if (this.disposed) return Promise.resolve("cancelled");
417
+ if (this.disposed) return Promise.resolve(mutationReport("cancelled"));
410
418
  const task = this.tail.then(async () => {
411
- if (this.disposed) return "cancelled";
419
+ if (this.disposed) return mutationReport("cancelled");
412
420
  return operation();
413
421
  });
414
422
  this.tail = task.then(() => {}, () => {});
@@ -780,22 +788,26 @@ window.__ModuleLoader__.load({
780
788
  return success;
781
789
  }
782
790
  async commit(ops, baseline) {
783
- if (this.closed) return "cancelled";
791
+ if (this.closed) return { result: "cancelled" };
784
792
  const snapshot = this.scope.getSnapshot();
785
- if (!snapshot.writable || this.scope.mutate === void 0) return "failed";
786
- if (baseline && [...baseline].some(([field, before]) => !sameValue(before, fieldBaseline(snapshot, field)))) return "conflict";
793
+ if (!snapshot.writable || this.scope.mutate === void 0) return { result: "failed" };
794
+ if (baseline && [...baseline].some(([field, before]) => !sameValue(before, fieldBaseline(snapshot, field)))) return { result: "conflict" };
787
795
  try {
788
796
  return await this.scope.mutate(ops, snapshot.revision);
789
- } catch {
790
- return "failed";
797
+ } catch (error) {
798
+ return {
799
+ result: "failed",
800
+ message: readableErrorText(error)
801
+ };
791
802
  }
792
803
  }
793
804
  async configure(outcome, ops, baseline) {
794
805
  if (ops.length === 0) return true;
795
806
  const confirmation = await this.commit(ops, baseline);
796
- outcome.config = confirmation === "accepted" ? "confirmed" : confirmation === "conflict" ? "conflict" : "unconfirmed";
797
- if (confirmation === "cancelled" || this.closed) outcome.issue = "closed";
798
- return confirmation === "accepted" && !this.closed;
807
+ outcome.config = confirmation.result === "accepted" ? "confirmed" : confirmation.result === "conflict" ? "conflict" : "unconfirmed";
808
+ if (confirmation.result === "failed" && confirmation.message !== void 0) outcome.hostError = confirmation.message;
809
+ if (confirmation.result === "cancelled" || this.closed) outcome.issue = "closed";
810
+ return confirmation.result === "accepted" && !this.closed;
799
811
  }
800
812
  async changeCredential(outcome, ref, value, defaultRef) {
801
813
  outcome.credential = "not-issued";
@@ -963,9 +975,15 @@ window.__ModuleLoader__.load({
963
975
  path: ["credentialCleanupRefs"],
964
976
  value: this.cleanupRefs().filter((entry) => entry !== ref)
965
977
  }])) {
966
- if (outcome.config === "none") outcome.config = cleanup.config;
978
+ if (outcome.config === "none") {
979
+ outcome.config = cleanup.config;
980
+ if (cleanup.hostError !== void 0) outcome.hostError = cleanup.hostError;
981
+ }
967
982
  outcome.issue = cleanup.issue ?? "cleanup-pending";
968
- } else if (outcome.config === "none") outcome.config = cleanup.config;
983
+ } else if (outcome.config === "none") {
984
+ outcome.config = cleanup.config;
985
+ if (cleanup.hostError !== void 0) outcome.hostError = cleanup.hostError;
986
+ }
969
987
  }
970
988
  };
971
989
  //#endregion
@@ -3243,7 +3261,9 @@ window.__ModuleLoader__.load({
3243
3261
  saved: "设置已保存",
3244
3262
  saveFailed: "保存失败,请重试。",
3245
3263
  writeConflict: "待提交字段已被其他操作修改,配置未写入;请核对后再保存。",
3246
- writeConfigUnconfirmed: "配置提交未获确认;密钥未写入,请核对宿主状态后重试。",
3264
+ writeConfigUnconfirmed: "配置提交未获确认,未写入宿主;请核对后重试。",
3265
+ writeConfigUnconfirmedKey: "配置与密钥均未写入宿主;请核对后重试。",
3266
+ writeHostError: "宿主返回:{message}",
3247
3267
  writeConfigSaved: "配置已保存。",
3248
3268
  writeKeyNotIssued: "密钥操作尚未发出;请核对后重试。",
3249
3269
  writeKeyUnconfirmed: "密钥操作未获确认;请核对后手动重试。",
@@ -3514,7 +3534,9 @@ window.__ModuleLoader__.load({
3514
3534
  saved: "Settings saved",
3515
3535
  saveFailed: "Save failed, please retry.",
3516
3536
  writeConflict: "Pending fields were modified by another operation; the configuration was not written. Please review and save again.",
3517
- writeConfigUnconfirmed: "The configuration submission was not confirmed; the key was not written. Please check the host state and retry.",
3537
+ writeConfigUnconfirmed: "The configuration submission was not confirmed and was not written. Please review and retry.",
3538
+ writeConfigUnconfirmedKey: "Neither the configuration nor the key was written. Please review and retry.",
3539
+ writeHostError: "Host returned: {message}",
3518
3540
  writeConfigSaved: "Configuration saved.",
3519
3541
  writeKeyNotIssued: "The key operation has not been issued; please review and retry.",
3520
3542
  writeKeyUnconfirmed: "The key operation was not confirmed; please review and retry manually.",
@@ -3699,7 +3721,7 @@ window.__ModuleLoader__.load({
3699
3721
  else if (result.issue === "target-changed") key = result.credential === "not-issued" ? "writeTargetBefore" : "writeTargetAfter";
3700
3722
  else if (result.issue === "cleanup-pending") key = "writeCleanupPending";
3701
3723
  else if (result.config === "conflict") key = "writeConflict";
3702
- else if (result.config === "unconfirmed") key = "writeConfigUnconfirmed";
3724
+ else if (result.config === "unconfirmed") key = result.credential === "not-issued" ? "writeConfigUnconfirmedKey" : "writeConfigUnconfirmed";
3703
3725
  else if (result.credential === "unconfirmed") key = "writeKeyUnconfirmed";
3704
3726
  else if (result.credential === "not-issued") key = "writeKeyNotIssued";
3705
3727
  const messages = key ? [t(key)] : [];
@@ -3709,8 +3731,18 @@ window.__ModuleLoader__.load({
3709
3731
  }
3710
3732
  if (key && result.config === "confirmed" && result.issue !== "cleanup-pending") messages.unshift(t("writeConfigSaved"));
3711
3733
  if (result.refreshFailed) messages.push(t("writeRefreshFailed"));
3734
+ const hostError = result.hostError;
3735
+ if (hostError !== void 0 && hostError.trim() !== "") messages.push(t("writeHostError").replace("{message}", () => hostErrorText(hostError)));
3712
3736
  return messages.join(" ");
3713
3737
  }
3738
+ /**
3739
+ * 宿主原文压成一行并限长:锁超时、写权限这类诊断足够;换行会撑高提示区,
3740
+ * 超长正文(整页 HTML、大对象)也不该被原样铺到设置页上。
3741
+ */
3742
+ function hostErrorText(message) {
3743
+ const oneLine = message.replace(/\s+/g, " ").trim();
3744
+ return oneLine.length > 240 ? `${oneLine.slice(0, 240)}…` : oneLine;
3745
+ }
3714
3746
  //#endregion
3715
3747
  //#region src/plan-tiers.ts
3716
3748
  /** Shared plan-tier presentation facts; safe to inline into the browser bundle. */
@@ -3864,7 +3896,7 @@ window.__ModuleLoader__.load({
3864
3896
  }
3865
3897
  //#endregion
3866
3898
  //#region package.json
3867
- var version = "0.16.2";
3899
+ var version = "0.16.4";
3868
3900
  var repository = {
3869
3901
  "type": "git",
3870
3902
  "url": "git+https://github.com/Mars-Sea/dsh-commandcode-provider.git"
package/lib/index.d.ts CHANGED
@@ -61,7 +61,7 @@ type CommandCodeProtocol = 'cli' | 'openai' | 'messages';
61
61
  declare const DEFAULT_MAX_OUTPUT_TOKENS = 131072;
62
62
  //#endregion
63
63
  //#region src/adapter.d.ts
64
- declare const COMMAND_CODE_CLI_VERSION = "1.88.1";
64
+ declare const COMMAND_CODE_CLI_VERSION = "1.88.2";
65
65
  declare const DEFAULT_API_BASE = "https://api.commandcode.ai";
66
66
  /**
67
67
  * The output budget this bundle asks for, and the ceiling it will never exceed.
@@ -1232,7 +1232,7 @@ declare const KNOWN_THINKING_MODELS: ReadonlySet<string>;
1232
1232
  * `/docs/plans/max` and `/docs/resources/pricing-limits`). Each plan's model
1233
1233
  * list is a superset of the one below it: Go ⊂ GOAT ⊂ Pro ⊂ Provider/Max.
1234
1234
  * Models absent from every plan list (Claude Opus/Fable, Fugu Ultra) are
1235
- * Provider-tier. Re-verified at command-code@1.88.1 (2026-10-28) against the
1235
+ * Provider-tier. Re-verified at command-code@1.88.2 (2026-10-31) against the
1236
1236
  * live `?view=client` catalog: 106 ids, still a strict superset chain. Mistral
1237
1237
  * Large 4 (1.75.0) was the only addition at the 1.77.0 check; the 1.78.0/1.79.0 pair
1238
1238
  * added Claude Haiku 5.5 at Pro and the free Glyph Cluster preview at Go.
package/lib/index.js CHANGED
@@ -123,7 +123,7 @@ const PLAN_ORDER = Object.fromEntries(PLAN_TIER_ORDER.map((tier, index) => [tier
123
123
  * subscription-plan labels, deals, and hourly (peak/off-peak) pricing.
124
124
  *
125
125
  * Everything here is synced from official sources — the command-code CLI
126
- * bundle's model table (`dist/cli.mjs`, re-verified at command-code@1.88.1), the
126
+ * bundle's model table (`dist/cli.mjs`, re-verified at command-code@1.88.2), the
127
127
  * live model catalog (`GET /provider/v1/models?view=client`, the authority for
128
128
  * capability fields since 1.82.4), and the official plan/pricing/model docs; see
129
129
  * the dsh-commandcode-upstream skill for the extraction procedures. The
@@ -145,7 +145,11 @@ const PLAN_ORDER = Object.fromEntries(PLAN_TIER_ORDER.map((tier, index) => [tier
145
145
  * step and `$2/$7.5` beyond. The 2026-10-28 check moves the constant to 1.88.1,
146
146
  * whose registry now names both catalog-only rows (Kimi K3.1 Pro and GPT-6.1
147
147
  * Terra) with efforts and context identical to this snapshot, so no table
148
- * changes. Keeping the snapshot in its own module
148
+ * changes. The 2026-10-31 check moves it to 1.88.2 — the npm package's own
149
+ * CHANGELOG names its sole change as a launch-crash fix after a fresh install
150
+ * or update — and again leaves every table here identical; that round's only
151
+ * data change is two per-model allowances in ./model-prices.ts. Keeping the
152
+ * snapshot in its own module
149
153
  * confines those frequent sync diffs here: src/adapter.ts holds only the stable
150
154
  * wire/runtime logic. The read helpers live here too, for the same reason.
151
155
  *
@@ -517,6 +521,11 @@ const KNOWN_EFFORTS = {
517
521
  "medium",
518
522
  "high"
519
523
  ],
524
+ "inclusionai/ling-3.1-flash-1019:free": [
525
+ "low",
526
+ "medium",
527
+ "high"
528
+ ],
520
529
  "mistral/mistral-large-4": [
521
530
  "low",
522
531
  "medium",
@@ -948,7 +957,7 @@ const MODEL_OUTPUT_TOKEN_LIMITS = /* @__PURE__ */ new Map([
948
957
  * `/docs/plans/max` and `/docs/resources/pricing-limits`). Each plan's model
949
958
  * list is a superset of the one below it: Go ⊂ GOAT ⊂ Pro ⊂ Provider/Max.
950
959
  * Models absent from every plan list (Claude Opus/Fable, Fugu Ultra) are
951
- * Provider-tier. Re-verified at command-code@1.88.1 (2026-10-28) against the
960
+ * Provider-tier. Re-verified at command-code@1.88.2 (2026-10-31) against the
952
961
  * live `?view=client` catalog: 106 ids, still a strict superset chain. Mistral
953
962
  * Large 4 (1.75.0) was the only addition at the 1.77.0 check; the 1.78.0/1.79.0 pair
954
963
  * added Claude Haiku 5.5 at Pro and the free Glyph Cluster preview at Go.
@@ -999,6 +1008,7 @@ const KNOWN_PLANS = {
999
1008
  "gpt-6-luna": "go",
1000
1009
  "meituan/LongCat-2.0": "go",
1001
1010
  "inclusionai/ling-3.1-flash": "go",
1011
+ "inclusionai/ling-3.1-flash-1019:free": "go",
1002
1012
  "meta/muse-spark-1.2-contributor": "go",
1003
1013
  "meta/muse-spark-1.3-contributor": "go",
1004
1014
  "meta/muse-spark-open": "go",
@@ -1203,6 +1213,11 @@ function modelVisibleForAnyAccount(modelId, accounts) {
1203
1213
  }
1204
1214
  const KNOWN_DEALS = {
1205
1215
  "MiniMaxAI/MiniMax-M3": { label: "50% off" },
1216
+ "inclusionai/ling-3.1-flash-1019:free": {
1217
+ label: "FREE",
1218
+ free: true,
1219
+ expiresAt: "2026-11-03T16:00:00Z"
1220
+ },
1206
1221
  "poolside/laguna-s-2.1-free": {
1207
1222
  label: "FREE",
1208
1223
  free: true
@@ -1651,8 +1666,8 @@ const MODEL_PRICE_ROWS = [
1651
1666
  ],
1652
1667
  allowance: {
1653
1668
  go: 10,
1654
- goat: 80,
1655
- pro: 90
1669
+ goat: 100,
1670
+ pro: 110
1656
1671
  }
1657
1672
  },
1658
1673
  {
@@ -1852,8 +1867,8 @@ const MODEL_PRICE_ROWS = [
1852
1867
  ],
1853
1868
  allowance: {
1854
1869
  go: 10,
1855
- goat: 80,
1856
- pro: 90
1870
+ goat: 100,
1871
+ pro: 110
1857
1872
  }
1858
1873
  },
1859
1874
  {
@@ -3934,6 +3949,76 @@ function readableErrorText(error) {
3934
3949
  }
3935
3950
  return String(error);
3936
3951
  }
3952
+ //#endregion
3953
+ //#region src/transport-failure.ts
3954
+ /**
3955
+ * Transport failure reasons for the usage and settings diagnostics.
3956
+ *
3957
+ * Node's fetch rejects with `TypeError('fetch failed')` and keeps the real
3958
+ * cause one level down (`error.cause`): `ECONNREFUSED` when nothing listens on
3959
+ * the port, `ENOTFOUND` on DNS, a connect timeout behind a dead proxy. That
3960
+ * message alone cannot tell "the reverse proxy changed port" from "the proxy is
3961
+ * not running" from "the API is down", so per-endpoint failure lines and the
3962
+ * settings notice append a short Chinese reason plus the raw code (issue #111).
3963
+ *
3964
+ * Only the code is quoted back, never the system message: those may embed
3965
+ * internal paths, and the original message stays available through
3966
+ * `readableErrorText` for the detail line. An error with no code yields no
3967
+ * reason — the caller still renders what it has instead of an invented guess.
3968
+ *
3969
+ * @module dsh-commandcode-provider/transport-failure
3970
+ */
3971
+ /** 原始错误码 → 界面原因;未列出的码只回显错误码,不做猜测。 */
3972
+ const REASONS$1 = {
3973
+ ECONNREFUSED: "连接被拒绝",
3974
+ ECONNRESET: "连接被重置",
3975
+ ENOTFOUND: "域名解析失败",
3976
+ EAI_AGAIN: "域名解析临时失败",
3977
+ EHOSTUNREACH: "主机不可达",
3978
+ ENETUNREACH: "网络不可达",
3979
+ ETIMEDOUT: "连接超时",
3980
+ UND_ERR_CONNECT_TIMEOUT: "连接超时",
3981
+ UND_ERR_SOCKET: "连接中断",
3982
+ UND_ERR_CLOSED: "连接已关闭",
3983
+ CERT_HAS_EXPIRED: "证书已过期",
3984
+ DEPTH_ZERO_SELF_SIGNED_CERT: "自签名证书不受信任",
3985
+ SELF_SIGNED_CERT_IN_CHAIN: "证书链中有自签名证书",
3986
+ UNABLE_TO_VERIFY_LEAF_SIGNATURE: "无法验证证书"
3987
+ };
3988
+ /** 取错误链上的第一个错误码:先看自身,再看 AggregateError 子项,最后看 cause。 */
3989
+ function codeOf(value, depth = 0) {
3990
+ if (depth > 4 || value === null || typeof value !== "object") return void 0;
3991
+ const entry = value;
3992
+ if (typeof entry.code === "string" && entry.code !== "") return entry.code;
3993
+ if (Array.isArray(entry.errors)) for (const nested of entry.errors) {
3994
+ const code = codeOf(nested, depth + 1);
3995
+ if (code !== void 0) return code;
3996
+ }
3997
+ return codeOf(entry.cause, depth + 1);
3998
+ }
3999
+ /**
4000
+ * One diagnostic clause for a transport failure, or undefined when the error
4001
+ * carries no recognizable code.
4002
+ */
4003
+ function transportFailureReason(error) {
4004
+ const code = codeOf(error);
4005
+ if (code === void 0) return void 0;
4006
+ const reason = REASONS$1[code];
4007
+ return reason === void 0 ? `网络错误(${code})` : `${reason}(${code})`;
4008
+ }
4009
+ /**
4010
+ * `scheme://host:port` of a base, with path and query dropped: enough to name
4011
+ * the endpoint a failed request aimed at without echoing proxy prefixes, and
4012
+ * without any chance of a token riding along in a path.
4013
+ */
4014
+ function httpOrigin(base) {
4015
+ try {
4016
+ const url = new URL(base);
4017
+ return `${url.protocol}//${url.host}`;
4018
+ } catch {
4019
+ return;
4020
+ }
4021
+ }
3937
4022
  const THROTTLED_CODE = "THROTTLED";
3938
4023
  const boundedCodes = /* @__PURE__ */ new Set([
3939
4024
  "EMPTY_RESPONSE",
@@ -4758,6 +4843,8 @@ function generateHttpError(status, errText, retryAfterMs, parsed = parseProvider
4758
4843
  if (status === 422 && isZdrNoProviders(providerCode, providerDetail, errText)) return bilingual("PROVIDER_HTTP_ERROR", `Command Code API error 422 (${detail}): zero data retention is enforced but no ZDR-capable upstream is available for this model — turn the ZDR setting off (or use another model)`, "Command Code API 返回 422:已开启零数据保留(ZDR),但该模型当前没有可用的 ZDR 上游——请关闭 ZDR 开关,或改用其他模型", { status: 422 });
4759
4844
  if (status === 401) return bilingual("INVALID_CREDENTIAL", `Command Code API error 401 (${detail}): the API key is missing or invalid — check the key stored for COMMANDCODE_API_KEY (Models page) or the auth file`, "Command Code API 返回 401:API 密钥缺失或无效——请在设置页检查 COMMANDCODE_API_KEY 存储的密钥,或检查 auth 文件", { status: 401 });
4760
4845
  if (status === 413) return bilingual("PROVIDER_HTTP_ERROR", "Command Code API error 413: the request body exceeds the provider's size limit (the cap is undocumented, measured at about 50 MB) — the session history is too large to send, usually because of accumulated image attachments. Start a new session, or drop the image-heavy part of this one, and retry", "Command Code API 返回 413:请求体超过服务端的体积上限(官方未公开,实测约 50 MB)——会话历史过大,通常是历史图片累积所致。请新建会话,或移除本会话中图片较多的部分后重试", { status: 413 });
4846
+ const creditsCode = providerCode?.toUpperCase();
4847
+ if (creditsCode !== void 0 && INSUFFICIENT_CREDITS_CODES.has(creditsCode) || isInsufficientCreditsText(overflowDetail) || isInsufficientCreditsText(errText)) return bilingual(httpErrorCode(status), `Command Code API error ${status}: this account has insufficient credits for the request — top up credits or switch to another account`, `Command Code API 返回 ${status}:账户额度不足,无法完成本次请求——请充值额度,或切换到其他账号`, { status });
4761
4848
  const safeIdentifier = (value) => value !== void 0 && /^[A-Za-z0-9_.:-]{1,80}$/.test(value) ? value : void 0;
4762
4849
  const diagnosticCode = safeIdentifier(parsed.code) ?? /^\s*(MODEL_NOT_IN_PLAN|INSUFFICIENT_CREDITS|USAGE_EXCEEDED|PREMIUM_CREDITS_EXHAUSTED)\b/i.exec(parsed.message ?? "")?.[1];
4763
4850
  const diagnosticType = safeIdentifier(parsed.type);
@@ -4850,6 +4937,30 @@ const ACCOUNT_UNAVAILABLE_CODES = /* @__PURE__ */ new Set([
4850
4937
  "PREMIUM_CREDITS_EXHAUSTED",
4851
4938
  "MODEL_NOT_IN_PLAN"
4852
4939
  ]);
4940
+ /**
4941
+ * 余额用尽的两种结构化代码(`USAGE_EXCEEDED`/`MODEL_NOT_IN_PLAN` 不属于这一类:
4942
+ * 前者是窗口额度,后者是套餐不含该模型,用户可行动的方向不同)。
4943
+ */
4944
+ const INSUFFICIENT_CREDITS_CODES = /* @__PURE__ */ new Set(["INSUFFICIENT_CREDITS", "PREMIUM_CREDITS_EXHAUSTED"]);
4945
+ /**
4946
+ * 正文里「额度用尽」的特征。
4947
+ *
4948
+ * 账号轮换判定与用户可见文案共用这一份 pattern:两处各写一份必然漂移,出现
4949
+ * 「轮换认得出、文案认不出」的错位——2026-10-31 的实测就是这样,账号池已经按
4950
+ * 这个原因换号,而单账号场景下用户看到的仍是通用 400 提示。
4951
+ *
4952
+ * 下划线与空格是同一个词:JSON 正文常写 `insufficient_credits`,CLI 自己的
4953
+ * marker 列表写 `insufficient credits`,先归一分隔符再匹配。
4954
+ */
4955
+ function isInsufficientCreditsText(text) {
4956
+ const lower = text.toLowerCase().replaceAll("_", " ");
4957
+ return lower.includes("insufficient credits") || lower.includes("premium credits exhausted") || /insufficient (credit|balance)/.test(lower) || /out of credit/.test(lower);
4958
+ }
4959
+ /** 正文里「模型不在本账号套餐内」的特征,与上者同一归一规则。 */
4960
+ function isModelOutsidePlanText(text) {
4961
+ const lower = text.toLowerCase().replaceAll("_", " ");
4962
+ return lower.includes("model not in plan") || /not (available|included)[^.]{0,40}plan/.test(lower) || lower.includes("upgrade your plan");
4963
+ }
4853
4964
  function classifyAccountRejection(status, errText, parsed = parseProviderError(errText)) {
4854
4965
  const code = (parsed.code ?? parsed.type)?.toUpperCase();
4855
4966
  if (status === 429 || code === "RATE_LIMITED") {
@@ -4863,8 +4974,7 @@ function classifyAccountRejection(status, errText, parsed = parseProviderError(e
4863
4974
  if (status === 401) return { reason: "invalid-credential" };
4864
4975
  if (code !== void 0 && ACCOUNT_UNAVAILABLE_CODES.has(code)) return { reason: "unavailable" };
4865
4976
  if (status < 400 || status >= 500) return void 0;
4866
- const lower = errText.toLowerCase().replaceAll("_", " ");
4867
- if (lower.includes("insufficient credits") || lower.includes("premium credits exhausted") || lower.includes("model not in plan") || /insufficient (credit|balance)/.test(lower) || /out of credit/.test(lower) || /not (available|included)[^.]{0,40}plan/.test(lower) || lower.includes("upgrade your plan")) return { reason: "unavailable" };
4977
+ if (isInsufficientCreditsText(errText) || isModelOutsidePlanText(errText)) return { reason: "unavailable" };
4868
4978
  }
4869
4979
  /**
4870
4980
  * The usage-window evidence carried by a rejected request, mirroring the CLI's
@@ -6535,7 +6645,7 @@ var GatewayFacts = class {
6535
6645
  * per-request options thunk and an API-key resolver from the plugin entry
6536
6646
  * (src/index.ts), so a settings change reaches the very next request.
6537
6647
  */
6538
- const COMMAND_CODE_CLI_VERSION = "1.88.1";
6648
+ const COMMAND_CODE_CLI_VERSION = "1.88.2";
6539
6649
  const DEFAULT_API_BASE = "https://api.commandcode.ai";
6540
6650
  /**
6541
6651
  * The output budget this bundle asks for, and the ceiling it will never exceed.
@@ -8719,7 +8829,9 @@ var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
8719
8829
  }
8720
8830
  return record;
8721
8831
  } catch (error) {
8722
- failures.push(`${path}: ${readableErrorText(error)}`);
8832
+ const reason = transportFailureReason(error);
8833
+ const origin = httpOrigin(base);
8834
+ failures.push(`${path}: ${readableErrorText(error)}${reason === void 0 ? "" : `;${reason}`}${origin === void 0 ? "" : `;目标 ${origin}`}`);
8723
8835
  failed.push({ kind: "transport" });
8724
8836
  return;
8725
8837
  }
@@ -9923,13 +10035,18 @@ function applyUsageRemote(ctx, deps) {
9923
10035
  remoteCtx.effect(() => () => void unregister(), "dsh-commandcode-provider: usage remote");
9924
10036
  });
9925
10037
  }
10038
+ /** The endpoint whose answer proves the API's auth layer is reachable. */
10039
+ const PROBE_PATH = "/alpha/whoami";
9926
10040
  /**
9927
- * HTTP statuses Upptime's default monitor accepts as `up` (from
9928
- * `uptime-monitor`'s `expectedStatusCodes` fallback). Kept in sync with the
9929
- * page's monitor rather than invented here: a 401 or 404 means the endpoint
9930
- * answered but is not the surface the official check calls healthy.
10041
+ * HTTP statuses that prove the probed endpoint answered.
10042
+ *
10043
+ * 2xx/3xx are the positive answers. 401/403 belong here because the probe is
10044
+ * deliberately credential-less: the expected answer to an unauthenticated GET
10045
+ * of `/alpha/whoami` IS 401, and treating it as a failure would report every
10046
+ * healthy host as down. Everything else (404 from a wrong base path, 429, 5xx)
10047
+ * means the endpoint the plugin actually uses did not answer as a live API.
9931
10048
  */
9932
- const UP_STATUSES = /* @__PURE__ */ new Set([
10049
+ const REACHABLE_STATUSES = /* @__PURE__ */ new Set([
9933
10050
  200,
9934
10051
  201,
9935
10052
  202,
@@ -9948,25 +10065,27 @@ const UP_STATUSES = /* @__PURE__ */ new Set([
9948
10065
  305,
9949
10066
  306,
9950
10067
  307,
9951
- 308
10068
+ 308,
10069
+ 401,
10070
+ 403
9952
10071
  ]);
9953
10072
  /** Turn the response status into the outcome the row renders. */
9954
10073
  function probeOutcomeForStatus(status) {
9955
- return UP_STATUSES.has(status) ? "ok" : "http-error";
10074
+ return REACHABLE_STATUSES.has(status) ? "ok" : "http-error";
9956
10075
  }
9957
10076
  /**
9958
10077
  * Probe the configured API base once. Never rejects: an invalid base, a
9959
10078
  * timeout and a transport failure all resolve to an outcome.
9960
10079
  *
9961
- * `redirect: 'manual'` keeps the classification on the FIRST response, exactly
9962
- * like the monitor's HTTP client; following a redirect would report the final
9963
- * destination's status as if it were the API's own.
10080
+ * `redirect: 'manual'` keeps the classification on the FIRST response; the
10081
+ * probe reports what this Host reached, not where a redirect chain ends up.
9964
10082
  */
9965
10083
  async function probeCommandCode(options) {
9966
10084
  const now = options.now ?? Date.now;
9967
10085
  let url;
9968
10086
  try {
9969
- url = new URL("/", options.apiBase).toString();
10087
+ if (!URL.canParse(options.apiBase)) throw new TypeError("invalid api base");
10088
+ url = `${options.apiBase}${PROBE_PATH}`;
9970
10089
  } catch {
9971
10090
  return {
9972
10091
  outcome: "unreachable",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mars-sea/dsh-commandcode-provider",
3
- "version": "0.16.2",
3
+ "version": "0.16.4",
4
4
  "description": "Unofficial DeepSeek Harness LLM provider plugin for Command Code, ported from pi-commandcode-provider (MIT). Registers the 'commandcode' provider route with a Models-page card and live model catalog.",
5
5
  "icon": "./icon.svg",
6
6
  "type": "module",