dsh-codearts 1.2.0 → 1.2.2
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 +64 -0
- package/lib/bin.mjs +2 -2
- package/lib/client/index.d.cts +4 -1
- package/lib/client/index.js +216 -84
- package/lib/index.d.mts +8 -1
- package/lib/index.mjs +72 -19
- package/lib/{usage-B0NfG8vZ.mjs → usage-DztQaswb.mjs} +96 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,70 @@
|
|
|
3
3
|
本文件记录本插件的重要改动。格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),
|
|
4
4
|
版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
|
|
5
5
|
|
|
6
|
+
## [1.2.2] - 2026-11-02
|
|
7
|
+
|
|
8
|
+
### 修复
|
|
9
|
+
|
|
10
|
+
- **`invalid_iam_token` 只会干等**:这类错误以**流内首帧**(HTTP 200)回来,既绕过
|
|
11
|
+
错误分类、也不会触发换号;而 `ensureFresh()` 只在距 `expiration` 不足 5 分钟时才
|
|
12
|
+
续期,token 被提前作废(另一个客户端续期 / 上游轮换)后每条消息都会撞同一堵墙。
|
|
13
|
+
现在首帧识别 `invalid_iam_token` / `Incorrect IAM authentication` / `APIG.0301`
|
|
14
|
+
等措辞,**强制续期一次**并让同一个账号重试;续期仍失败就冷却该账号,不再把
|
|
15
|
+
原始英文串甩给用户。HTTP 400 响应体里的同类错误走同一分支。
|
|
16
|
+
- **上游流内的「模型不存在 / 无权限」和「不吃图」都翻成人话**:
|
|
17
|
+
`InferHub.ModelArts.81003.401` 与 `InferHub.001001020.406` 以前会原样透传,
|
|
18
|
+
用户只看到两句英文。现在分别提示「模型不存在或当前账号没有权限(换号没用)」
|
|
19
|
+
与「该模型不支持图片输入」。识图能力本身仍以上游发现结果为准,不做本地覆盖。
|
|
20
|
+
|
|
21
|
+
## [1.2.1] - 2026-10-28
|
|
22
|
+
|
|
23
|
+
### 新增
|
|
24
|
+
|
|
25
|
+
- **底部那一行改成段式,与 `dsh-codebuddy-cli` 同构**:`积分 24,091 · Provider CodeArts ·
|
|
26
|
+
Model DeepSeek-V4.1-Flash-1017 · x0.18`。积分挪到最前,倍率单独成段(不再粘在模型名
|
|
27
|
+
尾巴上),段与段用 ` · ` 连接,和邻居那一行的观感一致。**只有积分那一段**是开积分弹层
|
|
28
|
+
的按钮(原来的设定不变):模型、倍率、排队都是纯文本,点它们不会弹面板。
|
|
29
|
+
`/api/codearts/status` 与 `/api/codearts/models` 的模型条目因此多了一个 `ratio` 字段
|
|
30
|
+
(`x0.18`;福利恒为 `x0.00`;上游没给倍率的模型缺席),免得客户端去拆显示名。
|
|
31
|
+
- **对话底部那行先说自己是谁了**:中间是 `Provider CodeArts · Model <模型名> · <倍率>`。
|
|
32
|
+
这一段**不受**「显示剩余积分 / 显示上游排队位置」两个开关影响,始终出现;
|
|
33
|
+
开关只管积分与排队两段。
|
|
34
|
+
- 这一行以前是靠邻居插件(`dsh-codebuddy-cli` 的 dock 会替任何 provider 画
|
|
35
|
+
`Provider <id> · Model <id>`)顺带显示的,但它在 codearts 下只给模型 **id**、
|
|
36
|
+
给不出倍率,而且受那个插件自己的「切换到其它 provider 时也显示这一行」开关控制
|
|
37
|
+
(关了整行就消失)。现在身份行由本插件自己负责,不依赖别人。
|
|
38
|
+
- **选中我们的模型时,那一行里别人的条目会暂时消失**:我们改不了别人的注册,但
|
|
39
|
+
`conversation.composer.dock` 是 list 槽位、每条注册都被宿主平铺成同一个
|
|
40
|
+
`[data-composer-dock]` 行里的兄弟节点,所以我们这条会认出新出现的**别人**、给它们
|
|
41
|
+
打上 `data-codearts-dock-suppressed` 属性,由我们注入的一条 CSS 隐藏。切换 provider
|
|
42
|
+
或卸载时逐个摘掉属性,别人的行立刻回来。**不碰**宿主自己的轮次/token 统计 pill
|
|
43
|
+
(`data-composer-stat`)和上下文占用格子。规则与边界在 `src/client/dock-suppress.ts`,
|
|
44
|
+
单测见 `test/dock-suppress.spec.ts`。
|
|
45
|
+
- **模型勾选区标出「识图」**:`/api/codearts/enabled-models` 的 choices 多了
|
|
46
|
+
`supportsImages`,卡片给支持图片输入的模型挂一个「识图」Tag。声明了 `reasoning` 之外
|
|
47
|
+
的模型能力本来就该在勾选时看得见,不然只有发了图才知道谁不吃图。
|
|
48
|
+
- **新增 `DeepSeek-V4.1-Flash-1017` 种子条目**(1,048,576 context)。
|
|
49
|
+
|
|
50
|
+
### 修复
|
|
51
|
+
|
|
52
|
+
- **`deepseek-v4.1-flash` 带图必报 `The request model is not multimodal`(issue #4)**:
|
|
53
|
+
这个裸别名在上游不算多模态,种子目录却标了 `supportsImages: true`,于是带图的请求在
|
|
54
|
+
出网前就被当成合法、到了上游才被拒。现在它标 false;多模态的是带日期的 `-1017`。
|
|
55
|
+
上游发现接口对 `-1017` 的 `supports_images` 也给 false,但 issue 报告者实测带图可用,
|
|
56
|
+
按实测为准标 true。
|
|
57
|
+
- **模型目录为空时会干等一小时**:启动/热重载后第一次模型发现如果正好撞上上游抖动,
|
|
58
|
+
目录会一直空着,期间每条消息都死在 pi-ai 的 `has no configured model`,而重试要等整
|
|
59
|
+
整 `CATALOG_REFRESH_MS`(1 小时)。现在发现循环自我重排:目录为空时 60 秒后重试,
|
|
60
|
+
有货后回到每小时。
|
|
61
|
+
|
|
62
|
+
### 开发
|
|
63
|
+
|
|
64
|
+
- **`scripts/deploy.mjs` 不再改写 profile 的依赖版本**:它以前把仓库 `version` 无条件写成
|
|
65
|
+
profile `dependencies` 的值,本地产物先于 npm 发布升版本时,profile 就会依赖一个
|
|
66
|
+
registry 里不存在的 specifier,宿主重载后整个 bundle 掉线(表现是模型选择器里
|
|
67
|
+
provider 消失,而 HTTP 路由还活着)。现在只在缺这一条时才补,版本不一致时打一行提示。
|
|
68
|
+
仍建议**先发布、再升仓库版本**。
|
|
69
|
+
|
|
6
70
|
## [1.2.0] - 2026-10-27
|
|
7
71
|
|
|
8
72
|
### 新增
|
package/lib/bin.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { B as codeartsAccountsPath, I as isBenefitModel, L as CodeArtsAccountStore, d as fetchCallerIdentity, n as codeartsUsagePath, r as discoverModels, t as CodeArtsUsageTracker } from "./usage-DztQaswb.mjs";
|
|
3
3
|
import { existsSync, statSync } from "node:fs";
|
|
4
4
|
//#region src/version.ts
|
|
5
|
-
const CODEARTS_VERSION = "1.2.
|
|
5
|
+
const CODEARTS_VERSION = "1.2.2";
|
|
6
6
|
//#endregion
|
|
7
7
|
//#region src/bin.ts
|
|
8
8
|
/**
|
package/lib/client/index.d.cts
CHANGED
|
@@ -40,6 +40,7 @@ interface CodeArtsSettingsKey {
|
|
|
40
40
|
benefit: string;
|
|
41
41
|
benefitBlocked: string;
|
|
42
42
|
benefitBlockedHint: string;
|
|
43
|
+
modelImages: string;
|
|
43
44
|
refreshModels: string;
|
|
44
45
|
refreshing: string;
|
|
45
46
|
refreshed: string;
|
|
@@ -85,7 +86,6 @@ interface CodeArtsSettingsKey {
|
|
|
85
86
|
creditsNeedSignIn: string;
|
|
86
87
|
creditsDock: string;
|
|
87
88
|
creditsDockTitle: string;
|
|
88
|
-
creditsDockExpand: string;
|
|
89
89
|
creditsDockPanelTitle: string;
|
|
90
90
|
creditsDockRefresh: string;
|
|
91
91
|
creditsDockRefreshing: string;
|
|
@@ -94,6 +94,9 @@ interface CodeArtsSettingsKey {
|
|
|
94
94
|
creditsDockEmpty: string;
|
|
95
95
|
creditsDockExpiring: string;
|
|
96
96
|
creditsDockUpdated: string;
|
|
97
|
+
dockProvider: string;
|
|
98
|
+
dockModel: string;
|
|
99
|
+
dockNoModel: string;
|
|
97
100
|
tabDisplay: string;
|
|
98
101
|
displayTitle: string;
|
|
99
102
|
displayHint: string;
|
package/lib/client/index.js
CHANGED
|
@@ -33,70 +33,70 @@ if (typeof document !== "undefined" && document.querySelector("style[data-plugin
|
|
|
33
33
|
document.head.appendChild(tag);
|
|
34
34
|
}
|
|
35
35
|
var CodeArtsPluginCard_module_css_default = {
|
|
36
|
-
"
|
|
37
|
-
"
|
|
36
|
+
"accountToolbar": "xoziZG_accountToolbar",
|
|
37
|
+
"choices": "xoziZG_choices",
|
|
38
|
+
"bar": "xoziZG_bar",
|
|
39
|
+
"modelTokens": "xoziZG_modelTokens",
|
|
40
|
+
"modelRequests": "xoziZG_modelRequests",
|
|
41
|
+
"tabPanel": "xoziZG_tabPanel",
|
|
42
|
+
"accountCard": "xoziZG_accountCard",
|
|
43
|
+
"check": "xoziZG_check",
|
|
44
|
+
"sectionHead": "xoziZG_sectionHead",
|
|
38
45
|
"hint": "xoziZG_hint",
|
|
39
|
-
"
|
|
40
|
-
"
|
|
46
|
+
"modelShare": "xoziZG_modelShare",
|
|
47
|
+
"creditBlock": "xoziZG_creditBlock",
|
|
48
|
+
"manualForm": "xoziZG_manualForm",
|
|
41
49
|
"updated": "xoziZG_updated",
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"accountCard": "xoziZG_accountCard",
|
|
45
|
-
"barFill": "xoziZG_barFill",
|
|
46
|
-
"choiceRow": "xoziZG_choiceRow",
|
|
47
|
-
"field": "xoziZG_field",
|
|
48
|
-
"body": "xoziZG_body",
|
|
49
|
-
"accountEntry": "xoziZG_accountEntry",
|
|
50
|
-
"modelChart": "xoziZG_modelChart",
|
|
51
|
-
"modelRequests": "xoziZG_modelRequests",
|
|
52
|
-
"link": "xoziZG_link",
|
|
53
|
-
"actions": "xoziZG_actions",
|
|
50
|
+
"accountRowActions": "xoziZG_accountRowActions",
|
|
51
|
+
"name": "xoziZG_name",
|
|
54
52
|
"creditRows": "xoziZG_creditRows",
|
|
55
|
-
"
|
|
56
|
-
"balanceRow": "xoziZG_balanceRow",
|
|
57
|
-
"creditLink": "xoziZG_creditLink",
|
|
53
|
+
"usageFill": "xoziZG_usageFill",
|
|
58
54
|
"modelName": "xoziZG_modelName",
|
|
55
|
+
"statusLine": "xoziZG_statusLine",
|
|
56
|
+
"count": "xoziZG_count",
|
|
57
|
+
"modelFill": "xoziZG_modelFill",
|
|
58
|
+
"accountInfo": "xoziZG_accountInfo",
|
|
59
|
+
"avatar": "xoziZG_avatar",
|
|
60
|
+
"headText": "xoziZG_headText",
|
|
59
61
|
"card": "xoziZG_card",
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
62
|
+
"expiryLabel": "xoziZG_expiryLabel",
|
|
63
|
+
"sectionLabel": "xoziZG_sectionLabel",
|
|
64
|
+
"desc": "xoziZG_desc",
|
|
65
|
+
"body": "xoziZG_body",
|
|
66
|
+
"claimAllRow": "xoziZG_claimAllRow",
|
|
64
67
|
"modelTrack": "xoziZG_modelTrack",
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
"
|
|
70
|
-
"
|
|
68
|
+
"manual": "xoziZG_manual",
|
|
69
|
+
"accountEntry": "xoziZG_accountEntry",
|
|
70
|
+
"creditLink": "xoziZG_creditLink",
|
|
71
|
+
"accountRow": "xoziZG_accountRow",
|
|
72
|
+
"creditHint": "xoziZG_creditHint",
|
|
73
|
+
"header": "xoziZG_header",
|
|
74
|
+
"accountName": "xoziZG_accountName",
|
|
75
|
+
"modelChart": "xoziZG_modelChart",
|
|
76
|
+
"creditFooter": "xoziZG_creditFooter",
|
|
77
|
+
"section": "xoziZG_section",
|
|
71
78
|
"fieldLabel": "xoziZG_fieldLabel",
|
|
72
79
|
"manualSummary": "xoziZG_manualSummary",
|
|
73
|
-
"creditRow": "xoziZG_creditRow",
|
|
74
|
-
"creditHint": "xoziZG_creditHint",
|
|
75
|
-
"statusLine": "xoziZG_statusLine",
|
|
76
|
-
"danger": "xoziZG_danger",
|
|
77
80
|
"modelRow": "xoziZG_modelRow",
|
|
78
|
-
"claimAllRow": "xoziZG_claimAllRow",
|
|
79
|
-
"accountToolbar": "xoziZG_accountToolbar",
|
|
80
81
|
"accountHint": "xoziZG_accountHint",
|
|
81
|
-
"
|
|
82
|
-
"
|
|
83
|
-
"
|
|
84
|
-
"
|
|
85
|
-
"
|
|
86
|
-
"
|
|
82
|
+
"expiryRow": "xoziZG_expiryRow",
|
|
83
|
+
"error": "xoziZG_error",
|
|
84
|
+
"actions": "xoziZG_actions",
|
|
85
|
+
"meta": "xoziZG_meta",
|
|
86
|
+
"balanceRow": "xoziZG_balanceRow",
|
|
87
|
+
"tabs": "xoziZG_tabs",
|
|
88
|
+
"link": "xoziZG_link",
|
|
89
|
+
"choiceRow": "xoziZG_choiceRow",
|
|
87
90
|
"balanceUnit": "xoziZG_balanceUnit",
|
|
88
|
-
"
|
|
91
|
+
"creditRow": "xoziZG_creditRow",
|
|
92
|
+
"field": "xoziZG_field",
|
|
89
93
|
"accountNameRow": "xoziZG_accountNameRow",
|
|
90
|
-
"accountInfo": "xoziZG_accountInfo",
|
|
91
|
-
"creditBlock": "xoziZG_creditBlock",
|
|
92
94
|
"balance": "xoziZG_balance",
|
|
93
|
-
"avatar": "xoziZG_avatar",
|
|
94
|
-
"manual": "xoziZG_manual",
|
|
95
95
|
"usageBar": "xoziZG_usageBar",
|
|
96
|
-
"
|
|
97
|
-
"
|
|
98
|
-
"
|
|
99
|
-
"
|
|
96
|
+
"balanceMeta": "xoziZG_balanceMeta",
|
|
97
|
+
"barFill": "xoziZG_barFill",
|
|
98
|
+
"area": "xoziZG_area",
|
|
99
|
+
"danger": "xoziZG_danger"
|
|
100
100
|
};
|
|
101
101
|
//#endregion
|
|
102
102
|
//#region src/client/CodeArtsPluginCard.tsx
|
|
@@ -1160,6 +1160,10 @@ function CodeArtsPluginCard({ t }) {
|
|
|
1160
1160
|
choice.ratio !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tag, {
|
|
1161
1161
|
tone: "quiet",
|
|
1162
1162
|
children: choice.ratio
|
|
1163
|
+
}),
|
|
1164
|
+
choice.supportsImages === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tag, {
|
|
1165
|
+
tone: "quiet",
|
|
1166
|
+
children: t("modelImages")
|
|
1163
1167
|
})
|
|
1164
1168
|
]
|
|
1165
1169
|
}, choice.id))
|
|
@@ -1311,14 +1315,94 @@ function CodeArtsPluginCard({ t }) {
|
|
|
1311
1315
|
});
|
|
1312
1316
|
}
|
|
1313
1317
|
//#endregion
|
|
1318
|
+
//#region src/client/dock-line.ts
|
|
1319
|
+
/** 段分隔符 —— 与邻居插件一致,改这里等于改整行的样子。 */
|
|
1320
|
+
const DOCK_SEGMENT_SEPARATOR = " · ";
|
|
1321
|
+
function buildDockLine(parts, t) {
|
|
1322
|
+
const rest = [];
|
|
1323
|
+
rest.push(t("dockProvider", { provider: parts.provider }));
|
|
1324
|
+
rest.push(parts.model === void 0 ? t("dockNoModel") : t("dockModel", { model: parts.model }));
|
|
1325
|
+
if (parts.ratio !== void 0) rest.push(parts.ratio);
|
|
1326
|
+
if (parts.queue !== void 0) rest.push(parts.queue);
|
|
1327
|
+
return {
|
|
1328
|
+
...parts.credits === void 0 ? {} : { credits: parts.credits },
|
|
1329
|
+
rest: rest.join(DOCK_SEGMENT_SEPARATOR)
|
|
1330
|
+
};
|
|
1331
|
+
}
|
|
1332
|
+
//#endregion
|
|
1333
|
+
//#region src/client/dock-suppress.ts
|
|
1334
|
+
/**
|
|
1335
|
+
* 选中我们的模型时,把对话底部那一行里**别人**的条目暂时压掉。
|
|
1336
|
+
*
|
|
1337
|
+
* 别人注册什么我们管不了 —— `conversation.composer.dock` 是 list 槽位,每条注册由
|
|
1338
|
+
* 宿主平铺成同一个 `[data-composer-dock]` 行里的一个直接子节点。所以我们能做的是:
|
|
1339
|
+
* 在自己这条的 effect 里认出兄弟节点、给它们打个属性,再用一条我们注入的 CSS 隐藏。
|
|
1340
|
+
* 切走或卸载时逐个摘掉属性,绝不留下别人的行永久消失。
|
|
1341
|
+
*
|
|
1342
|
+
* 不碰的:宿主自己的轮次/token 统计 pill(带 `data-composer-stat`),以及上下文占用
|
|
1343
|
+
* 那一格 —— 那些不是「别人的 provider 行」,压掉就是抢宿主的 UI。
|
|
1344
|
+
*
|
|
1345
|
+
* 这里刻意不写 `instanceof HTMLElement`:结构够了就能用,逻辑也就能在没装 jsdom 的
|
|
1346
|
+
* 测试环境里用假节点跑(见 test/dock-suppress.spec.ts)。
|
|
1347
|
+
*/
|
|
1348
|
+
/** 标记属性:带上它的节点被我们隐藏。 */
|
|
1349
|
+
const SUPPRESS_ATTR = "data-codearts-dock-suppressed";
|
|
1350
|
+
/** 注入的那条样式表 id。 */
|
|
1351
|
+
const SUPPRESS_STYLE_ID = "dsh-codearts-dock-suppress";
|
|
1352
|
+
/**
|
|
1353
|
+
* 这个兄弟节点该不该被压掉。
|
|
1354
|
+
*
|
|
1355
|
+
* 该压:别人的一条注册(既不是我们自己,也不包含我们,也不是宿主统计 pill)。
|
|
1356
|
+
* 不该压:我们自己、我们自己的子孙(有些宿主会包一层)、宿主的统计 pill。
|
|
1357
|
+
*/
|
|
1358
|
+
function isForeignDockEntry(node, self) {
|
|
1359
|
+
if (node === self) return false;
|
|
1360
|
+
if (node.contains?.(self) === true) return false;
|
|
1361
|
+
if (node.hasAttribute("data-composer-stat")) return false;
|
|
1362
|
+
if (node.querySelector("[data-composer-stat]") !== null) return false;
|
|
1363
|
+
return true;
|
|
1364
|
+
}
|
|
1365
|
+
/**
|
|
1366
|
+
* 扫一遍并给新发现的「别人」打标记。返回**这次**新标记的节点,调用方负责在清理时
|
|
1367
|
+
* 逐个摘掉(只摘自己打的那批,别动别人的状态)。
|
|
1368
|
+
*/
|
|
1369
|
+
function markForeignDockEntries(parent, self) {
|
|
1370
|
+
const marked = [];
|
|
1371
|
+
for (const child of Array.from(parent.children)) {
|
|
1372
|
+
if (!isForeignDockEntry(child, self)) continue;
|
|
1373
|
+
if (child.getAttribute("data-codearts-dock-suppressed") === "") continue;
|
|
1374
|
+
child.setAttribute(SUPPRESS_ATTR, "");
|
|
1375
|
+
marked.push(child);
|
|
1376
|
+
}
|
|
1377
|
+
return marked;
|
|
1378
|
+
}
|
|
1379
|
+
/** 摘掉标记 —— 切走 provider 或组件卸载时调用。 */
|
|
1380
|
+
function releaseDockEntries(nodes) {
|
|
1381
|
+
for (const node of nodes) node.removeAttribute(SUPPRESS_ATTR);
|
|
1382
|
+
}
|
|
1383
|
+
/** 注入一次隐藏规则;重复调用无副作用。没有 document 的环境(SSR/测试)直接跳过。 */
|
|
1384
|
+
function ensureSuppressStyle() {
|
|
1385
|
+
if (typeof document === "undefined") return;
|
|
1386
|
+
if (document.getElementById("dsh-codearts-dock-suppress") !== null) return;
|
|
1387
|
+
const style = document.createElement("style");
|
|
1388
|
+
style.id = SUPPRESS_STYLE_ID;
|
|
1389
|
+
style.textContent = `[${SUPPRESS_ATTR}]{display:none!important}`;
|
|
1390
|
+
document.head.appendChild(style);
|
|
1391
|
+
}
|
|
1392
|
+
//#endregion
|
|
1314
1393
|
//#region src/client/CodeArtsCreditDock.tsx
|
|
1315
1394
|
/**
|
|
1316
|
-
*
|
|
1395
|
+
* 对话底部的「模型 + 积分」条,以及点开后的积分弹层。
|
|
1396
|
+
*
|
|
1397
|
+
* 这一条挂在 `conversation.composer.dock` 槽位上(与宿主 session-stats strip 同位)。
|
|
1398
|
+
* 只在当前 provider 是 codearts 时才渲染,别的 provider 下完全不出现,不抢别人的行。
|
|
1317
1399
|
*
|
|
1318
|
-
*
|
|
1319
|
-
*
|
|
1320
|
-
*
|
|
1321
|
-
*
|
|
1400
|
+
* 第一段是 `Provider CodeArts · <模型名>`,模型名直接用目录里的 `name`
|
|
1401
|
+
* (`catalog.ts` 的 `modelDisplayName` 已经把倍率写进去了,如
|
|
1402
|
+
* `DeepSeek-V4.1-Flash-1017 · x0.18`),所以这一行自带倍率、不必另算。
|
|
1403
|
+
* 这段是**我们自己的**身份行:邻居插件(如 dsh-codebuddy-cli)对任何 provider
|
|
1404
|
+
* 都会画一行 `Provider <id> · Model <id>`,在 codearts 下那行只有模型 id、没有
|
|
1405
|
+
* 倍率;两边都开就会重复,所以优先我们这条,邻居那条应在它自己的设置里关掉。
|
|
1322
1406
|
*
|
|
1323
1407
|
* 那一行是个按钮:点开就是逐号明细(多账号下聚合数字本身没意义,得能看出是哪个号)。
|
|
1324
1408
|
* 显隐由设置卡片「显示」页那两个开关管着,开关值随 `/status` 一起下发 —— 底部条本来
|
|
@@ -1350,8 +1434,11 @@ function readSelection(useProjection) {
|
|
|
1350
1434
|
return;
|
|
1351
1435
|
}
|
|
1352
1436
|
}
|
|
1437
|
+
function currentOf(selection) {
|
|
1438
|
+
return selection?.next ?? selection?.lastUsed;
|
|
1439
|
+
}
|
|
1353
1440
|
function isCodeArts(selection) {
|
|
1354
|
-
return (selection
|
|
1441
|
+
return currentOf(selection)?.provider === CODEARTS_PROVIDER;
|
|
1355
1442
|
}
|
|
1356
1443
|
function formatNumber(value) {
|
|
1357
1444
|
return new Intl.NumberFormat().format(Math.round(value));
|
|
@@ -1407,10 +1494,18 @@ function anchorOf(rect) {
|
|
|
1407
1494
|
};
|
|
1408
1495
|
}
|
|
1409
1496
|
function CodeArtsCreditDock({ useProjection, t }) {
|
|
1410
|
-
const
|
|
1497
|
+
const selection = readSelection(useProjection);
|
|
1498
|
+
const active = isCodeArts(selection);
|
|
1411
1499
|
const [remain, setRemain] = (0, react.useState)(null);
|
|
1412
1500
|
const [total, setTotal] = (0, react.useState)(0);
|
|
1413
1501
|
const [rows, setRows] = (0, react.useState)([]);
|
|
1502
|
+
/**
|
|
1503
|
+
* 模型 id -> 目录条目(显示名 + 已格式化的倍率)。
|
|
1504
|
+
*
|
|
1505
|
+
* 显示名自带 ` · x0.18` 尾巴,倍率单独放一段,所以两个都要拿:
|
|
1506
|
+
* 名字用 `stripModelSuffixes` 剥掉尾巴,倍率直接用服务端算好的 `ratio`。
|
|
1507
|
+
*/
|
|
1508
|
+
const [modelInfo, setModelInfo] = (0, react.useState)({});
|
|
1414
1509
|
const [flags, setFlags] = (0, react.useState)({
|
|
1415
1510
|
credits: true,
|
|
1416
1511
|
queue: true
|
|
@@ -1421,6 +1516,8 @@ function CodeArtsCreditDock({ useProjection, t }) {
|
|
|
1421
1516
|
const mounted = (0, react.useRef)(true);
|
|
1422
1517
|
const triggerRef = (0, react.useRef)(null);
|
|
1423
1518
|
const panelRef = (0, react.useRef)(null);
|
|
1519
|
+
/** 这一行的根节点 —— 它的 parentElement 就是宿主那条共享 flex 行。 */
|
|
1520
|
+
const rootRef = (0, react.useRef)(null);
|
|
1424
1521
|
(0, react.useEffect)(() => {
|
|
1425
1522
|
mounted.current = true;
|
|
1426
1523
|
return () => {
|
|
@@ -1441,6 +1538,10 @@ function CodeArtsCreditDock({ useProjection, t }) {
|
|
|
1441
1538
|
queue: true
|
|
1442
1539
|
});
|
|
1443
1540
|
setRows(rowsOf(data));
|
|
1541
|
+
setModelInfo(Object.fromEntries((data.models ?? []).map((model) => [model.id, {
|
|
1542
|
+
name: model.name,
|
|
1543
|
+
...model.ratio === void 0 ? {} : { ratio: model.ratio }
|
|
1544
|
+
}])));
|
|
1444
1545
|
const snapshots = (data.creditsByAccount ?? []).map((entry) => entry.credits).filter((value) => value !== void 0);
|
|
1445
1546
|
const pool = snapshots.length > 0 ? snapshots : data.credits === void 0 ? [] : [data.credits];
|
|
1446
1547
|
if (pool.length === 0) {
|
|
@@ -1511,10 +1612,41 @@ function CodeArtsCreditDock({ useProjection, t }) {
|
|
|
1511
1612
|
window.removeEventListener("scroll", close, true);
|
|
1512
1613
|
};
|
|
1513
1614
|
}, [anchor, close]);
|
|
1615
|
+
(0, react.useEffect)(() => {
|
|
1616
|
+
if (!active) return;
|
|
1617
|
+
const self = rootRef.current;
|
|
1618
|
+
const slot = self?.closest("[data-slot=\"conversation.composer.dock\"]") ?? null;
|
|
1619
|
+
if (self === null || slot === null) return;
|
|
1620
|
+
const node = slot;
|
|
1621
|
+
const me = self;
|
|
1622
|
+
const marked = /* @__PURE__ */ new Set();
|
|
1623
|
+
const sweep = () => {
|
|
1624
|
+
for (const found of markForeignDockEntries(node, me)) marked.add(found);
|
|
1625
|
+
};
|
|
1626
|
+
ensureSuppressStyle();
|
|
1627
|
+
sweep();
|
|
1628
|
+
const observer = new MutationObserver(sweep);
|
|
1629
|
+
observer.observe(slot, { childList: true });
|
|
1630
|
+
return () => {
|
|
1631
|
+
observer.disconnect();
|
|
1632
|
+
releaseDockEntries(marked);
|
|
1633
|
+
marked.clear();
|
|
1634
|
+
};
|
|
1635
|
+
}, [active]);
|
|
1514
1636
|
if (!active) return null;
|
|
1515
1637
|
const showCredits = flags.credits && remain !== null;
|
|
1516
1638
|
const showQueue = flags.queue && queued !== null;
|
|
1517
|
-
|
|
1639
|
+
const modelId = currentOf(selection)?.model;
|
|
1640
|
+
const entry = modelId === void 0 ? void 0 : modelInfo[modelId];
|
|
1641
|
+
const modelLabel = entry === void 0 ? modelId ?? "" : stripModelSuffixes(entry.name);
|
|
1642
|
+
const ratio = entry?.ratio;
|
|
1643
|
+
const line = buildDockLine({
|
|
1644
|
+
...showCredits ? { credits: t("creditsDock", { remain: formatNumber(remain ?? 0) }) } : {},
|
|
1645
|
+
provider: "CodeArts",
|
|
1646
|
+
...modelLabel === "" ? {} : { model: modelLabel },
|
|
1647
|
+
...ratio === void 0 ? {} : { ratio },
|
|
1648
|
+
...showQueue && queued !== null ? { queue: t("queueDock", { position: queued.position }) } : {}
|
|
1649
|
+
}, t);
|
|
1518
1650
|
const queueTitle = queued === null ? void 0 : t("queueDockTitle", {
|
|
1519
1651
|
model: queued.model,
|
|
1520
1652
|
position: queued.position,
|
|
@@ -1542,10 +1674,11 @@ function CodeArtsCreditDock({ useProjection, t }) {
|
|
|
1542
1674
|
}
|
|
1543
1675
|
};
|
|
1544
1676
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1677
|
+
ref: rootRef,
|
|
1545
1678
|
style: {
|
|
1546
1679
|
display: "inline-flex",
|
|
1547
1680
|
alignItems: "center",
|
|
1548
|
-
gap:
|
|
1681
|
+
gap: 0,
|
|
1549
1682
|
color: "var(--dsw-alias-label-tertiary)",
|
|
1550
1683
|
fontSize: "var(--dsh-content-font-size-secondary, 13px)",
|
|
1551
1684
|
lineHeight: "18px",
|
|
@@ -1556,36 +1689,29 @@ function CodeArtsCreditDock({ useProjection, t }) {
|
|
|
1556
1689
|
},
|
|
1557
1690
|
title: queueTitle ?? creditsTitle,
|
|
1558
1691
|
children: [
|
|
1559
|
-
|
|
1560
|
-
showCredits && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1692
|
+
line.credits !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1561
1693
|
ref: triggerRef,
|
|
1562
1694
|
type: "button",
|
|
1695
|
+
"aria-haspopup": "dialog",
|
|
1563
1696
|
"aria-expanded": anchor !== null,
|
|
1564
|
-
"aria-label": t("
|
|
1697
|
+
"aria-label": t("creditsDockPanelTitle"),
|
|
1565
1698
|
title: creditsTitle,
|
|
1566
1699
|
onClick: toggle,
|
|
1567
1700
|
style: {
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
gap: 4,
|
|
1571
|
-
margin: 0,
|
|
1572
|
-
padding: 0,
|
|
1573
|
-
border: 0,
|
|
1574
|
-
background: "none",
|
|
1575
|
-
color: "inherit",
|
|
1701
|
+
all: "unset",
|
|
1702
|
+
cursor: "pointer",
|
|
1576
1703
|
font: "inherit",
|
|
1704
|
+
color: "inherit",
|
|
1705
|
+
fontVariantNumeric: "inherit",
|
|
1577
1706
|
lineHeight: "inherit",
|
|
1578
|
-
|
|
1707
|
+
whiteSpace: "inherit"
|
|
1579
1708
|
},
|
|
1580
|
-
children:
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
children: "▾"
|
|
1587
|
-
})]
|
|
1588
|
-
}),
|
|
1709
|
+
children: line.credits
|
|
1710
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1711
|
+
"aria-hidden": "true",
|
|
1712
|
+
children: " · "
|
|
1713
|
+
})] }),
|
|
1714
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: line.rest }),
|
|
1589
1715
|
anchor !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1590
1716
|
ref: panelRef,
|
|
1591
1717
|
role: "dialog",
|
|
@@ -1741,6 +1867,7 @@ const zh = {
|
|
|
1741
1867
|
benefit: "福利",
|
|
1742
1868
|
benefitBlocked: "福利今日不可用",
|
|
1743
1869
|
benefitBlockedHint: "{n} 个福利模型今天被上游判定没有权益(活动领完或渠道未开),发出去会直接报 benefit not found。",
|
|
1870
|
+
modelImages: "识图",
|
|
1744
1871
|
refreshModels: "刷新模型列表",
|
|
1745
1872
|
refreshing: "刷新中…",
|
|
1746
1873
|
refreshed: "已刷新:{n} 个模型,其中 {benefit} 个走福利路由",
|
|
@@ -1786,7 +1913,6 @@ const zh = {
|
|
|
1786
1913
|
creditsNeedSignIn: "这个账号需要重新登录",
|
|
1787
1914
|
creditsDock: "积分 {remain}",
|
|
1788
1915
|
creditsDockTitle: "CodeArts 剩余积分 {remain} / {total}",
|
|
1789
|
-
creditsDockExpand: "点开看各账号积分明细",
|
|
1790
1916
|
creditsDockPanelTitle: "CodeArts 积分",
|
|
1791
1917
|
creditsDockRefresh: "刷新积分",
|
|
1792
1918
|
creditsDockRefreshing: "刷新中…",
|
|
@@ -1795,9 +1921,12 @@ const zh = {
|
|
|
1795
1921
|
creditsDockEmpty: "暂时没有积分数据",
|
|
1796
1922
|
creditsDockExpiring: "{account}:{expiring} 积分将在本周期结束时作废",
|
|
1797
1923
|
creditsDockUpdated: "最近刷新 {time}",
|
|
1924
|
+
dockProvider: "Provider {provider}",
|
|
1925
|
+
dockModel: "Model {model}",
|
|
1926
|
+
dockNoModel: "未选择模型",
|
|
1798
1927
|
tabDisplay: "显示",
|
|
1799
1928
|
displayTitle: "对话底部显示",
|
|
1800
|
-
displayHint: "
|
|
1929
|
+
displayHint: "这两项只管输入框下方那一行的积分与排队两段;中间那段「Provider CodeArts · Model 模型 · 倍率」一直显示,不受开关影响。底部那条每 60 秒读一次设置,所以关掉最多要等一分钟才见效。",
|
|
1801
1930
|
displayCreditsToggle: "显示剩余积分(点开可看各账号明细)",
|
|
1802
1931
|
displayQueueToggle: "显示上游排队位置",
|
|
1803
1932
|
displayNotWritable: "这个宿主没给插件开配置存储,开关只能看不能改。",
|
|
@@ -1854,6 +1983,7 @@ const en = {
|
|
|
1854
1983
|
benefit: "Benefit",
|
|
1855
1984
|
benefitBlocked: "Benefit unavailable today",
|
|
1856
1985
|
benefitBlockedHint: "{n} benefit model(s) were told they have no entitlement today (campaign exhausted or channel not opened); sending to them fails with benefit not found.",
|
|
1986
|
+
modelImages: "Images",
|
|
1857
1987
|
refreshModels: "Refresh models",
|
|
1858
1988
|
refreshing: "Refreshing…",
|
|
1859
1989
|
refreshed: "Refreshed: {n} models, {benefit} on the benefit route",
|
|
@@ -1899,7 +2029,6 @@ const en = {
|
|
|
1899
2029
|
creditsNeedSignIn: "This account needs signing in again",
|
|
1900
2030
|
creditsDock: "Credits {remain}",
|
|
1901
2031
|
creditsDockTitle: "CodeArts credits left {remain} / {total}",
|
|
1902
|
-
creditsDockExpand: "Show per-account credit details",
|
|
1903
2032
|
creditsDockPanelTitle: "CodeArts credits",
|
|
1904
2033
|
creditsDockRefresh: "Refresh credits",
|
|
1905
2034
|
creditsDockRefreshing: "Refreshing…",
|
|
@@ -1908,9 +2037,12 @@ const en = {
|
|
|
1908
2037
|
creditsDockEmpty: "No credit data yet",
|
|
1909
2038
|
creditsDockExpiring: "{account}: {expiring} credits expire when this cycle ends",
|
|
1910
2039
|
creditsDockUpdated: "Last updated {time}",
|
|
2040
|
+
dockProvider: "Provider {provider}",
|
|
2041
|
+
dockModel: "Model {model}",
|
|
2042
|
+
dockNoModel: "No model selected",
|
|
1911
2043
|
tabDisplay: "Display",
|
|
1912
2044
|
displayTitle: "Under the chat input box",
|
|
1913
|
-
displayHint: "These two only control that line under the input box
|
|
2045
|
+
displayHint: "These two only control the credits and queue segments of that line under the input box; the middle “Provider CodeArts · Model … · rate” segments always show. The bar re-reads settings every 60 seconds, so hiding the rest can take up to a minute.",
|
|
1914
2046
|
displayCreditsToggle: "Show remaining credits (click it for per-account details)",
|
|
1915
2047
|
displayQueueToggle: "Show the upstream queue position",
|
|
1916
2048
|
displayNotWritable: "This host gives the plugin nowhere to persist settings, so the switches are read-only.",
|
package/lib/index.d.mts
CHANGED
|
@@ -870,7 +870,7 @@ interface OAuthToken {
|
|
|
870
870
|
//#endregion
|
|
871
871
|
//#region src/upstream.d.ts
|
|
872
872
|
declare const SNAP_HOST = "https://snap-access.cn-north-4.myhuaweicloud.com";
|
|
873
|
-
type UpstreamErrorKind = 'hard_credit' | 'soft_rate' | 'session_dead' | 'not_found' | 'server' | 'client';
|
|
873
|
+
type UpstreamErrorKind = 'hard_credit' | 'soft_rate' | 'session_dead' | 'iam_stale' | 'not_found' | 'server' | 'client';
|
|
874
874
|
interface ChatResultOk {
|
|
875
875
|
ok: true;
|
|
876
876
|
/** 已转成 OpenAI 增量 SSE 的 ReadableStream(文本,data: ...\n\n)。 */
|
|
@@ -1117,6 +1117,13 @@ declare class CodeArtsAccountStore {
|
|
|
1117
1117
|
clear(): Promise<void>;
|
|
1118
1118
|
/** 给 shim 用:解析某账号的签名材料(内部含续期)。 */
|
|
1119
1119
|
resolve(id: string): Promise<ResolvedCredential | undefined>;
|
|
1120
|
+
/**
|
|
1121
|
+
* 上游报 IAM token 无效时强制续期。
|
|
1122
|
+
*
|
|
1123
|
+
* `seenToken` 是这次请求实际用的 token:若别的请求已经换过凭证,就别再刷一次
|
|
1124
|
+
* (refresh_token 会轮换,白刷一次就多一次失败风险)。
|
|
1125
|
+
*/
|
|
1126
|
+
forceRefresh(id: string, seenToken?: string): Promise<CodeArtsCredential | undefined>;
|
|
1120
1127
|
}
|
|
1121
1128
|
//#endregion
|
|
1122
1129
|
//#region src/account-pool.d.ts
|
package/lib/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { A as CodeArtsCatalog, B as codeartsAccountsPath, C as isWelfareClaimedStatus, D as rateLimitFrame, E as prepareChatBody, F as isBenefitBlocked, H as startLogin, I as isBenefitModel, L as CodeArtsAccountStore, M as clearBenefitBlocked, N as creditRatioLabel, O as signedGet, P as filterEnabledModels, R as CodeArtsCredentialStore, S as isTpmLimit, T as pickDailyWelfareCampaign, V as codeartsAuthPath, _ as isIamTokenError, a as SNAP_HOST, b as isParamInvalidFrame, c as claimDailyWelfare, d as fetchCallerIdentity, f as fetchModels, g as isConcurrencyCap, h as firstFrameError, i as refreshCatalog, j as FALLBACK_CODEARTS_MODELS, k as upstreamErrorMessage, l as clearQueueStatus, m as fetchWelfareCampaigns, n as codeartsUsagePath, o as canonicalModel, p as fetchQueueStatus, r as discoverModels, s as chatStream, t as CodeArtsUsageTracker, u as degradeTokenCeiling, v as isModelNotAccessible, w as maxTokensCeilingFor, x as isQueueableRate, y as isNotMultimodal, z as MANUAL_ACCOUNT_ID } from "./usage-DztQaswb.mjs";
|
|
2
2
|
import { i as lazyApi, r as createProvider } from "./models-CgKCibpk.mjs";
|
|
3
3
|
import z from "@deepseek-ai/schemastery";
|
|
4
4
|
import { randomBytes, timingSafeEqual } from "node:crypto";
|
|
@@ -853,6 +853,7 @@ const KIND_STATUS = {
|
|
|
853
853
|
hard_credit: 402,
|
|
854
854
|
soft_rate: 429,
|
|
855
855
|
session_dead: 401,
|
|
856
|
+
iam_stale: 401,
|
|
856
857
|
not_found: 502,
|
|
857
858
|
server: 502,
|
|
858
859
|
client: 400
|
|
@@ -1091,7 +1092,8 @@ function createCodeArtsShim(options) {
|
|
|
1091
1092
|
const reader = result.stream.getReader();
|
|
1092
1093
|
const first = await reader.read();
|
|
1093
1094
|
const head = first.done ? void 0 : first.value;
|
|
1094
|
-
const
|
|
1095
|
+
const headText = head === void 0 ? void 0 : new TextDecoder().decode(head);
|
|
1096
|
+
const rate = headText === void 0 ? void 0 : rateLimitFrame(headText);
|
|
1095
1097
|
if (rate !== void 0) {
|
|
1096
1098
|
reader.releaseLock();
|
|
1097
1099
|
await result.stream.cancel().catch(() => {});
|
|
@@ -1106,7 +1108,26 @@ function createCodeArtsShim(options) {
|
|
|
1106
1108
|
}
|
|
1107
1109
|
};
|
|
1108
1110
|
}
|
|
1109
|
-
const
|
|
1111
|
+
const upstreamError = headText === void 0 ? void 0 : firstFrameError(headText);
|
|
1112
|
+
if (upstreamError !== void 0) {
|
|
1113
|
+
const detail = `${upstreamError.code}: ${upstreamError.message}`.replace(/^: /, "");
|
|
1114
|
+
if (isIamTokenError(detail) || isModelNotAccessible(detail) || isNotMultimodal(detail)) {
|
|
1115
|
+
reader.releaseLock();
|
|
1116
|
+
await result.stream.cancel().catch(() => {});
|
|
1117
|
+
logger?.warn(`dsh-codearts: ${prepared.model} 首帧报上游错误(${detail.slice(0, 160)})`);
|
|
1118
|
+
const kind = isIamTokenError(detail) ? "iam_stale" : isModelNotAccessible(detail) ? "not_found" : "client";
|
|
1119
|
+
return {
|
|
1120
|
+
prepared,
|
|
1121
|
+
result: {
|
|
1122
|
+
ok: false,
|
|
1123
|
+
kind,
|
|
1124
|
+
status: KIND_STATUS[kind],
|
|
1125
|
+
message: detail
|
|
1126
|
+
}
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
const shouldHeal = heal < MAX_CEILING_HEALS && headText !== void 0 && isParamInvalidFrame(headText);
|
|
1110
1131
|
reader.releaseLock();
|
|
1111
1132
|
if (!shouldHeal) return {
|
|
1112
1133
|
prepared,
|
|
@@ -1202,6 +1223,8 @@ function createCodeArtsShim(options) {
|
|
|
1202
1223
|
}
|
|
1203
1224
|
pool.sync(ids);
|
|
1204
1225
|
const tried = /* @__PURE__ */ new Set();
|
|
1226
|
+
/** 已经为它强制续期过一次的账号:同一个号只重试一次,避免把 refresh_token 连烧。 */
|
|
1227
|
+
const refreshed = /* @__PURE__ */ new Set();
|
|
1205
1228
|
let cappedAccount;
|
|
1206
1229
|
let tpmAccount;
|
|
1207
1230
|
let queueWaitedMs = 0;
|
|
@@ -1257,6 +1280,18 @@ function createCodeArtsShim(options) {
|
|
|
1257
1280
|
tpmAccount = id;
|
|
1258
1281
|
break;
|
|
1259
1282
|
}
|
|
1283
|
+
if (result.kind === "iam_stale") {
|
|
1284
|
+
if (!refreshed.has(id)) {
|
|
1285
|
+
refreshed.add(id);
|
|
1286
|
+
logger?.warn(`dsh-codearts: ${id} 的 IAM token 被判失效,强制续期后重试`);
|
|
1287
|
+
if (await store.forceRefresh(id, credential.token) !== void 0) {
|
|
1288
|
+
tried.delete(id);
|
|
1289
|
+
continue;
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
pool.cooldown(id, SERVER_COOLDOWN_MS, result.message);
|
|
1293
|
+
continue;
|
|
1294
|
+
}
|
|
1260
1295
|
if (result.kind === "session_dead") {
|
|
1261
1296
|
pool.disable(id, result.message);
|
|
1262
1297
|
continue;
|
|
@@ -1427,6 +1462,7 @@ function selectionOf(models, enabled, writable) {
|
|
|
1427
1462
|
benefit: m.benefit,
|
|
1428
1463
|
blocked: m.blocked,
|
|
1429
1464
|
ratio: m.ratio,
|
|
1465
|
+
supportsImages: m.supportsImages,
|
|
1430
1466
|
enabled: restricted ? allowed.has(m.id) : true
|
|
1431
1467
|
})),
|
|
1432
1468
|
restricted,
|
|
@@ -1443,6 +1479,23 @@ function choicesOf(deps) {
|
|
|
1443
1479
|
name: m.name,
|
|
1444
1480
|
benefit,
|
|
1445
1481
|
blocked: isBenefitBlocked(m.id),
|
|
1482
|
+
...ratio === void 0 ? {} : { ratio },
|
|
1483
|
+
...m.supportsImages === true ? { supportsImages: true } : {}
|
|
1484
|
+
};
|
|
1485
|
+
});
|
|
1486
|
+
}
|
|
1487
|
+
/**
|
|
1488
|
+
* 目录 -> 底栏/卡片用的模型徽章。
|
|
1489
|
+
*
|
|
1490
|
+
* 倍率在这里就算好(`creditRatioLabel`:福利恒为 `x0.00`),客户端拿到的是可以直接
|
|
1491
|
+
* 印出来的字符串 —— 底栏那一行要把它作为独立的一段,所以不能只给带尾巴的名字。
|
|
1492
|
+
*/
|
|
1493
|
+
function modelBadgesOf(models) {
|
|
1494
|
+
return models.map((m) => {
|
|
1495
|
+
const ratio = creditRatioLabel(m.ratio, isBenefitModel(m.id));
|
|
1496
|
+
return {
|
|
1497
|
+
id: m.id,
|
|
1498
|
+
name: m.name,
|
|
1446
1499
|
...ratio === void 0 ? {} : { ratio }
|
|
1447
1500
|
};
|
|
1448
1501
|
});
|
|
@@ -1593,10 +1646,7 @@ async function creditsOf(deps, force) {
|
|
|
1593
1646
|
}
|
|
1594
1647
|
}
|
|
1595
1648
|
async function buildStatus(deps, force = false) {
|
|
1596
|
-
const models = deps.models()
|
|
1597
|
-
id: m.id,
|
|
1598
|
-
name: m.name
|
|
1599
|
-
}));
|
|
1649
|
+
const models = modelBadgesOf(deps.models());
|
|
1600
1650
|
const accounts = await deps.store.list();
|
|
1601
1651
|
const cred = await deps.store.current();
|
|
1602
1652
|
const usage = usageOf(deps);
|
|
@@ -1714,10 +1764,7 @@ function modelsHandler(deps) {
|
|
|
1714
1764
|
json(res, 403, { error: "request-not-trusted" });
|
|
1715
1765
|
return;
|
|
1716
1766
|
}
|
|
1717
|
-
json(res, 200, { models: filterEnabledModels(deps.models(), deps.enabledModels?.())
|
|
1718
|
-
id: m.id,
|
|
1719
|
-
name: m.name
|
|
1720
|
-
})) });
|
|
1767
|
+
json(res, 200, { models: modelBadgesOf(filterEnabledModels(deps.models(), deps.enabledModels?.())) });
|
|
1721
1768
|
};
|
|
1722
1769
|
}
|
|
1723
1770
|
/**
|
|
@@ -2380,6 +2427,7 @@ function apply(ctx, rawConfig) {
|
|
|
2380
2427
|
shim.close().catch(() => {});
|
|
2381
2428
|
});
|
|
2382
2429
|
const CATALOG_REFRESH_MS = 36e5;
|
|
2430
|
+
const CATALOG_EMPTY_RETRY_MS = 6e4;
|
|
2383
2431
|
const abort = new AbortController();
|
|
2384
2432
|
const discover = async () => {
|
|
2385
2433
|
if (stopped) return;
|
|
@@ -2394,13 +2442,18 @@ function apply(ctx, rawConfig) {
|
|
|
2394
2442
|
ctx.logger.debug("dsh-codearts: model discovery failed; keeping the current catalog", error);
|
|
2395
2443
|
}
|
|
2396
2444
|
};
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2445
|
+
let discoveryTimer;
|
|
2446
|
+
/** 自我重排的发现循环:目录为空时加快到每分钟,有货后回到每小时。 */
|
|
2447
|
+
const discoverLoop = async () => {
|
|
2448
|
+
await discover();
|
|
2449
|
+
if (stopped) return;
|
|
2450
|
+
const delay = catalog.current().length === 0 ? CATALOG_EMPTY_RETRY_MS : CATALOG_REFRESH_MS;
|
|
2451
|
+
discoveryTimer = setTimeout(() => void discoverLoop(), delay);
|
|
2452
|
+
discoveryTimer.unref?.();
|
|
2453
|
+
};
|
|
2454
|
+
ctx.effect(() => () => {
|
|
2455
|
+
abort.abort();
|
|
2456
|
+
if (discoveryTimer !== void 0) clearTimeout(discoveryTimer);
|
|
2404
2457
|
});
|
|
2405
2458
|
const shim = createCodeArtsShim({
|
|
2406
2459
|
store,
|
|
@@ -2413,7 +2466,7 @@ function apply(ctx, rawConfig) {
|
|
|
2413
2466
|
usage.load().catch(() => {});
|
|
2414
2467
|
shim.ready.then(async () => {
|
|
2415
2468
|
if (stopped) return;
|
|
2416
|
-
|
|
2469
|
+
discoverLoop();
|
|
2417
2470
|
const incompatible = await probePiAiCompat();
|
|
2418
2471
|
if (incompatible !== void 0) ctx.logger.error(`dsh-codearts: ${incompatible}`);
|
|
2419
2472
|
const codearts = createCodeArtsAdapter({
|
|
@@ -561,6 +561,19 @@ var AccountSlot = class {
|
|
|
561
561
|
if (!cred.refreshToken || !cred.dpopPrivateKey || !cred.clientId) return cred;
|
|
562
562
|
const exp = cred.expiration ? Date.parse(cred.expiration) : NaN;
|
|
563
563
|
if (!(Number.isNaN(exp) ? false : exp - Date.now() < REFRESH_SKEW_MS)) return cred;
|
|
564
|
+
return this.forceRefresh();
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* 不管还剩多久,立刻续期一次。
|
|
568
|
+
*
|
|
569
|
+
* 给上游回 `invalid_iam_token` 时用:token 可能被提前作废(另一个客户端续期、
|
|
570
|
+
* 上游轮换),而 `expiration` 还写着几小时之后,`ensureFresh` 根本不会动。
|
|
571
|
+
* 仍然走单飞,避免并发请求把一次性的 refresh_token 烧掉。
|
|
572
|
+
*/
|
|
573
|
+
async forceRefresh() {
|
|
574
|
+
const cred = this._cred;
|
|
575
|
+
if (cred.stale === true) return cred;
|
|
576
|
+
if (!cred.refreshToken || !cred.dpopPrivateKey || !cred.clientId) return cred;
|
|
564
577
|
return this._inflight ??= this.refresh(cred, {
|
|
565
578
|
refreshToken: cred.refreshToken,
|
|
566
579
|
dpopPrivateKey: cred.dpopPrivateKey,
|
|
@@ -815,6 +828,19 @@ var CodeArtsAccountStore = class {
|
|
|
815
828
|
securityToken: cred.token
|
|
816
829
|
};
|
|
817
830
|
}
|
|
831
|
+
/**
|
|
832
|
+
* 上游报 IAM token 无效时强制续期。
|
|
833
|
+
*
|
|
834
|
+
* `seenToken` 是这次请求实际用的 token:若别的请求已经换过凭证,就别再刷一次
|
|
835
|
+
* (refresh_token 会轮换,白刷一次就多一次失败风险)。
|
|
836
|
+
*/
|
|
837
|
+
async forceRefresh(id, seenToken) {
|
|
838
|
+
await this.loaded();
|
|
839
|
+
const slot = this._slots.get(id);
|
|
840
|
+
if (slot === void 0) return void 0;
|
|
841
|
+
if (seenToken !== void 0 && slot.credential.token !== seenToken) return slot.credential;
|
|
842
|
+
return slot.forceRefresh();
|
|
843
|
+
}
|
|
818
844
|
};
|
|
819
845
|
//#endregion
|
|
820
846
|
//#region src/catalog.ts
|
|
@@ -905,6 +931,13 @@ const FALLBACK_CODEARTS_MODELS = [
|
|
|
905
931
|
name: "DeepSeek-V4.1-Flash",
|
|
906
932
|
contextWindow: 131072,
|
|
907
933
|
maxTokens: 65536,
|
|
934
|
+
supportsImages: false
|
|
935
|
+
},
|
|
936
|
+
{
|
|
937
|
+
id: "deepseek-v4.1-flash-1017",
|
|
938
|
+
name: "DeepSeek-V4.1-Flash-1017",
|
|
939
|
+
contextWindow: 1048576,
|
|
940
|
+
maxTokens: 65536,
|
|
908
941
|
supportsImages: true
|
|
909
942
|
},
|
|
910
943
|
{
|
|
@@ -1231,8 +1264,12 @@ var upstream_exports = /* @__PURE__ */ __exportAll({
|
|
|
1231
1264
|
fetchModels: () => fetchModels,
|
|
1232
1265
|
fetchQueueStatus: () => fetchQueueStatus,
|
|
1233
1266
|
fetchWelfareCampaigns: () => fetchWelfareCampaigns,
|
|
1267
|
+
firstFrameError: () => firstFrameError,
|
|
1234
1268
|
isBenefitMissing: () => isBenefitMissing,
|
|
1235
1269
|
isConcurrencyCap: () => isConcurrencyCap,
|
|
1270
|
+
isIamTokenError: () => isIamTokenError,
|
|
1271
|
+
isModelNotAccessible: () => isModelNotAccessible,
|
|
1272
|
+
isNotMultimodal: () => isNotMultimodal,
|
|
1236
1273
|
isParamInvalidFrame: () => isParamInvalidFrame,
|
|
1237
1274
|
isQueueableRate: () => isQueueableRate,
|
|
1238
1275
|
isResignRefusal: () => isResignRefusal,
|
|
@@ -1590,6 +1627,9 @@ function upstreamErrorMessage(message, status = 0) {
|
|
|
1590
1627
|
if (MODEL_RATE_CODE.test(message) || MODEL_RATE_TEXT.test(message)) return "华为侧该模型当前请求过多(共享 TPM 超限),不是你的账号或插件问题。稍后重试,或临时改用 GLM-5.2 / GLM-5.3。";
|
|
1591
1628
|
if (message.includes("TM.00001041")) return "CodeArts 免费档同时只允许 3 个会话,当前槽位一直占满(被打断的会话约 1 分钟才释放)。请稍后重试。";
|
|
1592
1629
|
if (isBenefitMissing("", message)) return "这个模型属于限时福利,当前福利通道不可用(不是账号问题)。可以刷新模型列表,改用非福利模型。";
|
|
1630
|
+
if (isIamTokenError(message)) return "上游判定本次登录凭据(IAM token)已失效。插件已尝试自动续期;若反复出现,请到设置卡片重新登录。";
|
|
1631
|
+
if (isModelNotAccessible(message)) return "上游说这个模型不存在或当前账号没有权限(可能已下线或改名)。请刷新模型列表后换一个模型;换号没有用,这是模型级的限制。";
|
|
1632
|
+
if (isNotMultimodal(message)) return "这个模型不支持图片输入(上游 406)。请移除消息里的图片,或改用支持识图的模型。";
|
|
1593
1633
|
if (message.toLowerCase().includes("model is not registered")) return "上游没有注册这个模型,它可能已经下线;请刷新模型列表后重选。";
|
|
1594
1634
|
return `codearts 上游错误 (http ${status}): ${message.slice(0, 400)}`;
|
|
1595
1635
|
}
|
|
@@ -1606,6 +1646,59 @@ function isBenefitMissing(code, message) {
|
|
|
1606
1646
|
return code === "InferHub.4004.200" || message.toLowerCase().includes("benefit not found");
|
|
1607
1647
|
}
|
|
1608
1648
|
/**
|
|
1649
|
+
* 上游判定本次请求的 IAM 凭据无效。
|
|
1650
|
+
*
|
|
1651
|
+
* 2026-11-02 实测:它会以**流内首帧**(HTTP 200)的形式回来,内容只有一句
|
|
1652
|
+
* `invalid_iam_token`,所以既不走 classify() 也不会触发换号 —— 之前只能干等到
|
|
1653
|
+
* 凭证自然过期(距过期不足 5 分钟才刷新),期间每条消息都撞同一堵墙。
|
|
1654
|
+
* 这些写法都见过:IAM 自身 / APIG 网关 / 非法 Token 三条链路各有一套措辞。
|
|
1655
|
+
*/
|
|
1656
|
+
const IAM_TOKEN_ERROR = /invalid_iam_token|incorrect iam authentication|apig\.0301|iam token is invalid|iam token (?:has )?expired|decrypt token fail/i;
|
|
1657
|
+
function isIamTokenError(message) {
|
|
1658
|
+
return IAM_TOKEN_ERROR.test(message);
|
|
1659
|
+
}
|
|
1660
|
+
/**
|
|
1661
|
+
* 「模型不存在或当前账号没权限」。
|
|
1662
|
+
*
|
|
1663
|
+
* 上游一边把它列进模型发现(`builtin` / `agent` 两路都有),一边在聊天时回
|
|
1664
|
+
* `InferHub.ModelArts.81003.401`。2026-11-02 实测 `deepseek-v4.1-flash`
|
|
1665
|
+
* 当天就是这种状态:列表里有、调用必拒,且换号也一样 —— 所以换号无意义。
|
|
1666
|
+
*/
|
|
1667
|
+
function isModelNotAccessible(message) {
|
|
1668
|
+
return message.includes("InferHub.ModelArts.81003.401") || message.toLowerCase().includes("the model does not exist or you do not have access to it");
|
|
1669
|
+
}
|
|
1670
|
+
/** 上游实际不吃图(模型级检查,与图片格式无关)。 */
|
|
1671
|
+
function isNotMultimodal(message) {
|
|
1672
|
+
return message.includes("InferHub.001001020.406") || message.toLowerCase().includes("the request model is not multimodal");
|
|
1673
|
+
}
|
|
1674
|
+
/**
|
|
1675
|
+
* 从**首帧**(已转成 OpenAI 形状)里取出上游错误码与文本;没有错误返回 undefined。
|
|
1676
|
+
*
|
|
1677
|
+
* 只用于「还没产出任何内容」的判断:此时重发是安全的。帧可能被 TCP 切断,解析
|
|
1678
|
+
* 不出来就当没有错误,绝不影响转发。
|
|
1679
|
+
*/
|
|
1680
|
+
function firstFrameError(text) {
|
|
1681
|
+
for (const line of text.split("\n")) {
|
|
1682
|
+
const trimmed = line.trim();
|
|
1683
|
+
if (!trimmed.startsWith("data:")) continue;
|
|
1684
|
+
const payload = trimmed.slice(5).trim();
|
|
1685
|
+
if (payload === "" || payload === "[DONE]") continue;
|
|
1686
|
+
let ev;
|
|
1687
|
+
try {
|
|
1688
|
+
ev = JSON.parse(payload);
|
|
1689
|
+
} catch {
|
|
1690
|
+
continue;
|
|
1691
|
+
}
|
|
1692
|
+
const code = String(ev.error?.code ?? ev.error_code ?? "");
|
|
1693
|
+
const message = String(ev.error?.message ?? ev.error_msg ?? "");
|
|
1694
|
+
if (code === "" && message === "") continue;
|
|
1695
|
+
return {
|
|
1696
|
+
code,
|
|
1697
|
+
message
|
|
1698
|
+
};
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
/**
|
|
1609
1702
|
* 本地代理「拒签」标记:billion-context 这类宿主层代理拦截出站请求做上下文压缩,
|
|
1610
1703
|
* 碰到 SDK-HMAC-SHA256 签名请求要用 CodeArts 凭据重签;找不到凭据时在**本地**回
|
|
1611
1704
|
* 403(根本没出网),body 里带这两个标记。这不是凭据失效 —— 归 session_dead 会把
|
|
@@ -1617,6 +1710,8 @@ function isResignRefusal(message) {
|
|
|
1617
1710
|
function classify(status, message) {
|
|
1618
1711
|
const m = message.toLowerCase();
|
|
1619
1712
|
if (isResignRefusal(message)) return "client";
|
|
1713
|
+
if (isIamTokenError(message)) return "iam_stale";
|
|
1714
|
+
if (isModelNotAccessible(message)) return "not_found";
|
|
1620
1715
|
if (status === 401 || status === 403) return "session_dead";
|
|
1621
1716
|
if (status === 402) return "hard_credit";
|
|
1622
1717
|
if (status === 429) return "soft_rate";
|
|
@@ -2769,4 +2864,4 @@ function sumOf(store) {
|
|
|
2769
2864
|
return out;
|
|
2770
2865
|
}
|
|
2771
2866
|
//#endregion
|
|
2772
|
-
export {
|
|
2867
|
+
export { CodeArtsCatalog as A, codeartsAccountsPath as B, isWelfareClaimedStatus as C, rateLimitFrame as D, prepareChatBody as E, isBenefitBlocked as F, startLogin as H, isBenefitModel as I, CodeArtsAccountStore as L, clearBenefitBlocked as M, creditRatioLabel as N, signedGet as O, filterEnabledModels as P, CodeArtsCredentialStore as R, isTpmLimit as S, pickDailyWelfareCampaign as T, codeartsAuthPath as V, isIamTokenError as _, SNAP_HOST as a, isParamInvalidFrame as b, claimDailyWelfare as c, fetchCallerIdentity as d, fetchModels as f, isConcurrencyCap as g, firstFrameError as h, refreshCatalog as i, FALLBACK_CODEARTS_MODELS as j, upstreamErrorMessage as k, clearQueueStatus as l, fetchWelfareCampaigns as m, codeartsUsagePath as n, canonicalModel as o, fetchQueueStatus as p, discoverModels as r, chatStream as s, CodeArtsUsageTracker as t, degradeTokenCeiling as u, isModelNotAccessible as v, maxTokensCeilingFor as w, isQueueableRate as x, isNotMultimodal as y, MANUAL_ACCOUNT_ID as z };
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "dsh-codearts",
|
|
3
3
|
"displayName": "DSH CodeArts Connect",
|
|
4
4
|
"description": "把华为云 CodeArts 接入 DeepSeek Harness —— 内置 OAuth2 登录(PKCE + DPoP),登录后即可在 DSH 对话窗口使用 CodeArts 的模型。",
|
|
5
|
-
"version": "1.2.
|
|
5
|
+
"version": "1.2.2",
|
|
6
6
|
"packageManager": "pnpm@11.24.0",
|
|
7
7
|
"author": "elegant01",
|
|
8
8
|
"keywords": [
|