dsh-codearts 1.2.10 → 1.2.11
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 +17 -0
- package/lib/bin.mjs +1 -4
- package/lib/client/index.d.cts +39 -0
- package/lib/client/index.js +502 -195
- package/lib/index.d.mts +75 -1
- package/lib/index.mjs +203 -13
- package/lib/{usage-CjPd0Gok.mjs → version-Dq3wXMop.mjs} +176 -13
- package/package.json +1 -1
package/lib/index.d.mts
CHANGED
|
@@ -787,6 +787,12 @@ interface CodeArtsModelInfo {
|
|
|
787
787
|
id: string;
|
|
788
788
|
name: string;
|
|
789
789
|
contextWindow: number;
|
|
790
|
+
/**
|
|
791
|
+
* `contextWindow` 是上游真的给的,还是兜底值(32768)。
|
|
792
|
+
*
|
|
793
|
+
* 卡片只在上游给值时印「1M 上下文」这类规格;兜底值印出来就是假数据。
|
|
794
|
+
*/
|
|
795
|
+
contextWindowKnown?: boolean;
|
|
790
796
|
maxTokens: number;
|
|
791
797
|
supportsImages: boolean;
|
|
792
798
|
/**
|
|
@@ -1013,6 +1019,15 @@ interface CodeArtsCredential {
|
|
|
1013
1019
|
storedAt?: number;
|
|
1014
1020
|
/** 续期已尝试且失败(多半已过期),需用户重新登录。 */
|
|
1015
1021
|
stale?: boolean;
|
|
1022
|
+
/**
|
|
1023
|
+
* 用户手动暂停:凭证还在、也未必过期,只是暂时不想用它。
|
|
1024
|
+
*
|
|
1025
|
+
* 和 `stale` 分开:`stale` 是「这个号坏了,必须重新登录」,这里只是「先别派活」。
|
|
1026
|
+
* 恢复时不需要重新登录,也不会清掉池里的禁用/冷却状态。
|
|
1027
|
+
*/
|
|
1028
|
+
paused?: boolean;
|
|
1029
|
+
/** 用户置顶:轮转时优先用这个号(仍受冷却与并发槽位约束)。 */
|
|
1030
|
+
pinned?: boolean;
|
|
1016
1031
|
}
|
|
1017
1032
|
declare function codeartsAuthPath(home?: string): string;
|
|
1018
1033
|
interface ResolvedCredential {
|
|
@@ -1064,6 +1079,10 @@ interface CodeArtsAccountSummary {
|
|
|
1064
1079
|
stale: boolean;
|
|
1065
1080
|
/** 带 AK/SK,能产生签名(只有 security_token 的降级凭证不能)。 */
|
|
1066
1081
|
signable: boolean;
|
|
1082
|
+
/** 用户手动暂停(不参与轮转)。 */
|
|
1083
|
+
paused: boolean;
|
|
1084
|
+
/** 用户置顶(轮转优先)。 */
|
|
1085
|
+
pinned: boolean;
|
|
1067
1086
|
}
|
|
1068
1087
|
declare function codeartsAccountsPath(home?: string): string;
|
|
1069
1088
|
/** 多账号凭证存储:一个账号一份槽位,共用一个文档文件。 */
|
|
@@ -1113,6 +1132,21 @@ declare class CodeArtsAccountStore {
|
|
|
1113
1132
|
/** 手工粘贴 security_token(降级态,无 AK/SK)——固定覆盖同一个「手工」账号。 */
|
|
1114
1133
|
set(token: string, label?: string): Promise<void>;
|
|
1115
1134
|
remove(id: string): Promise<void>;
|
|
1135
|
+
/**
|
|
1136
|
+
* 暂停 / 恢复一个账号。
|
|
1137
|
+
*
|
|
1138
|
+
* 只改标记,不碰凭证:恢复时不需要重新登录。重新装配槽位是因为 AccountSlot
|
|
1139
|
+
* 缓存的 `_cred` 是旧对象,续期回写会把它盖回来 —— 换了对象,续期写到新文档里。
|
|
1140
|
+
*/
|
|
1141
|
+
setPaused(id: string, paused: boolean): Promise<boolean>;
|
|
1142
|
+
/** 置顶 / 取消置顶:只改变轮转先后,不绕过冷却与并发上限。 */
|
|
1143
|
+
setPinned(id: string, pinned: boolean): Promise<boolean>;
|
|
1144
|
+
/** shim 用:参与轮转的账号 id(排除用户暂停的)。 */
|
|
1145
|
+
dispatchIds(): Promise<string[]>;
|
|
1146
|
+
/** 被用户暂停的账号 id(账号池据此保留状态但不派活)。 */
|
|
1147
|
+
pausedIds(): Promise<string[]>;
|
|
1148
|
+
/** 置顶账号的 id 列表(给账号池排序用)。 */
|
|
1149
|
+
pinnedIds(): Promise<string[]>;
|
|
1116
1150
|
/** 清空全部账号(整体登出)。 */
|
|
1117
1151
|
clear(): Promise<void>;
|
|
1118
1152
|
/** 给 shim 用:解析某账号的签名材料(内部含续期)。 */
|
|
@@ -1149,6 +1183,10 @@ interface PoolOptions {
|
|
|
1149
1183
|
}
|
|
1150
1184
|
declare class CodeArtsAccountPool {
|
|
1151
1185
|
private readonly accounts;
|
|
1186
|
+
/** 用户手动暂停的账号:运行时状态留着(卡片还看得见槽位),但不参与轮转。 */
|
|
1187
|
+
private readonly paused;
|
|
1188
|
+
/** 用户置顶的账号:选号时排在最前面,不再按「最久未用」轮它。 */
|
|
1189
|
+
private readonly preferred;
|
|
1152
1190
|
private readonly maxConcurrent;
|
|
1153
1191
|
private readonly now;
|
|
1154
1192
|
constructor(ids: readonly string[], options?: PoolOptions);
|
|
@@ -1158,8 +1196,12 @@ declare class CodeArtsAccountPool {
|
|
|
1158
1196
|
get(id: string): AccountRuntime | undefined;
|
|
1159
1197
|
/** 账号本身可用(没被禁用、不在冷却中)。 */
|
|
1160
1198
|
healthy(id: string): boolean;
|
|
1161
|
-
/** 现在就能接活的账号(健康 +
|
|
1199
|
+
/** 现在就能接活的账号(健康 + 未暂停 + 本地还有空槽位),置顶的优先,其余按「最久未用」。 */
|
|
1162
1200
|
private available;
|
|
1201
|
+
/** 设置暂停集合(账号 id)。与账号文档同步时调用。 */
|
|
1202
|
+
setPaused(ids: readonly string[]): void;
|
|
1203
|
+
/** 设置置顶集合(账号 id)。置顶只在「同时可用」时改变先后,不会绕过冷却/上限。 */
|
|
1204
|
+
setPreferred(ids: readonly string[]): void;
|
|
1163
1205
|
/**
|
|
1164
1206
|
* 选一个账号。
|
|
1165
1207
|
*
|
|
@@ -1345,6 +1387,24 @@ interface UsageSnapshot {
|
|
|
1345
1387
|
}[];
|
|
1346
1388
|
/** 今天按账号的合计。 */
|
|
1347
1389
|
byAccountToday: Record<string, UsageTotals>;
|
|
1390
|
+
/** 今天的失败次数:总数 + 按错误类别。 */
|
|
1391
|
+
failuresToday: {
|
|
1392
|
+
total: number;
|
|
1393
|
+
byKind: Record<string, number>;
|
|
1394
|
+
};
|
|
1395
|
+
/** 今天按账号的失败次数。 */
|
|
1396
|
+
failuresByAccountToday: Record<string, number>;
|
|
1397
|
+
/**
|
|
1398
|
+
* 今天的首字延迟分位(毫秒)。样本为 0 时 p50/p95 缺省。
|
|
1399
|
+
*
|
|
1400
|
+
* `overflow` 表示 P95 落进了「>30 秒」那一档,前端据此显示「30s+」。
|
|
1401
|
+
*/
|
|
1402
|
+
latencyToday: {
|
|
1403
|
+
samples: number;
|
|
1404
|
+
p50?: number;
|
|
1405
|
+
p95?: number;
|
|
1406
|
+
overflow: boolean;
|
|
1407
|
+
};
|
|
1348
1408
|
/** 账本覆盖的天数(便于「数据从哪天开始」的说明)。 */
|
|
1349
1409
|
days: number;
|
|
1350
1410
|
/**
|
|
@@ -1405,6 +1465,20 @@ declare class CodeArtsUsageTracker {
|
|
|
1405
1465
|
noteUsage(accountId: string, model: string, raw: RawUsage | undefined, at?: number): void;
|
|
1406
1466
|
/** 一天里的逐小时桶,第一次写的时候才建(老账本没有这个键)。 */
|
|
1407
1467
|
private hoursOf;
|
|
1468
|
+
/**
|
|
1469
|
+
* 记一次失败请求。
|
|
1470
|
+
*
|
|
1471
|
+
* 只在「这次请求最终回给用户一个错误」时调用一次 —— 插件内部换号重试不算失败,
|
|
1472
|
+
* 否则一个排队重试就能把失败率刷成好几倍。`kind` 用 shim 的失败出口标识。
|
|
1473
|
+
*/
|
|
1474
|
+
noteFailure(kind: string, accountId?: string, at?: number): void;
|
|
1475
|
+
/**
|
|
1476
|
+
* 记一次首字延迟(毫秒),落进直方图。
|
|
1477
|
+
*
|
|
1478
|
+
* 从请求进入 shim 算起,到上游第一帧真的写给 DSH 为止 —— 排队和换号的时间都算进去,
|
|
1479
|
+
* 因为那正是用户盯着转圈等掉的时长。
|
|
1480
|
+
*/
|
|
1481
|
+
noteLatency(ms: number, at?: number): void;
|
|
1408
1482
|
/**
|
|
1409
1483
|
* 丢弃保留期之外的天(按日期键比较即可,YYYY-MM-DD 是字典序)。
|
|
1410
1484
|
*
|
package/lib/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { A as signedGet, B as markModelUnavailable, C as isTpmLimit, D as pickDailyWelfareCampaign, E as modelUnavailableReasonText, F as creditRatioLabel, G as codeartsAccountsPath, H as CodeArtsAccountStore, I as filterEnabledModels, J as startLogin, K as codeartsAuthPath, L as forgetModelUnavailable, M as CodeArtsCatalog, N as FALLBACK_CODEARTS_MODELS, O as prepareChatBody, P as clearBenefitBlocked, R as isBenefitBlocked, S as isQueueableRate, T as maxTokensCeilingFor, U as CodeArtsCredentialStore, V as modelUnavailableReason, W as MANUAL_ACCOUNT_ID, _ as isConcurrencyCap, a as refreshCatalog, b as isNotMultimodal, c as chatStream, d as degradeTokenCeiling, f as fetchCallerIdentity, g as firstFrameError, h as fetchWelfareCampaigns, i as discoverModels, j as upstreamErrorMessage, k as rateLimitFrame, l as claimDailyWelfare, m as fetchQueueStatus, n as CodeArtsUsageTracker, o as SNAP_HOST, p as fetchModels, q as credentialFromOAuth, r as codeartsUsagePath, s as canonicalModel, t as CODEARTS_VERSION, u as clearQueueStatus, v as isIamTokenError, w as isWelfareClaimedStatus, x as isParamInvalidFrame, y as isModelNotAccessible, z as isBenefitModel } from "./version-Dq3wXMop.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";
|
|
@@ -16,6 +16,10 @@ import * as settingsModule from "@deepseek-ai/dsh-settings";
|
|
|
16
16
|
const CAPPED_HINT_MS = 3e4;
|
|
17
17
|
var CodeArtsAccountPool = class {
|
|
18
18
|
accounts = /* @__PURE__ */ new Map();
|
|
19
|
+
/** 用户手动暂停的账号:运行时状态留着(卡片还看得见槽位),但不参与轮转。 */
|
|
20
|
+
paused = /* @__PURE__ */ new Set();
|
|
21
|
+
/** 用户置顶的账号:选号时排在最前面,不再按「最久未用」轮它。 */
|
|
22
|
+
preferred = /* @__PURE__ */ new Set();
|
|
19
23
|
maxConcurrent;
|
|
20
24
|
now;
|
|
21
25
|
constructor(ids, options = {}) {
|
|
@@ -53,10 +57,23 @@ var CodeArtsAccountPool = class {
|
|
|
53
57
|
if (account === void 0) return false;
|
|
54
58
|
return !account.disabled && this.now() >= account.coolUntil;
|
|
55
59
|
}
|
|
56
|
-
/** 现在就能接活的账号(健康 +
|
|
60
|
+
/** 现在就能接活的账号(健康 + 未暂停 + 本地还有空槽位),置顶的优先,其余按「最久未用」。 */
|
|
57
61
|
available(exclude) {
|
|
58
62
|
const now = this.now();
|
|
59
|
-
return this.list().filter((a) => !a.disabled && now >= a.coolUntil && !exclude.has(a.id) && a.activeConcurrent < a.maxConcurrent).sort((a, b) =>
|
|
63
|
+
return this.list().filter((a) => !a.disabled && !this.paused.has(a.id) && now >= a.coolUntil && !exclude.has(a.id) && a.activeConcurrent < a.maxConcurrent).sort((a, b) => {
|
|
64
|
+
const pinned = Number(this.preferred.has(b.id)) - Number(this.preferred.has(a.id));
|
|
65
|
+
return pinned !== 0 ? pinned : a.lastUsed - b.lastUsed;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/** 设置暂停集合(账号 id)。与账号文档同步时调用。 */
|
|
69
|
+
setPaused(ids) {
|
|
70
|
+
this.paused.clear();
|
|
71
|
+
for (const id of ids) this.paused.add(id);
|
|
72
|
+
}
|
|
73
|
+
/** 设置置顶集合(账号 id)。置顶只在「同时可用」时改变先后,不会绕过冷却/上限。 */
|
|
74
|
+
setPreferred(ids) {
|
|
75
|
+
this.preferred.clear();
|
|
76
|
+
for (const id of ids) this.preferred.add(id);
|
|
60
77
|
}
|
|
61
78
|
/**
|
|
62
79
|
* 选一个账号。
|
|
@@ -1198,7 +1215,10 @@ function createCodeArtsShim(options) {
|
|
|
1198
1215
|
}
|
|
1199
1216
|
}
|
|
1200
1217
|
async function chatCompletions(req, res) {
|
|
1218
|
+
/** 首字延迟的起点:从读完请求头就开始算,排队与换号的时间都算「用户等了多久」。 */
|
|
1219
|
+
const requestStartedAt = Date.now();
|
|
1201
1220
|
if (!isJsonContentType(req)) {
|
|
1221
|
+
usage?.noteFailure("media_type");
|
|
1202
1222
|
writeOpenAIError(res, 415, "unsupported_media_type", "Content-Type must be application/json");
|
|
1203
1223
|
return;
|
|
1204
1224
|
}
|
|
@@ -1211,6 +1231,7 @@ function createCodeArtsShim(options) {
|
|
|
1211
1231
|
const checked = prepareChatBody(raw, contextWindow);
|
|
1212
1232
|
noteRequestShape(raw, checked.model);
|
|
1213
1233
|
if (checked.error !== void 0) {
|
|
1234
|
+
usage?.noteFailure("context_overflow");
|
|
1214
1235
|
writeOpenAIError(res, 400, "context_overflow", checked.error);
|
|
1215
1236
|
return;
|
|
1216
1237
|
}
|
|
@@ -1220,12 +1241,19 @@ function createCodeArtsShim(options) {
|
|
|
1220
1241
|
});
|
|
1221
1242
|
let ids;
|
|
1222
1243
|
try {
|
|
1223
|
-
|
|
1244
|
+
const all = await store.ids();
|
|
1245
|
+
const paused = new Set(await store.pausedIds());
|
|
1246
|
+
pool.sync(all);
|
|
1247
|
+
pool.setPaused([...paused]);
|
|
1248
|
+
pool.setPreferred(await store.pinnedIds());
|
|
1249
|
+
ids = all.filter((id) => !paused.has(id));
|
|
1224
1250
|
} catch (error) {
|
|
1251
|
+
usage?.noteFailure("not_signed_in");
|
|
1225
1252
|
writeOpenAIError(res, 401, "not_signed_in", String(error));
|
|
1226
1253
|
return;
|
|
1227
1254
|
}
|
|
1228
1255
|
if (ids.length === 0) {
|
|
1256
|
+
usage?.noteFailure("not_signed_in");
|
|
1229
1257
|
writeOpenAIError(res, 401, "not_signed_in", "no CodeArts account stored; sign in from the CodeArts settings card");
|
|
1230
1258
|
return;
|
|
1231
1259
|
}
|
|
@@ -1237,6 +1265,7 @@ function createCodeArtsShim(options) {
|
|
|
1237
1265
|
let tpmAccount;
|
|
1238
1266
|
let queueWaitedMs = 0;
|
|
1239
1267
|
let lastKind;
|
|
1268
|
+
let lastAccountId;
|
|
1240
1269
|
let lastStatus = 0;
|
|
1241
1270
|
let lastMessage = "";
|
|
1242
1271
|
for (let attempt = 0; attempt < MAX_ROTATE; attempt += 1) {
|
|
@@ -1254,10 +1283,12 @@ function createCodeArtsShim(options) {
|
|
|
1254
1283
|
const gate = await queue.wait(credential, checked.chatId, checked.model, controller.signal);
|
|
1255
1284
|
if (controller.signal.aborted) return;
|
|
1256
1285
|
if (gate.kind === "queued") {
|
|
1286
|
+
usage?.noteFailure("queued", id);
|
|
1257
1287
|
writeOpenAIError(res, 400, "queued", queuedMessage(checked.model, gate));
|
|
1258
1288
|
return;
|
|
1259
1289
|
}
|
|
1260
1290
|
if (gate.kind === "queue_full" || gate.kind === "error") {
|
|
1291
|
+
usage?.noteFailure(gate.kind, id);
|
|
1261
1292
|
writeOpenAIError(res, gate.kind === "queue_full" ? 429 : 502, gate.kind, gate.message);
|
|
1262
1293
|
return;
|
|
1263
1294
|
}
|
|
@@ -1272,11 +1303,13 @@ function createCodeArtsShim(options) {
|
|
|
1272
1303
|
pool.noteSuccess(id);
|
|
1273
1304
|
await pipeToResponse(res, result.stream, controller.signal, {
|
|
1274
1305
|
accountId: id,
|
|
1275
|
-
model: prepared.model
|
|
1306
|
+
model: prepared.model,
|
|
1307
|
+
startedAt: requestStartedAt
|
|
1276
1308
|
}, opened.head);
|
|
1277
1309
|
return;
|
|
1278
1310
|
}
|
|
1279
1311
|
lastKind = result.kind;
|
|
1312
|
+
lastAccountId = id;
|
|
1280
1313
|
lastStatus = result.status;
|
|
1281
1314
|
lastMessage = result.message;
|
|
1282
1315
|
if (isConcurrencyCap(result)) {
|
|
@@ -1331,11 +1364,13 @@ function createCodeArtsShim(options) {
|
|
|
1331
1364
|
pool.noteSuccess(queueAccount);
|
|
1332
1365
|
await pipeToResponse(res, result.stream, controller.signal, {
|
|
1333
1366
|
accountId: queueAccount,
|
|
1334
|
-
model: opened.prepared.model
|
|
1367
|
+
model: opened.prepared.model,
|
|
1368
|
+
startedAt: requestStartedAt
|
|
1335
1369
|
}, opened.head);
|
|
1336
1370
|
return;
|
|
1337
1371
|
}
|
|
1338
1372
|
lastKind = result.kind;
|
|
1373
|
+
lastAccountId = queueAccount;
|
|
1339
1374
|
lastStatus = result.status;
|
|
1340
1375
|
lastMessage = result.message;
|
|
1341
1376
|
}
|
|
@@ -1345,11 +1380,13 @@ function createCodeArtsShim(options) {
|
|
|
1345
1380
|
}
|
|
1346
1381
|
if (lastKind === void 0) {
|
|
1347
1382
|
const none = noAccountError(pool.list());
|
|
1383
|
+
usage?.noteFailure(none.kind);
|
|
1348
1384
|
writeOpenAIError(res, none.status, none.kind, none.message);
|
|
1349
1385
|
return;
|
|
1350
1386
|
}
|
|
1351
1387
|
const explanation = upstreamErrorMessage(lastMessage, lastStatus);
|
|
1352
1388
|
const waited = queueWaitedMs >= 1e3 ? `(本地已排队重试 ${Math.round(queueWaitedMs / 1e3)} 秒)` : "";
|
|
1389
|
+
usage?.noteFailure(lastKind, lastAccountId);
|
|
1353
1390
|
writeOpenAIError(res, KIND_STATUS[lastKind], lastKind, `${explanation}${waited}`);
|
|
1354
1391
|
}
|
|
1355
1392
|
/**
|
|
@@ -1379,6 +1416,8 @@ function createCodeArtsShim(options) {
|
|
|
1379
1416
|
let buf = "";
|
|
1380
1417
|
if (usage !== void 0 && attribution !== void 0) usage.noteRequest(attribution.accountId, attribution.model);
|
|
1381
1418
|
let sawDone = false;
|
|
1419
|
+
/** 首个真正写出的帧记一次 TTFT;之后不再记。 */
|
|
1420
|
+
let firstFrame = true;
|
|
1382
1421
|
const finish = () => {
|
|
1383
1422
|
if (!res.writable || res.writableEnded) return;
|
|
1384
1423
|
res.end(sawDone ? void 0 : "data: [DONE]\n\n");
|
|
@@ -1394,7 +1433,13 @@ function createCodeArtsShim(options) {
|
|
|
1394
1433
|
if (frame.trim() === "data: [DONE]") sawDone = true;
|
|
1395
1434
|
if (frame.includes("\"error\"")) logInStreamError(frame, attribution?.model ?? "");
|
|
1396
1435
|
if (usage !== void 0 && attribution !== void 0 && frame.includes("\"usage\"")) recordFrameUsage(frame, attribution);
|
|
1397
|
-
if (res.writable)
|
|
1436
|
+
if (res.writable) {
|
|
1437
|
+
if (firstFrame) {
|
|
1438
|
+
firstFrame = false;
|
|
1439
|
+
if (usage !== void 0 && attribution?.startedAt !== void 0) usage.noteLatency(Date.now() - attribution.startedAt);
|
|
1440
|
+
}
|
|
1441
|
+
res.write(frame + "\n\n");
|
|
1442
|
+
}
|
|
1398
1443
|
}
|
|
1399
1444
|
}
|
|
1400
1445
|
finish();
|
|
@@ -1471,6 +1516,13 @@ const CODEARTS_DOCK_PATH = "/api/codearts/dock";
|
|
|
1471
1516
|
/** 最近出站请求的**形状**摘要(只有 role 名与计数,不含内容),排障用。 */
|
|
1472
1517
|
const CODEARTS_DEBUG_PATH = "/api/codearts/debug";
|
|
1473
1518
|
/**
|
|
1519
|
+
* GET /api/codearts/doctor — 把 CLI `doctor` 的静态体检搬进卡片。
|
|
1520
|
+
*
|
|
1521
|
+
* 只做本机可判定的检查(版本、账号、凭证有效期、目录、账本、pi-ai 兼容),
|
|
1522
|
+
* 不额外打上游 —— 卡片本来就每 60 秒刷一次状态,不该因为点了体检再等超时。
|
|
1523
|
+
*/
|
|
1524
|
+
const CODEARTS_DOCTOR_PATH = "/api/codearts/doctor";
|
|
1525
|
+
/**
|
|
1474
1526
|
* 由完整目录 + 已存 enabled 计算 selection。
|
|
1475
1527
|
* - enabled 为空/未存 => 全部勾选(restricted=false)
|
|
1476
1528
|
* - enabled 为非空列表 => 仅勾选其中的(restricted=true),缺的按未勾选
|
|
@@ -1487,6 +1539,8 @@ function selectionOf(models, enabled, writable) {
|
|
|
1487
1539
|
blocked: m.blocked,
|
|
1488
1540
|
ratio: m.ratio,
|
|
1489
1541
|
supportsImages: m.supportsImages,
|
|
1542
|
+
contextWindow: m.contextWindow,
|
|
1543
|
+
maxTokens: m.maxTokens,
|
|
1490
1544
|
...m.error === void 0 ? {} : { error: m.error },
|
|
1491
1545
|
enabled: restricted ? allowed.has(m.id) : true
|
|
1492
1546
|
})),
|
|
@@ -1507,6 +1561,8 @@ function choicesOf(deps) {
|
|
|
1507
1561
|
blocked: isBenefitBlocked(m.id),
|
|
1508
1562
|
...ratio === void 0 ? {} : { ratio },
|
|
1509
1563
|
...m.supportsImages === true ? { supportsImages: true } : {},
|
|
1564
|
+
...m.contextWindowKnown === true ? { contextWindow: m.contextWindow } : {},
|
|
1565
|
+
...m.maxTokens > 0 ? { maxTokens: m.maxTokens } : {},
|
|
1510
1566
|
...error === void 0 ? {} : { error }
|
|
1511
1567
|
};
|
|
1512
1568
|
});
|
|
@@ -1704,6 +1760,15 @@ function usageOf(deps) {
|
|
|
1704
1760
|
days: snapshot?.days ?? 0,
|
|
1705
1761
|
recentDays: snapshot?.recentDays ?? [],
|
|
1706
1762
|
todayByHour: snapshot?.todayByHour ?? [],
|
|
1763
|
+
failuresToday: snapshot?.failuresToday ?? {
|
|
1764
|
+
total: 0,
|
|
1765
|
+
byKind: {}
|
|
1766
|
+
},
|
|
1767
|
+
failuresByAccountToday: snapshot?.failuresByAccountToday ?? {},
|
|
1768
|
+
latencyToday: snapshot?.latencyToday ?? {
|
|
1769
|
+
samples: 0,
|
|
1770
|
+
overflow: false
|
|
1771
|
+
},
|
|
1707
1772
|
...snapshot?.lastAt === void 0 ? {} : { lastAt: snapshot.lastAt },
|
|
1708
1773
|
slots: slotsOf(deps)
|
|
1709
1774
|
};
|
|
@@ -1788,6 +1853,89 @@ function statusHandler(deps) {
|
|
|
1788
1853
|
};
|
|
1789
1854
|
}
|
|
1790
1855
|
/**
|
|
1856
|
+
* 本机体检:与 CLI 的 `doctor` 同一份判据,但只做不联网的检查。
|
|
1857
|
+
*
|
|
1858
|
+
* 这里的每一项都能在 1ms 内判定;需要网络的(凭证能不能续期、上游目录是否正常)
|
|
1859
|
+
* 已经在 /status 与 /refresh-models 里体现,重复做只会让「体检」按钮转圈。
|
|
1860
|
+
*/
|
|
1861
|
+
async function doctorReport(deps) {
|
|
1862
|
+
const accounts = await deps.store.list();
|
|
1863
|
+
const models = deps.models();
|
|
1864
|
+
const checks = [];
|
|
1865
|
+
const hints = /* @__PURE__ */ new Set();
|
|
1866
|
+
checks.push({
|
|
1867
|
+
id: "runtime",
|
|
1868
|
+
ok: true,
|
|
1869
|
+
detail: `dsh-codearts ${CODEARTS_VERSION} · node ${process.version} · ${process.platform}`
|
|
1870
|
+
});
|
|
1871
|
+
checks.push({
|
|
1872
|
+
id: "accounts",
|
|
1873
|
+
ok: accounts.length > 0,
|
|
1874
|
+
detail: `${accounts.length}`
|
|
1875
|
+
});
|
|
1876
|
+
if (accounts.length === 0) hints.add("no-account");
|
|
1877
|
+
for (const account of accounts) {
|
|
1878
|
+
const problems = [];
|
|
1879
|
+
if (account.stale) problems.push("stale");
|
|
1880
|
+
if (!account.signable) problems.push("no-key");
|
|
1881
|
+
if (account.expiration !== void 0) {
|
|
1882
|
+
const expiresAt = Date.parse(account.expiration);
|
|
1883
|
+
if (!Number.isNaN(expiresAt) && expiresAt <= Date.now()) problems.push("expired");
|
|
1884
|
+
}
|
|
1885
|
+
checks.push({
|
|
1886
|
+
id: `account:${account.id}`,
|
|
1887
|
+
ok: problems.length === 0,
|
|
1888
|
+
detail: problems.length > 0 ? problems.join(",") : account.label ?? account.userName ?? account.expiration ?? "ok"
|
|
1889
|
+
});
|
|
1890
|
+
for (const problem of problems) hints.add(`account-${problem}`);
|
|
1891
|
+
}
|
|
1892
|
+
checks.push({
|
|
1893
|
+
id: "models",
|
|
1894
|
+
ok: models.length > 0,
|
|
1895
|
+
detail: `${models.length}`
|
|
1896
|
+
});
|
|
1897
|
+
if (models.length === 0) hints.add("empty-catalog");
|
|
1898
|
+
checks.push({
|
|
1899
|
+
id: "usage",
|
|
1900
|
+
ok: deps.usage !== void 0,
|
|
1901
|
+
detail: deps.usage === void 0 ? "tracker not wired" : "local ledger"
|
|
1902
|
+
});
|
|
1903
|
+
if (deps.usage === void 0) hints.add("usage-missing");
|
|
1904
|
+
if (process.env.DSH_HOME === void 0) hints.add("dsh-home-missing");
|
|
1905
|
+
const piAi = await probePiAiCompat();
|
|
1906
|
+
checks.push({
|
|
1907
|
+
id: "pi-ai",
|
|
1908
|
+
ok: piAi === void 0,
|
|
1909
|
+
...piAi === void 0 ? {} : { detail: piAi }
|
|
1910
|
+
});
|
|
1911
|
+
if (piAi !== void 0) hints.add("pi-ai");
|
|
1912
|
+
return {
|
|
1913
|
+
ok: checks.every((check) => check.ok),
|
|
1914
|
+
version: CODEARTS_VERSION,
|
|
1915
|
+
node: process.version,
|
|
1916
|
+
checks,
|
|
1917
|
+
hints: [...hints]
|
|
1918
|
+
};
|
|
1919
|
+
}
|
|
1920
|
+
/** GET /api/codearts/doctor */
|
|
1921
|
+
function doctorHandler(deps) {
|
|
1922
|
+
return async (req, res) => {
|
|
1923
|
+
if (req.method !== "GET") {
|
|
1924
|
+
json(res, 405, { error: "method not allowed" });
|
|
1925
|
+
return;
|
|
1926
|
+
}
|
|
1927
|
+
if (!loopbackRequest(req)) {
|
|
1928
|
+
json(res, 403, { error: "request-not-trusted" });
|
|
1929
|
+
return;
|
|
1930
|
+
}
|
|
1931
|
+
try {
|
|
1932
|
+
json(res, 200, await doctorReport(deps));
|
|
1933
|
+
} catch (error) {
|
|
1934
|
+
json(res, 500, { error: safeMessage(error) });
|
|
1935
|
+
}
|
|
1936
|
+
};
|
|
1937
|
+
}
|
|
1938
|
+
/**
|
|
1791
1939
|
* GET/POST /api/codearts/dock — 对话底部那一行的显隐开关读写。
|
|
1792
1940
|
*
|
|
1793
1941
|
* 和 enabled-models 同一套规矩:GET 给当前值 + writable,POST 只收布尔、
|
|
@@ -1957,13 +2105,14 @@ function tokenDeleteHandler(deps) {
|
|
|
1957
2105
|
}
|
|
1958
2106
|
/**
|
|
1959
2107
|
* DELETE /api/codearts/accounts?id=<accountId> — 删掉一个账号。
|
|
2108
|
+
* POST /api/codearts/accounts — 改账号标记:`{ id, paused?, pinned? }`。
|
|
1960
2109
|
*
|
|
1961
2110
|
* id 必须显式给:多账号下「删掉当前那个」是歧义的。改状态的路由,所以和
|
|
1962
2111
|
* /token 一样走 Host + Origin 双校验。
|
|
1963
2112
|
*/
|
|
1964
2113
|
function accountsHandler(deps) {
|
|
1965
2114
|
return async (req, res) => {
|
|
1966
|
-
if (req.method !== "DELETE") {
|
|
2115
|
+
if (req.method !== "DELETE" && req.method !== "POST") {
|
|
1967
2116
|
json(res, 405, { error: "method not allowed" });
|
|
1968
2117
|
return;
|
|
1969
2118
|
}
|
|
@@ -1971,12 +2120,47 @@ function accountsHandler(deps) {
|
|
|
1971
2120
|
json(res, 403, { error: "request-not-trusted" });
|
|
1972
2121
|
return;
|
|
1973
2122
|
}
|
|
1974
|
-
const
|
|
1975
|
-
if (id === "") {
|
|
1976
|
-
json(res, 400, { error: "expected ?id=<accountId>" });
|
|
1977
|
-
return;
|
|
1978
|
-
}
|
|
2123
|
+
const url = new URL(req.url ?? "", "http://localhost");
|
|
1979
2124
|
try {
|
|
2125
|
+
if (req.method === "POST") {
|
|
2126
|
+
const type = req.headers["content-type"];
|
|
2127
|
+
if (typeof type !== "string" || !type.trim().toLowerCase().startsWith("application/json")) {
|
|
2128
|
+
json(res, 415, { error: "content-type must be application/json" });
|
|
2129
|
+
return;
|
|
2130
|
+
}
|
|
2131
|
+
const body = JSON.parse(await readBody(req));
|
|
2132
|
+
const id = typeof body.id === "string" ? body.id.trim() : "";
|
|
2133
|
+
if (id === "") {
|
|
2134
|
+
json(res, 400, { error: "expected {\"id\": string}" });
|
|
2135
|
+
return;
|
|
2136
|
+
}
|
|
2137
|
+
let changed = false;
|
|
2138
|
+
if (typeof body.paused === "boolean") {
|
|
2139
|
+
if (!await deps.store.setPaused(id, body.paused)) {
|
|
2140
|
+
json(res, 404, { error: "unknown account" });
|
|
2141
|
+
return;
|
|
2142
|
+
}
|
|
2143
|
+
changed = true;
|
|
2144
|
+
}
|
|
2145
|
+
if (typeof body.pinned === "boolean") {
|
|
2146
|
+
if (!await deps.store.setPinned(id, body.pinned)) {
|
|
2147
|
+
json(res, 404, { error: "unknown account" });
|
|
2148
|
+
return;
|
|
2149
|
+
}
|
|
2150
|
+
changed = true;
|
|
2151
|
+
}
|
|
2152
|
+
if (!changed) {
|
|
2153
|
+
json(res, 400, { error: "expected paused and/or pinned boolean" });
|
|
2154
|
+
return;
|
|
2155
|
+
}
|
|
2156
|
+
json(res, 200, await buildStatus(deps));
|
|
2157
|
+
return;
|
|
2158
|
+
}
|
|
2159
|
+
const id = (url.searchParams.get("id") ?? "").trim();
|
|
2160
|
+
if (id === "") {
|
|
2161
|
+
json(res, 400, { error: "expected ?id=<accountId>" });
|
|
2162
|
+
return;
|
|
2163
|
+
}
|
|
1980
2164
|
await deps.store.remove(id);
|
|
1981
2165
|
json(res, 200, await buildStatus(deps));
|
|
1982
2166
|
} catch (error) {
|
|
@@ -2288,6 +2472,11 @@ function registerCodeArtsStatusRoute(ctx, deps) {
|
|
|
2288
2472
|
path: CODEARTS_DEBUG_PATH,
|
|
2289
2473
|
handler: debugHandler(deps)
|
|
2290
2474
|
});
|
|
2475
|
+
const disposeDoctor = ctx.webServer.register({
|
|
2476
|
+
kind: "exact",
|
|
2477
|
+
path: CODEARTS_DOCTOR_PATH,
|
|
2478
|
+
handler: doctorHandler(deps)
|
|
2479
|
+
});
|
|
2291
2480
|
return () => {
|
|
2292
2481
|
disposeStatus();
|
|
2293
2482
|
disposeModels();
|
|
@@ -2302,6 +2491,7 @@ function registerCodeArtsStatusRoute(ctx, deps) {
|
|
|
2302
2491
|
disposeQueue();
|
|
2303
2492
|
disposeDock();
|
|
2304
2493
|
disposeDebug();
|
|
2494
|
+
disposeDoctor();
|
|
2305
2495
|
for (const s of activeLogins.values()) s.close();
|
|
2306
2496
|
activeLogins.clear();
|
|
2307
2497
|
reloginTargets.clear();
|