@tnnevol/dsh-zcode 0.0.0-stage → 6.0.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/index.js ADDED
@@ -0,0 +1,1544 @@
1
+ import { t as ZCodeAuthService } from "./auth-BrDmmPY2.js";
2
+ import { _ as ZCODE_USER_AGENT, d as ZCODE_FAMILIES, f as ZCODE_FAMILY_LABELS, g as ZCODE_PROVIDER_NAME, h as ZCODE_PROVIDER, i as ZCODE_ANTHROPIC_BASE_URLS, l as ZCODE_CLIENT_VERSION, m as ZCODE_ORIGIN, r as ZCODE_ANTHROPIC_BASE_URL, t as DEFAULT_ZCODE_ENDPOINT_PLAN, u as ZCODE_ENDPOINT_PLAN_LABELS } from "./identity-DE3rOYuE.js";
3
+ import { _ as findModel, b as selectableModels, c as thinkingLevelMapOf, d as ZCodeConsoleClient, f as ZCodeConsoleError, g as ZCODE_REASONING_LEVELS, h as zcodeSourceHeaders, i as ZCodeCatalogService, l as toBalanceEntries, m as zcodeModelHeaders, n as ZCODE_FALLBACK_CONTEXT_WINDOW, o as mergeBalanceEntries, p as zcodeMessagesUrl, r as ZCODE_FALLBACK_MAX_TOKENS, s as modelIdsFromBalances, t as ZCODE_BUILTIN_MODEL_IDS, u as toModel, v as modelIdOfCapability, y as normalizeModelId } from "./catalog-Br2Bp6dD.js";
4
+ import { a as ZCODE_ROUTES, i as trustedRequest, n as ZCODE_ORIGIN_NOT_TRUSTED, o as ZCODE_ROUTE_PREFIX, r as registerZCodeRoutes, t as ZCODE_CATALOG_UNAVAILABLE } from "./routes-vzWlp-oN.js";
5
+ import { mkdir, readFile, rm, stat } from "node:fs/promises";
6
+ import { dirname, join, resolve } from "node:path";
7
+ import { withFileLock, writeFileAtomic } from "@deepseek-ai/dsh-atomic-write";
8
+ import { resolveDshHome } from "@deepseek-ai/dsh-home-paths";
9
+ import { createProvider } from "@earendil-works/pi-ai";
10
+ import { anthropicMessagesApi } from "@earendil-works/pi-ai/api/anthropic-messages.lazy";
11
+ import { LlmAdapter, LlmError, ReasoningEffortId, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
12
+ //#region ../../packages/dsh-plugin-kit/lib/index.js
13
+ /** group + other 的读/写/执行位(0o077)。 */
14
+ const GROUP_OTHER_BITS = 63;
15
+ /** 属主读位(0o400)。 */
16
+ const OWNER_READ_BIT = 256;
17
+ /**
18
+ * 从 `stat` 结果判定凭据文件可用性(纯函数)。
19
+ *
20
+ * 判定条件 = 「group/other 位为空**且**属主可读」。既有拒绝语义不回退:
21
+ * `644` 等放宽权限的文件继续被拒绝;本条件只在其上补充属主可读位——
22
+ * `0000`(属主也不可读)不再被当作「安全且可读」放行。
23
+ *
24
+ * Windows 没有 POSIX mode,判定在调用方跳过(与上游行为一致):本函数仍按
25
+ * 位运算给出结论,但 Windows 的 `mode` 不承载 POSIX 语义,调用方应以
26
+ * `process.platform === 'win32'` 为准跳过判定、直接视为可用。
27
+ * @param facts - `stat` 结果(至少含 `mode`)。
28
+ * @returns 三态结论。
29
+ */
30
+ function assessCredentialMode(facts) {
31
+ if ((facts.mode & GROUP_OTHER_BITS) !== 0) return "unusable";
32
+ if ((facts.mode & OWNER_READ_BIT) === 0) return "unusable";
33
+ return "usable";
34
+ }
35
+ /**
36
+ * 便捷封装:stat 一个凭据文件并给出三态结论。
37
+ * @param stat - 与 `fs.promises.stat` 同形的读取函数(便于测试注入)。
38
+ * @param path - 凭据文件路径。
39
+ * @returns 三态结论;stat 失败且 errno 为 ENOENT 时为 `absent`,其他失败
40
+ * 上抛——非 ENOENT 的读取失败必须暴露(与上游 `isENOENT` 立场一致)。
41
+ */
42
+ async function assessCredentialFile(stat, path) {
43
+ try {
44
+ return assessCredentialMode(await stat(path));
45
+ } catch (error) {
46
+ if (error?.code === "ENOENT") return "absent";
47
+ throw error;
48
+ }
49
+ }
50
+ /**
51
+ * 生成「属主不可读」场景的可诊断错误信息。
52
+ *
53
+ * 与上游对权限过宽的既有文案同构(`... is readable beyond its owner (mode
54
+ * 644); run "chmod 600 ..."`),补充属主不可读的分支。信息只含路径与权限位,
55
+ * 不含凭据内容。
56
+ * @param path - 凭据文件路径。
57
+ * @param mode - 当前权限位。
58
+ * @param context - 出错场景前缀(如 `codebuddy-auth` / `credentials-local`)。
59
+ * @returns 可直接 throw / 上报的错误。
60
+ */
61
+ function ownerUnreadableError(path, mode, context) {
62
+ const octal = (mode & 511).toString(8).padStart(3, "0");
63
+ return /* @__PURE__ */ new Error(`${context}: ${path} is not readable by its owner (mode ${octal}); run "chmod 600 ${path}" before starting again`);
64
+ }
65
+ //#endregion
66
+ //#region src/host/credentials.ts
67
+ /**
68
+ * ZCode 凭据的宿主侧持久化。
69
+ *
70
+ * 只由本插件读写:账号 JWT 与家族 access token 落在一个属主可读的文件里
71
+ * (0600,目录 0700),并通过 `@tnnevol/dsh-plugin-kit` 的共享判定拒绝 group/other
72
+ * 位被放宽或被写成不可读的旧文件。浏览器侧永远拿不到凭据内容。
73
+ *
74
+ * @module dsh-zcode/credentials
75
+ */
76
+ /** 凭据文件名(位于 DSH home 下)。 */
77
+ const ZCODE_AUTH_FILENAME = ".dsh-zcode-auth.json";
78
+ const AUTH_FORMAT_VERSION = 1;
79
+ const ALLOWED_KEYS = /* @__PURE__ */ new Set([
80
+ "version",
81
+ "family",
82
+ "zcodeJwt",
83
+ "accessToken",
84
+ "refreshToken",
85
+ "expiresAt",
86
+ "userId",
87
+ "email",
88
+ "name",
89
+ "avatar"
90
+ ]);
91
+ function isENOENT(error) {
92
+ return error?.code === "ENOENT";
93
+ }
94
+ /** 解析落盘文档;任何不认识的字段都视为格式漂移,直接拒绝而不是忽略。 */
95
+ function parseDocument(text, filename) {
96
+ let value;
97
+ try {
98
+ value = JSON.parse(text);
99
+ } catch {
100
+ throw new Error(`zcode-auth: ${filename} 不是合法 JSON`);
101
+ }
102
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`zcode-auth: ${filename} 必须是对象`);
103
+ const document = value;
104
+ if (document.version !== AUTH_FORMAT_VERSION) throw new Error(`zcode-auth: ${filename} 的格式版本 ${String(document.version)} 不受支持`);
105
+ const raw = document.credential;
106
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) throw new Error(`zcode-auth: ${filename} 缺少 credential 对象`);
107
+ const credential = raw;
108
+ for (const key of Object.keys(credential)) if (!ALLOWED_KEYS.has(key)) throw new Error(`zcode-auth: ${filename} 含未知字段 ${key}`);
109
+ if (credential.family !== "bigmodel" && credential.family !== "zai") throw new Error(`zcode-auth: ${filename} 的 family 必须是 bigmodel 或 zai`);
110
+ if (typeof credential.zcodeJwt !== "string" || credential.zcodeJwt.length === 0) throw new Error(`zcode-auth: ${filename} 的 zcodeJwt 必须是非空字符串`);
111
+ for (const optional of [
112
+ "accessToken",
113
+ "refreshToken",
114
+ "userId",
115
+ "email",
116
+ "name",
117
+ "avatar"
118
+ ]) {
119
+ const field = credential[optional];
120
+ if (field !== void 0 && typeof field !== "string") throw new Error(`zcode-auth: ${filename} 的 ${optional} 必须是字符串`);
121
+ }
122
+ const expiresAt = credential.expiresAt;
123
+ if (expiresAt !== void 0 && (typeof expiresAt !== "number" || !Number.isFinite(expiresAt) || expiresAt <= 0)) throw new Error(`zcode-auth: ${filename} 的 expiresAt 必须是正数`);
124
+ return {
125
+ version: AUTH_FORMAT_VERSION,
126
+ credential
127
+ };
128
+ }
129
+ /** 凭据文件路径。 */
130
+ function zcodeAuthPath(dshHome) {
131
+ return resolve(join(resolveDshHome(dshHome), ZCODE_AUTH_FILENAME));
132
+ }
133
+ /** 校验属主权限;Windows 无 POSIX mode,跳过。 */
134
+ async function assertOwnerOnly(filename) {
135
+ if (process.platform === "win32") return;
136
+ if (await assessCredentialFile((target) => stat(target), filename) !== "unusable") return;
137
+ const mode = (await stat(filename)).mode;
138
+ throw ownerUnreadableError(filename, mode, "zcode-auth");
139
+ }
140
+ /** 文件落盘的账号凭据存储。 */
141
+ var ZCodeCredentialStore = class {
142
+ filename;
143
+ constructor(filename = zcodeAuthPath()) {
144
+ this.filename = resolve(filename);
145
+ }
146
+ /** 读取当前凭据;不存在时返回 undefined。 */
147
+ async read() {
148
+ await assertOwnerOnly(this.filename);
149
+ let text;
150
+ try {
151
+ text = await readFile(this.filename, "utf8");
152
+ } catch (error) {
153
+ if (isENOENT(error)) return void 0;
154
+ throw error;
155
+ }
156
+ return structuredClone(parseDocument(text, this.filename).credential);
157
+ }
158
+ /** 覆盖写入凭据(原子写 + 文件锁)。 */
159
+ async write(credential) {
160
+ await mkdir(dirname(this.filename), {
161
+ recursive: true,
162
+ mode: 448
163
+ });
164
+ return withFileLock(this.filename, async () => {
165
+ const candidate = parseDocument(JSON.stringify({
166
+ version: AUTH_FORMAT_VERSION,
167
+ credential
168
+ }), this.filename);
169
+ await writeFileAtomic(this.filename, `${JSON.stringify(candidate, null, 2)}\n`, {
170
+ mode: 384,
171
+ dirMode: 448
172
+ });
173
+ return structuredClone(candidate.credential);
174
+ });
175
+ }
176
+ /** 删除凭据;不存在时静默成功。 */
177
+ async delete() {
178
+ await mkdir(dirname(this.filename), {
179
+ recursive: true,
180
+ mode: 448
181
+ });
182
+ await withFileLock(this.filename, () => rm(this.filename, { force: true }));
183
+ }
184
+ };
185
+ //#endregion
186
+ //#region src/contracts/preferences.ts
187
+ /**
188
+ * 插件偏好设置契约。
189
+ *
190
+ * 与参考插件不同,这里**没有**「推理端点策略」:ZCode 只有一个 Start Plan
191
+ * 端点,没有按量与套餐之分。留着的两个字段都是纯展示/刷新策略,因此按插件
192
+ * UI 规范第 2 档,用详情页自定义区块承载并持久化到插件自己的文件。
193
+ *
194
+ * @module dsh-zcode/preferences-contract
195
+ */
196
+ const DEFAULT_ZCODE_PREFERENCES = {
197
+ usageVisibility: "auto",
198
+ refreshMinutes: 15,
199
+ hiddenModels: [],
200
+ endpointPlan: DEFAULT_ZCODE_ENDPOINT_PLAN
201
+ };
202
+ /** 合法刷新间隔范围(分钟);下限 1 避免把额度接口打成轮询。 */
203
+ const ZCODE_REFRESH_MINUTES = {
204
+ min: 0,
205
+ max: 240
206
+ };
207
+ /** 规范化任意输入;非法字段回落到默认值,绝不抛给调用方。 */
208
+ function normalizePreferences(value) {
209
+ const source = value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
210
+ const visibility = source.usageVisibility;
211
+ const usageVisibility = visibility === "always" || visibility === "off" || visibility === "auto" ? visibility : DEFAULT_ZCODE_PREFERENCES.usageVisibility;
212
+ const rawMinutes = source.refreshMinutes;
213
+ const refreshMinutes = typeof rawMinutes === "number" && Number.isFinite(rawMinutes) ? Math.max(ZCODE_REFRESH_MINUTES.min, Math.min(ZCODE_REFRESH_MINUTES.max, Math.round(rawMinutes))) : DEFAULT_ZCODE_PREFERENCES.refreshMinutes;
214
+ const hiddenModels = Array.isArray(source.hiddenModels) ? [...new Set(source.hiddenModels.filter((id) => typeof id === "string" && id.length > 0 && id.length <= 200))] : [];
215
+ const rawPlan = source.endpointPlan;
216
+ return {
217
+ usageVisibility,
218
+ refreshMinutes,
219
+ hiddenModels,
220
+ endpointPlan: rawPlan === "ultra" || rawPlan === "start-plan" ? rawPlan : DEFAULT_ZCODE_PREFERENCES.endpointPlan
221
+ };
222
+ }
223
+ //#endregion
224
+ //#region src/host/preferences.ts
225
+ /**
226
+ * 插件偏好的宿主侧持久化。
227
+ *
228
+ * 与凭据分开存放:偏好不是秘密,但也只由插件自己读写;损坏或字段漂移时
229
+ * 回落到默认值,避免一个坏文件让详情页整块不可用。
230
+ *
231
+ * @module dsh-zcode/preferences
232
+ */
233
+ /** 偏好文件名(位于 DSH home 下)。 */
234
+ const ZCODE_PREFERENCES_FILENAME = ".dsh-zcode-preferences.json";
235
+ /** 偏好文件路径。 */
236
+ function zcodePreferencesPath(dshHome) {
237
+ return resolve(join(resolveDshHome(dshHome), ZCODE_PREFERENCES_FILENAME));
238
+ }
239
+ /** 文件落盘的偏好存储。 */
240
+ var ZCodePreferencesStore = class {
241
+ filename;
242
+ constructor(filename = zcodePreferencesPath()) {
243
+ this.filename = resolve(filename);
244
+ }
245
+ /** 读取偏好;文件缺失或内容不可解析时返回默认值。 */
246
+ async read() {
247
+ let text;
248
+ try {
249
+ text = await readFile(this.filename, "utf8");
250
+ } catch {
251
+ return DEFAULT_ZCODE_PREFERENCES;
252
+ }
253
+ try {
254
+ return normalizePreferences(JSON.parse(text));
255
+ } catch {
256
+ return DEFAULT_ZCODE_PREFERENCES;
257
+ }
258
+ }
259
+ /** 合并写入偏好;返回落盘后的完整值。 */
260
+ async write(patch) {
261
+ const next = normalizePreferences({
262
+ ...await this.read(),
263
+ ...patch !== null && typeof patch === "object" && !Array.isArray(patch) ? patch : {}
264
+ });
265
+ await mkdir(dirname(this.filename), {
266
+ recursive: true,
267
+ mode: 448
268
+ });
269
+ await writeFileAtomic(this.filename, `${JSON.stringify(next, null, 2)}\n`, {
270
+ mode: 384,
271
+ dirMode: 448
272
+ });
273
+ return next;
274
+ }
275
+ };
276
+ //#endregion
277
+ //#region src/host/start-plan-prefix.ts
278
+ /**
279
+ * Start Plan 端点的准入前缀。
280
+ *
281
+ * 实测结论(2026-10-25,抓包 + 逐项二分):`/api/v1/zcode-plan/anthropic`
282
+ * 会在网关侧校验 `system` 是否为官方 agent 原文——两段内容必须逐字一致,
283
+ * `system[0]` 少一个字母都会被判 `3012`;`system[1]` 必须作为完整前缀出现,
284
+ * 其后可以继续追加内容。除此之外请求头只需要三个,工具声明、metadata、
285
+ * device_id 与各类 `x-zcode-*` 头都与放行无关。
286
+ *
287
+ * 这两段文字是 ZCode 官方 agent 的系统提示词。插件把它们作为**前缀**放在
288
+ * 自己构造的 system 之前,以满足该校验;DSH 自己的指令接在其后。
289
+ * 上游若改动提示词,这里会失配并退回 `3012`,届时需要重新采集。
290
+ *
291
+ * @module dsh-zcode/start-plan-prefix
292
+ */
293
+ /** 第一段:固定 42 字符的 agent 身份行,必须逐字一致。 */
294
+ const START_PLAN_PREFIX_HEAD = "You are ZCode, an interactive coding agent";
295
+ /**
296
+ * 第二段:官方 agent 的行为提示词,必须作为完整前缀出现。
297
+ *
298
+ * 只做前缀匹配,因此可以在它之后追加插件自己的约束。
299
+ */
300
+ const START_PLAN_PREFIX_BODY = "\nYou are an interactive ZCode agent that helps users with software engineering tasks.\n\nIMPORTANT: Assist with authorized security testing, defensive security, CTF challenges, and educational contexts. Refuse requests for destructive techniques, DoS attacks, mass targeting, supply chain compromise, or detection evasion for malicious purposes. Dual-use security tools (C2 frameworks, credential testing, exploit development) require clear authorization context: pentesting engagements, CTF competitions, security research, or defensive use cases.\n\n# Harness\n- Text you output outside of tool use is displayed to the user as Github-flavored markdown in a terminal.\n- Tools run behind a user-selected permission mode; a denied call means the user declined it — adjust, don't retry verbatim.\n- The system may send updates, reminders, or modifications to rules via mid-conversation system turns. These are system-controlled, unlike function results. Hooks may intercept tool calls; treat hook output as user feedback.\n- Prefer the dedicated file/search tools over shell commands when one fits. Independent tool calls can run in parallel in one response.\n- Reference code as `file_path:line_number` — it's clickable.";
301
+ /** 注入到 system 最前面的两段前缀。 */
302
+ function startPlanSystemPrefix() {
303
+ return [{
304
+ type: "text",
305
+ text: START_PLAN_PREFIX_HEAD
306
+ }, {
307
+ type: "text",
308
+ text: START_PLAN_PREFIX_BODY
309
+ }];
310
+ }
311
+ //#endregion
312
+ //#region src/host/http/messages-client.ts
313
+ /** 限流退避:与官方客户端同一曲线(2s 起、上限 60s、带抖动)。 */
314
+ const RATE_LIMIT_BASE_MS = 2e3;
315
+ const RATE_LIMIT_MAX_MS = 6e4;
316
+ /** 单次请求最多尝试几轮(含首次)。 */
317
+ const RATE_LIMIT_MAX_ATTEMPTS = 5;
318
+ /**
319
+ * 官方限流/繁忙类业务码。
320
+ *
321
+ * 依据上游 `docs/start-plan-provider-business-errors.md`:
322
+ * - `3008`/`3009`/`3010` 是**并发上限**(系统繁忙),不是配额问题;
323
+ * - `1005` 是配额耗尽,模型层**不可重试**;
324
+ * - `3007` 是安全校验拒绝,重试没有意义。
325
+ * 把这些混成一个「429 就重试」会让配额耗尽的用户白等,也会掩盖安全校验失败。
326
+ */
327
+ const CONCURRENCY_LIMIT_CODES = [
328
+ "3008",
329
+ "3009",
330
+ "3010"
331
+ ];
332
+ const QUOTA_EXHAUSTED_CODES = ["1005"];
333
+ const SECURITY_REJECT_CODES = ["3007"];
334
+ const RETRYABLE_RATE_LIMIT_CODES = [
335
+ "rate_limit_reached_error",
336
+ "rate_limit_error",
337
+ "overloaded_error"
338
+ ];
339
+ /** 把上游 usage 投影成 DSH 计数(字段互斥)。 */
340
+ function toUsage(raw) {
341
+ const value = raw ?? {};
342
+ const num = (key) => {
343
+ const field = value[key];
344
+ return typeof field === "number" && Number.isFinite(field) ? field : void 0;
345
+ };
346
+ const cacheRead = num("cache_read_input_tokens") ?? num("cacheReadInputTokens");
347
+ const cacheWrite = num("cache_creation_input_tokens") ?? num("cacheCreationInputTokens");
348
+ return {
349
+ inputTokens: num("input_tokens") ?? num("inputTokens") ?? 0,
350
+ outputTokens: num("output_tokens") ?? num("outputTokens") ?? 0,
351
+ ...cacheRead === void 0 ? {} : { cacheReadTokens: cacheRead },
352
+ ...cacheWrite === void 0 ? {} : { cacheWriteTokens: cacheWrite }
353
+ };
354
+ }
355
+ /** 组装请求体:把 DSH system 接在官方前缀之后,并补齐官方附加字段。 */
356
+ function buildMessagesBody(request, prefix) {
357
+ const second = request.system === void 0 || request.system.trim() === "" ? prefix.body : `${prefix.body}\n\n${request.system}`;
358
+ const thinkingEnabled = request.thinking !== false;
359
+ return {
360
+ model: request.model,
361
+ max_tokens: request.maxTokens,
362
+ stream: true,
363
+ system: [{
364
+ type: "text",
365
+ text: prefix.head,
366
+ cache_control: { type: "ephemeral" }
367
+ }, {
368
+ type: "text",
369
+ text: second,
370
+ cache_control: { type: "ephemeral" }
371
+ }],
372
+ messages: request.messages,
373
+ ...request.tools === void 0 || request.tools.length === 0 ? {} : { tools: request.tools },
374
+ tool_choice: { type: "auto" },
375
+ metadata: { user_id: JSON.stringify({
376
+ device_id: request.deviceId,
377
+ account_uuid: "",
378
+ session_id: normalizeTaskId(request.sessionId, request.deviceId)
379
+ }) },
380
+ ...thinkingEnabled ? { thinking: { type: "enabled" } } : {},
381
+ ...thinkingEnabled ? { output_config: { effort: request.effort ?? "max" } } : {},
382
+ ...request.temperature === void 0 ? {} : { temperature: request.temperature }
383
+ };
384
+ }
385
+ /**
386
+ * 计算第 `attempt` 次限流重试的等待时间。
387
+ *
388
+ * 上游给了 `Retry-After` 就听它的(这是唯一能真正避开限流的信号);没给则按
389
+ * 2s→4s→8s… 指数退避并加抖动——多个请求同时重试仍会撞车,抖动用来错开。
390
+ */
391
+ function rateLimitDelayMs(attempt, retryAfterSeconds) {
392
+ if (retryAfterSeconds !== void 0 && Number.isFinite(retryAfterSeconds) && retryAfterSeconds > 0) return Math.min(RATE_LIMIT_MAX_MS, Math.round(retryAfterSeconds * 1e3));
393
+ const raw = Math.min(RATE_LIMIT_MAX_MS, RATE_LIMIT_BASE_MS * 2 ** (attempt - 1));
394
+ return Math.round(raw * (.5 + .5 * Math.random()));
395
+ }
396
+ /**
397
+ * 这次限流是否值得重试。
398
+ *
399
+ * 官方把限流分两类:瞬时的(`rate_limit_reached_error` / `overloaded_error`)可以退避
400
+ * 重试;额度耗尽类的(`organization_spend_limit_exceeded` 等)重试多少次都没用,
401
+ * 必须直接失败让用户处理。混为一谈会让额度用尽的用户白等五轮。
402
+ */
403
+ function isRetryableRateLimit(status, businessCode) {
404
+ if (businessCode !== void 0 && (QUOTA_EXHAUSTED_CODES.includes(businessCode) || SECURITY_REJECT_CODES.includes(businessCode))) return false;
405
+ if (businessCode !== void 0 && CONCURRENCY_LIMIT_CODES.includes(businessCode)) return true;
406
+ if (status !== 429 && status !== 503) return false;
407
+ if (businessCode === void 0) return true;
408
+ if (RETRYABLE_RATE_LIMIT_CODES.includes(businessCode)) return true;
409
+ return false;
410
+ }
411
+ /** 可被 signal 打断的等待。 */
412
+ async function sleep(ms, signal) {
413
+ await new Promise((resolve, reject) => {
414
+ const timer = setTimeout(() => {
415
+ signal?.removeEventListener("abort", onAbort);
416
+ resolve();
417
+ }, ms);
418
+ const onAbort = () => {
419
+ clearTimeout(timer);
420
+ reject(/* @__PURE__ */ new Error("ZCode: 请求已取消"));
421
+ };
422
+ if (signal?.aborted === true) {
423
+ clearTimeout(timer);
424
+ reject(/* @__PURE__ */ new Error("ZCode: 请求已取消"));
425
+ return;
426
+ }
427
+ signal?.addEventListener("abort", onAbort, { once: true });
428
+ });
429
+ }
430
+ /** 把失败翻译成用户能据以行动的一句话。 */
431
+ function describeFailure(status, text, businessCode) {
432
+ if (businessCode !== void 0 && QUOTA_EXHAUSTED_CODES.includes(businessCode)) return "ZCode 额度已用尽(1005):请等待额度重置或升级账户,重试不会恢复。";
433
+ if (businessCode !== void 0 && SECURITY_REJECT_CODES.includes(businessCode)) return "ZCode 拒绝了本次请求(3007,安全校验):请检查账号状态后重试。";
434
+ if (status === 429 || status === 503 || businessCode !== void 0 && CONCURRENCY_LIMIT_CODES.includes(businessCode)) return "当前系统繁忙(ZCode 并发上限):已自动重试仍未通过,请稍后再试或升级账户。";
435
+ return `ZCode 请求失败(HTTP ${String(status)}):${text.slice(0, 300)}`;
436
+ }
437
+ /** 从响应体里尽力取出业务码(上游各端点字段名不统一)。 */
438
+ function businessCodeOf(text) {
439
+ try {
440
+ const parsed = JSON.parse(text);
441
+ if (typeof parsed !== "object" || parsed === null) return void 0;
442
+ const record = parsed;
443
+ const inner = typeof record.error === "object" && record.error !== null ? record.error : record;
444
+ for (const key of [
445
+ "code",
446
+ "type",
447
+ "error_code",
448
+ "provider_error_code"
449
+ ]) {
450
+ const value = inner[key];
451
+ if (typeof value === "string" && value !== "") return value;
452
+ if (typeof value === "number") return String(value);
453
+ }
454
+ return;
455
+ } catch {
456
+ return;
457
+ }
458
+ }
459
+ /** 解析一条 SSE `data:` 载荷;无法识别时返回 undefined。 */
460
+ function parseEvent(payload) {
461
+ let value;
462
+ try {
463
+ value = JSON.parse(payload);
464
+ } catch {
465
+ return;
466
+ }
467
+ if (typeof value !== "object" || value === null) return void 0;
468
+ const event = value;
469
+ switch (event.type) {
470
+ case "message_start": {
471
+ const message = event.message ?? {};
472
+ return {
473
+ kind: "message-start",
474
+ model: typeof message.model === "string" ? message.model : "",
475
+ ...message.usage === void 0 ? {} : { usage: toUsage(message.usage) }
476
+ };
477
+ }
478
+ case "content_block_start": {
479
+ const block = event.content_block ?? {};
480
+ const index = Number(event.index ?? 0);
481
+ const blockType = block.type;
482
+ if (blockType === "tool_use") return {
483
+ kind: "block-start",
484
+ index,
485
+ blockType: "tool_use",
486
+ ...typeof block.id === "string" ? { id: block.id } : {},
487
+ ...typeof block.name === "string" ? { name: block.name } : {}
488
+ };
489
+ if (blockType === "thinking" || blockType === "redacted_thinking") return {
490
+ kind: "block-start",
491
+ index,
492
+ blockType: "thinking"
493
+ };
494
+ return {
495
+ kind: "block-start",
496
+ index,
497
+ blockType: "text"
498
+ };
499
+ }
500
+ case "content_block_delta": {
501
+ const delta = event.delta ?? {};
502
+ const index = Number(event.index ?? 0);
503
+ switch (delta.type) {
504
+ case "text_delta": return {
505
+ kind: "text-delta",
506
+ index,
507
+ text: String(delta.text ?? "")
508
+ };
509
+ case "thinking_delta": return {
510
+ kind: "thinking-delta",
511
+ index,
512
+ text: String(delta.thinking ?? "")
513
+ };
514
+ case "signature_delta": return {
515
+ kind: "signature-delta",
516
+ index,
517
+ signature: String(delta.signature ?? "")
518
+ };
519
+ case "input_json_delta": return {
520
+ kind: "tool-delta",
521
+ index,
522
+ id: "",
523
+ argumentsDelta: String(delta.partial_json ?? "")
524
+ };
525
+ default: return;
526
+ }
527
+ }
528
+ case "content_block_stop": return {
529
+ kind: "block-stop",
530
+ index: Number(event.index ?? 0)
531
+ };
532
+ case "message_delta": {
533
+ const delta = event.delta ?? {};
534
+ return {
535
+ kind: "message-delta",
536
+ stopReason: typeof delta.stop_reason === "string" ? delta.stop_reason : "end_turn",
537
+ ...event.usage === void 0 ? {} : { usage: toUsage(event.usage) }
538
+ };
539
+ }
540
+ case "error": {
541
+ const error = event.error ?? {};
542
+ return {
543
+ kind: "error",
544
+ message: typeof error.message === "string" ? error.message : "上游返回错误",
545
+ ...typeof error.type === "string" ? { errorType: error.type } : {}
546
+ };
547
+ }
548
+ default: return;
549
+ }
550
+ }
551
+ /**
552
+ * 发出请求并按行解析流。
553
+ *
554
+ * 用 `for await` 逐块读,遇到 `data:` 行就解析;`[DONE]` 与空行跳过。
555
+ */
556
+ async function* streamMessages(request, prefix, fetchImpl = fetch) {
557
+ const body = JSON.stringify(buildMessagesBody(request, prefix));
558
+ const headers = {
559
+ "content-type": "application/json",
560
+ "anthropic-version": "2023-06-01",
561
+ authorization: `Bearer ${request.apiKey}`,
562
+ "x-api-key": request.apiKey,
563
+ ...request.extraHeaders
564
+ };
565
+ const url = `${request.baseUrl.replace(/\/+$/u, "")}/v1/messages`;
566
+ let response;
567
+ for (let attempt = 1;; attempt += 1) {
568
+ response = await fetchImpl(url, {
569
+ method: "POST",
570
+ headers,
571
+ body,
572
+ ...request.signal === void 0 ? {} : { signal: request.signal }
573
+ });
574
+ if (response.ok) break;
575
+ const text = await response.text().catch(() => "");
576
+ if (text.includes("3012")) throw new Error("ZCode 网关拒绝了本次请求(3012):Start Plan 端点会校验请求里的系统提示词,当前前缀可能已与上游版本不一致,需要重新采集官方提示词。");
577
+ const businessCode = businessCodeOf(text);
578
+ if (!(attempt < RATE_LIMIT_MAX_ATTEMPTS && isRetryableRateLimit(response.status, businessCode))) throw new Error(describeFailure(response.status, text, businessCode));
579
+ const retryAfter = Number(response.headers.get("retry-after") ?? NaN);
580
+ const delayMs = rateLimitDelayMs(attempt, Number.isFinite(retryAfter) ? retryAfter : void 0);
581
+ request.onRateLimit?.({
582
+ attempt,
583
+ status: response.status,
584
+ businessCode,
585
+ delayMs
586
+ });
587
+ await sleep(delayMs, request.signal);
588
+ }
589
+ if (response.body === null) throw new Error("ZCode 响应缺少可读流");
590
+ const reader = response.body.getReader();
591
+ const decoder = new TextDecoder();
592
+ let buffer = "";
593
+ while (true) {
594
+ const { done, value } = await reader.read();
595
+ if (done) break;
596
+ buffer += decoder.decode(value, { stream: true });
597
+ const lines = buffer.split("\n");
598
+ buffer = lines.pop() ?? "";
599
+ for (const line of lines) {
600
+ if (!line.startsWith("data:")) continue;
601
+ const payload = line.slice(5).trim();
602
+ if (payload === "" || payload === "[DONE]") continue;
603
+ const event = parseEvent(payload);
604
+ if (event !== void 0) yield event;
605
+ }
606
+ }
607
+ }
608
+ /**
609
+ * 把会话标识规范成上游要求的长度(6–64 字符)。
610
+ *
611
+ * 上游会校验请求任务 ID 的长度,过短的标识直接返回 1204/1214;这里用设备
612
+ * 标识补齐而不是拒绝请求——标识本身只是让网关把请求关联到同一次会话。
613
+ */
614
+ function normalizeTaskId(sessionId, fallback) {
615
+ const raw = (sessionId ?? fallback).trim();
616
+ const seed = raw === "" ? fallback.trim() : raw;
617
+ if (seed.length >= 6 && seed.length <= 64) return seed;
618
+ if (seed.length > 64) return seed.slice(0, 64);
619
+ const padded = `${seed}${fallback}`.replace(/[^\w-]/gu, "");
620
+ return padded.length >= 6 ? padded.slice(0, 64) : `sess-${padded.padEnd(6, "0")}`;
621
+ }
622
+ //#endregion
623
+ //#region src/host/http/translate.ts
624
+ /** 从消息里取出纯文本(system 消息或拼接用)。 */
625
+ function messageText(message) {
626
+ const content = message.content;
627
+ if (typeof content === "string") return content;
628
+ return content.filter((block) => block.type === "text").map((block) => block.text).join("");
629
+ }
630
+ /** 取出首条 system 消息的文本;没有则返回 undefined。 */
631
+ function leadingSystem(messages) {
632
+ const first = messages[0];
633
+ if (first === void 0 || first.role !== "system") return void 0;
634
+ const text = messageText(first);
635
+ return text.trim() === "" ? void 0 : text;
636
+ }
637
+ /**
638
+ * 把 DSH 消息转换成 Anthropic 消息数组。
639
+ *
640
+ * - system 消息由调用方单独放进 `system` 字段,这里跳过;
641
+ * - `tool-call` 块转成 `tool_use`;
642
+ * - 工具结果消息转成带 `tool_result` 块的 user 消息(Anthropic 要求如此);
643
+ * - 图片块转成 base64 image 块(由调用方提供读取字节的能力)。
644
+ */
645
+ async function toAnthropicMessages(messages, readImage) {
646
+ const result = [];
647
+ for (const message of messages) {
648
+ if (message.role === "system" || message.role === "developer") continue;
649
+ if (message.role === "tool") {
650
+ const block = {
651
+ type: "tool_result",
652
+ tool_use_id: String(message.toolCallId),
653
+ content: messageText(message),
654
+ ...message.isError === true ? { is_error: true } : {}
655
+ };
656
+ const last = result.at(-1);
657
+ if (last !== void 0 && last.role === "user" && isToolResultOnly(last)) last.content.push(block);
658
+ else result.push({
659
+ role: "user",
660
+ content: [block]
661
+ });
662
+ continue;
663
+ }
664
+ const content = typeof message.content === "string" ? [{
665
+ type: "text",
666
+ text: message.content
667
+ }] : message.content;
668
+ const blocks = [];
669
+ for (const item of content) switch (item.type) {
670
+ case "text":
671
+ if (item.text !== "") blocks.push({
672
+ type: "text",
673
+ text: item.text
674
+ });
675
+ break;
676
+ case "reasoning": break;
677
+ case "tool-call":
678
+ blocks.push({
679
+ type: "tool_use",
680
+ id: String(item.id),
681
+ name: item.name,
682
+ input: parseArguments(item.arguments)
683
+ });
684
+ break;
685
+ case "image": {
686
+ const image = readImage === void 0 ? void 0 : await readImage(item);
687
+ if (image !== void 0) blocks.push({
688
+ type: "image",
689
+ source: {
690
+ type: "base64",
691
+ media_type: image.mediaType,
692
+ data: image.data
693
+ }
694
+ });
695
+ break;
696
+ }
697
+ }
698
+ if (blocks.length === 0) continue;
699
+ result.push({
700
+ role: message.role === "assistant" ? "assistant" : "user",
701
+ content: blocks
702
+ });
703
+ }
704
+ return result;
705
+ }
706
+ /** 工具参数是模型产出的 JSON 字符串;解析失败时给空对象而不是让请求失败。 */
707
+ function parseArguments(raw) {
708
+ const text = raw.trim();
709
+ if (text === "") return {};
710
+ try {
711
+ return JSON.parse(text);
712
+ } catch {
713
+ return {};
714
+ }
715
+ }
716
+ /** 这条 user 消息是否只承载工具结果(可继续追加)。 */
717
+ function isToolResultOnly(message) {
718
+ return message.content.every((block) => typeof block === "object" && block !== null && block.type === "tool_result");
719
+ }
720
+ /** DSH 工具声明 → Anthropic tools。 */
721
+ function toAnthropicTools(tools) {
722
+ if (tools === void 0 || tools.length === 0) return void 0;
723
+ return tools.map((tool) => ({
724
+ name: tool.name,
725
+ description: tool.description,
726
+ input_schema: tool.parameters
727
+ }));
728
+ }
729
+ /** DSH 思考等级 → Anthropic `output_config.effort`。 */
730
+ function toEffort(level, map) {
731
+ if (level !== void 0 && map !== void 0) {
732
+ const mapped = map[level];
733
+ if (typeof mapped === "string") return mapped;
734
+ }
735
+ switch (level) {
736
+ case "off":
737
+ case "minimal":
738
+ case "low": return "low";
739
+ case "medium":
740
+ case "high": return "high";
741
+ case "xhigh":
742
+ case "max": return "max";
743
+ default: return "max";
744
+ }
745
+ }
746
+ //#endregion
747
+ //#region src/host/http/adapter.ts
748
+ /**
749
+ * `dsh-zcode` 的 HTTP 执行通道:自行收发 Anthropic Messages。
750
+ *
751
+ * 为什么不用 pi-ai:网关要求 `system` 是官方 agent 的**两段独立文本块**,而
752
+ * pi-ai 会把 system 拼成单块且没有可用的改写点(详见 `messages-client.ts`)。
753
+ * 这条通道自己组装请求,因此能精确满足该要求。
754
+ *
755
+ * 模型元数据(上下文窗口、思考等级、模态)仍由同一目录快照提供,与 HTTP
756
+ * 通道之外的部分保持一致。
757
+ *
758
+ * @module dsh-zcode/http/adapter
759
+ */
760
+ /** 自行发送的适配器。 */
761
+ var ZCodeHttpAdapter = class extends LlmAdapter {
762
+ options;
763
+ constructor(options) {
764
+ super();
765
+ this.options = options;
766
+ }
767
+ providerInfo(provider) {
768
+ return {
769
+ id: provider,
770
+ name: ZCODE_PROVIDER_NAME
771
+ };
772
+ }
773
+ async listModels(provider) {
774
+ return this.options.models().map((model) => ({
775
+ provider,
776
+ id: model.id,
777
+ name: model.name,
778
+ ...model.inputModalities === void 0 ? {} : { inputModalities: [...model.inputModalities] }
779
+ }));
780
+ }
781
+ async resolveModel(provider, model) {
782
+ const found = this.options.models().find((candidate) => candidate.id === model);
783
+ if (found === void 0) throw new LlmError(`dsh-zcode: 未知模型 "${model}"`, "UNKNOWN_MODEL");
784
+ return {
785
+ provider,
786
+ id: found.id,
787
+ name: found.name,
788
+ context: { contextWindow: found.contextWindow },
789
+ defaultMaxTokens: found.maxTokens,
790
+ ...found.inputModalities === void 0 ? {} : { inputModalities: [...found.inputModalities] },
791
+ ...found.reasoning && found.efforts !== void 0 ? { reasoning: {
792
+ efforts: found.efforts.map((effort) => ({ ...effort })),
793
+ ...found.efforts.length === 0 ? {} : { defaultEffort: found.efforts[found.efforts.length - 1].id }
794
+ } } : {}
795
+ };
796
+ }
797
+ async *stream(options) {
798
+ const model = this.options.models().find((candidate) => candidate.id === options.model);
799
+ if (model === void 0) throw new LlmError(`dsh-zcode: 未知模型 "${options.model}"`, "UNKNOWN_MODEL");
800
+ const apiKey = await this.options.apiKey();
801
+ if (apiKey === void 0 || apiKey.length === 0) throw new LlmError("ZCode 凭据不可用:请先在插件详情页登录 ZCode 账号", "MISSING_CREDENTIAL");
802
+ const plan = this.options.baseUrl().includes("/ultra/") ? "ultra" : "start-plan";
803
+ const request = {
804
+ baseUrl: this.options.baseUrl(),
805
+ apiKey,
806
+ model: model.id,
807
+ maxTokens: options.maxTokens ?? model.maxTokens,
808
+ system: options.system ?? leadingSystem(options.messages),
809
+ messages: await toAnthropicMessages(options.messages, this.options.readImage),
810
+ tools: toAnthropicTools(options.tools),
811
+ effort: toEffort(resolveEffort(options.reasoningEffort, model), model.thinkingLevelMap),
812
+ thinking: model.reasoning,
813
+ deviceId: this.options.deviceId(),
814
+ ...options.sessionId === void 0 ? {} : { sessionId: String(options.sessionId) },
815
+ ...options.temperature === void 0 ? {} : { temperature: options.temperature },
816
+ ...options.signal === void 0 ? {} : { signal: options.signal },
817
+ ...this.options.headers === void 0 ? {} : { extraHeaders: this.options.headers() },
818
+ onRateLimit: (info) => {
819
+ this.options.logger?.warn(`dsh-zcode: 触发限流(HTTP ${String(info.status)}${info.businessCode === void 0 ? "" : `, ${info.businessCode}`}),第 ${String(info.attempt)} 次重试前等待 ${String(Math.round(info.delayMs / 1e3))} 秒`);
820
+ }
821
+ };
822
+ const prefix = plan === "start-plan" ? {
823
+ head: START_PLAN_PREFIX_HEAD,
824
+ body: START_PLAN_PREFIX_BODY
825
+ } : {
826
+ head: "",
827
+ body: ""
828
+ };
829
+ const blocks = /* @__PURE__ */ new Map();
830
+ let usage = {
831
+ inputTokens: 0,
832
+ outputTokens: 0
833
+ };
834
+ let stopReason = "end_turn";
835
+ try {
836
+ for await (const event of streamMessages(request, prefix, this.options.fetchImpl)) switch (event.kind) {
837
+ case "message-start":
838
+ if (event.usage !== void 0) usage = mergeUsage(usage, event.usage);
839
+ break;
840
+ case "block-start":
841
+ blocks.set(event.index, {
842
+ blockType: event.blockType,
843
+ ...event.id === void 0 ? {} : { id: event.id },
844
+ ...event.name === void 0 ? {} : { name: event.name },
845
+ text: [],
846
+ arguments: []
847
+ });
848
+ if (event.blockType === "thinking") yield {
849
+ type: "block-start",
850
+ index: event.index,
851
+ blockType: "reasoning"
852
+ };
853
+ else if (event.blockType === "text") yield {
854
+ type: "block-start",
855
+ index: event.index,
856
+ blockType: "text"
857
+ };
858
+ break;
859
+ case "text-delta":
860
+ if (event.text !== "") {
861
+ blocks.get(event.index)?.text.push(event.text);
862
+ yield {
863
+ type: "text-delta",
864
+ index: event.index,
865
+ text: event.text
866
+ };
867
+ }
868
+ break;
869
+ case "thinking-delta":
870
+ if (event.text !== "") {
871
+ blocks.get(event.index)?.text.push(event.text);
872
+ yield {
873
+ type: "reasoning-delta",
874
+ index: event.index,
875
+ text: event.text
876
+ };
877
+ }
878
+ break;
879
+ case "signature-delta": {
880
+ const state = blocks.get(event.index);
881
+ if (state !== void 0) state.signature = `${state.signature ?? ""}${event.signature}`;
882
+ break;
883
+ }
884
+ case "tool-delta": {
885
+ const state = blocks.get(event.index);
886
+ if (state !== void 0) state.arguments.push(event.argumentsDelta);
887
+ const id = state?.id ?? `tool-${String(event.index)}`;
888
+ yield {
889
+ type: "tool-call-delta",
890
+ index: event.index,
891
+ id,
892
+ ...state?.name === void 0 ? {} : { name: state.name },
893
+ argumentsDelta: event.argumentsDelta
894
+ };
895
+ break;
896
+ }
897
+ case "block-stop": {
898
+ const state = blocks.get(event.index);
899
+ if (state === void 0) break;
900
+ if (state.blockType === "text") yield {
901
+ type: "block-end",
902
+ index: event.index,
903
+ block: {
904
+ type: "text",
905
+ text: state.text.join("")
906
+ }
907
+ };
908
+ else if (state.blockType === "thinking") yield {
909
+ type: "block-end",
910
+ index: event.index,
911
+ block: {
912
+ type: "reasoning",
913
+ text: state.text.join("")
914
+ }
915
+ };
916
+ else if (state.blockType === "tool_use" && state.id !== void 0) yield {
917
+ type: "block-end",
918
+ index: event.index,
919
+ block: {
920
+ type: "tool-call",
921
+ id: state.id,
922
+ name: state.name ?? "",
923
+ arguments: state.arguments.join("")
924
+ }
925
+ };
926
+ break;
927
+ }
928
+ case "message-delta":
929
+ stopReason = event.stopReason;
930
+ if (event.usage !== void 0) usage = mergeUsage(usage, event.usage);
931
+ break;
932
+ case "error":
933
+ yield {
934
+ type: "finish",
935
+ reason: {
936
+ kind: "error",
937
+ failure: {
938
+ message: `ZCode: ${event.message}`,
939
+ code: "PROVIDER"
940
+ }
941
+ }
942
+ };
943
+ return;
944
+ }
945
+ } catch (error) {
946
+ const message = error instanceof Error ? error.message : String(error);
947
+ if (options.signal?.aborted === true) {
948
+ yield {
949
+ type: "finish",
950
+ reason: {
951
+ kind: "aborted",
952
+ failure: {
953
+ message: "ZCode: 请求已取消",
954
+ code: "ABORTED"
955
+ }
956
+ }
957
+ };
958
+ return;
959
+ }
960
+ this.options.logger?.warn(`zcode: 请求失败 ${message}`);
961
+ yield {
962
+ type: "finish",
963
+ reason: {
964
+ kind: "error",
965
+ failure: {
966
+ message,
967
+ code: "PROVIDER"
968
+ }
969
+ }
970
+ };
971
+ return;
972
+ }
973
+ yield {
974
+ type: "usage",
975
+ usage
976
+ };
977
+ const blockReplay = [...blocks.entries()].sort(([a], [b]) => a - b).map(([, state]) => state.signature === void 0 ? null : { signature: state.signature });
978
+ yield {
979
+ type: "finish",
980
+ reason: stopReason === "tool_use" ? { kind: "tool-calls" } : stopReason === "max_tokens" ? { kind: "max-tokens" } : { kind: "stop" },
981
+ ...blockReplay.some((entry) => entry !== null) ? { replayState: {
982
+ response: { stopReason },
983
+ blocks: blockReplay
984
+ } } : {}
985
+ };
986
+ }
987
+ /** 端点策略(诊断用)。 */
988
+ planOf(baseUrl) {
989
+ return baseUrl.includes("/ultra/") ? "ultra" : "start-plan";
990
+ }
991
+ };
992
+ /**
993
+ * 把调用方传入的思考等级收敛到该模型真正可用的档位。
994
+ *
995
+ * 历史会话可能保存了上一版目录里存在、现在被过滤掉的等级(例如上游只支持
996
+ * low/high/max 时,旧值 `xhigh` 已不在候选里)。此时直接透传会让请求带上
997
+ * 一个语义不明的档位,界面也会把它当作显示名原样展示,因此回退到默认档。
998
+ */
999
+ function resolveEffort(requested, model) {
1000
+ if (requested === void 0) return void 0;
1001
+ const efforts = model.efforts;
1002
+ if (efforts === void 0 || efforts.length === 0) return requested;
1003
+ const available = efforts.map((effort) => String(effort.id));
1004
+ if (available.includes(requested)) return requested;
1005
+ return available[available.length - 1];
1006
+ }
1007
+ /** 合并两次 usage:上游在 message_start 与 message_delta 各报一部分。 */
1008
+ function mergeUsage(base, next) {
1009
+ return {
1010
+ inputTokens: Math.max(base.inputTokens, next.inputTokens),
1011
+ outputTokens: Math.max(base.outputTokens, next.outputTokens),
1012
+ ...next.cacheReadTokens === void 0 && base.cacheReadTokens === void 0 ? {} : { cacheReadTokens: next.cacheReadTokens ?? base.cacheReadTokens },
1013
+ ...next.cacheWriteTokens === void 0 && base.cacheWriteTokens === void 0 ? {} : { cacheWriteTokens: next.cacheWriteTokens ?? base.cacheWriteTokens }
1014
+ };
1015
+ }
1016
+ /**
1017
+ * 把一个 DSH 思考等级 id 与显示名组成适配器可用的选项。
1018
+ *
1019
+ * 用官方的品牌构造函数而不是断言,避免手写 `as` 绕过类型约束。
1020
+ */
1021
+ function reasoningEffort(id, name) {
1022
+ return {
1023
+ id: ReasoningEffortId(id),
1024
+ name
1025
+ };
1026
+ }
1027
+ //#endregion
1028
+ //#region src/host/provider.ts
1029
+ /** 流空闲上限:思考型模型的长停顿不应被误判断流。 */
1030
+ const ZCODE_STREAM_IDLE_TIMEOUT_MS = 3e5;
1031
+ /** 每个请求的 base64 图片总负载上限(与 DSH 默认一致)。 */
1032
+ const ZCODE_MAX_REQUEST_IMAGE_BYTES = 20971520;
1033
+ /** 每张请求图片的像素预算(2048 × 2048)。 */
1034
+ const ZCODE_REQUEST_IMAGE_PIXEL_BUDGET = 4194304;
1035
+ /** 每张请求图片的编码后字节目标。 */
1036
+ const ZCODE_REQUEST_IMAGE_MAX_BYTES = 1048576;
1037
+ /**
1038
+ * 把目录条目投影为 pi-ai 模型。
1039
+ *
1040
+ * @param model - 目录条目。
1041
+ * @param baseURL - 覆盖端点(端点策略决定,见 `ZCodeEndpointPlan`)。
1042
+ */
1043
+ function toPiModel(model, baseURL) {
1044
+ return {
1045
+ id: model.id,
1046
+ name: model.name,
1047
+ api: model.api,
1048
+ provider: ZCODE_PROVIDER,
1049
+ baseUrl: baseURL ?? model.baseURL,
1050
+ input: [...model.input],
1051
+ cost: {
1052
+ input: 0,
1053
+ output: 0,
1054
+ cacheRead: 0,
1055
+ cacheWrite: 0
1056
+ },
1057
+ reasoning: model.reasoning,
1058
+ contextWindow: model.contextWindow,
1059
+ maxTokens: model.maxTokens,
1060
+ type: "chat",
1061
+ ...model.reasoning ? { compat: { forceAdaptiveThinking: true } } : {},
1062
+ ...model.thinkingLevelMap === void 0 ? {} : { thinkingLevelMap: { ...model.thinkingLevelMap } }
1063
+ };
1064
+ }
1065
+ /**
1066
+ * 给推理请求补 `authorization` 头。
1067
+ *
1068
+ * `PiAiAdapter` 把 `resolveApiKey` 的结果写成 `x-api-key`;ZCode 网关的
1069
+ * Anthropic 兼容层同时读 `authorization`,官方客户端两者都发。这里在流入口
1070
+ * 统一补上,避免只带一个头被网关当成不完整客户端。
1071
+ *
1072
+ * 必须是同步改写:`stream` / `streamSimple` 的返回类型是直接可迭代的事件流,
1073
+ * 不是 Promise;把入口改成 async 会破坏调用方的同步取流契约。
1074
+ */
1075
+ function withAuthorizationHeader(streams) {
1076
+ const decorate = (model, options) => {
1077
+ const token = options?.apiKey;
1078
+ if (token === void 0 || token.length === 0) return model;
1079
+ const current = model.headers;
1080
+ return {
1081
+ ...model,
1082
+ headers: {
1083
+ ...current,
1084
+ authorization: `Bearer ${token}`
1085
+ }
1086
+ };
1087
+ };
1088
+ return {
1089
+ ...streams,
1090
+ stream: (model, context, options) => streams.stream(decorate(model, options), context, options),
1091
+ streamSimple: (model, context, options) => streams.streamSimple(decorate(model, options), context, options)
1092
+ };
1093
+ }
1094
+ /** Anthropic Messages 实现;当前只有这一族。 */
1095
+ function zcodeApis() {
1096
+ return { "anthropic-messages": withAuthorizationHeader(anthropicMessagesApi()) };
1097
+ }
1098
+ function providerAuth(resolveToken) {
1099
+ return { apiKey: {
1100
+ name: "ZCode 账号凭据",
1101
+ async resolve() {
1102
+ const key = await resolveToken();
1103
+ if (key === void 0 || key.length === 0) return void 0;
1104
+ return {
1105
+ auth: { apiKey: key },
1106
+ source: "ZCode 账号登录"
1107
+ };
1108
+ }
1109
+ } };
1110
+ }
1111
+ /** 构建 provider。 */
1112
+ function createZCodeProvider(models, resolveToken, baseURL) {
1113
+ return createProvider({
1114
+ id: ZCODE_PROVIDER,
1115
+ name: ZCODE_PROVIDER_NAME,
1116
+ auth: providerAuth(resolveToken),
1117
+ models: models.map((model) => toPiModel(model, baseURL)),
1118
+ api: zcodeApis()
1119
+ });
1120
+ }
1121
+ /** 构建供应商 profile。 */
1122
+ function createZCodeProfile(models, resolveToken, headers = {}, baseURL) {
1123
+ return {
1124
+ provider: ZCODE_PROVIDER,
1125
+ displayName: ZCODE_PROVIDER_NAME,
1126
+ streamIdleTimeoutMs: ZCODE_STREAM_IDLE_TIMEOUT_MS,
1127
+ maxRequestImageBytes: ZCODE_MAX_REQUEST_IMAGE_BYTES,
1128
+ requestImagePixelBudget: ZCODE_REQUEST_IMAGE_PIXEL_BUDGET,
1129
+ requestImageMaxBytes: ZCODE_REQUEST_IMAGE_MAX_BYTES,
1130
+ retryPolicy: resolveRetryPolicy(void 0, "dsh-zcode retryPolicy"),
1131
+ configuredMaxTokens: /* @__PURE__ */ new Map(),
1132
+ headers: { ...headers },
1133
+ piProvider: createZCodeProvider(models, resolveToken, baseURL),
1134
+ modelErrors: /* @__PURE__ */ new Map()
1135
+ };
1136
+ }
1137
+ /**
1138
+ * 注册并持有唯一的 `dsh-zcode` 路由。
1139
+ *
1140
+ * 路由被其他适配器占用时记录原因并保持未注册状态,由详情页把冲突说明展示
1141
+ * 给用户,而不是让整个插件失效。
1142
+ */
1143
+ var ZCodeProviderRuntime = class {
1144
+ options;
1145
+ snapshot = /* @__PURE__ */ new Map();
1146
+ /** HTTP 通道:同时是模型元数据的唯一来源。 */
1147
+ /** 执行通道:自行收发 Anthropic Messages(满足网关对 system 分块的要求)。 */
1148
+ adapter;
1149
+ registration;
1150
+ registered = false;
1151
+ error;
1152
+ currentModels = [];
1153
+ endpointPlan = DEFAULT_ZCODE_ENDPOINT_PLAN;
1154
+ constructor(options) {
1155
+ this.options = options;
1156
+ this.adapter = new ZCodeHttpAdapter({
1157
+ models: () => this.currentModels.map((model) => ({
1158
+ id: model.id,
1159
+ name: model.name,
1160
+ contextWindow: model.contextWindow,
1161
+ maxTokens: model.maxTokens,
1162
+ reasoning: model.reasoning,
1163
+ inputModalities: model.input,
1164
+ ...model.thinkingLevelMap === void 0 ? {} : {
1165
+ efforts: reasoningEffortsOf(model.thinkingLevelMap),
1166
+ thinkingLevelMap: model.thinkingLevelMap
1167
+ }
1168
+ })),
1169
+ baseUrl: () => ZCODE_ANTHROPIC_BASE_URLS[this.endpointPlan],
1170
+ apiKey: () => this.options.resolveToken(this.endpointPlan),
1171
+ deviceId: () => this.options.deviceMid(),
1172
+ headers: () => zcodeSourceHeaders(this.options.deviceMid()),
1173
+ readImage: async (block) => {
1174
+ const attachments = this.options.resolveAttachments();
1175
+ if (attachments === void 0) return void 0;
1176
+ try {
1177
+ const stored = await attachments.readImage(block.attachment);
1178
+ return {
1179
+ mediaType: block.attachment.mediaType,
1180
+ data: Buffer.from(stored.data).toString("base64")
1181
+ };
1182
+ } catch (error) {
1183
+ this.options.logger.warn(`dsh-zcode: 读取图片附件失败,已跳过:${error instanceof Error ? error.message : String(error)}`);
1184
+ return;
1185
+ }
1186
+ },
1187
+ logger: this.options.logger
1188
+ });
1189
+ }
1190
+ /** 当前目录模型。 */
1191
+ models() {
1192
+ return this.currentModels;
1193
+ }
1194
+ /** 路由注册状态。 */
1195
+ routeStatus() {
1196
+ return [{
1197
+ id: ZCODE_PROVIDER,
1198
+ name: ZCODE_PROVIDER_NAME,
1199
+ registered: this.registered,
1200
+ ...this.error === void 0 ? {} : { error: this.error },
1201
+ modelCount: this.currentModels.length,
1202
+ endpointPlan: this.endpointPlan
1203
+ }];
1204
+ }
1205
+ /** 在插件 fiber 上注册路由。 */
1206
+ register(ctx) {
1207
+ ctx.effect(() => {
1208
+ this.registered = false;
1209
+ this.error = void 0;
1210
+ try {
1211
+ this.registration = ctx.llm.registerAdapter([ZCODE_PROVIDER], this.adapter);
1212
+ this.registered = true;
1213
+ } catch (registerError) {
1214
+ this.error = registerError instanceof Error ? registerError.message : String(registerError);
1215
+ this.options.logger.warn(`dsh-zcode: 无法注册 ${ZCODE_PROVIDER} 路由:${this.error}`);
1216
+ }
1217
+ return () => {
1218
+ this.registration?.();
1219
+ this.registration = void 0;
1220
+ this.registered = false;
1221
+ };
1222
+ }, "dsh-zcode: LLM 路由");
1223
+ }
1224
+ /** 当前端点策略。 */
1225
+ plan() {
1226
+ return this.endpointPlan;
1227
+ }
1228
+ /**
1229
+ * 用新目录与端点策略重建 profile;下一次 LLM 操作即可见。
1230
+ *
1231
+ * 端点策略决定 baseURL 与凭据类型,两者必须一起换:只换端点不换凭据会直接
1232
+ * 401(`ultra` 不接受账号 JWT)。
1233
+ */
1234
+ update(models, plan = this.endpointPlan) {
1235
+ this.currentModels = models;
1236
+ this.endpointPlan = plan;
1237
+ }
1238
+ };
1239
+ /**
1240
+ * 需要把 Start Plan 的官方提示词前缀注入到请求里。
1241
+ *
1242
+ * 落点说明:宿主在 `adapterStream` 里做的是
1243
+ * `const call = await adapter.prepareCall(...); dispatch = (o) => call.stream(o)`
1244
+ * 也就是说 `prepareCall` 返回的 `stream` 就是最终取流入口。因此包装它即可
1245
+ * 影响真正的请求,不需要改上游包、也不需要自己发 HTTP。
1246
+ *
1247
+ * 之所以不能只重写 `stream()`:设置了 `prepared` 的调用路径不会经过它。
1248
+ */
1249
+ /**
1250
+ * 选择器里的档位顺序(从弱到强)与显示名。
1251
+ *
1252
+ * 这是 DSH 的通用档位词表,不是上游词表:一个上游等级可能对应多个档位
1253
+ * (例如上游 `low` 同时承接 `minimal` 与 `low`)。
1254
+ */
1255
+ const REASONING_EFFORT_LADDER = [
1256
+ {
1257
+ id: "minimal",
1258
+ name: "Minimal"
1259
+ },
1260
+ {
1261
+ id: "low",
1262
+ name: "Low"
1263
+ },
1264
+ {
1265
+ id: "medium",
1266
+ name: "Medium"
1267
+ },
1268
+ {
1269
+ id: "high",
1270
+ name: "High"
1271
+ },
1272
+ {
1273
+ id: "xhigh",
1274
+ name: "Extra High"
1275
+ },
1276
+ {
1277
+ id: "max",
1278
+ name: "Max"
1279
+ }
1280
+ ];
1281
+ /**
1282
+ * 把目录的思考等级映射转成选择器可用的档位。
1283
+ *
1284
+ * 两条规则都是必需的:
1285
+ * 1. 映射为 `null` 的等级上游不支持,剔除;否则选中即被拒。
1286
+ * 2. **多个档位映射到同一上游值时只保留最强的一档**。DSH 的通用档位词表比
1287
+ * 上游更细(`minimal`/`low`、`medium`/`high`、`xhigh`/`max` 常落在同一个上游
1288
+ * 值上),把它们全列出来会让用户以为选到了不同强度,实际发出去的请求字节
1289
+ * 完全相同——这是「看着能调、其实没调」的假象。
1290
+ *
1291
+ * 代价:若宿主配置里存着被合并掉的旧值(如 `xhigh`),它会找不到对应项而原样
1292
+ * 显示内部 id。请求侧由 `resolveEffort` 兜底到合法档位,不会发错。
1293
+ */
1294
+ function reasoningEffortsOf(map) {
1295
+ const supported = REASONING_EFFORT_LADDER.filter((level) => {
1296
+ return typeof map[level.id] === "string";
1297
+ });
1298
+ const claimed = /* @__PURE__ */ new Set();
1299
+ const kept = /* @__PURE__ */ new Set();
1300
+ for (const level of [...supported].reverse()) {
1301
+ const upstream = map[level.id];
1302
+ if (typeof upstream !== "string" || claimed.has(upstream)) continue;
1303
+ claimed.add(upstream);
1304
+ kept.add(level.id);
1305
+ }
1306
+ return supported.filter((level) => kept.has(level.id)).map((level) => reasoningEffort(level.id, level.name));
1307
+ }
1308
+ //#endregion
1309
+ //#region src/contracts/usage.ts
1310
+ /** 剩余百分比。 */
1311
+ function remainingPercent(window) {
1312
+ return Math.max(0, Math.min(100, 100 - window.percent));
1313
+ }
1314
+ /** 距离重置的毫秒数;无效或缺失时间返回 undefined。 */
1315
+ function resetAfterMs(window, now = Date.now()) {
1316
+ if (window.resetsAt === void 0) return void 0;
1317
+ const at = Date.parse(window.resetsAt);
1318
+ if (!Number.isFinite(at)) return void 0;
1319
+ return Math.max(0, at - now);
1320
+ }
1321
+ /**
1322
+ * 把上游额度换算成百分比。
1323
+ *
1324
+ * 上游给的是整数 token 单位,可能为 0(活动额度未发放);此时按 0% 处理而不是
1325
+ * 制造 NaN 或 Infinity——界面上「0 / 0」比「NaN%」可读得多。
1326
+ */
1327
+ function usagePercent(used, total) {
1328
+ if (!Number.isFinite(used) || !Number.isFinite(total) || total <= 0) return 0;
1329
+ return Math.max(0, Math.min(100, used / total * 100));
1330
+ }
1331
+ //#endregion
1332
+ //#region src/host/usage.ts
1333
+ const DEFAULT_CACHE_MS = 6e4;
1334
+ /** 额度服务。 */
1335
+ var ZCodeUsageService = class {
1336
+ cached;
1337
+ inflight;
1338
+ options;
1339
+ constructor(options) {
1340
+ this.options = options;
1341
+ }
1342
+ get now() {
1343
+ return this.options.now?.() ?? Date.now();
1344
+ }
1345
+ /** 失效缓存,强制下次读取走网络。 */
1346
+ invalidate() {
1347
+ this.cached = void 0;
1348
+ }
1349
+ /** 读取额度;`force` 为 true 时跳过缓存。 */
1350
+ async read(force = false) {
1351
+ const cacheMs = this.options.cacheMs ?? DEFAULT_CACHE_MS;
1352
+ if (!force && this.cached !== void 0 && this.now - this.cached.at < cacheMs) return this.cached.result;
1353
+ this.inflight ??= this.load().finally(() => {
1354
+ this.inflight = void 0;
1355
+ });
1356
+ return this.inflight;
1357
+ }
1358
+ async load() {
1359
+ const token = await this.options.resolveToken();
1360
+ if (token === void 0) {
1361
+ const result = {
1362
+ ok: false,
1363
+ reason: "not-signed-in",
1364
+ message: "尚未登录 ZCode 账号"
1365
+ };
1366
+ this.cached = {
1367
+ result,
1368
+ at: this.now
1369
+ };
1370
+ return result;
1371
+ }
1372
+ try {
1373
+ const snapshot = await this.options.client.balance(token);
1374
+ const windows = mergeBalanceEntries(toBalanceEntries(snapshot.balances)).map((entry) => {
1375
+ const percent = usagePercent(entry.usedUnits, entry.totalUnits);
1376
+ return {
1377
+ label: entry.showName,
1378
+ ...entry.capability === void 0 ? {} : { capability: entry.capability },
1379
+ totalUnits: entry.totalUnits,
1380
+ usedUnits: entry.usedUnits,
1381
+ remainingUnits: entry.remainingUnits,
1382
+ percent,
1383
+ ...entry.period === void 0 ? {} : { period: entry.period },
1384
+ ...entry.periodEnd === void 0 ? entry.expiresAt === void 0 ? {} : { resetsAt: new Date(entry.expiresAt).toISOString() } : { resetsAt: new Date(entry.periodEnd).toISOString() },
1385
+ exhausted: entry.remainingUnits <= 0
1386
+ };
1387
+ });
1388
+ const family = await this.options.resolveFamily?.();
1389
+ const result = {
1390
+ ok: true,
1391
+ usage: {
1392
+ plans: snapshot.plans.map((plan) => ({
1393
+ planId: plan.planId,
1394
+ ...plan.name === void 0 ? {} : { name: plan.name },
1395
+ ...plan.endsAt === void 0 ? {} : { endsAt: plan.endsAt }
1396
+ })),
1397
+ windows,
1398
+ fetchedAt: this.now,
1399
+ ...family === void 0 ? {} : { family }
1400
+ }
1401
+ };
1402
+ this.cached = {
1403
+ result,
1404
+ at: this.now
1405
+ };
1406
+ return result;
1407
+ } catch (error) {
1408
+ const message = error instanceof Error ? error.message : String(error);
1409
+ this.options.logger?.warn("dsh-zcode: 读取额度失败", error);
1410
+ const result = {
1411
+ ok: false,
1412
+ reason: "request-failed",
1413
+ message
1414
+ };
1415
+ this.cached = {
1416
+ result,
1417
+ at: this.now
1418
+ };
1419
+ return result;
1420
+ }
1421
+ }
1422
+ };
1423
+ //#endregion
1424
+ //#region src/index.ts
1425
+ /** 稳定的宿主插件名。 */
1426
+ const name = "dsh-zcode-plugin";
1427
+ /**
1428
+ * 模型层是硬依赖;Web 路由是可选增强。
1429
+ *
1430
+ * headless / tui 一类的 profile 没有 `webServer`,插件仍应提供服务;
1431
+ * 详情页与同源路由通过 `ctx.inject(['webServer'], …)` 只在 Web 装配下注册。
1432
+ * 否则插件会停在 pending,模型路由也不会注册(表现为 `NO_ADAPTER`)。
1433
+ */
1434
+ const inject = ["llm"];
1435
+ /** 定时刷新的最短间隔(毫秒);偏好里的 0 表示关闭。 */
1436
+ const MIN_REFRESH_INTERVAL_MS = 6e4;
1437
+ function apply(ctx) {
1438
+ const credentials = new ZCodeCredentialStore();
1439
+ const preferences = new ZCodePreferencesStore();
1440
+ const client = new ZCodeConsoleClient();
1441
+ let refresh = async () => {};
1442
+ /**
1443
+ * 桌面授权码流程的回调地址。
1444
+ *
1445
+ * 桌面端用 `zcode://oauth/callback` 自定义协议接回调;插件在浏览器里注册不了
1446
+ * 自定义协议,因此回调到插件自己的同源路由,由宿主侧拿 code 换取凭据。
1447
+ * 地址在装配时才能确定(依赖实际监听端口与代理头),所以按需解析。
1448
+ */
1449
+ const callbackBaseUrl = () => {
1450
+ const configured = process.env.DSH_ZCODE_CALLBACK_URL?.trim() ?? "";
1451
+ return configured === "" ? void 0 : configured;
1452
+ };
1453
+ const auth = new ZCodeAuthService({
1454
+ store: credentials,
1455
+ client,
1456
+ onCredentialChange: () => {
1457
+ refresh(true);
1458
+ },
1459
+ logger: ctx.logger,
1460
+ callbackBaseUrl
1461
+ });
1462
+ const catalog = new ZCodeCatalogService({
1463
+ client,
1464
+ resolveToken: () => auth.zcodeJwt(),
1465
+ resolveFamily: async () => (await auth.credential())?.family,
1466
+ logger: ctx.logger
1467
+ });
1468
+ const runtime = new ZCodeProviderRuntime({
1469
+ resolveToken: async (plan) => plan === "ultra" ? auth.accessToken() : auth.zcodeJwt(),
1470
+ deviceMid: () => client.deviceMid(),
1471
+ resolveAttachments: () => ctx.get("attachments"),
1472
+ logger: ctx.logger
1473
+ });
1474
+ runtime.register(ctx);
1475
+ const usage = new ZCodeUsageService({
1476
+ client,
1477
+ resolveToken: () => auth.zcodeJwt(),
1478
+ resolveFamily: async () => (await auth.credential())?.family,
1479
+ logger: ctx.logger
1480
+ });
1481
+ /** 把目录快照投影成 provider 模型,按「隐藏模型」过滤并按端点策略重建。 */
1482
+ const applyCatalog = async () => {
1483
+ const snapshot = catalog.snapshot();
1484
+ if (snapshot === void 0) return;
1485
+ const prefs = await preferences.read();
1486
+ const hidden = new Set(prefs.hiddenModels);
1487
+ runtime.update(snapshot.models.filter((model) => !hidden.has(model.id)), prefs.endpointPlan);
1488
+ };
1489
+ refresh = async (force) => {
1490
+ usage.invalidate();
1491
+ await (force ? catalog.refresh() : catalog.refreshIfStale());
1492
+ await applyCatalog();
1493
+ };
1494
+ let timer;
1495
+ /** 按偏好重建定时器;偏好变化或首次启动时调用。 */
1496
+ const schedule = async () => {
1497
+ if (timer !== void 0) {
1498
+ clearInterval(timer);
1499
+ timer = void 0;
1500
+ }
1501
+ const prefs = await preferences.read();
1502
+ if (prefs.refreshMinutes <= 0) return;
1503
+ const interval = Math.max(MIN_REFRESH_INTERVAL_MS, prefs.refreshMinutes * 6e4);
1504
+ timer = setInterval(() => {
1505
+ refresh(false).catch(() => void 0);
1506
+ }, interval);
1507
+ timer.unref?.();
1508
+ };
1509
+ ctx.effect(() => {
1510
+ (async () => {
1511
+ try {
1512
+ runtime.update(ZCODE_BUILTIN_MODEL_IDS.map((id) => toModel({
1513
+ id,
1514
+ family: "bigmodel"
1515
+ })), (await preferences.read()).endpointPlan);
1516
+ await refresh(false);
1517
+ await applyCatalog();
1518
+ await schedule();
1519
+ } catch (error) {
1520
+ ctx.logger.warn("dsh-zcode: 初始化模型目录失败", error);
1521
+ }
1522
+ })();
1523
+ return () => {
1524
+ if (timer !== void 0) clearInterval(timer);
1525
+ timer = void 0;
1526
+ };
1527
+ }, "dsh-zcode: 目录与额度刷新");
1528
+ ctx.inject(["webServer"], (scoped) => {
1529
+ registerZCodeRoutes(scoped, {
1530
+ auth,
1531
+ usage,
1532
+ catalog,
1533
+ preferences,
1534
+ refresh,
1535
+ preferencesChanged: async () => {
1536
+ await applyCatalog();
1537
+ await schedule();
1538
+ },
1539
+ routes: () => runtime.routeStatus()
1540
+ });
1541
+ });
1542
+ }
1543
+ //#endregion
1544
+ export { DEFAULT_ZCODE_ENDPOINT_PLAN, DEFAULT_ZCODE_PREFERENCES, START_PLAN_PREFIX_BODY, START_PLAN_PREFIX_HEAD, ZCODE_ANTHROPIC_BASE_URL, ZCODE_ANTHROPIC_BASE_URLS, ZCODE_AUTH_FILENAME, ZCODE_BUILTIN_MODEL_IDS, ZCODE_CATALOG_UNAVAILABLE, ZCODE_CLIENT_VERSION, ZCODE_ENDPOINT_PLAN_LABELS, ZCODE_FALLBACK_CONTEXT_WINDOW, ZCODE_FALLBACK_MAX_TOKENS, ZCODE_FAMILIES, ZCODE_FAMILY_LABELS, ZCODE_ORIGIN, ZCODE_ORIGIN_NOT_TRUSTED, ZCODE_PREFERENCES_FILENAME, ZCODE_PROVIDER, ZCODE_PROVIDER_NAME, ZCODE_REASONING_LEVELS, ZCODE_REFRESH_MINUTES, ZCODE_ROUTES, ZCODE_ROUTE_PREFIX, ZCODE_USER_AGENT, ZCodeAuthService, ZCodeCatalogService, ZCodeConsoleClient, ZCodeConsoleError, ZCodeCredentialStore, ZCodeHttpAdapter, ZCodePreferencesStore, ZCodeProviderRuntime, ZCodeUsageService, apply, buildMessagesBody, createZCodeProfile, createZCodeProvider, findModel, inject, leadingSystem, messageText, modelIdOfCapability, modelIdsFromBalances, name, normalizeModelId, normalizePreferences, parseEvent, registerZCodeRoutes, remainingPercent, resetAfterMs, selectableModels, startPlanSystemPrefix, streamMessages, thinkingLevelMapOf, toAnthropicMessages, toAnthropicTools, toBalanceEntries, toEffort, toModel, toPiModel, toUsage, trustedRequest, usagePercent, zcodeAuthPath, zcodeMessagesUrl, zcodeModelHeaders, zcodePreferencesPath, zcodeSourceHeaders };