dsh-codearts 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/bin.mjs +5 -5
- package/lib/client/index.d.cts +16 -0
- package/lib/client/index.js +308 -34
- package/lib/index.d.mts +143 -17
- package/lib/index.mjs +472 -36
- package/lib/{usage-ClRZWUS4.mjs → usage-3W88T_H1.mjs} +104 -18
- package/package.json +1 -1
|
@@ -1163,15 +1163,20 @@ function signRequest(req, body, cred) {
|
|
|
1163
1163
|
var upstream_exports = /* @__PURE__ */ __exportAll({
|
|
1164
1164
|
CONCURRENCY_CAP_CODE: () => CONCURRENCY_CAP_CODE,
|
|
1165
1165
|
MAX_TOKENS_CEILING: () => MAX_TOKENS_CEILING,
|
|
1166
|
+
PARAM_INVALID_CODE: () => PARAM_INVALID_CODE,
|
|
1166
1167
|
SNAP_HOST: () => SNAP_HOST,
|
|
1167
1168
|
canonicalModel: () => canonicalModel,
|
|
1168
1169
|
chatStream: () => chatStream,
|
|
1169
1170
|
claimBenefit: () => claimBenefit,
|
|
1171
|
+
degradeTokenCeiling: () => degradeTokenCeiling,
|
|
1172
|
+
estimateTokens: () => estimateTokens,
|
|
1170
1173
|
fetchCallerIdentity: () => fetchCallerIdentity,
|
|
1171
1174
|
fetchModels: () => fetchModels,
|
|
1172
1175
|
isConcurrencyCap: () => isConcurrencyCap,
|
|
1176
|
+
isParamInvalidFrame: () => isParamInvalidFrame,
|
|
1173
1177
|
maxTokensCeilingFor: () => maxTokensCeilingFor,
|
|
1174
|
-
prepareChatBody: () => prepareChatBody
|
|
1178
|
+
prepareChatBody: () => prepareChatBody,
|
|
1179
|
+
signedGet: () => signedGet
|
|
1175
1180
|
});
|
|
1176
1181
|
const SNAP_HOST = "https://snap-access.cn-north-4.myhuaweicloud.com";
|
|
1177
1182
|
const EP_CHAT = "/api/v2/chat/completions";
|
|
@@ -1213,14 +1218,20 @@ function randomTraceId() {
|
|
|
1213
1218
|
}
|
|
1214
1219
|
/** 上游对 max_tokens 的服务端硬上限(2026-09-30 直连二分实测)。 */
|
|
1215
1220
|
const MAX_TOKENS_CEILING = 65536;
|
|
1221
|
+
/** token 估算的余量:混排误差 + 上游自己注入的会话头,宁可少给也别整轮失败。 */
|
|
1222
|
+
const PROMPT_ESTIMATE_MARGIN = 2048;
|
|
1223
|
+
/** 短于此的生成长度就没有实际意义了,此时直接报具体错误而不是硬挤。 */
|
|
1224
|
+
const MIN_USEFUL_OUTPUT_TOKENS = 1024;
|
|
1216
1225
|
/**
|
|
1217
1226
|
* 实测过的、**按模型**的 max_tokens 上限(小写 id → 上限)。
|
|
1218
1227
|
*
|
|
1219
1228
|
* 这个上限是模型相关的,不能用一个全局值:
|
|
1220
1229
|
* - 免费(限时福利)DeepSeek 系列:> 65536 会被流内 InferHub.001001005.400
|
|
1221
1230
|
* "The request param is invalid" 拒掉,而福利网关标称的是 393216 / 384000;
|
|
1222
|
-
* - GLM-5.2
|
|
1223
|
-
*
|
|
1231
|
+
* - GLM-5.2:2026-09-30 逐值直连验证过 8000/65536/131072 均正常,但
|
|
1232
|
+
* **2026-10-10 复测 131072 已经被拒**(同一个 InferHub.001001005.400),
|
|
1233
|
+
* 65536 仍然正常。也就是说这条上限由服务端控制、会漂,目录里留着的旧实测值
|
|
1234
|
+
* 不会报错,只会让该模型的每一轮在流的第一帧就失败 —— 所以复测要按日期记。
|
|
1224
1235
|
*
|
|
1225
1236
|
* 没测过的模型一律退回 `MAX_TOKENS_CEILING`(保守但可用),而不是照抄标称值 ——
|
|
1226
1237
|
* DSH 会把 catalog 的 maxTokens 原样当 max_tokens 发出去,照抄标称值等于让请求失败。
|
|
@@ -1232,11 +1243,63 @@ const MEASURED_MAX_TOKENS = {
|
|
|
1232
1243
|
"deepseek-v4-pro-0813": 65536,
|
|
1233
1244
|
"deepseek-v4.1-flash": 65536,
|
|
1234
1245
|
"glm-5.3-flash": 65536,
|
|
1235
|
-
"glm-5.2":
|
|
1246
|
+
"glm-5.2": 65536
|
|
1236
1247
|
};
|
|
1237
|
-
/** 该模型可用的 max_tokens
|
|
1248
|
+
/** 该模型可用的 max_tokens 上限(实测值与会话内学到的更低值取小)。 */
|
|
1238
1249
|
function maxTokensCeilingFor(id) {
|
|
1239
|
-
|
|
1250
|
+
const measured = measuredCeilingFor(id);
|
|
1251
|
+
const learned = learnedMaxTokens.get(ceilingKey(id));
|
|
1252
|
+
return learned === void 0 ? measured : Math.min(measured, learned);
|
|
1253
|
+
}
|
|
1254
|
+
/**
|
|
1255
|
+
* 上游的「参数非法」错误码。它是**流内**帧(HTTP 仍是 200),所以既不走
|
|
1256
|
+
* classify(),也不会告诉我们是哪个参数错、更不区分「max_tokens 太大」和
|
|
1257
|
+
* 「prompt 装不下」—— 一切得我们自己判。
|
|
1258
|
+
*/
|
|
1259
|
+
const PARAM_INVALID_CODE = "InferHub.001001005.400";
|
|
1260
|
+
/** 一帧(chatStream 已把上游 error_code 转成 OpenAI error 帧)是不是那条「参数非法」。 */
|
|
1261
|
+
function isParamInvalidFrame(text) {
|
|
1262
|
+
return text.includes("InferHub.001001005.400") || text.includes("The request param is invalid");
|
|
1263
|
+
}
|
|
1264
|
+
/**
|
|
1265
|
+
* 本次进程内学到的上限(小写 id → 上限)。
|
|
1266
|
+
*
|
|
1267
|
+
* 上游会把「实测过」的值悄悄调低(GLM-5.2 的 131072 于 2026-10-10 被收),
|
|
1268
|
+
* 手工表必然滞后。所以撞到「参数非法」时就地折半降档重试,并把结果记在这里,
|
|
1269
|
+
* 免得该模型后面每一轮都先白撞一次。
|
|
1270
|
+
*/
|
|
1271
|
+
const learnedMaxTokens = /* @__PURE__ */ new Map();
|
|
1272
|
+
/** 降档下限:再低就没有可用的生成长度了,此时宁可把具体错误报给用户。 */
|
|
1273
|
+
const MAX_TOKENS_FLOOR = 8192;
|
|
1274
|
+
function ceilingKey(id) {
|
|
1275
|
+
return id.trim().toLowerCase();
|
|
1276
|
+
}
|
|
1277
|
+
function measuredCeilingFor(id) {
|
|
1278
|
+
return MEASURED_MAX_TOKENS[ceilingKey(id)] ?? 65536;
|
|
1279
|
+
}
|
|
1280
|
+
/**
|
|
1281
|
+
* 把该模型的上限折半降一档(收到「参数非法」后重发之前调用)。
|
|
1282
|
+
* @returns 新的上限;已经降到底则返回 0,表示别再试了。
|
|
1283
|
+
*/
|
|
1284
|
+
function degradeTokenCeiling(id) {
|
|
1285
|
+
const next = Math.floor(maxTokensCeilingFor(id) / 2);
|
|
1286
|
+
if (next < MAX_TOKENS_FLOOR) return 0;
|
|
1287
|
+
learnedMaxTokens.set(ceilingKey(id), next);
|
|
1288
|
+
return next;
|
|
1289
|
+
}
|
|
1290
|
+
/**
|
|
1291
|
+
* 粗估一段文本占多少 token:CJK 每字约 1,其余每 4 字符约 1。
|
|
1292
|
+
*
|
|
1293
|
+
* 只要保守就够用 —— 它的用途是「在我们这边就把装不下的情况判掉」,
|
|
1294
|
+
* 而不是精确计费(上游不会告诉我们超没超,只会回一句参数非法)。
|
|
1295
|
+
*/
|
|
1296
|
+
function estimateTokens(text) {
|
|
1297
|
+
let cjk = 0;
|
|
1298
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
1299
|
+
const code = text.charCodeAt(i);
|
|
1300
|
+
if (code >= 12352 && code <= 40959 || code >= 44032 && code <= 64511 || code >= 65280 && code <= 65376) cjk += 1;
|
|
1301
|
+
}
|
|
1302
|
+
return Math.ceil(cjk + (text.length - cjk) / 4);
|
|
1240
1303
|
}
|
|
1241
1304
|
/** 上游要求 chat_id 为 32 位小写十六进制。 */
|
|
1242
1305
|
const CHAT_ID_RE = /^[0-9a-f]{32}$/;
|
|
@@ -1285,16 +1348,20 @@ function sessionIdFor(chatId) {
|
|
|
1285
1348
|
* 该端点虽是 OpenAI 兼容,但网关按自己的 schema 严格校验参数。所以这里
|
|
1286
1349
|
* 不做原样透传,而是:
|
|
1287
1350
|
* 1. model 走 canonicalModel(大小写敏感)
|
|
1288
|
-
* 2. max_completion_tokens → max_tokens
|
|
1289
|
-
*
|
|
1290
|
-
*
|
|
1351
|
+
* 2. max_completion_tokens → max_tokens(华为只认后者),并钳到该模型的上限;
|
|
1352
|
+
* 再按 contextWindow 扣掉 prompt 估算,保证 prompt + max_tokens 装得下
|
|
1353
|
+
* (两者任一超限,上游都只会流内回 InferHub.001001005.400
|
|
1354
|
+
* "The request param is invalid, Please check it",不告诉我们是哪一个)
|
|
1291
1355
|
* 3. 丢弃非白名单字段(stream_options / store / logprobs 等虽实测可容忍,
|
|
1292
1356
|
* 但按 codearts2api 观察到的真实流量收窄,避免网关后续收紧)
|
|
1293
1357
|
* 4. tools[].function 去掉 strict(OpenAI 专有)
|
|
1294
1358
|
* 5. 注入 CodeArts 特有字段:chat_id(按对话派生,见 conversationChatId)、
|
|
1295
1359
|
* prompt_cache_key、tool_stream
|
|
1360
|
+
*
|
|
1361
|
+
* @param contextWindow 该模型的上下文窗口(来自 catalog)。传 0 表示不知道,
|
|
1362
|
+
* 此时只做上限钳制、不做「装不下」判断。
|
|
1296
1363
|
*/
|
|
1297
|
-
function prepareChatBody(rawJson) {
|
|
1364
|
+
function prepareChatBody(rawJson, contextWindow = 0) {
|
|
1298
1365
|
let parsed = {};
|
|
1299
1366
|
try {
|
|
1300
1367
|
parsed = JSON.parse(rawJson);
|
|
@@ -1326,7 +1393,25 @@ function prepareChatBody(rawJson) {
|
|
|
1326
1393
|
else body.stream_options.include_usage = true;
|
|
1327
1394
|
if (body.max_tokens === void 0 && typeof parsed.max_completion_tokens === "number") body.max_tokens = parsed.max_completion_tokens;
|
|
1328
1395
|
const ceiling = maxTokensCeilingFor(model);
|
|
1329
|
-
if (typeof body.max_tokens === "number"
|
|
1396
|
+
if (typeof body.max_tokens === "number") {
|
|
1397
|
+
let maxTokens = Math.min(body.max_tokens, ceiling);
|
|
1398
|
+
if (contextWindow > 0) {
|
|
1399
|
+
const promptTokens = estimateTokens(`${JSON.stringify(body.messages ?? "")}${JSON.stringify(body.tools ?? "")}`);
|
|
1400
|
+
const room = contextWindow - promptTokens - PROMPT_ESTIMATE_MARGIN;
|
|
1401
|
+
if (room < MIN_USEFUL_OUTPUT_TOKENS) {
|
|
1402
|
+
body.max_tokens = maxTokens;
|
|
1403
|
+
return {
|
|
1404
|
+
url: `${SNAP_HOST}${EP_CHAT}`,
|
|
1405
|
+
body: JSON.stringify(body),
|
|
1406
|
+
model,
|
|
1407
|
+
chatId,
|
|
1408
|
+
error: `上下文放不下:${model} 的窗口是 ${contextWindow} tokens,本次请求(含 ${Array.isArray(body.messages) ? body.messages.length : 0} 条消息与工具定义)估算约 ${promptTokens} tokens,剩余不足 ${MIN_USEFUL_OUTPUT_TOKENS} tokens 的生成余量。请新建会话或改用上下文更大的模型。`
|
|
1409
|
+
};
|
|
1410
|
+
}
|
|
1411
|
+
maxTokens = Math.min(maxTokens, room);
|
|
1412
|
+
}
|
|
1413
|
+
body.max_tokens = maxTokens;
|
|
1414
|
+
}
|
|
1330
1415
|
if (Array.isArray(body.tools)) body.tools = body.tools.map((tool) => {
|
|
1331
1416
|
if (tool && typeof tool === "object" && tool.function && typeof tool.function === "object") {
|
|
1332
1417
|
const fn = { ...tool.function };
|
|
@@ -1921,14 +2006,15 @@ async function refreshCatalog(store, catalog, signal) {
|
|
|
1921
2006
|
/**
|
|
1922
2007
|
* 用量账本:把聊天流里的 usage 帧累计成「今天用了多少」。
|
|
1923
2008
|
*
|
|
1924
|
-
*
|
|
1925
|
-
*
|
|
1926
|
-
*
|
|
2009
|
+
* 为什么还要自己记:账号总额度有积分接口了(见 credits.ts,2026-10 从官方 Agent
|
|
2010
|
+
* 客户端挖出并实测通过),但**本机经插件发出的请求**仍需自己累计 —— 上游不按
|
|
2011
|
+
* 「这台机器」维度给统计。能拿到的真实数据有三样:
|
|
1927
2012
|
* 1. 每个请求的 token `usage`(上游在流末尾回一帧 `{choices:[],usage:{...}}`);
|
|
1928
|
-
* 2. 插件自己知道的账号会话槽位占用(见 account-pool:每账号 3
|
|
2013
|
+
* 2. 插件自己知道的账号会话槽位占用(见 account-pool:每账号 3 个并发会话);
|
|
2014
|
+
* 3. 账号积分/额度(见 credits.ts)。
|
|
1929
2015
|
*
|
|
1930
|
-
*
|
|
1931
|
-
*
|
|
2016
|
+
* 所以这里累计的是**本机经插件发出的请求**,与卡片上的积分区块互补 —— 一个是本机
|
|
2017
|
+
* 级、一个是账号级。官方额度明细在 `codearts.huaweicloud.com/portal/settings/personal-usage`。
|
|
1932
2018
|
*
|
|
1933
2019
|
* 落盘在 `$DSH_HOME/.codearts-usage.json`,按天存,保留最近 `RETENTION_DAYS` 天。
|
|
1934
2020
|
*
|
|
@@ -2134,4 +2220,4 @@ function sumOf(store) {
|
|
|
2134
2220
|
return out;
|
|
2135
2221
|
}
|
|
2136
2222
|
//#endregion
|
|
2137
|
-
export {
|
|
2223
|
+
export { codeartsAccountsPath as C, CodeArtsCredentialStore as S, startLogin as T, CodeArtsCatalog as _, SNAP_HOST as a, isBenefitModel as b, claimBenefit as c, fetchModels as d, isConcurrencyCap as f, signedGet as g, prepareChatBody as h, refreshCatalog as i, degradeTokenCeiling as l, maxTokensCeilingFor as m, codeartsUsagePath as n, canonicalModel as o, isParamInvalidFrame as p, discoverModels as r, chatStream as s, CodeArtsUsageTracker as t, fetchCallerIdentity as u, FALLBACK_CODEARTS_MODELS as v, codeartsAuthPath as w, CodeArtsAccountStore as x, filterEnabledModels as y };
|
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": "0.
|
|
5
|
+
"version": "0.4.0",
|
|
6
6
|
"author": "elegant01",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"dsh-plugin",
|