@tnnevol/dsh-zcode 0.0.0-stage → 6.0.6

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,685 @@
1
+ import { _ as ZCODE_USER_AGENT, a as ZCODE_ANTHROPIC_BETA, c as ZCODE_CLIENT_CONFIGS_URL, f as ZCODE_FAMILY_LABELS, l as ZCODE_CLIENT_VERSION, n as ZCODE_AGENT_CONFIGS_URL, o as ZCODE_ANTHROPIC_VERSION, p as ZCODE_OAUTH_TOKEN_URL, r as ZCODE_ANTHROPIC_BASE_URL, s as ZCODE_BALANCE_URL } from "./identity-DE3rOYuE.js";
2
+ //#region src/contracts/catalog.ts
3
+ /** pi-ai 的思考等级全集,顺序即选择器展示顺序。 */
4
+ const ZCODE_REASONING_LEVELS = [
5
+ "off",
6
+ "minimal",
7
+ "low",
8
+ "medium",
9
+ "high",
10
+ "xhigh",
11
+ "max"
12
+ ];
13
+ /** 一天的秒数,用于推导额度周期。 */
14
+ const DAY_SECONDS = 86400;
15
+ /**
16
+ * 从窗口时长推导额度周期。
17
+ *
18
+ * 上游的额度条目**没有** `period` 字段,只有 `period_start` / `period_end`
19
+ * 两个 Unix 秒。早期实现读 `period` 得到 undefined,界面上所有额度都显示成
20
+ * "其它",用户看不出"这条是每日额度、那条是活动额度"——而这正是他判断还能
21
+ * 不能继续用的关键。
22
+ *
23
+ * 判定按"接近某个标准周期"而不是"不超过某个上界":日额度窗口实测是
24
+ * 86,399 秒(起止都取整点边界,差 1 秒),而某些套餐的额度窗口是"激活时刻
25
+ * 到套餐结束",实测 168,557 秒(约 1.95 天)。用上界判定会把后者误标成
26
+ * "每周"——那是一条两天后就要重置的额度,标成每周等于给了错误预期。
27
+ * 因此每个周期给一个带宽,落不进任何带宽的标为 `custom`。
28
+ */
29
+ function deriveBalancePeriod(periodStart, periodEnd) {
30
+ if (periodStart === void 0 || periodEnd === void 0) return "unknown";
31
+ const seconds = periodEnd - periodStart;
32
+ if (!Number.isFinite(seconds) || seconds <= 0) return "unknown";
33
+ const days = seconds / DAY_SECONDS;
34
+ if (days >= .9 && days <= 1.1) return "daily";
35
+ if (days >= 6.5 && days <= 7.5) return "weekly";
36
+ if (days >= 27 && days <= 32) return "monthly";
37
+ if (days >= 180) return "one_time";
38
+ return "custom";
39
+ }
40
+ /**
41
+ * 把 `capability` 还原成模型 id。
42
+ *
43
+ * 上游写法是 `model:<小写 id>`(如 `model:glm-5.3`),而网关接受的模型 id
44
+ * 是官方大小写形态(`GLM-5.3`)。这里只剥前缀并保留原始大小写,大小写归一
45
+ * 交给 {@link normalizeModelId}。
46
+ */
47
+ function modelIdOfCapability(capability) {
48
+ const trimmed = capability.trim();
49
+ if (!trimmed.toLowerCase().startsWith("model:")) return void 0;
50
+ const id = trimmed.slice(6).trim();
51
+ return id.length === 0 ? void 0 : id;
52
+ }
53
+ /**
54
+ * 官方模型 id 的大小写映射表。
55
+ *
56
+ * 额度接口给的是小写 id,网关要的是官方拼写;这份表与 ZCode 客户端内置的
57
+ * 归一化表一致,用于把 `glm-5.3` 还原成 `GLM-5.3`,未命中的保持原样。
58
+ */
59
+ const OFFICIAL_MODEL_IDS = new Map([
60
+ "GLM-5.3",
61
+ "GLM-5.3-Flash",
62
+ "GLM-5V-Turbo",
63
+ "GLM-5.2",
64
+ "GLM-5.1",
65
+ "GLM-5.1-Highspeed",
66
+ "GLM-5",
67
+ "GLM-5-Turbo",
68
+ "GLM-4.7",
69
+ "GLM-4.7-FlashX",
70
+ "GLM-4.7-Flash",
71
+ "GLM-4.6",
72
+ "GLM-4.5-Air",
73
+ "GLM-4.5",
74
+ "GLM-4.6V",
75
+ "GLM-4.6V-Flash",
76
+ "GLM-4.6V-FlashX",
77
+ "GLM-4.1V-Thinking-FlashX",
78
+ "GLM-4.1V-Thinking-Flash",
79
+ "GLM-4-FlashX-250414",
80
+ "GLM-4-Flash-250414",
81
+ "GLM-4V-Flash"
82
+ ].map((id) => [id.toLowerCase(), id]));
83
+ /** 把模型 id 归一成官方大小写;未命中时原样返回。 */
84
+ function normalizeModelId(id) {
85
+ const trimmed = id.trim();
86
+ if (trimmed.length === 0) return trimmed;
87
+ return OFFICIAL_MODEL_IDS.get(trimmed.toLowerCase()) ?? trimmed;
88
+ }
89
+ /** 目录中真正可用的模型(当前全部可对话,保留过滤钩子便于将来扩展)。 */
90
+ function selectableModels(catalog) {
91
+ return catalog.models;
92
+ }
93
+ /** 按 id 找模型。 */
94
+ function findModel(catalog, id) {
95
+ return catalog.models.find((model) => model.id === id);
96
+ }
97
+ //#endregion
98
+ //#region src/host/console-client.ts
99
+ /**
100
+ * ZCode 控制面客户端:OAuth 授权码换取、额度、客户端配置。
101
+ *
102
+ * 三个接口共享一套「ZCode 客户端身份头」({@link zcodeSourceHeaders})。
103
+ * 这不是可选装饰:额度接口在没有 `X-Device-Mid` 时直接返回
104
+ * `{"code":3001,"msg":"parameter error"}`,而 `X-Device-Mid` 必须是稳定值,
105
+ * 否则同一个账号每次请求都会被当成新设备——额度接口会要求重新授权。
106
+ *
107
+ * 失败一律抛 {@link ZCodeConsoleError},带上游业务码,让上层能区分
108
+ * 「未登录」「无权益」「被限流」并给出可读提示。
109
+ *
110
+ * @module dsh-zcode/console-client
111
+ */
112
+ /** 上游业务错误。 */
113
+ var ZCodeConsoleError = class extends Error {
114
+ code;
115
+ status;
116
+ detail;
117
+ constructor(message, code, status, detail) {
118
+ super(message);
119
+ this.code = code;
120
+ this.status = status;
121
+ this.detail = detail;
122
+ this.name = "ZCodeConsoleError";
123
+ }
124
+ };
125
+ const DEFAULT_TIMEOUT_MS = 2e4;
126
+ function isRecord(value) {
127
+ return typeof value === "object" && value !== null && !Array.isArray(value);
128
+ }
129
+ function pickString(source, key) {
130
+ const value = source[key];
131
+ return typeof value === "string" && value.length > 0 ? value : void 0;
132
+ }
133
+ function pickNumber(source, key) {
134
+ const value = source[key];
135
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
136
+ }
137
+ /**
138
+ * ZCode 客户端身份头。
139
+ *
140
+ * `x-client-language` / `x-client-timezone` / `x-os-*` 参与网关的来源归因,
141
+ * 固定成稳定值比"跟随运行环境"更可靠:服务端对同一账号看到一致指纹。
142
+ */
143
+ function zcodeSourceHeaders(deviceMid) {
144
+ return {
145
+ "http-referer": "https://zcode.z.ai",
146
+ "user-agent": ZCODE_USER_AGENT,
147
+ "x-app-id": "zcode",
148
+ "x-client-language": "zh-CN",
149
+ "x-client-timezone": "Asia/Shanghai",
150
+ "x-client-version": ZCODE_CLIENT_VERSION,
151
+ "x-device-mid": deviceMid,
152
+ "x-os-category": process.platform === "darwin" ? "macos" : process.platform === "win32" ? "windows" : "linux",
153
+ "x-os-version": process.version.replace(/^v/, ""),
154
+ "x-platform": `${process.platform}-${process.arch}`,
155
+ "x-release-channel": "production",
156
+ "x-title": "Z Code@cli",
157
+ "x-zcode-agent": "glm",
158
+ "x-zcode-app-version": ZCODE_CLIENT_VERSION
159
+ };
160
+ }
161
+ /** 推理请求头:在客户端身份头上补 Anthropic 协议头与凭据。 */
162
+ function zcodeModelHeaders(deviceMid, token) {
163
+ return {
164
+ ...zcodeSourceHeaders(deviceMid),
165
+ "content-type": "application/json",
166
+ "anthropic-version": ZCODE_ANTHROPIC_VERSION,
167
+ "anthropic-beta": ZCODE_ANTHROPIC_BETA,
168
+ authorization: `Bearer ${token}`,
169
+ "x-api-key": token
170
+ };
171
+ }
172
+ /** 把上游 JSON 信封解出 `data`;业务码非 0 时抛出带码错误。 */
173
+ function unwrapEnvelope(payload, status, url) {
174
+ if (!isRecord(payload)) throw new ZCodeConsoleError(`${url} 返回了非对象响应`, void 0, status);
175
+ const code = pickNumber(payload, "code");
176
+ if (code !== void 0 && code !== 0) throw new ZCodeConsoleError(pickString(payload, "msg") ?? `上游业务码 ${code}`, code, status, JSON.stringify(payload).slice(0, 500));
177
+ return payload.data;
178
+ }
179
+ /** ZCode 控制面客户端。 */
180
+ var ZCodeConsoleClient = class {
181
+ fetchImpl;
182
+ timeoutMs;
183
+ deviceMidFactory;
184
+ cachedDeviceMid;
185
+ constructor(options = {}) {
186
+ this.fetchImpl = options.fetch ?? ((input, init) => globalThis.fetch(input, init));
187
+ this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
188
+ this.deviceMidFactory = options.deviceMid ?? (() => crypto.randomUUID());
189
+ }
190
+ /** 当前设备标识;首次调用时生成并缓存,保证同一进程内稳定。 */
191
+ deviceMid() {
192
+ this.cachedDeviceMid ??= this.deviceMidFactory();
193
+ return this.cachedDeviceMid;
194
+ }
195
+ /** 注入固定设备标识(从磁盘恢复时使用)。 */
196
+ setDeviceMid(value) {
197
+ if (value.trim().length > 0) this.cachedDeviceMid = value.trim();
198
+ }
199
+ async request(url, init, what) {
200
+ const controller = new AbortController();
201
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
202
+ timer.unref?.();
203
+ let response;
204
+ try {
205
+ response = await this.fetchImpl(url, {
206
+ ...init,
207
+ signal: controller.signal
208
+ });
209
+ } catch (error) {
210
+ const aborted = controller.signal.aborted;
211
+ throw new ZCodeConsoleError(aborted ? `${what} 超时(${this.timeoutMs}ms)` : `${what} 网络请求失败:${String(error)}`, void 0, 0);
212
+ } finally {
213
+ clearTimeout(timer);
214
+ }
215
+ const text = await response.text();
216
+ let json;
217
+ try {
218
+ json = text.length === 0 ? void 0 : JSON.parse(text);
219
+ } catch {
220
+ throw new ZCodeConsoleError(`${what} 返回了非 JSON 响应(HTTP ${response.status})`, void 0, response.status, text.slice(0, 300));
221
+ }
222
+ if (!response.ok) {
223
+ const message = isRecord(json) ? pickString(json, "msg") : void 0;
224
+ const code = isRecord(json) ? pickNumber(json, "code") : void 0;
225
+ throw new ZCodeConsoleError(message ?? `${what} 失败(HTTP ${response.status})`, code, response.status, text.slice(0, 400));
226
+ }
227
+ return {
228
+ status: response.status,
229
+ json
230
+ };
231
+ }
232
+ /**
233
+ * 用桌面端授权码换取凭据。
234
+ *
235
+ * 浏览器授权完成后由上游回跳带授权码,这里用 code 换取 `data.token` 与
236
+ * `data.<family>.access_token`。`redirect_uri` 与 `state` 必须与授权时逐字一致。
237
+ */
238
+ async exchangeDesktopCode(family, code, redirectUri, state) {
239
+ const { status, json } = await this.request(ZCODE_OAUTH_TOKEN_URL, {
240
+ method: "POST",
241
+ headers: { "content-type": "application/json" },
242
+ body: JSON.stringify({
243
+ provider: family,
244
+ code,
245
+ redirect_uri: redirectUri,
246
+ state
247
+ })
248
+ }, `${ZCODE_FAMILY_LABELS[family]} 授权码换取`);
249
+ const data = unwrapEnvelope(json, status, ZCODE_OAUTH_TOKEN_URL);
250
+ if (!isRecord(data)) throw new ZCodeConsoleError("授权码换取响应缺少 data", void 0, status);
251
+ const zcodeJwt = pickString(data, "token");
252
+ if (zcodeJwt === void 0 || zcodeJwt.trim().length === 0) throw new ZCodeConsoleError("授权码换取响应缺少 token", void 0, status);
253
+ const familyNode = isRecord(data[family]) ? data[family] : {};
254
+ const accessToken = pickString(familyNode, "access_token");
255
+ const refreshToken = pickString(familyNode, "refresh_token");
256
+ const userNode = isRecord(data.user) ? data.user : {};
257
+ return {
258
+ status: "ready",
259
+ zcodeJwt,
260
+ ...accessToken === void 0 ? {} : { accessToken },
261
+ ...refreshToken === void 0 ? {} : { refreshToken },
262
+ user: {
263
+ userId: pickString(userNode, "user_id"),
264
+ email: pickString(userNode, "email"),
265
+ name: pickString(userNode, "name"),
266
+ avatar: pickString(userNode, "avatar")
267
+ }
268
+ };
269
+ }
270
+ /**
271
+ * 读取 Start Plan 额度。
272
+ *
273
+ * 这里是目录的真源:`balances[].capabilities` 决定账号此刻能用哪些模型。
274
+ */
275
+ async balance(zcodeJwt) {
276
+ const url = `${ZCODE_BALANCE_URL}?app_version=${encodeURIComponent(ZCODE_CLIENT_VERSION)}`;
277
+ const { status, json } = await this.request(url, {
278
+ method: "GET",
279
+ headers: {
280
+ ...zcodeSourceHeaders(this.deviceMid()),
281
+ authorization: zcodeJwt
282
+ }
283
+ }, "Start Plan 额度");
284
+ const data = unwrapEnvelope(json, status, url);
285
+ if (!isRecord(data)) throw new ZCodeConsoleError("额度响应缺少 data", void 0, status);
286
+ const rawPlans = Array.isArray(data.plans) ? data.plans : [];
287
+ const rawBalances = Array.isArray(data.balances) ? data.balances : [];
288
+ const plans = [];
289
+ for (const entry of rawPlans) {
290
+ if (!isRecord(entry)) continue;
291
+ const planId = pickString(entry, "plan_id");
292
+ if (planId === void 0) continue;
293
+ plans.push({
294
+ planId,
295
+ ...pickString(entry, "name") === void 0 ? {} : { name: pickString(entry, "name") },
296
+ ...pickString(entry, "status") === void 0 ? {} : { status: pickString(entry, "status") },
297
+ ...pickString(entry, "ends_at") === void 0 ? {} : { endsAt: pickString(entry, "ends_at") }
298
+ });
299
+ }
300
+ const balances = [];
301
+ for (const entry of rawBalances) {
302
+ if (!isRecord(entry)) continue;
303
+ const showName = pickString(entry, "show_name") ?? pickString(entry, "entitlement_id");
304
+ if (showName === void 0) continue;
305
+ const capability = (Array.isArray(entry.capabilities) ? entry.capabilities : []).find((value) => typeof value === "string");
306
+ const planId = pickString(entry, "plan_id");
307
+ balances.push({
308
+ showName,
309
+ totalUnits: pickNumber(entry, "total_units") ?? 0,
310
+ usedUnits: pickNumber(entry, "used_units") ?? 0,
311
+ remainingUnits: pickNumber(entry, "remaining_units") ?? 0,
312
+ ...capability === void 0 ? {} : { capability },
313
+ ...planId === void 0 ? {} : { planId },
314
+ ...pickNumber(entry, "period_start") === void 0 ? {} : { periodStartSeconds: pickNumber(entry, "period_start") },
315
+ ...pickNumber(entry, "period_end") === void 0 ? {} : { periodEndSeconds: pickNumber(entry, "period_end") },
316
+ ...pickNumber(entry, "expires_at") === void 0 ? {} : { expiresAtSeconds: pickNumber(entry, "expires_at") },
317
+ ...pickNumber(entry, "available_units") === void 0 ? {} : { availableUnits: pickNumber(entry, "available_units") }
318
+ });
319
+ }
320
+ return {
321
+ plans,
322
+ balances
323
+ };
324
+ }
325
+ /** 读取客户端配置:模型能力、验证码策略、Start Plan 预览权益。 */
326
+ async clientConfigs(zcodeJwt) {
327
+ const url = `${ZCODE_CLIENT_CONFIGS_URL}?app_version=${encodeURIComponent(ZCODE_CLIENT_VERSION)}&platform=${encodeURIComponent(`${process.platform === "darwin" ? "darwin" : process.platform}-${process.arch === "arm64" ? "aarch64" : process.arch}`)}`;
328
+ const { status, json } = await this.request(url, {
329
+ method: "GET",
330
+ headers: {
331
+ ...zcodeSourceHeaders(this.deviceMid()),
332
+ authorization: zcodeJwt
333
+ }
334
+ }, "客户端配置");
335
+ const data = unwrapEnvelope(json, status, url);
336
+ if (!isRecord(data)) throw new ZCodeConsoleError("客户端配置缺少 data", void 0, status);
337
+ const configs = isRecord(data.configs) ? data.configs : {};
338
+ const models = [];
339
+ const builtin = Array.isArray(data.builtinModels) ? data.builtinModels : [];
340
+ for (const entry of builtin) {
341
+ if (!isRecord(entry)) continue;
342
+ const modelId = pickString(entry, "modelId");
343
+ if (modelId === void 0) continue;
344
+ if (models.some((model) => model.modelId === modelId)) continue;
345
+ const reasoning = isRecord(entry.reasoning) ? entry.reasoning : {};
346
+ const levelsNode = isRecord(reasoning.levels) ? reasoning.levels : {};
347
+ const levels = Object.keys(levelsNode);
348
+ const capabilities = isRecord(entry.capabilities) ? entry.capabilities : {};
349
+ models.push({
350
+ modelId,
351
+ name: pickString(entry, "name") ?? modelId,
352
+ ...pickNumber(entry, "contextWindow") === void 0 ? {} : { contextWindow: pickNumber(entry, "contextWindow") },
353
+ ...pickNumber(entry, "maxCompletionTokens") === void 0 ? {} : { maxCompletionTokens: pickNumber(entry, "maxCompletionTokens") },
354
+ ...levels.length === 0 ? {} : { reasoningLevels: levels },
355
+ ...pickString(reasoning, "defaultLevel") === void 0 ? {} : { defaultReasoningLevel: pickString(reasoning, "defaultLevel") },
356
+ ...capabilities.vision === true ? { supportsImage: true } : {},
357
+ ...pickString(entry, "description") === void 0 ? {} : { description: pickString(entry, "description") }
358
+ });
359
+ }
360
+ const captchaNode = isRecord(configs.captcha) ? configs.captcha : void 0;
361
+ const previewNode = isRecord(configs.startPlanPreview) ? configs.startPlanPreview : void 0;
362
+ const entitlements = previewNode !== void 0 && Array.isArray(previewNode.entitlements) ? previewNode.entitlements.filter(isRecord).map((entry) => ({
363
+ showName: pickString(entry, "showName") ?? "",
364
+ grantUnits: pickNumber(entry, "grantUnits") ?? 0
365
+ })).filter((entry) => entry.showName.length > 0) : void 0;
366
+ return {
367
+ models,
368
+ ...captchaNode === void 0 ? {} : { captcha: {
369
+ enabled: captchaNode.enabled === true,
370
+ skipModelRequest: captchaNode.skip_model_request === true,
371
+ ...pickString(captchaNode, "region") === void 0 ? {} : { region: pickString(captchaNode, "region") }
372
+ } },
373
+ ...entitlements === void 0 ? {} : { startPlanEntitlements: entitlements }
374
+ };
375
+ }
376
+ /** 读取 agent 运行时配置(官方客户端的端点改写映射)。 */
377
+ async agentConfigs(zcodeJwt) {
378
+ const url = `${ZCODE_AGENT_CONFIGS_URL}?app_version=${encodeURIComponent(ZCODE_CLIENT_VERSION)}&platform=darwin-aarch64`;
379
+ const { status, json } = await this.request(url, {
380
+ method: "GET",
381
+ headers: {
382
+ ...zcodeSourceHeaders(this.deviceMid()),
383
+ authorization: `Bearer ${zcodeJwt}`
384
+ }
385
+ }, "Agent 配置");
386
+ const data = unwrapEnvelope(json, status, url);
387
+ if (!isRecord(data)) throw new ZCodeConsoleError("Agent 配置缺少 data", void 0, status);
388
+ const proxy = isRecord(data.proxyEndpoint) ? data.proxyEndpoint : void 0;
389
+ const mapping = proxy !== void 0 && Array.isArray(proxy.mapping) ? proxy.mapping : [];
390
+ const signature = isRecord(data.codingPlanSignature) ? data.codingPlanSignature : void 0;
391
+ return {
392
+ proxyMapping: mapping.filter(isRecord).flatMap((entry) => {
393
+ const from = pickString(entry, "from");
394
+ const to = pickString(entry, "to");
395
+ if (from === void 0 || to === void 0) return [];
396
+ const header = pickString(entry, "zcodeAuthorizationHeader");
397
+ return [{
398
+ from,
399
+ to,
400
+ ...header === void 0 ? {} : { zcodeAuthorizationHeader: header }
401
+ }];
402
+ }),
403
+ ...signature === void 0 ? {} : { codingPlanSignatureEnabled: signature.enable === true }
404
+ };
405
+ }
406
+ };
407
+ /** 推理端点完整地址。 */
408
+ function zcodeMessagesUrl(baseURL = ZCODE_ANTHROPIC_BASE_URL) {
409
+ return `${baseURL.replace(/\/+$/u, "")}/v1/messages`;
410
+ }
411
+ //#endregion
412
+ //#region src/host/catalog.ts
413
+ /**
414
+ * 未在 `client/configs` 里描述时的兜底能力。
415
+ *
416
+ * 取 GLM-5 系列的实际取值:40 万上下文、8 万最大输出。宁可保守(更早提示
417
+ * 上下文超限),也不要把上限猜高导致请求被网关拒绝。
418
+ */
419
+ const ZCODE_FALLBACK_CONTEXT_WINDOW = 4e5;
420
+ const ZCODE_FALLBACK_MAX_TOKENS = 8e4;
421
+ /**
422
+ * 内置兜底模型清单。
423
+ *
424
+ * 与 ZCode 内置 provider 规则的 `builtinModelIds` 一致;只在额度接口不可读时
425
+ * 使用,用于保证未登录状态下的选择器不是空的。
426
+ */
427
+ const ZCODE_BUILTIN_MODEL_IDS = [
428
+ "GLM-5.3-Flash",
429
+ "GLM-5.2",
430
+ "GLM-5-Turbo"
431
+ ];
432
+ /**
433
+ * 思考等级映射。
434
+ *
435
+ * GLM 系列在 Anthropic 协议下用 `thinking` 块表达推理,pi-ai 的等级键与上游
436
+ * 取值不是一一对应:`off` 表示不请求思考,其余按强度映射到上游 `low` /
437
+ * `high` / `max` 三档。上游只认这三档,因此中间等级向上取整而不是透传,
438
+ * 否则会得到 400。
439
+ */
440
+ function thinkingLevelMapOf(levels) {
441
+ if (levels === void 0 || levels.length === 0) return {
442
+ map: {},
443
+ reasoning: false
444
+ };
445
+ const normalized = new Set(levels.map((level) => level.trim().toLowerCase()));
446
+ const map = {};
447
+ if (!normalized.has("low") && !normalized.has("medium") && !normalized.has("high") && !normalized.has("max")) return {
448
+ map: { off: null },
449
+ reasoning: false
450
+ };
451
+ map.off = null;
452
+ if (normalized.has("low")) map.minimal = "low";
453
+ if (normalized.has("low")) map.low = "low";
454
+ if (normalized.has("high")) map.medium = "high";
455
+ if (normalized.has("high")) map.high = "high";
456
+ if (normalized.has("max")) map.xhigh = "max";
457
+ if (normalized.has("max")) map.max = "max";
458
+ return {
459
+ map,
460
+ reasoning: true
461
+ };
462
+ }
463
+ /**
464
+ * 上游是否因凭据失效而拒绝。
465
+ *
466
+ * ZCode 的账号令牌有有效期,过期后额度接口返回 HTTP 401(无错误信封)。
467
+ * 这个状态与「网络不通」「没有套餐」的处理方式完全不同,必须先分开。
468
+ */
469
+ function isUnauthorized(error) {
470
+ return error instanceof ZCodeConsoleError && error.status === 401;
471
+ }
472
+ /** 把一个额度 capability 投影成模型。 */
473
+ function toModel(params) {
474
+ const { id, capability, clientModel, family } = params;
475
+ const levels = clientModel?.reasoningLevels;
476
+ const { map, reasoning } = thinkingLevelMapOf(levels);
477
+ const supportsImage = clientModel?.supportsImage === true;
478
+ return {
479
+ id,
480
+ name: clientModel?.name ?? id,
481
+ ...clientModel?.description === void 0 ? {} : { description: clientModel.description },
482
+ family: family === "zai" ? "zai" : "bigmodel",
483
+ api: "anthropic-messages",
484
+ baseURL: ZCODE_ANTHROPIC_BASE_URL,
485
+ reasoning,
486
+ ...reasoning ? { thinkingLevelMap: map } : {},
487
+ input: supportsImage ? ["text", "image"] : ["text"],
488
+ contextWindow: clientModel?.contextWindow ?? 4e5,
489
+ maxTokens: clientModel?.maxCompletionTokens ?? 8e4,
490
+ tools: true,
491
+ structuredOutput: true,
492
+ accountFamily: family,
493
+ ...capability === void 0 ? {} : { capability }
494
+ };
495
+ }
496
+ /** 从额度条目推导模型 id 列表(保序去重)。 */
497
+ function modelIdsFromBalances(balances) {
498
+ const ids = [];
499
+ const seen = /* @__PURE__ */ new Set();
500
+ for (const balance of balances) {
501
+ const raw = balance.capability === void 0 ? void 0 : modelIdOfCapability(balance.capability);
502
+ if (raw === void 0) continue;
503
+ const id = normalizeModelId(raw);
504
+ if (seen.has(id)) continue;
505
+ seen.add(id);
506
+ ids.push(id);
507
+ }
508
+ return ids;
509
+ }
510
+ /** Unix 秒 → epoch 毫秒;非法值返回 undefined。 */
511
+ function secondsToMs(value) {
512
+ if (value === void 0 || !Number.isFinite(value) || value <= 0) return void 0;
513
+ return value * 1e3;
514
+ }
515
+ /** 把额度条目归一成契约形状(周期由窗口时长推导)。 */
516
+ function toBalanceEntries(balances) {
517
+ return balances.map((balance) => {
518
+ const periodStart = secondsToMs(balance.periodStartSeconds);
519
+ const periodEnd = secondsToMs(balance.periodEndSeconds);
520
+ return {
521
+ entitlementId: balance.capability ?? balance.showName,
522
+ showName: balance.showName,
523
+ ...balance.capability === void 0 ? {} : { capability: balance.capability },
524
+ totalUnits: balance.totalUnits,
525
+ usedUnits: balance.usedUnits,
526
+ remainingUnits: balance.remainingUnits,
527
+ period: deriveBalancePeriod(balance.periodStartSeconds, balance.periodEndSeconds),
528
+ ...periodStart === void 0 ? {} : { periodStart },
529
+ ...periodEnd === void 0 ? {} : { periodEnd },
530
+ ...secondsToMs(balance.expiresAtSeconds) === void 0 ? {} : { expiresAt: secondsToMs(balance.expiresAtSeconds) },
531
+ ...balance.planId === void 0 ? {} : { planId: balance.planId }
532
+ };
533
+ });
534
+ }
535
+ /**
536
+ * 同一模型在多份套餐下会有多条额度,逐条展示会让界面出现重复行。
537
+ *
538
+ * 这里按「模型 + 周期」合并:保留额度最大的那条作为主额度,把其余总量累加。
539
+ * 累加是有意义的——两份套餐的同一模型日额度确实可以叠加使用;但**不合并
540
+ * 不同周期**,否则会掩盖"日额度用完但活动额度还有"。
541
+ */
542
+ function mergeBalanceEntries(entries) {
543
+ const merged = /* @__PURE__ */ new Map();
544
+ for (const entry of entries) {
545
+ const key = `${entry.capability ?? entry.showName}::${entry.period}`;
546
+ const existing = merged.get(key);
547
+ if (existing === void 0) {
548
+ merged.set(key, entry);
549
+ continue;
550
+ }
551
+ const totalUnits = existing.totalUnits + entry.totalUnits;
552
+ const usedUnits = existing.usedUnits + entry.usedUnits;
553
+ const remainingUnits = existing.remainingUnits + entry.remainingUnits;
554
+ merged.set(key, {
555
+ ...existing,
556
+ totalUnits,
557
+ usedUnits,
558
+ remainingUnits,
559
+ ...earliest(existing.periodEnd, entry.periodEnd) === void 0 ? {} : { periodEnd: earliest(existing.periodEnd, entry.periodEnd) }
560
+ });
561
+ }
562
+ return [...merged.values()];
563
+ }
564
+ /** 两个时间戳中较早且存在的那个。 */
565
+ function earliest(left, right) {
566
+ if (left === void 0) return right;
567
+ if (right === void 0) return left;
568
+ return Math.min(left, right);
569
+ }
570
+ const DEFAULT_STALE_AFTER_MS = 3e5;
571
+ /** 目录服务。 */
572
+ var ZCodeCatalogService = class {
573
+ current;
574
+ inflight;
575
+ options;
576
+ constructor(options) {
577
+ this.options = options;
578
+ }
579
+ get now() {
580
+ return this.options.now?.() ?? Date.now();
581
+ }
582
+ /** 当前快照;从未刷新过时返回 undefined。 */
583
+ snapshot() {
584
+ return this.current;
585
+ }
586
+ /** 快照是否已过期。 */
587
+ isStale() {
588
+ if (this.current === void 0) return true;
589
+ const staleAfter = this.options.staleAfterMs ?? DEFAULT_STALE_AFTER_MS;
590
+ return this.now - this.current.fetchedAt >= staleAfter;
591
+ }
592
+ /** 失效快照,强制下次刷新走网络。 */
593
+ invalidate() {
594
+ this.current = void 0;
595
+ }
596
+ /** 已过期才刷新,否则复用。 */
597
+ async refreshIfStale() {
598
+ if (this.current !== void 0 && !this.isStale()) return this.current;
599
+ return this.refresh();
600
+ }
601
+ /** 强制刷新;并发调用合并成一次请求。 */
602
+ async refresh() {
603
+ this.inflight ??= this.load().finally(() => {
604
+ this.inflight = void 0;
605
+ });
606
+ return this.inflight;
607
+ }
608
+ async load() {
609
+ const token = await this.options.resolveToken();
610
+ const family = await this.options.resolveFamily();
611
+ if (token === void 0 || family === void 0) {
612
+ const builtin = this.builtinCatalog(family ?? "bigmodel", ["尚未登录 ZCode 账号,当前展示内置模型清单;登录后按账号权益刷新"]);
613
+ this.current = builtin;
614
+ return builtin;
615
+ }
616
+ const warnings = [];
617
+ let balances = [];
618
+ let plans = [];
619
+ let source = "builtin";
620
+ try {
621
+ const snapshot = await this.options.client.balance(token);
622
+ balances = mergeBalanceEntries(toBalanceEntries(snapshot.balances));
623
+ plans = snapshot.plans.map((plan) => ({
624
+ planId: plan.planId,
625
+ ...plan.name === void 0 ? {} : { name: plan.name },
626
+ ...plan.status === void 0 ? {} : { status: plan.status },
627
+ ...plan.endsAt === void 0 ? {} : { endsAt: plan.endsAt }
628
+ }));
629
+ source = "balance";
630
+ if (plans.length === 0) warnings.push("额度接口未返回任何套餐记录,账号可能尚未订阅 Start Plan");
631
+ } catch (error) {
632
+ if (isUnauthorized(error)) warnings.push("ZCode 账号凭据已失效,请重新登录后刷新模型清单");
633
+ else warnings.push(`额度接口不可读,暂用内置模型清单:${error instanceof Error ? error.message : String(error)}`);
634
+ this.options.logger?.warn("dsh-zcode: 读取额度失败", error);
635
+ }
636
+ let clientModels = [];
637
+ try {
638
+ clientModels = (await this.options.client.clientConfigs(token)).models;
639
+ } catch (error) {
640
+ warnings.push("客户端配置不可读,模型能力使用保守默认值");
641
+ this.options.logger?.warn("dsh-zcode: 读取客户端配置失败", error);
642
+ }
643
+ const ids = source === "balance" ? modelIdsFromBalances(balances) : [];
644
+ const modelIds = ids.length > 0 ? ids : [...ZCODE_BUILTIN_MODEL_IDS];
645
+ if (source === "balance" && ids.length === 0) {
646
+ warnings.push("账号权益里没有可用模型,已回落到内置模型清单");
647
+ source = "builtin";
648
+ }
649
+ const models = modelIds.map((id) => {
650
+ const balance = balances.find((entry) => entry.capability !== void 0 && normalizeModelId(modelIdOfCapability(entry.capability) ?? "") === id);
651
+ const clientModel = clientModels.find((entry) => normalizeModelId(entry.modelId) === id);
652
+ return toModel({
653
+ id,
654
+ ...balance?.capability === void 0 ? {} : { capability: balance.capability },
655
+ ...clientModel === void 0 ? {} : { clientModel },
656
+ family
657
+ });
658
+ });
659
+ const catalog = {
660
+ fetchedAt: this.now,
661
+ source,
662
+ models,
663
+ plans,
664
+ balances: source === "balance" ? balances : [],
665
+ warnings
666
+ };
667
+ this.current = catalog;
668
+ return catalog;
669
+ }
670
+ builtinCatalog(family, warnings) {
671
+ return {
672
+ fetchedAt: this.now,
673
+ source: "builtin",
674
+ models: ZCODE_BUILTIN_MODEL_IDS.map((id) => toModel({
675
+ id,
676
+ family
677
+ })),
678
+ plans: [],
679
+ balances: [],
680
+ warnings
681
+ };
682
+ }
683
+ };
684
+ //#endregion
685
+ export { findModel as _, isUnauthorized as a, selectableModels as b, thinkingLevelMapOf as c, ZCodeConsoleClient as d, ZCodeConsoleError as f, ZCODE_REASONING_LEVELS as g, zcodeSourceHeaders as h, ZCodeCatalogService as i, toBalanceEntries as l, zcodeModelHeaders as m, ZCODE_FALLBACK_CONTEXT_WINDOW as n, mergeBalanceEntries as o, zcodeMessagesUrl as p, ZCODE_FALLBACK_MAX_TOKENS as r, modelIdsFromBalances as s, ZCODE_BUILTIN_MODEL_IDS as t, toModel as u, modelIdOfCapability as v, normalizeModelId as y };