dsh-connect-workbuddy 4.9.3 → 4.9.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 +24 -0
- package/lib/bin.js +1 -1
- package/lib/{host-heartbeat-BHaBzzOr.js → host-heartbeat-BpArQi1Q.js} +21 -10
- package/lib/index.d.ts +17 -0
- package/lib/index.js +43 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 4.9.4 (2026-10-19)
|
|
4
|
+
|
|
5
|
+
> 版本号是 **patch**:只改**错误文案的取材方式**,发往上游的请求体、路由 id、模型 id、凭据、计费与换号规则一律未变(换号规则的变化只有一处「不再多问一次」的减少,见下)。但它修的是一个**会把人送去查错地方**的故障——一个普通的 400 被渲染成「账号池空了」,而上游唯一那句可执行建议被截断丢掉。
|
|
6
|
+
|
|
7
|
+
### Fixes
|
|
8
|
+
|
|
9
|
+
- **issue #78:400/11133 被渲染成「池中其余账号本次请求都已尝试过」,且 `displayTips` 被 400 字截断丢弃**
|
|
10
|
+
- **原来错在哪**:三个缺陷叠在同一行错误里。
|
|
11
|
+
1. **描述了一次从未发生的账号遍历**。`isFailoverWorthy()` 明确排除 `client`(同一个请求体换哪个账号都是 400),换号循环因此一次都没跑;但通用分支判断「要不要问池子要解释」用的条件是**回调是否存在**,而真实宿主两个回调都给了。于是 `explainPoolShortfall()` 被问了一次,它看到池里每个成员都健康(400 不记账号故障)、`excluded.length === 0`,命中那条分支返回「池中其余账号本次请求都已尝试过」——**池子其实没被问过任何候选**。句中再加上尾部固定的「池中没有其他可用账号」,整句读起来就是「账号用完了/额度耗尽了」,而真实原因是请求参数。
|
|
12
|
+
2. **`displayTips` 从未被解析**。`parseUpstreamErrorDetail()` 只取 `code`/`requestId`/`displayMsg`;通用分支把 `result.message.slice(0, 400)` 直接拼进用户可见文本,而这类报文 637–777 字符,前 400 字符全是 `code`/`requestId`/`extError` 的骨架(`param: ""`、`Request: null`),那句「请新建任务、换个模型或稍后重试」必然落在截断之外。报告者看到的回复停在 `Pleas` 中间。
|
|
13
|
+
3. **状态码印两次**。`failoverReasonText()` 的 `default` 回「不可用(http 400)」,而调用方自己又拼了一次「(http 400)」。
|
|
14
|
+
- **为什么测试没抓住**:既有测试全都用**不带 `displayTips`** 的报文(#56 的 14003、#51 的策略拒绝),所以「可执行建议被截断」这件事在夹具里根本不存在;而「问了没问池子」只被间接覆盖——`shortfall` 回调存在的用例恰好都是有换号的用例,于是「有没有问」和「该不该问」在测试里始终是同一个问题。状态码重复更无从断言:**没有任何测试读过用户看到的那一行**,只断言过 `detail` 字段本身。
|
|
15
|
+
- **现在的形状**:
|
|
16
|
+
- `UpstreamErrorDetail` 增加 `tips?: string`,`parseUpstreamErrorDetail()` 用同一个 `localizedText()` 读 `displayTips` 与 `displayMsg`(字符串与 `{ zh, en }` 两种形态都接受)。两个字段分开存:`displayMsg` 说「错在哪」,`displayTips` 说「该怎么办」——合并会让其中一个失去自己的意思。
|
|
17
|
+
- 通用分支的正文改成 `detail.tips ?? detail.displayMsg ?? clippedUpstreamText(message, 400)`,`code`/`requestId` 收进紧凑后缀「(code 11133,requestId 33221fe5-…)」(support 要的就是这两个,此前只能从被切碎的 JSON 里自己找)。`clippedUpstreamText()` 压平空白、超长时优先在句末断开并补 `…`——它**只**接住解析不出来的报文(非 JSON 网关页),有 `detail` 的报文永远走不到它。
|
|
18
|
+
- `failoverReasonText()` 增加 `case 'client': return '请求被上游拒绝'`,`default` 改为 `'不可用'`(不再自带状态码)。另两个分支(`policy_reject` / `model_busy`)里同样的 `slice(0, 200)` 也换成 `clippedUpstreamText()`,形状统一。
|
|
19
|
+
- **问池子的条件改为「真的搜过」**:`failoverShortfall !== undefined && failoverAccount !== undefined && isFailoverWorthy(result.kind)`。`client` 与 `model_busy` 两类不再触发那次排序(它们本来就不换号,问出来的答案必然是假的);`client` 另给一句正确的建议——「该请求被上游按参数拒绝:与网络、账号、额度都无关,换账号也不会改变结果」。
|
|
20
|
+
- **同一份报文现在渲染为**(177 字符,原来是 490):
|
|
21
|
+
```
|
|
22
|
+
workbuddy 上游失败:请求被上游拒绝(http 400)(code 11133,requestId 33221fe5-f6d5-4ea1-8882-669411e07a5b)。这次对话的内容这个模型不接受,与网络无关。请新建任务、换个模型或稍后重试。该请求被上游按参数拒绝:与网络、账号、额度都无关,换账号也不会改变结果,请按上面的提示调整后重试
|
|
23
|
+
```
|
|
24
|
+
- **测试**:新增 `tests/upstream-error-copy.spec.ts`(12 例)。它**不测 helper 而测渲染出来的那句话**——helper 只值它换来的用户可见结果,且 `detail` 由真实的 `parseUpstreamErrorDetail()` 产出,所以解析与渲染是一起被覆盖的(手搓 `detail` 的夹具可以在解析坏掉时仍然是绿的)。用例含:报告者那份 11133 报文逐字节渲染出上游建议、`message` 里**没有** `extError`/`"requestId"` 等骨架、长度 < 300、`(http 400)` 恰好出现**一次**(数出来的,因为「含有 http 400」对坏文案也成立)、`client` 失败时 shortfall 回调**调用次数为 0**且不出现两句池子术语、429 这类**会**换号的失败仍然既问池子又打印池子的结论并保留上游的重置时间、超长非 JSON 页补 `…` 且长度有界、短的非 JSON 页原样显示且不加 `…`、以及 `policy_reject`/`model_busy` 两个分支的文案不回归。删掉 `tips` 的解析、或把「问池子」的条件改回看回调是否存在,这组里都有用例会失败——已实测确认判别力。
|
|
25
|
+
- **刻意没做的**:没有改 `explainPoolShortfall()` 本身(它那句「池中其余账号本次请求都已尝试过」在**真的**搜过池子之后是诚实的,问题在于以前没搜过也会被问);没有把 `triedAccountIds` 传进 shortfall 回调(`tests/pool-e2e.spec.ts` 有源码文本守卫固定 `explainPoolShortfall(region)` 这个调用形状,且现有签名「不从 shim 接收参数」是 `src/index.ts:837-844` 写明的设计);没有改 400 的换号策略本身——它本来就是对的,错的是文案在描述一次没发生的换号。
|
|
26
|
+
|
|
3
27
|
## 4.9.3 (2026-10-19)
|
|
4
28
|
|
|
5
29
|
> 版本号是 **patch**:新增一个**可选**的查询参数(`detail=fast`)和一条**只读**的本地取数路径;不带参数的老请求逐字节不变,发往上游的请求体、路由 id、模型 id、凭据与配置项一律未改。但**它修的是一个用户可见的等待**——打开设置页后要等半分钟到一分钟才出现账号列表。
|
package/lib/bin.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { C as defaultDesktopAuthDirs, F as WorkBuddyUpstreamClient, K as WORKBUDDY_APP_EXECUTABLE_ENV, O as legacyWorkbuddyOwnAuthPath, Q as findWorkbuddyAppExecutableWithSource, S as defaultDesktopAuthCandidates, a as readHostHeartbeat, c as WORKBUDDY_CONNECT_VERSION, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, j as workbuddyOwnAuthPath, l as piAiRuntimeInfo, o as workbuddyHostHeartbeatPath, ot as hostPeersInfo, r as isHeartbeatProcessAlive, u as FALLBACK_WORKBUDDY_MODELS, v as WORKBUDDY_AUTH_FILE_ENV, y as WorkBuddyCredentialStore } from "./host-heartbeat-
|
|
2
|
+
import { C as defaultDesktopAuthDirs, F as WorkBuddyUpstreamClient, K as WORKBUDDY_APP_EXECUTABLE_ENV, O as legacyWorkbuddyOwnAuthPath, Q as findWorkbuddyAppExecutableWithSource, S as defaultDesktopAuthCandidates, a as readHostHeartbeat, c as WORKBUDDY_CONNECT_VERSION, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, j as workbuddyOwnAuthPath, l as piAiRuntimeInfo, o as workbuddyHostHeartbeatPath, ot as hostPeersInfo, r as isHeartbeatProcessAlive, u as FALLBACK_WORKBUDDY_MODELS, v as WORKBUDDY_AUTH_FILE_ENV, y as WorkBuddyCredentialStore } from "./host-heartbeat-BpArQi1Q.js";
|
|
3
3
|
import { realpathSync } from "node:fs";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
//#region src/bin.ts
|
|
@@ -1095,6 +1095,23 @@ function classifyUpstreamError(status, body) {
|
|
|
1095
1095
|
return "client";
|
|
1096
1096
|
}
|
|
1097
1097
|
/**
|
|
1098
|
+
* Read one of the upstream's localized fields, in either shape it ships: a plain
|
|
1099
|
+
* string, or a `{ zh, en }` object.
|
|
1100
|
+
*
|
|
1101
|
+
* One reader for every such field on purpose. `displayMsg` and `displayTips`
|
|
1102
|
+
* arrive in the same two shapes from the same envelope, so parsing them with two
|
|
1103
|
+
* separate hand-rolled branches is how one of them ends up only handling the
|
|
1104
|
+
* object form (which is exactly what happened to `displayTips`: it was never
|
|
1105
|
+
* read at all, issue #78).
|
|
1106
|
+
*/
|
|
1107
|
+
function localizedText(value) {
|
|
1108
|
+
if (typeof value === "string") return value;
|
|
1109
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
1110
|
+
const localized = value;
|
|
1111
|
+
const pick = localized.zh ?? localized.en;
|
|
1112
|
+
return typeof pick === "string" ? pick : void 0;
|
|
1113
|
+
}
|
|
1114
|
+
/**
|
|
1098
1115
|
* Parse the known-good fields out of an upstream failure body. Non-JSON
|
|
1099
1116
|
* bodies (HTML error pages, empty strings) yield undefined rather than a guess.
|
|
1100
1117
|
*/
|
|
@@ -1107,19 +1124,13 @@ function parseUpstreamErrorDetail(body) {
|
|
|
1107
1124
|
}
|
|
1108
1125
|
if (typeof parsed !== "object" || parsed === null) return void 0;
|
|
1109
1126
|
const raw = parsed;
|
|
1110
|
-
|
|
1111
|
-
const
|
|
1112
|
-
if (typeof display === "string") displayMsg = display;
|
|
1113
|
-
else if (typeof display === "object" && display !== null) {
|
|
1114
|
-
const localized = display;
|
|
1115
|
-
const pick = localized.zh ?? localized.en;
|
|
1116
|
-
if (typeof pick === "string") displayMsg = pick;
|
|
1117
|
-
}
|
|
1118
|
-
if (displayMsg === void 0 && typeof raw.msg === "string") displayMsg = raw.msg;
|
|
1127
|
+
const displayMsg = localizedText(raw.displayMsg) ?? (typeof raw.msg === "string" ? raw.msg : void 0);
|
|
1128
|
+
const tips = localizedText(raw.displayTips);
|
|
1119
1129
|
const detail = {};
|
|
1120
1130
|
if (typeof raw.code === "number") detail.upstreamCode = raw.code;
|
|
1121
1131
|
if (typeof raw.requestId === "string") detail.requestId = raw.requestId;
|
|
1122
1132
|
if (displayMsg !== void 0) detail.displayMsg = displayMsg;
|
|
1133
|
+
if (tips !== void 0) detail.tips = tips;
|
|
1123
1134
|
return detail;
|
|
1124
1135
|
}
|
|
1125
1136
|
/**
|
|
@@ -3966,7 +3977,7 @@ function generationOf(version) {
|
|
|
3966
3977
|
//#endregion
|
|
3967
3978
|
//#region src/version.ts
|
|
3968
3979
|
/** The npm package version this build was produced from. */
|
|
3969
|
-
const WORKBUDDY_CONNECT_VERSION = "4.9.
|
|
3980
|
+
const WORKBUDDY_CONNECT_VERSION = "4.9.4";
|
|
3970
3981
|
//#endregion
|
|
3971
3982
|
//#region src/host-heartbeat.ts
|
|
3972
3983
|
/**
|
package/lib/index.d.ts
CHANGED
|
@@ -754,6 +754,23 @@ interface UpstreamErrorDetail {
|
|
|
754
754
|
requestId?: string;
|
|
755
755
|
/** Best human-facing message: `displayMsg.zh`, falling back to `.en`, then `msg`. */
|
|
756
756
|
displayMsg?: string;
|
|
757
|
+
/**
|
|
758
|
+
* What the upstream says to DO about it: `displayTips.zh`, falling back to
|
|
759
|
+
* `.en` (issue #78).
|
|
760
|
+
*
|
|
761
|
+
* Kept separate from {@link displayMsg} because the two answer different
|
|
762
|
+
* questions and both are needed: `displayMsg` names what was rejected
|
|
763
|
+
* ("模型无法处理此请求"), while `displayTips` names the REMEDY ("请新建任务、换个
|
|
764
|
+
* 模型或稍后重试"). The generic failure path used to discard it and render the
|
|
765
|
+
* raw body instead, whose first 400 characters are the `code`/`requestId`/
|
|
766
|
+
* `extError` skeleton — so the one actionable sentence was always cut off, and
|
|
767
|
+
* a plain parameter rejection read like an empty account pool.
|
|
768
|
+
*
|
|
769
|
+
* Recorded as data rather than folded into `displayMsg` so a caller that wants
|
|
770
|
+
* to tell the user what to change can do so without the "what went wrong"
|
|
771
|
+
* clause losing its own meaning.
|
|
772
|
+
*/
|
|
773
|
+
tips?: string;
|
|
757
774
|
}
|
|
758
775
|
/**
|
|
759
776
|
* Region for a login domain; an empty domain means CN (matching upstream tooling).
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as isEncryptedFieldWrapper, A as workbuddyAccountId, B as modelIdOfBody, C as defaultDesktopAuthDirs, D as isVaultAccountId, E as isEncryptedCredentialError, F as WorkBuddyUpstreamClient, G as regionOf, H as parseReasoning, I as classifyUpstreamError, J as deriveAtRestKey, K as WORKBUDDY_APP_EXECUTABLE_ENV, L as declaredTools, M as CREDENTIAL_REJECTED_CODE, N as WORKBUDDY_FALLBACK_SYSTEM_PROMPT, O as legacyWorkbuddyOwnAuthPath, P as WorkBuddyCredentialRejectedError, R as isCredentialRejectedError, S as defaultDesktopAuthCandidates, T as hasEncryptedCredentialFields, U as parseUpstreamModel, V as parseCreditMultiplier, W as prepareChatBody, X as fetchAtRestKeyPayload, Y as deriveAtRestKeyId, Z as findWorkbuddyAppExecutable, _ as WORKBUDDY_AUTH_FILENAME, a as readHostHeartbeat, at as workbuddyAppExecutableCandidates, b as WorkBuddyEncryptedCredentialError, c as WORKBUDDY_CONNECT_VERSION, ct as withFileLock, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, et as isWorkbuddyBundle, f as WorkBuddyCatalog, g as ENCRYPTED_CREDENTIAL_CODE, h as fallbackModelsFor, i as processStartTimeMs, it as readAtRestKey, j as workbuddyOwnAuthPath, k as parseWorkBuddyAuth, l as piAiRuntimeInfo, lt as writeFileAtomic, m as deriveCatalog, n as clearHostHeartbeat, nt as macosNestedAppBundles, o as workbuddyHostHeartbeatPath, p as applyContextBudgets, q as clearAtRestKeyCache, r as isHeartbeatProcessAlive, rt as openEncryptedField, s as writeHostHeartbeat, st as resolveDshHome, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, tt as macosBundleExecutable, u as FALLBACK_WORKBUDDY_MODELS, v as WORKBUDDY_AUTH_FILE_ENV, w as defaultDesktopAuthPath, x as authFileName, y as WorkBuddyCredentialStore, z as knownRegionOf } from "./host-heartbeat-
|
|
1
|
+
import { $ as isEncryptedFieldWrapper, A as workbuddyAccountId, B as modelIdOfBody, C as defaultDesktopAuthDirs, D as isVaultAccountId, E as isEncryptedCredentialError, F as WorkBuddyUpstreamClient, G as regionOf, H as parseReasoning, I as classifyUpstreamError, J as deriveAtRestKey, K as WORKBUDDY_APP_EXECUTABLE_ENV, L as declaredTools, M as CREDENTIAL_REJECTED_CODE, N as WORKBUDDY_FALLBACK_SYSTEM_PROMPT, O as legacyWorkbuddyOwnAuthPath, P as WorkBuddyCredentialRejectedError, R as isCredentialRejectedError, S as defaultDesktopAuthCandidates, T as hasEncryptedCredentialFields, U as parseUpstreamModel, V as parseCreditMultiplier, W as prepareChatBody, X as fetchAtRestKeyPayload, Y as deriveAtRestKeyId, Z as findWorkbuddyAppExecutable, _ as WORKBUDDY_AUTH_FILENAME, a as readHostHeartbeat, at as workbuddyAppExecutableCandidates, b as WorkBuddyEncryptedCredentialError, c as WORKBUDDY_CONNECT_VERSION, ct as withFileLock, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, et as isWorkbuddyBundle, f as WorkBuddyCatalog, g as ENCRYPTED_CREDENTIAL_CODE, h as fallbackModelsFor, i as processStartTimeMs, it as readAtRestKey, j as workbuddyOwnAuthPath, k as parseWorkBuddyAuth, l as piAiRuntimeInfo, lt as writeFileAtomic, m as deriveCatalog, n as clearHostHeartbeat, nt as macosNestedAppBundles, o as workbuddyHostHeartbeatPath, p as applyContextBudgets, q as clearAtRestKeyCache, r as isHeartbeatProcessAlive, rt as openEncryptedField, s as writeHostHeartbeat, st as resolveDshHome, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, tt as macosBundleExecutable, u as FALLBACK_WORKBUDDY_MODELS, v as WORKBUDDY_AUTH_FILE_ENV, w as defaultDesktopAuthPath, x as authFileName, y as WorkBuddyCredentialStore, z as knownRegionOf } from "./host-heartbeat-BpArQi1Q.js";
|
|
2
2
|
import z from "@deepseek-ai/schemastery";
|
|
3
3
|
import { appendFile, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
4
4
|
import { randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
|
@@ -2691,10 +2691,39 @@ function failoverReasonText(failure) {
|
|
|
2691
2691
|
case "policy_reject": return "被服务端策略拒绝";
|
|
2692
2692
|
case "not_found": return "模型不可用";
|
|
2693
2693
|
case "server": return "上游异常";
|
|
2694
|
-
|
|
2694
|
+
case "client": return "请求被上游拒绝";
|
|
2695
|
+
default: return "不可用";
|
|
2695
2696
|
}
|
|
2696
2697
|
}
|
|
2697
2698
|
/**
|
|
2699
|
+
* One upstream sentence, made safe to paste into a single-line error message.
|
|
2700
|
+
*
|
|
2701
|
+
* `result.message` carries the WHOLE upstream body — for a classified failure
|
|
2702
|
+
* that is a JSON envelope 637–777 characters long whose first 400 characters are
|
|
2703
|
+
* the `code`/`requestId`/`extError` skeleton (`param: ""`, `Request: null`). A
|
|
2704
|
+
* plain `slice(0, 400)` therefore cut the one actionable sentence in half: the
|
|
2705
|
+
* reply ended mid-word at `Pleas` and the reader lost `displayTips` entirely
|
|
2706
|
+
* (issue #78).
|
|
2707
|
+
*
|
|
2708
|
+
* Two rules keep that from happening again:
|
|
2709
|
+
* - whitespace collapses to single spaces, because the body is JSON/HTML and its
|
|
2710
|
+
* newlines would otherwise render as a ragged block inside one paragraph;
|
|
2711
|
+
* - the cut prefers a sentence boundary once one exists past the halfway point,
|
|
2712
|
+
* so a clipped sentence reads as "truncated" instead of as broken prose, and
|
|
2713
|
+
* the `…` always says the text continues.
|
|
2714
|
+
*
|
|
2715
|
+
* This is the LAST resort: a body that parsed into a `detail` with `displayMsg`
|
|
2716
|
+
* or `tips` never reaches it, and those are short by construction.
|
|
2717
|
+
*/
|
|
2718
|
+
function clippedUpstreamText(text, limit) {
|
|
2719
|
+
const collapsed = text.replace(/\s+/gu, " ").trim();
|
|
2720
|
+
if (collapsed.length <= limit) return collapsed;
|
|
2721
|
+
const cut = collapsed.slice(0, limit);
|
|
2722
|
+
const stop = Math.max(cut.lastIndexOf("。"), cut.lastIndexOf("!"), cut.lastIndexOf("?"), cut.lastIndexOf(". "), cut.lastIndexOf("! "), cut.lastIndexOf("? "));
|
|
2723
|
+
if (stop >= Math.floor(limit / 2)) return `${cut.slice(0, stop + 1).trimEnd()}…`;
|
|
2724
|
+
return `${cut.trimEnd()}…`;
|
|
2725
|
+
}
|
|
2726
|
+
/**
|
|
2698
2727
|
* The line prepended to an answer that came from a failover account.
|
|
2699
2728
|
*
|
|
2700
2729
|
* Block-quoted and bracketed so it reads as machinery rather than as the model's
|
|
@@ -3094,11 +3123,11 @@ function createWorkBuddyShim(options) {
|
|
|
3094
3123
|
if (!result.ok) {
|
|
3095
3124
|
const attempts = triedAccountIds.length;
|
|
3096
3125
|
const note = attempts > 1 ? `(已尝试 ${attempts} 个账号)` : "";
|
|
3097
|
-
const shortfall = failoverShortfall
|
|
3126
|
+
const shortfall = failoverShortfall !== void 0 && failoverAccount !== void 0 && isFailoverWorthy(result.kind) ? await failoverShortfall().catch(() => void 0) : void 0;
|
|
3098
3127
|
const why = shortfall === void 0 ? "" : `:${shortfall}`;
|
|
3099
3128
|
if (result.kind === "policy_reject") {
|
|
3100
3129
|
const detail = result.detail;
|
|
3101
|
-
const lead = detail?.displayMsg ?? result.message
|
|
3130
|
+
const lead = detail?.displayMsg ?? clippedUpstreamText(result.message, 200);
|
|
3102
3131
|
const meta = ["服务端策略拒绝"];
|
|
3103
3132
|
if (detail?.upstreamCode !== void 0) meta.push(`code ${detail.upstreamCode}`);
|
|
3104
3133
|
if (detail?.requestId !== void 0) meta.push(`requestId ${detail.requestId}`);
|
|
@@ -3108,7 +3137,7 @@ function createWorkBuddyShim(options) {
|
|
|
3108
3137
|
}
|
|
3109
3138
|
if (result.kind === "model_busy") {
|
|
3110
3139
|
const detail = result.detail;
|
|
3111
|
-
const lead = detail?.displayMsg ?? result.message
|
|
3140
|
+
const lead = detail?.displayMsg ?? clippedUpstreamText(result.message, 200);
|
|
3112
3141
|
const meta = ["模型当前负载较高"];
|
|
3113
3142
|
if (detail?.upstreamCode !== void 0) meta.push(`code ${detail.upstreamCode}`);
|
|
3114
3143
|
if (detail?.requestId !== void 0) meta.push(`requestId ${detail.requestId}`);
|
|
@@ -3116,7 +3145,15 @@ function createWorkBuddyShim(options) {
|
|
|
3116
3145
|
writeOpenAIError(res, KIND_STATUS[result.kind], result.kind, message, hostErrorCode(result.kind));
|
|
3117
3146
|
return;
|
|
3118
3147
|
}
|
|
3119
|
-
|
|
3148
|
+
const detail = result.detail;
|
|
3149
|
+
const meta = [];
|
|
3150
|
+
if (detail?.upstreamCode !== void 0) meta.push(`code ${detail.upstreamCode}`);
|
|
3151
|
+
if (detail?.requestId !== void 0) meta.push(`requestId ${detail.requestId}`);
|
|
3152
|
+
const stamp = meta.length === 0 ? "" : `(${meta.join(",")})`;
|
|
3153
|
+
const receipt = detail?.tips ?? detail?.displayMsg ?? clippedUpstreamText(result.message, 400);
|
|
3154
|
+
const advice = result.kind === "client" ? "该请求被上游按参数拒绝:与网络、账号、额度都无关,换账号也不会改变结果,请按上面的提示调整后重试" : shortfall === void 0 ? "" : "建议换一个模型继续,或稍后等待该账号的额度恢复";
|
|
3155
|
+
const body = advice === "" ? receipt : `${receipt.replace(/[。.]$/u, "")}。${advice}`;
|
|
3156
|
+
writeOpenAIError(res, KIND_STATUS[result.kind], result.kind, `workbuddy 上游失败:${failoverReasonText(result)}(http ${result.status})${note}${why}${stamp}。${body}`, hostErrorCode(result.kind));
|
|
3120
3157
|
return;
|
|
3121
3158
|
}
|
|
3122
3159
|
res.writeHead(200, {
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "dsh-connect-workbuddy",
|
|
3
3
|
"displayName": "DSH Connect WorkBuddy",
|
|
4
4
|
"description": "把本机登录的 WorkBuddy 模型接入 DeepSeek Harness,并提供只读的积分概览与模型管理。",
|
|
5
|
-
"version": "4.9.
|
|
5
|
+
"version": "4.9.4",
|
|
6
6
|
"author": "dingminhua",
|
|
7
7
|
"copyright": "Copyright (c) 2026 LaoDing",
|
|
8
8
|
"license": "MIT",
|