dsh-codearts 0.0.0-stage → 0.3.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.
@@ -0,0 +1,697 @@
1
+ import z from "@deepseek-ai/schemastery";
2
+ import { PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
3
+ import { SettingsNamespace } from "@deepseek-ai/dsh-settings";
4
+ import { Context } from "@deepseek-ai/cordis";
5
+ import { AttachmentStore } from "@deepseek-ai/dsh-attachment";
6
+ //#region src/catalog.d.ts
7
+ /**
8
+ * CodeArts model catalog.
9
+ *
10
+ * 目录有两条来源:
11
+ * 1. **动态发现**(`src/discovery.ts` + `upstream.fetchModels`):启动与每小时
12
+ * 各拉一次,取内置 / 限时福利 / agent-center 三路的并集。这是正常路径。
13
+ * 2. **静态种子**(`FALLBACK_CODEARTS_MODELS`):仅在发现失败或尚未登录时兜底。
14
+ *
15
+ * 种子的来龙去脉(诚实标注,别把它当权威):
16
+ * - 福利模型的 id 已与真实账号的 `gateway/config` 对齐;
17
+ * - 商业模型的 id 可信(来自 codearts2api 的 `seedKnownModels`),但
18
+ * `contextWindow` / `maxTokens` / `supportsImages` 是**占位值**,发现成功时
19
+ * 会被上游真实值覆盖 —— 这正是 `fillFromSeed` 只补缺、不覆盖的原因。
20
+ * 写错的 contextWindow 只影响 DSH 的上下文计量与截断余量,不影响路由;
21
+ * 写错的 maxTokens 会直接让请求失败(见 `MEASURED_MAX_TOKENS`)。
22
+ *
23
+ * @module dsh-codearts/catalog
24
+ */
25
+ /** One model the adapter exposes to DSH. */
26
+ interface CodeArtsModelInfo {
27
+ id: string;
28
+ name: string;
29
+ contextWindow: number;
30
+ maxTokens: number;
31
+ supportsImages: boolean;
32
+ /**
33
+ * 该模型走限时福利(免费套餐)路由。
34
+ *
35
+ * 由动态发现填入:福利是按账号授予的,只有拉过 `gateway/config` 才知道某个账号
36
+ * 到底有没有它。种子里那份静态名单只是冷启动兜底(见 `isBenefitModel`)。
37
+ */
38
+ benefit?: boolean;
39
+ }
40
+ /**
41
+ * Static seed roster. Ids are the *upstream registered* spellings (case
42
+ * matters — `canonicalModel` in upstream.ts passes these through verbatim).
43
+ * Updated 2026-09 from codearts2api's known model set.
44
+ *
45
+ * maxTokens 注记(2026-09-30 实测,scripts/param-probe.mjs 直连上游二分验证):
46
+ * 免费福利 DeepSeek 模型的 max_tokens 服务端硬上限是 **65536**,更大的值
47
+ * (73728 / 98304 / 131072 / 384000 / 393216)一律流内报
48
+ * InferHub.001001005.400 "The request param is invalid, Please check it"。
49
+ * 此前的 384000/393216 是占位值:DSH 会把 catalog 的 maxTokens 作为
50
+ * max_tokens 原样发给上游,导致所有请求直接失败。
51
+ */
52
+ declare const FALLBACK_CODEARTS_MODELS: readonly CodeArtsModelInfo[];
53
+ /** Mutable catalog shared by the shim's `/v1/models` and the adapter. */
54
+ declare class CodeArtsCatalog {
55
+ private models;
56
+ current(): readonly CodeArtsModelInfo[];
57
+ set(models: readonly CodeArtsModelInfo[]): void;
58
+ }
59
+ /**
60
+ * Narrow a catalog to the user's enabled selection (allowlist of model ids).
61
+ * Same semantics as the codebuddy plugin: absent or empty => serve all.
62
+ */
63
+ declare function filterEnabledModels(models: readonly CodeArtsModelInfo[], enabled: readonly string[] | undefined): readonly CodeArtsModelInfo[];
64
+ //#endregion
65
+ //#region src/dpop.d.ts
66
+ /**
67
+ * DPoP(RFC 9449)证明 JWT:ES256 + P-256,供 oauth2/tokens 请求头使用。
68
+ * 翻译自 codearts2api 的 internal/upstream/dpop.go。
69
+ *
70
+ * 关键约束:refresh_token 与首次换取时的 DPoP 公钥绑定,刷新时必须复用同一
71
+ * 私钥,否则 STS 报 InvalidDPoPHeader。因此该私钥必须随 refresh_token 一起落盘。
72
+ *
73
+ * @module dsh-codearts/dpop
74
+ */
75
+ /** 可持久化的 P-256 DPoP 私钥(JWK 形式,含 d)。 */
76
+ type DPoPPrivateJWK = {
77
+ kty: string;
78
+ crv: string;
79
+ x: string;
80
+ y: string;
81
+ d: string;
82
+ };
83
+ //#endregion
84
+ //#region src/oauth.d.ts
85
+ interface OAuthToken {
86
+ userId: string;
87
+ userName: string;
88
+ domainId: string;
89
+ securityToken: string;
90
+ accessKeyId: string;
91
+ secretAccessKey: string;
92
+ expiration: string;
93
+ refreshToken: string;
94
+ clientId: string;
95
+ dpopPrivateKey: DPoPPrivateJWK;
96
+ }
97
+ //#endregion
98
+ //#region src/upstream.d.ts
99
+ declare const SNAP_HOST = "https://snap-access.cn-north-4.myhuaweicloud.com";
100
+ type UpstreamErrorKind = 'hard_credit' | 'soft_rate' | 'session_dead' | 'not_found' | 'server' | 'client';
101
+ interface ChatResultOk {
102
+ ok: true;
103
+ /** 已转成 OpenAI 增量 SSE 的 ReadableStream(文本,data: ...\n\n)。 */
104
+ stream: ReadableStream<Uint8Array>;
105
+ }
106
+ interface ChatResultErr {
107
+ ok: false;
108
+ kind: UpstreamErrorKind;
109
+ status: number;
110
+ message: string;
111
+ }
112
+ type ChatResult = ChatResultOk | ChatResultErr;
113
+ /** 给 shim 用的凭证(来自 credentialStore.resolve)。 */
114
+ interface CodeArtsResolved {
115
+ token: string;
116
+ accessKeyId?: string;
117
+ secretAccessKey?: string;
118
+ securityToken: string;
119
+ }
120
+ declare function canonicalModel(id: string): string;
121
+ /** 该模型可用的 max_tokens 上限(实测值与会话内学到的更低值取小)。 */
122
+ declare function maxTokensCeilingFor(id: string): number;
123
+ interface PreparedChatBody {
124
+ url: string;
125
+ body: string;
126
+ model: string;
127
+ chatId: string;
128
+ /**
129
+ * 不用发出去就能判定的失败(目前是「上下文放不下」)。非空时调用方必须直接把它
130
+ * 回给 DSH —— 上游遇到同类问题只会给一句 "The request param is invalid",
131
+ * 那句话里没有模型、没有 token 数,用户无从下手。
132
+ */
133
+ error?: string;
134
+ }
135
+ /**
136
+ * 把 OpenAI 风格 chat body 适配给 CodeArts /api/v2/chat/completions。
137
+ * 该端点虽是 OpenAI 兼容,但网关按自己的 schema 严格校验参数。所以这里
138
+ * 不做原样透传,而是:
139
+ * 1. model 走 canonicalModel(大小写敏感)
140
+ * 2. max_completion_tokens → max_tokens(华为只认后者),并钳到该模型的上限;
141
+ * 再按 contextWindow 扣掉 prompt 估算,保证 prompt + max_tokens 装得下
142
+ * (两者任一超限,上游都只会流内回 InferHub.001001005.400
143
+ * "The request param is invalid, Please check it",不告诉我们是哪一个)
144
+ * 3. 丢弃非白名单字段(stream_options / store / logprobs 等虽实测可容忍,
145
+ * 但按 codearts2api 观察到的真实流量收窄,避免网关后续收紧)
146
+ * 4. tools[].function 去掉 strict(OpenAI 专有)
147
+ * 5. 注入 CodeArts 特有字段:chat_id(按对话派生,见 conversationChatId)、
148
+ * prompt_cache_key、tool_stream
149
+ *
150
+ * @param contextWindow 该模型的上下文窗口(来自 catalog)。传 0 表示不知道,
151
+ * 此时只做上限钳制、不做「装不下」判断。
152
+ */
153
+ declare function prepareChatBody(rawJson: string, contextWindow?: number): PreparedChatBody;
154
+ /**
155
+ * 把上游累计快照 SSE 转换成 OpenAI 增量流。
156
+ *
157
+ * 上游行为(reverse-engineering.md §5):
158
+ * - {"type":"answer", ...} 起始
159
+ * - {"text": "<累计全文>"} 全文快照(用替换语义)
160
+ * - {"delta":{"content":"..."}} 真增量
161
+ * - {"text":"[DONE]","error_code":"0"} 结束;error_code != 0 为错误
162
+ */
163
+ declare function chatStream(credential: CodeArtsResolved, prepared: {
164
+ url: string;
165
+ body: string;
166
+ model: string;
167
+ chatId?: string;
168
+ }, signal?: AbortSignal): Promise<ChatResult>;
169
+ /** 登录身份(上游对同一账号恒定,可用来当账号的自然键)。 */
170
+ interface CallerIdentity {
171
+ userId: string;
172
+ userName: string;
173
+ domainId: string;
174
+ }
175
+ /** 模型清单的三路来源。任一路失败都不阻断,只有三路全失败才算失败。 */
176
+ type ModelSource = 'builtin' | 'benefit' | 'agent';
177
+ interface ModelDiscoveryResult {
178
+ /** 合并后的清单(已去重、已排序)。 */
179
+ models: readonly CodeArtsModelInfo[];
180
+ /** 本轮成功应答的来源。 */
181
+ sources: readonly ModelSource[];
182
+ /** 失败来源及原因(不阻断,供 doctor/日志展示)。 */
183
+ errors: readonly {
184
+ source: ModelSource;
185
+ message: string;
186
+ }[];
187
+ }
188
+ /**
189
+ * 动态发现当前账号可用的模型清单(三路合并)。
190
+ *
191
+ * 与 codearts2api 的 `Client.FetchModels` 等价:内置、限时福利、agent-center 三路
192
+ * 各自独立,任一路失败只记进 `errors` 不阻断 —— 福利网关抖动时仍能拿到内置模型。
193
+ *
194
+ * 上标的两个「实测 vs 标称」处理(很重要,别照搬标称值):
195
+ * - **maxTokens 钳到该模型的实测上限**(`MEASURED_MAX_TOKENS`)。上游
196
+ * `gateway/config` 给福利模型标的是 393216 / 384000,而聊天端点实测超过 65536
197
+ * 就流内报 InferHub.001001005.400;DSH 会把 catalog 的 maxTokens 原样当
198
+ * max_tokens 发出去,照抄标称值等于让每个请求都失败。
199
+ * - contextWindow 用发现值(只影响 DSH 的上下文计量与截断余量,不影响路由)。
200
+ *
201
+ * @throws 三路全失败,或合并结果为空。
202
+ */
203
+ declare function fetchModels(credential: CodeArtsResolved, signal?: AbortSignal): Promise<ModelDiscoveryResult>;
204
+ //#endregion
205
+ //#region src/auth.d.ts
206
+ /** 持久化的完整凭证。 */
207
+ interface CodeArtsCredential {
208
+ token: string;
209
+ /** 临时 AK/SK(HMAC 签名用)。手动粘贴场景可能为空。 */
210
+ accessKeyId?: string;
211
+ secretAccessKey?: string;
212
+ /** refresh_token(续期用)。 */
213
+ refreshToken?: string;
214
+ /** 续期绑定的 DPoP 私钥(JWK)。 */
215
+ dpopPrivateKey?: OAuthToken['dpopPrivateKey'];
216
+ /** OAuth client_id(刷新需与登录时一致)。 */
217
+ clientId?: string;
218
+ /** STS 过期时间(ISO)。 */
219
+ expiration?: string;
220
+ /** 用户标识。 */
221
+ userId?: string;
222
+ userName?: string;
223
+ /** 华为云账号的 domain(`/snap-manager/v1/current/user` 会返回)。 */
224
+ domainId?: string;
225
+ /** 可选标注。 */
226
+ label?: string;
227
+ /** 存盘时间(epoch ms)。 */
228
+ storedAt?: number;
229
+ /** 续期已尝试且失败(多半已过期),需用户重新登录。 */
230
+ stale?: boolean;
231
+ }
232
+ declare function codeartsAuthPath(home?: string): string;
233
+ interface ResolvedCredential {
234
+ token: string;
235
+ accessKeyId?: string;
236
+ secretAccessKey?: string;
237
+ securityToken: string;
238
+ }
239
+ declare class CodeArtsCredentialStore {
240
+ private _current;
241
+ private _path;
242
+ /** 正在进行的刷新。并发请求必须共用它,不能各刷一次。 */
243
+ private _inflight;
244
+ /**
245
+ * 凭证代际。重登/清除会推进它,让还在飞的刷新发现自己过期,
246
+ * 从而不把刷新结果写回去覆盖掉刚拿到的新凭证。
247
+ */
248
+ private _gen;
249
+ /** 作废在飞的刷新;任何直接写凭证的路径都要先调它。 */
250
+ private invalidateInflight;
251
+ constructor(path: string);
252
+ setPath(path: string): void;
253
+ /** 给 shim 用:返回签名所需全套。 */
254
+ resolve(): Promise<ResolvedCredential | undefined>;
255
+ current(): Promise<CodeArtsCredential | undefined>;
256
+ /** 从 OAuth 登录结果落库。 */
257
+ setFromOAuth(t: OAuthToken, label?: string): Promise<void>;
258
+ /** 领取限时福利(幂等);无 AK/SK 时静默跳过。 */
259
+ private claimIfPossible;
260
+ /** 手动粘贴 security_token(降级态,无 AK/SK)。 */
261
+ set(token: string, label?: string): Promise<void>;
262
+ clear(): Promise<void>;
263
+ hasCredential(): Promise<boolean>;
264
+ /**
265
+ * 自动续期:若距过期不足 5 分钟且具备 refresh 条件,则刷新并落库。
266
+ * 返回当前(可能已刷新)凭证。
267
+ */
268
+ ensureFresh(): Promise<CodeArtsCredential | undefined>;
269
+ /** 真正去刷一次;失败则把凭证标成 stale 落库,让卡片提示重新登录。 */
270
+ private refresh;
271
+ }
272
+ interface CodeArtsAccountSummary {
273
+ id: string;
274
+ label?: string;
275
+ userName?: string;
276
+ expiration?: string;
277
+ storedAt?: number;
278
+ /** 上次续期失败,需要重新登录。 */
279
+ stale: boolean;
280
+ /** 带 AK/SK,能产生签名(只有 security_token 的降级凭证不能)。 */
281
+ signable: boolean;
282
+ }
283
+ declare function codeartsAccountsPath(home?: string): string;
284
+ /** 多账号凭证存储:一个账号一份槽位,共用一个文档文件。 */
285
+ declare class CodeArtsAccountStore {
286
+ private _doc;
287
+ private _slots;
288
+ private _loading;
289
+ /** 落库串行化:多个账号的续期可能同时想写同一个文档。 */
290
+ private _writeChain;
291
+ private readonly path;
292
+ /** 旧版单凭证文件;新文档不存在时把它收编成第一个账号。 */
293
+ private readonly legacyPath;
294
+ constructor(path: string, legacyPath?: string);
295
+ private loaded;
296
+ private load;
297
+ private adopt;
298
+ private persisterFor;
299
+ private persistDoc;
300
+ /** 按展示顺序返回账号摘要。 */
301
+ list(): Promise<CodeArtsAccountSummary[]>;
302
+ ids(): Promise<string[]>;
303
+ credential(id: string): Promise<CodeArtsCredential | undefined>;
304
+ /** 首个账号——卡片顶部与 CLI 的「当前」。 */
305
+ current(): Promise<CodeArtsCredential | undefined>;
306
+ hasCredential(): Promise<boolean>;
307
+ /** 新增或更新一个账号,返回它的 id。 */
308
+ upsert(cred: CodeArtsCredential, id?: string): Promise<string>;
309
+ /**
310
+ * 从一次 OAuth 登录结果新增/更新账号。
311
+ *
312
+ * 「再登录一次」天然就是「添加账号」:同一账号是更新,别的账号就是新增。
313
+ *
314
+ * 传了 `identity`(来自 `fetchCallerIdentity` 的稳定 user_id)就用它当账号键 ——
315
+ * 这正是「对同一个账号重新登录会被当成新账号」的解药;拿不到身份时退回旧行为
316
+ * (DPoP 公钥当键),也就是每次登录各自成一条。
317
+ */
318
+ upsertFromOAuth(t: OAuthToken, label?: string, identity?: CallerIdentity): Promise<string>;
319
+ /**
320
+ * 把一条已有账号迁移到它的稳定身份键上(同键则合并)。
321
+ *
322
+ * 用在两处:登录后回填身份(老条目已经存在时合并掉),以及 `dsh-codearts identity --fix`
323
+ * 清理历史遗留的重复条目。合并时保留 `storedAt` 较新的一份凭证,位置沿用目标键原有的顺序。
324
+ *
325
+ * @returns 迁移结果:`merged` 表示并入了已存在的那条,`moved` 表示只换了键,`noop` 表示键没变
326
+ */
327
+ adoptIdentity(id: string, identity: CallerIdentity): Promise<'merged' | 'moved' | 'noop'>;
328
+ /** 手工粘贴 security_token(降级态,无 AK/SK)——固定覆盖同一个「手工」账号。 */
329
+ set(token: string, label?: string): Promise<void>;
330
+ remove(id: string): Promise<void>;
331
+ /** 清空全部账号(整体登出)。 */
332
+ clear(): Promise<void>;
333
+ /** 给 shim 用:解析某账号的签名材料(内部含续期)。 */
334
+ resolve(id: string): Promise<ResolvedCredential | undefined>;
335
+ }
336
+ //#endregion
337
+ //#region src/account-pool.d.ts
338
+ interface AccountRuntime {
339
+ id: string;
340
+ /** 我们这边正在飞的请求数。 */
341
+ activeConcurrent: number;
342
+ maxConcurrent: number;
343
+ /** 冷却截止时间(epoch ms);0 表示没在冷却。 */
344
+ coolUntil: number;
345
+ /** 撞上游并发上限后的「优先避开」截止时间。 */
346
+ cappedUntil: number;
347
+ disabled: boolean;
348
+ lastError?: string;
349
+ /** 上次被选中的时间,用于「最久未用优先」。 */
350
+ lastUsed: number;
351
+ errorCount: number;
352
+ }
353
+ interface PoolOptions {
354
+ maxConcurrent?: number;
355
+ /** 可注入的时钟,便于测试。 */
356
+ now?: () => number;
357
+ }
358
+ declare class CodeArtsAccountPool {
359
+ private readonly accounts;
360
+ private readonly maxConcurrent;
361
+ private readonly now;
362
+ constructor(ids: readonly string[], options?: PoolOptions);
363
+ /** 让池与账号文档对齐:新增的进来,删掉的出去。已有账号的运行时状态保留。 */
364
+ sync(ids: readonly string[]): void;
365
+ list(): AccountRuntime[];
366
+ get(id: string): AccountRuntime | undefined;
367
+ /** 账号本身可用(没被禁用、不在冷却中)。 */
368
+ healthy(id: string): boolean;
369
+ /** 现在就能接活的账号(健康 + 本地还有空槽位),按「最久未用」排序。 */
370
+ private available;
371
+ /**
372
+ * 选一个账号。
373
+ *
374
+ * 优先挑没人撞上限的(`cappedUntil` 已过的);如果全都撞了,就退回其中任意
375
+ * 一个 —— 排它的队总比直接失败强。都没得选(全禁用/全在冷却)时返回 undefined。
376
+ */
377
+ pick(exclude?: ReadonlySet<string>): string | undefined;
378
+ /** 占一个本地槽位。占用失败(已满/不存在)返回 false。 */
379
+ acquire(id: string): boolean;
380
+ /** 释放槽位;重复释放不会把计数压到负数。 */
381
+ release(id: string): void;
382
+ /**
383
+ * 撞到上游并发上限。
384
+ *
385
+ * **不冷却账号** —— 账号没坏,只是会话槽位暂时满了(约 52 秒释放)。冷却会
386
+ * 让它在一分钟内彻底不可用,反而把后续请求也一起误伤(Go 参考里也是这么
387
+ * 处理的)。这里只记一个「优先避开」的提示,好让并发的请求先去用别的账号。
388
+ */
389
+ noteConcurrencyCap(id: string): void;
390
+ /** 凭据失效等硬错误:直接停用,等用户重新登录。 */
391
+ disable(id: string, reason: string): void;
392
+ /** 上游 5xx / 软限流:短时冷却。 */
393
+ cooldown(id: string, ms: number, reason: string): void;
394
+ /** 累计错误;达到阈值就冷却。 */
395
+ noteError(id: string, threshold: number, cooldownMs: number, reason: string): void;
396
+ /** 一次成功:清掉错误计数与冷却。 */
397
+ noteSuccess(id: string): void;
398
+ /** 有没有任何账号可用(含被撞上限提示的)。 */
399
+ hasUsable(): boolean;
400
+ }
401
+ //#endregion
402
+ //#region src/usage.d.ts
403
+ /**
404
+ * 用量账本:把聊天流里的 usage 帧累计成「今天用了多少」。
405
+ *
406
+ * 为什么还要自己记:账号总额度有积分接口了(见 credits.ts,2026-10 从官方 Agent
407
+ * 客户端挖出并实测通过),但**本机经插件发出的请求**仍需自己累计 —— 上游不按
408
+ * 「这台机器」维度给统计。能拿到的真实数据有三样:
409
+ * 1. 每个请求的 token `usage`(上游在流末尾回一帧 `{choices:[],usage:{...}}`);
410
+ * 2. 插件自己知道的账号会话槽位占用(见 account-pool:每账号 3 个并发会话);
411
+ * 3. 账号积分/额度(见 credits.ts)。
412
+ *
413
+ * 所以这里累计的是**本机经插件发出的请求**,与卡片上的积分区块互补 —— 一个是本机
414
+ * 级、一个是账号级。官方额度明细在 `codearts.huaweicloud.com/portal/settings/personal-usage`。
415
+ *
416
+ * 落盘在 `$DSH_HOME/.codearts-usage.json`,按天存,保留最近 `RETENTION_DAYS` 天。
417
+ *
418
+ * @module dsh-codearts/usage
419
+ */
420
+ /** 用量账本路径:与账号文档同一层(`$DSH_HOME` 或 `~/.dsh`)。 */
421
+ declare function codeartsUsagePath(home?: string): string;
422
+ /** 累计计数。 */
423
+ interface UsageTotals {
424
+ requests: number;
425
+ promptTokens: number;
426
+ completionTokens: number;
427
+ totalTokens: number;
428
+ }
429
+ /** 上游 usage 帧里我们关心的字段。 */
430
+ interface RawUsage {
431
+ prompt_tokens?: unknown;
432
+ completion_tokens?: unknown;
433
+ total_tokens?: unknown;
434
+ }
435
+ interface UsageSnapshot {
436
+ /** 今天的合计。 */
437
+ today: UsageTotals;
438
+ /** 账本里所有保留天数的合计。 */
439
+ allTime: UsageTotals;
440
+ /** 今天按模型的合计,按 totalTokens 降序。 */
441
+ byModelToday: {
442
+ id: string;
443
+ totals: UsageTotals;
444
+ }[];
445
+ /** 今天按账号的合计。 */
446
+ byAccountToday: Record<string, UsageTotals>;
447
+ /** 账本覆盖的天数(便于「数据从哪天开始」的说明)。 */
448
+ days: number;
449
+ /** 最近一次记账时间(epoch ms)。 */
450
+ lastAt?: number;
451
+ }
452
+ interface UsageTrackerOptions {
453
+ /** 账本文件路径;省略则只记在内存里(测试用)。 */
454
+ path?: string;
455
+ /** 可注入的时钟。 */
456
+ now?: () => number;
457
+ /** 落盘防抖毫秒数(流式请求里每帧都写盘太浪费)。 */
458
+ saveDebounceMs?: number;
459
+ }
460
+ /**
461
+ * 用量账本。
462
+ *
463
+ * 记账分两步,因为一次请求可能只有其一:
464
+ * - `noteRequest()` 在请求发出时调用(拿不到 usage 的流也要计入请求数);
465
+ * - `noteUsage()` 在收到 usage 帧时调用(补 token 数)。
466
+ */
467
+ declare class CodeArtsUsageTracker {
468
+ private days;
469
+ private lastAt;
470
+ private loaded;
471
+ private loading;
472
+ private saveTimer;
473
+ private saving;
474
+ private readonly path;
475
+ private readonly now;
476
+ private readonly saveDebounceMs;
477
+ constructor(options?: UsageTrackerOptions);
478
+ /** 读账本(懒加载,只读一次)。 */
479
+ load(): Promise<void>;
480
+ private readFile;
481
+ private day;
482
+ private bucket;
483
+ /** 记一次请求(不等到 usage 帧)。 */
484
+ noteRequest(accountId: string, model: string, at?: number): void;
485
+ /** 记一次 usage 帧。 */
486
+ noteUsage(accountId: string, model: string, raw: RawUsage | undefined, at?: number): void;
487
+ /** 丢弃保留期之外的天(按日期键比较即可,YYYY-MM-DD 是字典序)。 */
488
+ private prune;
489
+ /** 当前快照(卡片与 CLI 都读它)。 */
490
+ snapshot(at?: number): UsageSnapshot;
491
+ private scheduleSave;
492
+ /** 立即落盘(关停时调用,别丢最后几秒的账)。 */
493
+ flush(): Promise<void>;
494
+ private save;
495
+ }
496
+ //#endregion
497
+ //#region src/shim.d.ts
498
+ interface ShimLogger {
499
+ warn(...args: unknown[]): void;
500
+ error(...args: unknown[]): void;
501
+ }
502
+ interface CodeArtsShim {
503
+ ready: Promise<void>;
504
+ baseUrl(): string;
505
+ token(): string;
506
+ close(): Promise<void>;
507
+ }
508
+ interface CodeArtsShimOptions {
509
+ store: CodeArtsAccountStore;
510
+ catalog: CodeArtsCatalog;
511
+ /** 账号池。不传则内部自建一个,并在每次请求前与账号文档对齐。 */
512
+ pool?: CodeArtsAccountPool;
513
+ /** 用量账本。给了就在转发流的同时把 usage 帧记下来(卡片上的「今日用量」)。 */
514
+ usage?: CodeArtsUsageTracker;
515
+ logger?: ShimLogger;
516
+ }
517
+ declare function createCodeArtsShim(options: CodeArtsShimOptions): CodeArtsShim;
518
+ //#endregion
519
+ //#region src/adapter.d.ts
520
+ /** Provider route this bundle owns. Keep in sync with cordis.patch.yml. */
521
+ declare const CODEARTS_PROVIDER = "codearts";
522
+ interface CodeArtsAdapterOptions {
523
+ shim: CodeArtsShim;
524
+ catalog: CodeArtsCatalog;
525
+ enabledModels?: () => readonly string[] | undefined;
526
+ resolveAttachments?: () => AttachmentStore | undefined;
527
+ }
528
+ interface CodeArtsAdapter {
529
+ adapter: PiAiAdapter;
530
+ invalidate: () => void;
531
+ }
532
+ declare function createCodeArtsAdapter(options: CodeArtsAdapterOptions): CodeArtsAdapter;
533
+ //#endregion
534
+ //#region src/credits.d.ts
535
+ /** 福利网关:token 余额接口在这里(与 benefit/claim、gateway/config 同一个 host)。 */
536
+ declare const BENEFIT_GATEWAY_HOST = "https://opengw.developer.huaweicloud.com";
537
+ /** 一类积分(总量/已用/剩余/即将过期)。上游给不出的字段留 undefined。 */
538
+ interface CodeArtsCreditCategory {
539
+ total: number;
540
+ used: number;
541
+ remain: number;
542
+ /** 会在本周期结束时作废的部分。 */
543
+ expiring: number;
544
+ }
545
+ /** 积分快照(来自 statistics/plugin)。 */
546
+ interface CodeArtsCredits {
547
+ /** 套餐显示名,例如「体验版」。 */
548
+ planName: string;
549
+ /** 套餐英文名(中文缺失时的兜底)。 */
550
+ planNameEn: string;
551
+ /** 上游给的 spec_code,例如 codearts.agent.individual.trial。 */
552
+ specCode: string;
553
+ /** normal → active、closed → expired、freeze → frozen。 */
554
+ planStatus: 'active' | 'expired' | 'frozen' | 'unknown';
555
+ /** 是否按积分计费(false 表示 token 套餐)。 */
556
+ isCreditPackage: boolean;
557
+ /** 变更/购买套餐的官方页面。 */
558
+ packageUrl?: string;
559
+ /** 额度周期起止(end 就是「限额重置日期」)。 */
560
+ periodStart?: string;
561
+ periodEnd?: string;
562
+ /** 总积分(usageTotalPackageCredit)。 */
563
+ total: CodeArtsCreditCategory;
564
+ /** 赠送积分(usageBonusPackageCredit)。 */
565
+ bonus?: CodeArtsCreditCategory;
566
+ /** 基础套餐积分(usageBasicPackageCredit)。 */
567
+ basic?: CodeArtsCreditCategory;
568
+ /** 按需积分(usageOnDemandPackageCredit)。 */
569
+ onDemand?: CodeArtsCreditCategory;
570
+ /** 抓取时刻(毫秒)。 */
571
+ fetchedAt: number;
572
+ }
573
+ /** token 额度快照(来自 tokens/balance)。 */
574
+ interface CodeArtsTokenQuota {
575
+ dailyLimit: number;
576
+ dailyUsed: number;
577
+ monthlyLimit: number;
578
+ monthlyUsed: number;
579
+ /** 剩余额度。 */
580
+ balance: number;
581
+ /** 总额度。 */
582
+ quota: number;
583
+ }
584
+ interface CodeArtsCreditSnapshot {
585
+ credits: CodeArtsCredits;
586
+ /** 拿不到(接口失败/不支持)时为 undefined,不影响积分本身。 */
587
+ tokens?: CodeArtsTokenQuota;
588
+ }
589
+ /** 缓存里的一个结果:成功给值,失败给消息 —— 失败也要能被卡片看见。 */
590
+ type CodeArtsCreditState = {
591
+ phase: 'ok';
592
+ value: CodeArtsCreditSnapshot;
593
+ at: number;
594
+ } | {
595
+ phase: 'error';
596
+ message: string;
597
+ at: number;
598
+ };
599
+ /**
600
+ * 解析 `GET /snap-manager/v1/statistics/plugin` 的响应。
601
+ *
602
+ * 返回 undefined 表示这不是一份能用的积分文档(上游改了形状/账号没有积分体系)——
603
+ * 调用方据此报「积分不可用」,而不是显示一排 0 假装是真的。
604
+ */
605
+ declare function parseCreditStatistics(raw: unknown): CodeArtsCredits | undefined;
606
+ /** 解析 `GET /api/v1/user/tokens/balance` 的响应(`result` 里是额度口径)。 */
607
+ declare function parseTokenBalance(raw: unknown): CodeArtsTokenQuota | undefined;
608
+ /**
609
+ * 拉一次积分与 token 额度。
610
+ *
611
+ * 积分是主口径:它失败就整体失败(卡片要能看见原因)。token 额度是附带的,
612
+ * 它失败只让 `tokens` 缺席 —— 别让一个次要接口把主数字一起拖没。
613
+ */
614
+ declare function fetchCredits(credential: CodeArtsResolved, signal?: AbortSignal): Promise<CodeArtsCreditSnapshot>;
615
+ interface CreditCacheOptions {
616
+ ttlMs?: number;
617
+ failureTtlMs?: number;
618
+ /** 便于测试注入。 */
619
+ load?: (credential: CodeArtsResolved, signal?: AbortSignal) => Promise<CodeArtsCreditSnapshot>;
620
+ now?: () => number;
621
+ }
622
+ /**
623
+ * 积分缓存(TTL + 单飞)。
624
+ *
625
+ * 单飞很重要:卡片刷新、对话底部的轮询可能同时打进来,没有它就会并发问上游好几次;
626
+ * 而上游对同一账号的并发是有成本/风险的。
627
+ */
628
+ declare class CodeArtsCreditCache {
629
+ private state;
630
+ private inflight;
631
+ private readonly ttlMs;
632
+ private readonly failureTtlMs;
633
+ private readonly load;
634
+ private readonly now;
635
+ constructor(options?: CreditCacheOptions);
636
+ /** 当前缓存内容(不触发请求),供只想读快照的场景使用。 */
637
+ peek(): CodeArtsCreditState | undefined;
638
+ /** 丢掉缓存,下次 get 会重新拉(重新登录/添加账号后调用)。 */
639
+ invalidate(): void;
640
+ /**
641
+ * 取一次积分。命中缓存直接返回;否则合并成一次上游请求。
642
+ * **不抛异常**:失败以 `phase: 'error'` 返回,调用方(状态路由)照样能渲染。
643
+ */
644
+ get(credential: CodeArtsResolved, signal?: AbortSignal): Promise<CodeArtsCreditState>;
645
+ private run;
646
+ }
647
+ //#endregion
648
+ //#region src/discovery.d.ts
649
+ interface AccountDiscovery {
650
+ accountId: string;
651
+ label?: string;
652
+ ok: boolean;
653
+ models: readonly CodeArtsModelInfo[];
654
+ sources: readonly ModelSource[];
655
+ errors: readonly {
656
+ source: ModelSource;
657
+ message: string;
658
+ }[];
659
+ }
660
+ interface DiscoveryOutcome {
661
+ /** 合并所有账号后的目录(已去重、已排序、已钳制上限)。 */
662
+ models: readonly CodeArtsModelInfo[];
663
+ /** 逐账号的结果,含失败原因。 */
664
+ accounts: readonly AccountDiscovery[];
665
+ /** 至少一个账号成功。 */
666
+ anySucceeded: boolean;
667
+ }
668
+ /**
669
+ * 逐账号发现并合并(不改全局状态)。
670
+ *
671
+ * @param store 账号文档;逐个 `resolve()` 以拿到已续期的签名材料
672
+ * @param signal 取消信号(宿主关停时用)
673
+ */
674
+ declare function discoverModels(store: CodeArtsAccountStore, signal?: AbortSignal): Promise<DiscoveryOutcome>;
675
+ /**
676
+ * 发现并**提交**到全局目录:目录内容 + 福利集合。
677
+ *
678
+ * @returns 提交后的目录;没有任何账号成功时返回 `undefined`(调用方保留原目录)
679
+ */
680
+ declare function refreshCatalog(store: CodeArtsAccountStore, catalog: CodeArtsCatalog, signal?: AbortSignal): Promise<DiscoveryOutcome | undefined>;
681
+ //#endregion
682
+ //#region src/index.d.ts
683
+ /** Stable Cordis plugin name. */
684
+ declare const name = "llm-codearts";
685
+ /** The llm service is required before the provider can register. */
686
+ declare const inject: string[];
687
+ /** Settings namespace owning the configuration card. */
688
+ declare const CODEARTS_SETTINGS_NS: SettingsNamespace;
689
+ /** Plugin configuration (MVP: just the model allowlist). */
690
+ interface Config {
691
+ /** Allowlist of CodeArts model ids offered in the model pickers. */
692
+ enabledModels?: string[];
693
+ }
694
+ declare const Config: z<Config>;
695
+ declare function apply(ctx: Context, rawConfig: Config): void;
696
+ //#endregion
697
+ export { type AccountRuntime, BENEFIT_GATEWAY_HOST, CODEARTS_PROVIDER, CODEARTS_SETTINGS_NS, CodeArtsAccountPool, CodeArtsAccountStore, type CodeArtsAccountSummary, type CodeArtsAdapter, CodeArtsCatalog, type CodeArtsCredential, CodeArtsCredentialStore, CodeArtsCreditCache, type CodeArtsCreditCategory, type CodeArtsCreditSnapshot, type CodeArtsCreditState, type CodeArtsCredits, type CodeArtsModelInfo, type CodeArtsShim, type CodeArtsTokenQuota, CodeArtsUsageTracker, Config, type DiscoveryOutcome, FALLBACK_CODEARTS_MODELS, type ModelDiscoveryResult, SNAP_HOST, type UpstreamErrorKind, type UsageSnapshot, type UsageTotals, apply, canonicalModel, chatStream, codeartsAccountsPath, codeartsAuthPath, codeartsUsagePath, createCodeArtsAdapter, createCodeArtsShim, discoverModels, fetchCredits, fetchModels, filterEnabledModels, inject, maxTokensCeilingFor, name, parseCreditStatistics, parseTokenBalance, prepareChatBody, refreshCatalog };