dsh-opendesign-corouter 0.1.1 → 0.1.2

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 CHANGED
@@ -52,13 +52,13 @@ import { randomUUID as randomUUID2, createHash } from "node:crypto";
52
52
  import { credentialKey, credentialRef } from "@deepseek-ai/dsh-credentials";
53
53
  import { launchEnvironmentOf } from "@deepseek-ai/dsh-launch-environment";
54
54
  import { Remote, RemoteError, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
55
+ import { LlmError as LlmError11 } from "@deepseek-ai/dsh-llm";
55
56
 
56
57
  // src/adapter.ts
57
58
  import {
58
59
  LlmAdapter,
59
- LlmError as LlmError3,
60
+ LlmError as LlmError10,
60
61
  ReasoningEffortId,
61
- attributionHeaders,
62
62
  assertUsableApiKey
63
63
  } from "@deepseek-ai/dsh-llm";
64
64
 
@@ -210,24 +210,134 @@ var MODELS = [
210
210
  { id: "qwen3.7-max", name: "Qwen3.7 Max", group: "paid" }
211
211
  ];
212
212
  var BY_ID = new Map(MODELS.map((model) => [model.id, model]));
213
- function modelsFor(group2) {
214
- return MODELS.filter((model) => model.group === group2);
215
- }
216
213
  function findModel(id) {
217
214
  return BY_ID.get(id);
218
215
  }
219
216
 
220
217
  // src/config.ts
221
218
  import Schema from "@deepseek-ai/schemastery";
219
+
220
+ // src/protocol-options.ts
221
+ var API_PROTOCOL_IDS = ["chat-completions", "responses", "messages"];
222
+ function openAiBaseURL(baseURL) {
223
+ const base = baseURL.replace(/\/+$/u, "");
224
+ return new URL(base).pathname === "/" ? `${base}/v1` : base;
225
+ }
226
+ function inferenceEndpoint(baseURL, protocol) {
227
+ const suffix = protocol === "responses" ? "/responses" : protocol === "messages" ? "/messages" : "/chat/completions";
228
+ return baseURL.trim() === "" ? suffix : `${openAiBaseURL(baseURL.trim())}${suffix}`;
229
+ }
230
+
231
+ // src/util.ts
232
+ function record(value) {
233
+ return value !== null && typeof value === "object" && !Array.isArray(value);
234
+ }
235
+ function finite(value) {
236
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
237
+ }
238
+ function safeSecret(raw) {
239
+ const value = raw.trim();
240
+ if (!value || /[^\x20-\x7e]/u.test(value)) throw new Error("\u51ED\u636E\u4E3A\u7A7A\u6216\u542B\u6709\u4E0D\u53EF\u7528\u4E8E HTTP \u8BF7\u6C42\u5934\u7684\u5B57\u7B26");
241
+ return value;
242
+ }
243
+ function combinedSignal(signal, timeoutMs) {
244
+ const deadline = AbortSignal.timeout(timeoutMs);
245
+ return signal ? AbortSignal.any([signal, deadline]) : deadline;
246
+ }
247
+ function sleep(ms, signal) {
248
+ return new Promise((resolve) => {
249
+ if (signal.aborted) return resolve();
250
+ const done = () => {
251
+ clearTimeout(timer);
252
+ signal.removeEventListener("abort", done);
253
+ resolve();
254
+ };
255
+ const timer = setTimeout(done, ms);
256
+ signal.addEventListener("abort", done, { once: true });
257
+ });
258
+ }
259
+
260
+ // src/model-settings.ts
261
+ var MAX_ENTRIES = 512;
262
+ var MAX_MODEL_ID_LENGTH = 128;
263
+ var MODEL_ID = /^[A-Za-z0-9._:/-]{1,128}$/u;
264
+ var MAX_TEMPERATURE = 2;
265
+ var MAX_TOP_P = 1;
266
+ var MAX_TOKENS = 1e7;
267
+ var MAX_CONTEXT_WINDOW = 1e8;
268
+ var REASONING_EFFORTS = ["none", "minimal", "low", "medium", "high", "xhigh", "ultra", "max"];
269
+ var RESERVED_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
270
+ var SETTINGS_FIELDS = /* @__PURE__ */ new Set(["enabled", "temperature", "topP", "maxTokens", "contextWindow", "reasoningEffort"]);
271
+ function flag(value) {
272
+ if (typeof value !== "boolean") throw new Error("\u6A21\u578B\u8BBE\u7F6E\u7684 enabled \u5FC5\u987B\u662F\u5E03\u5C14\u503C");
273
+ return value;
274
+ }
275
+ function bounded(value, max, label) {
276
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > max) throw new Error(`\u6A21\u578B\u8BBE\u7F6E\u7684 ${label} \u5FC5\u987B\u662F 0 \u5230 ${max} \u4E4B\u95F4\u7684\u6709\u9650\u6570\u5B57`);
277
+ return value;
278
+ }
279
+ function capacity(value, max, label) {
280
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1 || value > max) throw new Error(`\u6A21\u578B\u8BBE\u7F6E\u7684 ${label} \u5FC5\u987B\u662F 1 \u5230 ${max} \u4E4B\u95F4\u7684\u6574\u6570`);
281
+ return value;
282
+ }
283
+ function effort(value) {
284
+ const hit = REASONING_EFFORTS.find((candidate) => candidate === value);
285
+ if (hit === void 0) throw new Error("\u6A21\u578B\u8BBE\u7F6E\u7684 reasoningEffort \u4E0D\u5728\u5141\u8BB8\u7684\u53D6\u503C\u5185");
286
+ return hit;
287
+ }
288
+ function settingsOf(value) {
289
+ if (!record(value)) throw new Error("\u6BCF\u4E2A\u6A21\u578B\u7684\u8BBE\u7F6E\u5FC5\u987B\u662F\u5BF9\u8C61");
290
+ for (const field of Object.keys(value)) {
291
+ if (!SETTINGS_FIELDS.has(field)) throw new Error("\u6A21\u578B\u8BBE\u7F6E\u542B\u6709\u4E0D\u652F\u6301\u7684\u5B57\u6BB5");
292
+ }
293
+ const out = {};
294
+ if (value.enabled !== void 0) out.enabled = flag(value.enabled);
295
+ if (value.temperature !== void 0) out.temperature = bounded(value.temperature, MAX_TEMPERATURE, "temperature");
296
+ if (value.topP !== void 0) out.topP = bounded(value.topP, MAX_TOP_P, "topP");
297
+ if (value.maxTokens !== void 0) out.maxTokens = capacity(value.maxTokens, MAX_TOKENS, "maxTokens");
298
+ if (value.contextWindow !== void 0) out.contextWindow = capacity(value.contextWindow, MAX_CONTEXT_WINDOW, "contextWindow");
299
+ if (value.reasoningEffort !== void 0) out.reasoningEffort = effort(value.reasoningEffort);
300
+ return out;
301
+ }
302
+ function parseModelSettingsMap(value) {
303
+ if (!record(value)) throw new Error("\u6A21\u578B\u8BBE\u7F6E\u5FC5\u987B\u662F\u5BF9\u8C61");
304
+ const keys = Object.keys(value);
305
+ if (keys.length > MAX_ENTRIES) throw new Error(`\u6A21\u578B\u8BBE\u7F6E\u6700\u591A\u5141\u8BB8 ${MAX_ENTRIES} \u4E2A\u6A21\u578B`);
306
+ const out = {};
307
+ for (const id of keys) {
308
+ if (RESERVED_KEYS.has(id)) throw new Error("\u6A21\u578B\u8BBE\u7F6E\u4E0D\u5141\u8BB8\u4FDD\u7559\u5B57\u6BB5\u540D");
309
+ if (!MODEL_ID.test(id)) throw new Error(`\u6A21\u578B ID \u5FC5\u987B\u662F 1\u2013${MAX_MODEL_ID_LENGTH} \u4F4D\u5B57\u6BCD\u3001\u6570\u5B57\u6216 . _ : / -`);
310
+ out[id] = settingsOf(value[id]);
311
+ }
312
+ return out;
313
+ }
314
+ function applyModelSettings(row, settings) {
315
+ const { contextWindow, maxTokens } = settings ?? {};
316
+ if (contextWindow === void 0 && maxTokens === void 0) return { ...row };
317
+ return {
318
+ ...row,
319
+ ...contextWindow === void 0 ? {} : { contextWindow: capacity(contextWindow, MAX_CONTEXT_WINDOW, "contextWindow") },
320
+ ...maxTokens === void 0 ? {} : { maxOutputTokens: capacity(maxTokens, MAX_TOKENS, "maxTokens") }
321
+ };
322
+ }
323
+ function isModelEnabled(id, settings = {}) {
324
+ if (RESERVED_KEYS.has(id) || !Object.hasOwn(settings, id)) return true;
325
+ return settings[id]?.enabled !== false;
326
+ }
327
+
328
+ // src/config.ts
222
329
  var SETTINGS_NS = "llm-opendesign-corouter";
223
330
  var PLAN_PROVIDER = "opendesign-corouter-plan";
224
331
  var PAID_PROVIDER = "opendesign-corouter-paid";
225
332
  var DEFAULT_BASE_URL = "https://amr-link.open-design.ai/v1";
226
333
  var Config = Schema.object({
227
334
  apiKeyEnv: Schema.string().pattern(/^[A-Za-z_][A-Za-z0-9_]*$/u).default("COROUTER_API_KEY").description("CoRouter API Key \u7684\u73AF\u5883\u53D8\u91CF\u6216 DSH \u51ED\u636E\u5F15\u7528\u540D").volatile(),
228
- baseURL: Schema.transform(Schema.string(), (value) => assertBaseURL(value)).default(DEFAULT_BASE_URL).description("OpenAI \u517C\u5BB9\u63A8\u7406\u5730\u5740\uFF1B\u4E0D\u7528\u4E8E\u63A7\u5236\u53F0\u767B\u5F55\u6216\u989D\u5EA6\u67E5\u8BE2").volatile(),
335
+ baseURL: Schema.transform(Schema.string(), (value) => assertBaseURL(value)).default(DEFAULT_BASE_URL).description("CoRouter \u7F51\u5173\u5730\u5740\uFF1B\u4E0D\u7528\u4E8E\u63A7\u5236\u53F0\u767B\u5F55\u6216\u989D\u5EA6\u67E5\u8BE2").volatile(),
336
+ apiProtocol: Schema.union(API_PROTOCOL_IDS).default("chat-completions").description("\u63A8\u7406 API \u534F\u8BAE\uFF1B\u6A21\u578B\u53D1\u73B0\u59CB\u7EC8\u4F7F\u7528 OpenAI models \u63A5\u53E3").volatile(),
337
+ modelSettings: Schema.transform(Schema.dict(Schema.any()), (value) => parseModelSettingsMap(value)).default({}).description("\u9010\u6A21\u578B\u542F\u7528\u72B6\u6001\u548C\u8BF7\u6C42\u53C2\u6570").volatile(),
229
338
  useDesktopCredential: Schema.boolean().default(true).description("\u4F18\u5148\u8BFB\u53D6 OpenDesign \u684C\u9762\u7AEF\u767B\u5F55\u6001\u67E5\u8BE2\u989D\u5EA6").volatile(),
230
339
  emptyTurnGuard: Schema.boolean().default(true).description("\u76D1\u63A7\u5957\u9910\u8D60\u9001\u6A21\u578B\u7684\u7A7A\u8F6E/\u534F\u8BAE\u6B8B\u6E23\uFF1B\u7531\u5BBF\u4E3B\u91CD\u8BD5\u7B56\u7565\u5904\u7406\uFF0C\u53EF\u80FD\u989D\u5916\u6D88\u8017\u8D60\u9001\u989D\u5EA6").volatile(),
340
+ quotaRefreshSeconds: Schema.natural().min(5).max(3600).default(60).description("\u989D\u5EA6\u81EA\u52A8\u5237\u65B0\u95F4\u9694\uFF08\u79D2\uFF09").volatile(),
231
341
  requestTimeoutMs: Schema.natural().min(1e3).max(36e5).default(18e5).description("\u63A8\u7406\u8BF7\u6C42\u603B\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09").volatile(),
232
342
  streamIdleTimeoutMs: Schema.natural().min(1e3).max(18e5).default(3e5).description("\u6D41\u5F0F\u54CD\u5E94\u65E0\u6570\u636E\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09").volatile()
233
343
  });
@@ -246,6 +356,9 @@ function readConfig(config) {
246
356
  return {
247
357
  apiKeyEnv: config.apiKeyEnv.get(),
248
358
  baseURL: assertBaseURL(config.baseURL.get()),
359
+ apiProtocol: config.apiProtocol.get(),
360
+ modelSettings: parseModelSettingsMap(config.modelSettings.get()),
361
+ quotaRefreshSeconds: config.quotaRefreshSeconds.get(),
249
362
  useDesktopCredential: config.useDesktopCredential.get(),
250
363
  emptyTurnGuard: config.emptyTurnGuard.get(),
251
364
  requestTimeoutMs: config.requestTimeoutMs.get(),
@@ -347,49 +460,214 @@ function estimateTokens(value) {
347
460
  }
348
461
 
349
462
  // src/stream.ts
350
- import { LlmError as LlmError2, ToolCallId } from "@deepseek-ai/dsh-llm";
463
+ import { LlmError as LlmError4, ToolCallId } from "@deepseek-ai/dsh-llm";
351
464
 
352
- // src/util.ts
353
- function record(value) {
354
- return value !== null && typeof value === "object" && !Array.isArray(value);
465
+ // src/protocols/errors.ts
466
+ import {
467
+ ACCOUNT_QUOTA_EXCEEDED_CODE,
468
+ CONTEXT_WINDOW_EXCEEDED_CODE,
469
+ INVALID_CREDENTIAL_CODE,
470
+ LlmError as LlmError2,
471
+ QUOTA_EXCEEDED_CODE,
472
+ isContextWindowExceededError,
473
+ isQuotaExceededError
474
+ } from "@deepseek-ai/dsh-llm";
475
+ var MESSAGES = {
476
+ [CONTEXT_WINDOW_EXCEEDED_CODE]: "OpenDesign\uFF1A\u8BF7\u6C42\u8D85\u51FA\u6A21\u578B\u4E0A\u4E0B\u6587\u957F\u5EA6\uFF0C\u8BF7\u7F29\u77ED\u8F93\u5165\u540E\u91CD\u8BD5",
477
+ [ACCOUNT_QUOTA_EXCEEDED_CODE]: "OpenDesign\uFF1A\u5957\u9910\u6216\u8D26\u6237\u989D\u5EA6\u4E0D\u8DB3\uFF0C\u8BF7\u68C0\u67E5\u5957\u9910\u989D\u5EA6\u540E\u91CD\u8BD5",
478
+ [QUOTA_EXCEEDED_CODE]: "OpenDesign\uFF1A\u94B1\u5305\u6216\u79EF\u5206\u4F59\u989D\u4E0D\u8DB3\uFF0C\u8BF7\u5145\u503C\u540E\u91CD\u8BD5",
479
+ [INVALID_CREDENTIAL_CODE]: "OpenDesign\uFF1AAPI Key \u65E0\u6548\u6216\u7F3A\u5931\uFF0C\u8BF7\u5728\u8BBE\u7F6E\u4E2D\u66F4\u65B0\u51ED\u636E",
480
+ AUTH: "OpenDesign\uFF1A\u8BA4\u8BC1\u672A\u901A\u8FC7\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55\u540E\u91CD\u8BD5",
481
+ PERMISSION: "OpenDesign\uFF1A\u5F53\u524D\u8D26\u6237\u65E0\u6743\u8BBF\u95EE\u8BE5\u6A21\u578B",
482
+ RATE_LIMIT: "OpenDesign\uFF1A\u8BF7\u6C42\u8FC7\u4E8E\u9891\u7E41\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5",
483
+ TIMEOUT: "OpenDesign\uFF1A\u8BF7\u6C42\u8D85\u65F6\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5",
484
+ SERVER: "OpenDesign\uFF1A\u4E0A\u6E38\u670D\u52A1\u6682\u4E0D\u53EF\u7528\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5",
485
+ INVALID_REQUEST: "OpenDesign\uFF1A\u8BF7\u6C42\u88AB\u4E0A\u6E38\u62D2\u7EDD",
486
+ INVALID_RESPONSE: "OpenDesign\uFF1A\u4E0A\u6E38\u54CD\u5E94\u683C\u5F0F\u65E0\u6548"
487
+ };
488
+ var UNKNOWN_MESSAGE = "OpenDesign\uFF1A\u8BF7\u6C42\u5931\u8D25";
489
+ var STRUCTURED_CREDENTIAL = /invalid[_-]?(?:x[_-]?)?api[_-]?key|missing[_-]?(?:x[_-]?)?api[_-]?key|incorrect[_-]?(?:x[_-]?)?api[_-]?key|api[_-]?key[_-]?(?:invalid|missing|incorrect)|invalid[_-]?credentials?|credentials?[_-]?(?:invalid|missing)/u;
490
+ var STRUCTURED_CONTEXT = /context[_-]?(?:length|window)[_-]?(?:exceeded|exceed|overflow)|context[_-]?overflow|(?:prompt|input)[_-]?too[_-]?long/u;
491
+ var STRUCTURED_WALLET = /(?:insufficient|low|exhausted|depleted|empty|zero)[_-]?(?:credits?|balance|funds)|(?:credits?|balance|wallet|funds)[_-]?(?:insufficient|exhausted|depleted|low|empty)|(?:credit|wallet)[_-]balance[_-](?:too[_-]low|insufficient|exhausted)/u;
492
+ var STRUCTURED_PLAN = /(?:insufficient|exceeded|exhausted|reached|over)[_-]?(?:quota|plan|usage[_-]?limit)|quota[_-]?(?:exceeded|exhausted|reached)|billing[_-]?hard[_-]?limit|usage[_-]?limit[_-]?(?:exceeded|reached)|(?:subscription|membership|plan)(?:[_-](?:quota|credits?|limit))?[_-](?:exceeded|exhausted|insufficient|reached)/u;
493
+ var STRUCTURED_RATE = /rate[_-]?limit|too[_-]?many[_-]?requests|resource[_-]?exhausted|(?:requests?|tokens?)[_-]?per[_-]?(?:minute|second)/u;
494
+ var STRUCTURED_TIMEOUT = /timeout|timed[_-]?out|deadline[_-]?exceeded/u;
495
+ var STRUCTURED_SERVER = /server[_-]?error|api[_-]?error|internal[_-]?(?:server[_-]?)?error|service[_-]?unavailable|unavailable|overloaded|bad[_-]?gateway|gateway[_-]?error/u;
496
+ var STRUCTURED_AUTH = /authentication|unauthorized|not[_-]?authenticated/u;
497
+ var STRUCTURED_PERMISSION = /permission|forbidden|access[_-]?denied|insufficient[_-]?scope/u;
498
+ var PROSE_CREDENTIAL = /invalid[ _-]?(?:x[ _-]?)?api[ _-]?key|(?:x[ _-]?)?api[ _-]?key[ _-]?(?:is[ _-]?)?(?:invalid|missing|incorrect|not[ _-]?valid)|missing[ _-]?(?:x[ _-]?)?api[ _-]?key|incorrect[ _-]?(?:x[ _-]?)?api[ _-]?key|invalid[ _-]?credentials?\b|api[ _-]?key\s*(?:无效|缺失|错误)|密钥(?:无效|缺失|错误)/u;
499
+ var PROSE_CONTEXT_EN = /\b(?:prompt|input|messages?)\s+(?:is\s+|are\s+)?too\s+(?:long|large)\b|\bmaximum context\b|\btokens? > \d+ maximum\b/u;
500
+ var PROSE_CONTEXT_ZH = /超出[^。]{0,8}上下文|上下文[^。]{0,16}(?:超出|超过|超限|过长|已满)|(?:输入|请求|提示词)[^。]{0,8}(?:过长|太长)|超过[^。]{0,8}上下文/u;
501
+ var PROSE_QUOTA_EN = /\b(?:credits?|balance|quota|funds|wallet)\b[^.;]{0,24}\b(?:insufficient|exhausted|depleted|used[ _-]?up|too[ _-]?low|empty|zero|exceeded)\b|\b(?:insufficient|exhausted|depleted|empty|zero)\b[^.;]{0,24}\b(?:credits?|balance|quota|funds|wallet)\b/u;
502
+ var PROSE_QUOTA_ZH = /(?:余额|积分|额度|配额|钱包)[^。]{0,12}(?:不足|用尽|耗尽|用完|超限|超出|已达上限|超过上限)|(?:不足|用尽|耗尽)[^。]{0,6}(?:余额|额度|配额|积分)/u;
503
+ var PROSE_QUOTA_PLAN = /套餐|订阅|会员|账户额度|账户配额|用量上限|使用上限|\bplan\b|\bsubscription\b|\bmembership\b|\bbilling\b|hard[ _-]?limit|usage[ _-]?limit/u;
504
+ var PROSE_QUOTA_WALLET = /钱包|充值|余额|积分|\bwallet\b|\brecharge\b|top[ _-]?up|\bbalance\b|\bcredits?\b|\bfunds?\b/u;
505
+ var PROSE_RATE = /rate[ _-]?limit|\btoo many requests\b|\bresource[ _-]?exhausted\b|请求过于频繁|频率超限|触发限流|接口限流/u;
506
+ var PROSE_TIMEOUT = /\btimeout\b|\btimed out\b|\bdeadline exceeded\b|请求超时|超时/u;
507
+ var PROSE_SERVER = /\bserver error\b|\bapi error\b|\binternal (?:server )?error\b|\bservice unavailable\b|\boverloaded\b|\bbad gateway\b|服务暂不可用|服务不可用|服务器错误|服务器繁忙|内部错误/u;
508
+ var PROSE_AUTH = /\bauthentication\b|\bunauthorized\b|\bnot authenticated\b|认证失败|登录已失效|未登录/u;
509
+ var PROSE_PERMISSION = /\bpermission\b|\bforbidden\b|\baccess denied\b|\bnot authorized\b|\binsufficient (?:permissions?|scope)\b|权限不足|无权限|无权访问/u;
510
+ function fail(code, status) {
511
+ const options = status === void 0 ? {} : { status };
512
+ return new LlmError2(MESSAGES[code] ?? UNKNOWN_MESSAGE, code, options);
355
513
  }
356
- function finite(value) {
357
- return typeof value === "number" && Number.isFinite(value) ? value : void 0;
514
+ function matches(fields, pattern) {
515
+ return fields.some((field) => pattern.test(field));
358
516
  }
359
- function safeSecret(raw) {
360
- const value = raw.trim();
361
- if (!value || /[^\x20-\x7e]/u.test(value)) throw new Error("\u51ED\u636E\u4E3A\u7A7A\u6216\u542B\u6709\u4E0D\u53EF\u7528\u4E8E HTTP \u8BF7\u6C42\u5934\u7684\u5B57\u7B26");
362
- return value;
517
+ function structuredQuota(fields) {
518
+ if (matches(fields, STRUCTURED_WALLET)) return QUOTA_EXCEEDED_CODE;
519
+ if (matches(fields, STRUCTURED_PLAN)) return ACCOUNT_QUOTA_EXCEEDED_CODE;
520
+ return void 0;
363
521
  }
364
- function combinedSignal(signal, timeoutMs) {
365
- const deadline = AbortSignal.timeout(timeoutMs);
366
- return signal ? AbortSignal.any([signal, deadline]) : deadline;
522
+ function proseQuota(detail) {
523
+ if (PROSE_QUOTA_PLAN.test(detail)) return ACCOUNT_QUOTA_EXCEEDED_CODE;
524
+ if (PROSE_QUOTA_WALLET.test(detail)) return QUOTA_EXCEEDED_CODE;
525
+ return ACCOUNT_QUOTA_EXCEEDED_CODE;
526
+ }
527
+ function classifyStructured(fields) {
528
+ if (matches(fields, STRUCTURED_CREDENTIAL)) return INVALID_CREDENTIAL_CODE;
529
+ if (matches(fields, STRUCTURED_CONTEXT)) return CONTEXT_WINDOW_EXCEEDED_CODE;
530
+ const quota = structuredQuota(fields);
531
+ if (quota !== void 0) return quota;
532
+ if (matches(fields, STRUCTURED_RATE)) return "RATE_LIMIT";
533
+ if (matches(fields, STRUCTURED_TIMEOUT)) return "TIMEOUT";
534
+ if (matches(fields, STRUCTURED_SERVER)) return "SERVER";
535
+ if (matches(fields, STRUCTURED_AUTH)) return "AUTH";
536
+ if (matches(fields, STRUCTURED_PERMISSION)) return "PERMISSION";
537
+ return void 0;
367
538
  }
368
- function sleep(ms, signal) {
369
- return new Promise((resolve) => {
370
- if (signal.aborted) return resolve();
371
- const done = () => {
372
- clearTimeout(timer);
373
- signal.removeEventListener("abort", done);
374
- resolve();
375
- };
376
- const timer = setTimeout(done, ms);
377
- signal.addEventListener("abort", done, { once: true });
378
- });
539
+ function classifyProse(detail) {
540
+ if (detail === "") return void 0;
541
+ if (isContextWindowExceededError(detail) || PROSE_CONTEXT_EN.test(detail) || PROSE_CONTEXT_ZH.test(detail)) return CONTEXT_WINDOW_EXCEEDED_CODE;
542
+ if (isQuotaExceededError(detail) || PROSE_QUOTA_EN.test(detail) || PROSE_QUOTA_ZH.test(detail)) return proseQuota(detail);
543
+ if (PROSE_RATE.test(detail)) return "RATE_LIMIT";
544
+ if (PROSE_TIMEOUT.test(detail)) return "TIMEOUT";
545
+ if (PROSE_SERVER.test(detail)) return "SERVER";
546
+ if (PROSE_AUTH.test(detail)) return "AUTH";
547
+ if (PROSE_PERMISSION.test(detail)) return "PERMISSION";
548
+ return void 0;
549
+ }
550
+ function classifyStatus(status) {
551
+ if (status === void 0) return void 0;
552
+ if (status === 401) return "AUTH";
553
+ if (status === 403) return "PERMISSION";
554
+ if (status === 402) return QUOTA_EXCEEDED_CODE;
555
+ if (status === 429) return "RATE_LIMIT";
556
+ if (status === 408 || status === 504) return "TIMEOUT";
557
+ if (status >= 500) return "SERVER";
558
+ return void 0;
559
+ }
560
+ function fieldsOf(error) {
561
+ const structured = [];
562
+ for (const value of [error.code, error.type]) {
563
+ if (typeof value !== "string") continue;
564
+ const normalized = value.trim().toLowerCase();
565
+ if (normalized !== "") structured.push(normalized);
566
+ }
567
+ return { structured, prose: typeof error.message === "string" ? error.message.toLowerCase() : "" };
568
+ }
569
+ function classify(fields, status, fallback) {
570
+ const structured = classifyStructured(fields.structured);
571
+ if (structured !== void 0 && structured !== "AUTH" && structured !== "PERMISSION") return fail(structured, status);
572
+ if (structured === void 0 && status !== void 0 && status >= 500 && status !== 504) return fail("SERVER", status);
573
+ if (PROSE_CREDENTIAL.test(fields.prose)) return fail(INVALID_CREDENTIAL_CODE, status);
574
+ if (structured !== void 0) return fail(structured, status);
575
+ const prose = classifyProse(fields.prose);
576
+ if (prose !== void 0) return fail(prose, status);
577
+ const byStatus = classifyStatus(status);
578
+ return fail(byStatus ?? fallback, status);
379
579
  }
380
-
381
- // src/stream.ts
382
580
  function gatewayError(status, body) {
383
581
  const error = record(body) && record(body.error) ? body.error : record(body) ? body : {};
384
- const detail = [error.code, error.type, error.message].filter((x) => typeof x === "string").join(" ").toLowerCase();
385
- const options = status === void 0 ? {} : { status };
386
- if (/context.{0,30}(exceed|length|window)|too many tokens|max.*context/u.test(detail)) return new LlmError2("OpenDesign\uFF1A\u8BF7\u6C42\u8D85\u8FC7\u4E0A\u4E0B\u6587\u9650\u5236", "CONTEXT_WINDOW_EXCEEDED", options);
387
- if (/quota|insufficient.{0,20}(credit|balance)|credit.{0,20}exhaust|balance.{0,20}insufficient/u.test(detail)) return new LlmError2("OpenDesign\uFF1A\u5F53\u524D\u8BA1\u8D39\u6C60\u989D\u5EA6\u4E0D\u8DB3", "ACCOUNT_QUOTA", options);
388
- if (status === 401 || status === 403 || /invalid_api_key|missing_api_key/u.test(detail)) return new LlmError2("OpenDesign\uFF1A\u8BA4\u8BC1\u5931\u8D25\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55\u6216\u68C0\u67E5\u51ED\u636E", "INVALID_CREDENTIAL", options);
389
- if (status === 429) return new LlmError2("OpenDesign\uFF1A\u8BF7\u6C42\u8FC7\u4E8E\u9891\u7E41\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5", "RATE_LIMIT", options);
390
- if (status === 408 || status === 504) return new LlmError2("OpenDesign\uFF1A\u8BF7\u6C42\u8D85\u65F6", "TIMEOUT", options);
391
- return new LlmError2(`OpenDesign\uFF1A${status !== void 0 && status >= 500 ? "\u670D\u52A1\u6682\u4E0D\u53EF\u7528" : "\u8BF7\u6C42\u88AB\u62D2\u7EDD"}${status === void 0 ? "" : `\uFF08HTTP ${status}\uFF09`}`, status !== void 0 && status >= 500 ? "SERVER" : "INVALID_REQUEST", options);
582
+ return classify(fieldsOf(error), status, "INVALID_REQUEST");
583
+ }
584
+ function anthropicError(status, body) {
585
+ const error = record(body) && record(body.error) ? body.error : {};
586
+ return classify(fieldsOf(error), status, "INVALID_REQUEST");
587
+ }
588
+ function inBandError(error, fallback = "INVALID_RESPONSE") {
589
+ return classify(fieldsOf(record(error) ? error : {}), void 0, fallback);
590
+ }
591
+
592
+ // src/protocols/sse.ts
593
+ import { LlmError as LlmError3 } from "@deepseek-ai/dsh-llm";
594
+ var MAX_FRAME_CHARS = 4 * 1024 * 1024;
595
+ async function* sseFrames(body, signal, idleMs) {
596
+ const reader = body.getReader();
597
+ const decoder = new TextDecoder();
598
+ let buffer = "";
599
+ let data = [];
600
+ let dataSize = 0;
601
+ let name2;
602
+ function dispatch() {
603
+ const payload = data.join("\n");
604
+ const event = name2;
605
+ data = [];
606
+ dataSize = 0;
607
+ name2 = void 0;
608
+ return payload ? [{ ...event === void 0 ? {} : { event }, data: payload }] : [];
609
+ }
610
+ function line(value) {
611
+ if (!value) return dispatch();
612
+ if (value.startsWith(":")) return [];
613
+ if (value.startsWith("data:")) {
614
+ const part = value.slice(5).replace(/^ /u, "");
615
+ dataSize += part.length;
616
+ if (dataSize > MAX_FRAME_CHARS) throw new LlmError3("OpenDesign\uFF1A\u6D41\u5F0F\u4E8B\u4EF6\u8D85\u8FC7\u5927\u5C0F\u9650\u5236", "INVALID_RESPONSE");
617
+ data.push(part);
618
+ return [];
619
+ }
620
+ if (value.startsWith("event:")) {
621
+ const event = value.slice(6).trim();
622
+ name2 = event === "" ? void 0 : event;
623
+ }
624
+ return [];
625
+ }
626
+ async function read() {
627
+ signal.throwIfAborted();
628
+ let timer;
629
+ let abort = () => {
630
+ };
631
+ try {
632
+ return await Promise.race([
633
+ reader.read(),
634
+ new Promise((_, reject) => {
635
+ timer = setTimeout(() => reject(new LlmError3("OpenDesign\uFF1A\u6D41\u5F0F\u54CD\u5E94\u957F\u65F6\u95F4\u6CA1\u6709\u6570\u636E", "TIMEOUT")), idleMs);
636
+ abort = () => reject(signal.reason);
637
+ signal.addEventListener("abort", abort, { once: true });
638
+ if (signal.aborted) abort();
639
+ })
640
+ ]);
641
+ } finally {
642
+ clearTimeout(timer);
643
+ signal.removeEventListener("abort", abort);
644
+ }
645
+ }
646
+ try {
647
+ while (true) {
648
+ const result = await read();
649
+ buffer += result.done ? decoder.decode() : decoder.decode(result.value, { stream: true });
650
+ let match;
651
+ while (match = /\r\n|\n|\r(?!$)/u.exec(buffer)) {
652
+ const at = match.index;
653
+ const value = buffer.slice(0, at);
654
+ buffer = buffer.slice(at + match[0].length);
655
+ for (const frame of line(value)) yield frame;
656
+ }
657
+ if (buffer.length > MAX_FRAME_CHARS) throw new LlmError3("OpenDesign\uFF1A\u6D41\u5F0F\u884C\u8D85\u8FC7\u5927\u5C0F\u9650\u5236", "INVALID_RESPONSE");
658
+ if (result.done) {
659
+ if (buffer) for (const frame of line(buffer.replace(/\r$/u, ""))) yield frame;
660
+ for (const frame of dispatch()) yield frame;
661
+ break;
662
+ }
663
+ }
664
+ } finally {
665
+ await reader.cancel().catch(() => void 0);
666
+ reader.releaseLock();
667
+ }
392
668
  }
669
+
670
+ // src/stream.ts
393
671
  function tokenUsage(value) {
394
672
  const prompt = finite(value.prompt_tokens), output = finite(value.completion_tokens);
395
673
  if (prompt === void 0 || output === void 0 || prompt < 0 || output < 0) return;
@@ -409,9 +687,8 @@ function tokenUsage(value) {
409
687
  };
410
688
  }
411
689
  async function* consumeSse(body, signal, idleMs) {
412
- const reader = body.getReader(), decoder = new TextDecoder();
413
- let buffer = "", data = [], dataSize = 0, done = false;
414
- let nextIndex = 0, count2 = 0, sawTool = false, text2, reasoning;
690
+ let nextIndex = 0, count2 = 0, sawTool = false, done = false;
691
+ let text2, reasoning;
415
692
  const tools = /* @__PURE__ */ new Map();
416
693
  let finish, usage;
417
694
  function close() {
@@ -419,17 +696,14 @@ async function* consumeSse(body, signal, idleMs) {
419
696
  if (text2) out.push({ type: "block-end", index: text2.index, block: { type: "text", text: text2.text } });
420
697
  if (reasoning) out.push({ type: "block-end", index: reasoning.index, block: { type: "reasoning", text: reasoning.text } });
421
698
  for (const call of tools.values()) {
422
- if (!call.id || !call.name) throw new LlmError2("OpenDesign\uFF1A\u5DE5\u5177\u8C03\u7528\u7F3A\u5C11\u6807\u8BC6\u6216\u540D\u79F0", "INVALID_RESPONSE");
699
+ if (!call.id || !call.name) throw new LlmError4("OpenDesign\uFF1A\u5DE5\u5177\u8C03\u7528\u7F3A\u5C11\u6807\u8BC6\u6216\u540D\u79F0", "INVALID_RESPONSE");
423
700
  out.push({ type: "block-end", index: call.index, block: { type: "tool-call", id: ToolCallId(call.id), name: call.name, arguments: call.arguments } });
424
701
  }
425
702
  text2 = reasoning = void 0;
426
703
  tools.clear();
427
704
  return out.sort((a, b) => ("index" in a ? a.index : 0) - ("index" in b ? b.index : 0));
428
705
  }
429
- function event() {
430
- const payload = data.join("\n");
431
- data = [];
432
- dataSize = 0;
706
+ function event(payload) {
433
707
  if (!payload) return [];
434
708
  if (payload.trim() === "[DONE]") {
435
709
  done = true;
@@ -439,18 +713,18 @@ async function* consumeSse(body, signal, idleMs) {
439
713
  try {
440
714
  value = JSON.parse(payload);
441
715
  } catch {
442
- throw new LlmError2("OpenDesign\uFF1A\u65E0\u6548\u7684\u6D41\u5F0F JSON", "INVALID_RESPONSE");
716
+ throw new LlmError4("OpenDesign\uFF1A\u65E0\u6548\u7684\u6D41\u5F0F JSON", "INVALID_RESPONSE");
443
717
  }
444
- if (!record(value)) throw new LlmError2("OpenDesign\uFF1A\u6D41\u5F0F\u4E8B\u4EF6\u4E0D\u662F\u5BF9\u8C61", "INVALID_RESPONSE");
718
+ if (!record(value)) throw new LlmError4("OpenDesign\uFF1A\u6D41\u5F0F\u4E8B\u4EF6\u4E0D\u662F\u5BF9\u8C61", "INVALID_RESPONSE");
445
719
  if (record(value.error)) throw gatewayError(void 0, value);
446
720
  if (record(value.usage)) usage = tokenUsage(value.usage) ?? usage;
447
721
  if (!Array.isArray(value.choices) || !value.choices.length) return [];
448
722
  const choice = value.choices[0];
449
- if (!record(choice)) throw new LlmError2("OpenDesign\uFF1A\u65E0\u6548\u7684\u6D41\u5F0F\u9009\u9879", "INVALID_RESPONSE");
723
+ if (!record(choice)) throw new LlmError4("OpenDesign\uFF1A\u65E0\u6548\u7684\u6D41\u5F0F\u9009\u9879", "INVALID_RESPONSE");
450
724
  const delta = record(choice.delta) ? choice.delta : {};
451
725
  const out = [];
452
726
  const hasDelta = typeof delta.content === "string" && delta.content !== "" || typeof delta.reasoning_content === "string" && delta.reasoning_content !== "" || Array.isArray(delta.tool_calls) && delta.tool_calls.length > 0;
453
- if (finish && hasDelta) throw new LlmError2("OpenDesign\uFF1A\u54CD\u5E94\u7ED3\u675F\u540E\u4ECD\u6536\u5230\u5185\u5BB9", "INVALID_RESPONSE");
727
+ if (finish && hasDelta) throw new LlmError4("OpenDesign\uFF1A\u54CD\u5E94\u7ED3\u675F\u540E\u4ECD\u6536\u5230\u5185\u5BB9", "INVALID_RESPONSE");
454
728
  const thinking = typeof delta.reasoning_content === "string" ? delta.reasoning_content : typeof delta.reasoning === "string" ? delta.reasoning : "";
455
729
  if (thinking) {
456
730
  if (!reasoning) {
@@ -471,7 +745,7 @@ async function* consumeSse(body, signal, idleMs) {
471
745
  out.push({ type: "text-delta", index: text2.index, text: delta.content });
472
746
  }
473
747
  if (Array.isArray(delta.tool_calls)) for (const item of delta.tool_calls) {
474
- if (!record(item) || typeof item.index !== "number" || !Number.isInteger(item.index) || item.index < 0) throw new LlmError2("OpenDesign\uFF1A\u65E0\u6548\u7684\u5DE5\u5177\u8C03\u7528\u7D22\u5F15", "INVALID_RESPONSE");
748
+ if (!record(item) || typeof item.index !== "number" || !Number.isInteger(item.index) || item.index < 0) throw new LlmError4("OpenDesign\uFF1A\u65E0\u6548\u7684\u5DE5\u5177\u8C03\u7528\u7D22\u5F15", "INVALID_RESPONSE");
475
749
  let call = tools.get(item.index);
476
750
  if (!call) {
477
751
  call = { index: nextIndex++, id: "", name: "", arguments: "", started: false };
@@ -481,7 +755,7 @@ async function* consumeSse(body, signal, idleMs) {
481
755
  }
482
756
  const fn = record(item.function) ? item.function : {};
483
757
  if (typeof item.id === "string" && item.id) {
484
- if (call.id && call.id !== item.id) throw new LlmError2("OpenDesign\uFF1A\u5DE5\u5177\u8C03\u7528\u6807\u8BC6\u53D1\u751F\u53D8\u5316", "INVALID_RESPONSE");
758
+ if (call.id && call.id !== item.id) throw new LlmError4("OpenDesign\uFF1A\u5DE5\u5177\u8C03\u7528\u6807\u8BC6\u53D1\u751F\u53D8\u5316", "INVALID_RESPONSE");
485
759
  call.id = item.id;
486
760
  }
487
761
  if (typeof fn.name === "string" && fn.name) call.name = fn.name;
@@ -496,7 +770,7 @@ async function* consumeSse(body, signal, idleMs) {
496
770
  }
497
771
  }
498
772
  if (typeof choice.finish_reason === "string" && choice.finish_reason) {
499
- if (finish) throw new LlmError2("OpenDesign\uFF1A\u91CD\u590D\u7684\u54CD\u5E94\u7EC8\u6B62\u4E8B\u4EF6", "INVALID_RESPONSE");
773
+ if (finish) throw new LlmError4("OpenDesign\uFF1A\u91CD\u590D\u7684\u54CD\u5E94\u7EC8\u6B62\u4E8B\u4EF6", "INVALID_RESPONSE");
500
774
  switch (choice.finish_reason) {
501
775
  case "length":
502
776
  finish = { kind: "max-tokens" };
@@ -509,70 +783,22 @@ async function* consumeSse(body, signal, idleMs) {
509
783
  finish = { kind: "stop" };
510
784
  break;
511
785
  default:
512
- throw new LlmError2("OpenDesign\uFF1A\u6A21\u578B\u54CD\u5E94\u88AB\u8FC7\u6EE4\u6216\u5F02\u5E38\u7EC8\u6B62", "INVALID_RESPONSE");
786
+ throw new LlmError4("OpenDesign\uFF1A\u6A21\u578B\u54CD\u5E94\u88AB\u8FC7\u6EE4\u6216\u5F02\u5E38\u7EC8\u6B62", "INVALID_RESPONSE");
513
787
  }
514
788
  out.push(...close());
515
789
  }
516
790
  return out;
517
791
  }
518
- function line(value) {
519
- if (!value) return event();
520
- if (value.startsWith("data:")) {
521
- const part = value.slice(5).replace(/^ /u, "");
522
- dataSize += part.length;
523
- if (dataSize > 4 * 1024 * 1024) throw new LlmError2("OpenDesign\uFF1A\u6D41\u5F0F\u4E8B\u4EF6\u8D85\u8FC7\u5927\u5C0F\u9650\u5236", "INVALID_RESPONSE");
524
- data.push(part);
525
- }
526
- return [];
527
- }
528
- async function read() {
529
- signal.throwIfAborted();
530
- let timer;
531
- let abort = () => {
532
- };
533
- try {
534
- return await Promise.race([
535
- reader.read(),
536
- new Promise((_, reject) => {
537
- timer = setTimeout(() => reject(new LlmError2("OpenDesign\uFF1A\u6D41\u5F0F\u54CD\u5E94\u957F\u65F6\u95F4\u6CA1\u6709\u6570\u636E", "TIMEOUT")), idleMs);
538
- abort = () => reject(signal.reason);
539
- signal.addEventListener("abort", abort, { once: true });
540
- if (signal.aborted) abort();
541
- })
542
- ]);
543
- } finally {
544
- clearTimeout(timer);
545
- signal.removeEventListener("abort", abort);
546
- }
547
- }
548
- try {
549
- while (!done) {
550
- const result = await read();
551
- buffer += result.done ? decoder.decode() : decoder.decode(result.value, { stream: true });
552
- let match;
553
- while (match = /\r\n|\n|\r(?!$)/u.exec(buffer)) {
554
- const at = match.index, value = buffer.slice(0, at);
555
- buffer = buffer.slice(at + match[0].length);
556
- for (const chunk of line(value)) yield chunk;
557
- if (done) break;
558
- }
559
- if (buffer.length > 4 * 1024 * 1024) throw new LlmError2("OpenDesign\uFF1A\u6D41\u5F0F\u884C\u8D85\u8FC7\u5927\u5C0F\u9650\u5236", "INVALID_RESPONSE");
560
- if (result.done) {
561
- if (buffer && !done) for (const chunk of line(buffer.replace(/\r$/u, ""))) yield chunk;
562
- if (!done) for (const chunk of event()) yield chunk;
563
- break;
564
- }
565
- }
566
- signal.throwIfAborted();
567
- if (!finish) throw new LlmError2("OpenDesign\uFF1A\u54CD\u5E94\u6D41\u63D0\u524D\u4E2D\u65AD\uFF0C\u672A\u6536\u5230\u7ED3\u675F\u539F\u56E0", "INVALID_RESPONSE");
568
- if (!count2) throw new LlmError2("OpenDesign\uFF1A\u6A21\u578B\u8FD4\u56DE\u7A7A\u54CD\u5E94", "EMPTY_RESPONSE");
569
- if (finish.kind === "tool-calls" && !sawTool) throw new LlmError2("OpenDesign\uFF1A\u54CD\u5E94\u58F0\u660E\u5DE5\u5177\u8C03\u7528\uFF0C\u4F46\u672A\u8FD4\u56DE\u4EFB\u4F55\u5DE5\u5177\u5757", "INVALID_RESPONSE");
570
- if (usage) yield { type: "usage", usage };
571
- yield { type: "finish", reason: finish };
572
- } finally {
573
- await reader.cancel().catch(() => void 0);
574
- reader.releaseLock();
792
+ for await (const frame of sseFrames(body, signal, idleMs)) {
793
+ for (const chunk of event(frame.data)) yield chunk;
794
+ if (done) break;
575
795
  }
796
+ signal.throwIfAborted();
797
+ if (!finish) throw new LlmError4("OpenDesign\uFF1A\u54CD\u5E94\u6D41\u63D0\u524D\u4E2D\u65AD\uFF0C\u672A\u6536\u5230\u7ED3\u675F\u539F\u56E0", "INVALID_RESPONSE");
798
+ if (!count2) throw new LlmError4("OpenDesign\uFF1A\u6A21\u578B\u8FD4\u56DE\u7A7A\u54CD\u5E94", "EMPTY_RESPONSE");
799
+ if (finish.kind === "tool-calls" && !sawTool) throw new LlmError4("OpenDesign\uFF1A\u54CD\u5E94\u58F0\u660E\u5DE5\u5177\u8C03\u7528\uFF0C\u4F46\u672A\u8FD4\u56DE\u4EFB\u4F55\u5DE5\u5177\u5757", "INVALID_RESPONSE");
800
+ if (usage) yield { type: "usage", usage };
801
+ yield { type: "finish", reason: finish };
576
802
  }
577
803
 
578
804
  // src/empty-turn.ts
@@ -698,22 +924,775 @@ var EmptyTurnTracker = class {
698
924
  }
699
925
  };
700
926
 
927
+ // src/protocols/index.ts
928
+ import { LlmError as LlmError9 } from "@deepseek-ai/dsh-llm";
929
+
930
+ // src/protocols/chat-completions.ts
931
+ import { LlmError as LlmError5 } from "@deepseek-ai/dsh-llm";
932
+
933
+ // src/protocols/headers.ts
934
+ import { attributionHeaders } from "@deepseek-ai/dsh-llm";
935
+ function streamHeaders(apiKey, extra = {}) {
936
+ return {
937
+ ...attributionHeaders(),
938
+ authorization: `Bearer ${apiKey}`,
939
+ "content-type": "application/json",
940
+ accept: "text/event-stream",
941
+ ...extra
942
+ };
943
+ }
944
+
945
+ // src/protocols/chat-completions.ts
946
+ function buildChatCompletionsRequest(ctx) {
947
+ const { options, model } = ctx;
948
+ const replayReasoning = model.id.startsWith("deepseek-") || model.id.startsWith("kimi-");
949
+ const messages2 = chatMessages(options, (ref) => ctx.images.get(ref.attachmentId), replayReasoning);
950
+ const tools = chatTools(options);
951
+ const body = { model: model.id, messages: messages2, stream: true, stream_options: { include_usage: true } };
952
+ if (tools) body.tools = tools;
953
+ if (options.temperature !== void 0) body.temperature = options.temperature;
954
+ if (ctx.topP !== void 0) body.top_p = ctx.topP;
955
+ if (options.maxTokens !== void 0) {
956
+ if (!Number.isSafeInteger(options.maxTokens) || options.maxTokens <= 0) throw new LlmError5("maxTokens \u5FC5\u987B\u662F\u6B63\u6574\u6570", "INVALID_REQUEST");
957
+ body.max_tokens = options.maxTokens;
958
+ }
959
+ if (options.reasoningEffort !== void 0) body.reasoning_effort = options.reasoningEffort;
960
+ if (options.stop?.length) body.stop = options.stop;
961
+ return { url: inferenceEndpoint(ctx.baseURL, "chat-completions"), headers: streamHeaders(ctx.apiKey), body };
962
+ }
963
+ var chatCompletions = {
964
+ id: "chat-completions",
965
+ build: buildChatCompletionsRequest,
966
+ stream(body, signal, idleMs) {
967
+ return consumeSse(body, signal, idleMs);
968
+ }
969
+ };
970
+
971
+ // src/protocols/messages.ts
972
+ import {
973
+ LlmError as LlmError7,
974
+ ToolCallId as ToolCallId2
975
+ } from "@deepseek-ai/dsh-llm";
976
+
977
+ // src/protocols/parts.ts
978
+ import { LlmError as LlmError6, textOnlyImageText as textOnlyImageText2 } from "@deepseek-ai/dsh-llm";
979
+ function projectContent(blocks, image) {
980
+ let text2 = "";
981
+ const images = [];
982
+ for (const block of blocks) {
983
+ switch (block.type) {
984
+ case "text":
985
+ text2 += block.text;
986
+ break;
987
+ case "image": {
988
+ const url = block.offloaded ? void 0 : image?.(block.attachment);
989
+ if (url) images.push(url);
990
+ else text2 += `${text2 ? "\n" : ""}${textOnlyImageText2(block.attachment)}`;
991
+ break;
992
+ }
993
+ case "file":
994
+ throw new LlmError6("\u539F\u59CB\u6587\u4EF6\u672A\u88AB\u5BBF\u4E3B\u6295\u5F71\u4E3A\u6587\u672C\u53E5\u67C4", "UNSUPPORTED_OPTION");
995
+ // 工具定义已由 Core 物化进 options.tools;工具更新块只对声明 in-history 的路由有意义。
996
+ default:
997
+ break;
998
+ }
999
+ }
1000
+ return { text: text2, images };
1001
+ }
1002
+
1003
+ // src/protocols/usage.ts
1004
+ function valid(value) {
1005
+ return value !== void 0 && Number.isFinite(value) && value >= 0;
1006
+ }
1007
+ function openAiUsage(raw) {
1008
+ const input = raw.input, output = raw.output;
1009
+ if (!valid(input) || !valid(output)) return void 0;
1010
+ const read = valid(raw.cacheRead) ? raw.cacheRead : 0;
1011
+ const write = valid(raw.cacheWrite) ? raw.cacheWrite : 0;
1012
+ const cached = read + write <= input;
1013
+ const reasoning = valid(raw.reasoning) && raw.reasoning <= output ? raw.reasoning : void 0;
1014
+ return {
1015
+ inputTokens: cached ? input - read - write : input,
1016
+ outputTokens: output,
1017
+ totalTokens: input + output,
1018
+ ...cached && read > 0 ? { cacheReadTokens: read } : {},
1019
+ ...cached && write > 0 ? { cacheWriteTokens: write } : {},
1020
+ ...reasoning !== void 0 ? { reasoningTokens: reasoning } : {}
1021
+ };
1022
+ }
1023
+ function anthropicUsage(raw) {
1024
+ const input = raw.input, output = raw.output;
1025
+ if (!valid(input) || !valid(output)) return void 0;
1026
+ const read = valid(raw.cacheRead) ? raw.cacheRead : void 0;
1027
+ const write = valid(raw.cacheWrite) ? raw.cacheWrite : void 0;
1028
+ const reasoning = valid(raw.reasoning) && raw.reasoning <= output ? raw.reasoning : void 0;
1029
+ const total = valid(raw.total) ? raw.total : input + (read ?? 0) + (write ?? 0) + output;
1030
+ return {
1031
+ inputTokens: input,
1032
+ outputTokens: output,
1033
+ totalTokens: total,
1034
+ ...read !== void 0 && read > 0 ? { cacheReadTokens: read } : {},
1035
+ ...write !== void 0 && write > 0 ? { cacheWriteTokens: write } : {},
1036
+ ...reasoning !== void 0 ? { reasoningTokens: reasoning } : {}
1037
+ };
1038
+ }
1039
+
1040
+ // src/protocols/messages.ts
1041
+ var MESSAGES_DEFAULT_MAX_TOKENS = 4096;
1042
+ var ANTHROPIC_VERSION = "2023-06-01";
1043
+ var THINKING_EFFORTS = ["low", "medium", "high", "xhigh", "max"];
1044
+ var DATA_URL = /^data:([^;,]+);base64,(.*)$/su;
1045
+ function messagesURL(baseURL) {
1046
+ return inferenceEndpoint(baseURL, "messages");
1047
+ }
1048
+ function imagePart(url) {
1049
+ const match = DATA_URL.exec(url);
1050
+ return match?.[1] && match[2] ? { type: "image", source: { type: "base64", media_type: match[1], data: match[2] } } : { type: "image", source: { type: "url", url } };
1051
+ }
1052
+ function toolInput(argumentsText) {
1053
+ if (argumentsText.trim() === "") return {};
1054
+ let parsed;
1055
+ try {
1056
+ parsed = JSON.parse(argumentsText);
1057
+ } catch {
1058
+ throw new LlmError7("\u5386\u53F2\u5DE5\u5177\u8C03\u7528\u53C2\u6570\u4E0D\u662F\u5408\u6CD5 JSON\uFF0C\u65E0\u6CD5\u6295\u5F71\u4E3A Anthropic tool_use.input", "INVALID_REQUEST");
1059
+ }
1060
+ if (!record(parsed)) throw new LlmError7("\u5386\u53F2\u5DE5\u5177\u8C03\u7528\u53C2\u6570\u4E0D\u662F JSON \u5BF9\u8C61\uFF0C\u65E0\u6CD5\u6295\u5F71\u4E3A Anthropic tool_use.input", "INVALID_REQUEST");
1061
+ return parsed;
1062
+ }
1063
+ function buildMessagesRequest(ctx) {
1064
+ const { options, model } = ctx;
1065
+ const maxTokens = options.maxTokens ?? MESSAGES_DEFAULT_MAX_TOKENS;
1066
+ if (!Number.isSafeInteger(maxTokens) || maxTokens <= 0) throw new LlmError7("maxTokens \u5FC5\u987B\u662F\u6B63\u6574\u6570", "INVALID_REQUEST");
1067
+ const system = [];
1068
+ const messages2 = [];
1069
+ const lookup = ctx.images.size ? (ref) => ctx.images.get(ref.attachmentId) : void 0;
1070
+ let results = [];
1071
+ let lifted = [];
1072
+ const flushResults = () => {
1073
+ if (results.length) {
1074
+ messages2.push({ role: "user", content: results });
1075
+ results = [];
1076
+ }
1077
+ };
1078
+ const flushLifted = () => {
1079
+ flushResults();
1080
+ if (lifted.length) {
1081
+ messages2.push({
1082
+ role: "user",
1083
+ content: [{ type: "text", text: "(Image content from the preceding tool result.)" }, ...lifted.map(imagePart)]
1084
+ });
1085
+ lifted = [];
1086
+ }
1087
+ };
1088
+ for (const message of options.messages) {
1089
+ if (message.role !== "tool") flushLifted();
1090
+ const projected = projectContent(message.content, message.role === "user" || message.role === "tool" ? lookup : void 0);
1091
+ switch (message.role) {
1092
+ case "system":
1093
+ case "developer":
1094
+ if (projected.text) system.push(projected.text);
1095
+ break;
1096
+ case "user": {
1097
+ const content2 = [
1098
+ ...projected.text ? [{ type: "text", text: projected.text }] : [],
1099
+ ...projected.images.map(imagePart)
1100
+ ];
1101
+ if (content2.length) messages2.push({ role: "user", content: content2 });
1102
+ break;
1103
+ }
1104
+ case "assistant": {
1105
+ const content2 = [];
1106
+ if (projected.text) content2.push({ type: "text", text: projected.text });
1107
+ for (const block of message.content) {
1108
+ if (block.type === "tool-call") content2.push({ type: "tool_use", id: block.id, name: block.name, input: toolInput(block.arguments) });
1109
+ }
1110
+ if (content2.length) messages2.push({ role: "assistant", content: content2 });
1111
+ break;
1112
+ }
1113
+ case "tool": {
1114
+ results.push({
1115
+ type: "tool_result",
1116
+ tool_use_id: message.toolCallId,
1117
+ content: projected.text || (projected.images.length ? "(Image content follows in the next user message.)" : "")
1118
+ });
1119
+ lifted.push(...projected.images);
1120
+ break;
1121
+ }
1122
+ }
1123
+ }
1124
+ flushLifted();
1125
+ if (options.system?.trim()) system.unshift(options.system);
1126
+ const body = { model: model.id, max_tokens: maxTokens, messages: messages2, stream: true };
1127
+ if (system.length) body.system = system.join("\n\n");
1128
+ if (options.tools?.length) body.tools = options.tools.map((tool) => ({ name: tool.name, description: tool.description, input_schema: tool.parameters }));
1129
+ if (options.temperature !== void 0) body.temperature = options.temperature;
1130
+ if (ctx.topP !== void 0) body.top_p = ctx.topP;
1131
+ if (options.stop?.length) body.stop_sequences = options.stop;
1132
+ if (options.reasoningEffort !== void 0) {
1133
+ const effort2 = String(options.reasoningEffort);
1134
+ if (effort2 === "none") body.thinking = { type: "disabled" };
1135
+ else if (THINKING_EFFORTS.includes(effort2)) {
1136
+ body.thinking = { type: "adaptive" };
1137
+ body.output_config = { effort: effort2 };
1138
+ } else throw new LlmError7(`Anthropic Messages \u534F\u8BAE\u4E0D\u63A5\u53D7\u601D\u8003\u6DF1\u5EA6\u300C${effort2}\u300D`, "UNSUPPORTED_OPTION");
1139
+ }
1140
+ return { url: messagesURL(ctx.baseURL), headers: streamHeaders(ctx.apiKey, { "anthropic-version": ANTHROPIC_VERSION }), body };
1141
+ }
1142
+ function num(value) {
1143
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
1144
+ }
1145
+ async function* streamMessages(body, signal, idleMs) {
1146
+ let nextIndex = 0;
1147
+ let count2 = 0;
1148
+ let finish;
1149
+ let stopReason;
1150
+ let sawTool = false;
1151
+ const blocks = /* @__PURE__ */ new Map();
1152
+ const raw = {
1153
+ input: void 0,
1154
+ output: void 0,
1155
+ cacheRead: void 0,
1156
+ cacheWrite: void 0
1157
+ };
1158
+ let usage;
1159
+ function stateAt(index) {
1160
+ const state = typeof index === "number" ? blocks.get(index) : void 0;
1161
+ if (!state) throw new LlmError7("OpenDesign\uFF1A\u6D41\u5F0F\u5185\u5BB9\u5757\u7D22\u5F15\u65E0\u6548", "INVALID_RESPONSE");
1162
+ return state;
1163
+ }
1164
+ function assign(state) {
1165
+ if (state.index < 0) {
1166
+ state.index = nextIndex++;
1167
+ count2++;
1168
+ }
1169
+ }
1170
+ function close() {
1171
+ const out = [];
1172
+ for (const state of blocks.values()) {
1173
+ if (!state.started) continue;
1174
+ if (state.kind === "text") out.push({ type: "block-end", index: state.index, block: { type: "text", text: state.text } });
1175
+ else if (state.kind === "reasoning") out.push({ type: "block-end", index: state.index, block: { type: "reasoning", text: state.text } });
1176
+ else if (state.kind === "tool") {
1177
+ if (!state.id || !state.name) throw new LlmError7("OpenDesign\uFF1A\u5DE5\u5177\u8C03\u7528\u7F3A\u5C11\u6807\u8BC6\u6216\u540D\u79F0", "INVALID_RESPONSE");
1178
+ out.push({ type: "block-end", index: state.index, block: { type: "tool-call", id: ToolCallId2(state.id), name: state.name, arguments: state.args } });
1179
+ }
1180
+ }
1181
+ return out.sort((a, b) => ("index" in a ? a.index : 0) - ("index" in b ? b.index : 0));
1182
+ }
1183
+ function reasonFromStop(stop) {
1184
+ switch (stop) {
1185
+ case "end_turn":
1186
+ case "stop_sequence":
1187
+ return { kind: "stop" };
1188
+ case "max_tokens":
1189
+ case "model_context_window_exceeded":
1190
+ return { kind: "max-tokens" };
1191
+ case "tool_use":
1192
+ return { kind: "tool-calls" };
1193
+ case "refusal":
1194
+ throw new LlmError7("OpenDesign\uFF1A\u6A21\u578B\u62D2\u7EDD\u56DE\u7B54\uFF08refusal\uFF09", "INVALID_RESPONSE");
1195
+ case "pause_turn":
1196
+ throw new LlmError7("OpenDesign\uFF1A\u6A21\u578B\u6682\u505C\u8F6E\u6B21\uFF0C\u9700\u91CD\u65B0\u53D1\u8D77\u8BF7\u6C42", "INVALID_RESPONSE");
1197
+ default:
1198
+ throw new LlmError7("OpenDesign\uFF1A\u6A21\u578B\u54CD\u5E94\u88AB\u8FC7\u6EE4\u6216\u5F02\u5E38\u7EC8\u6B62", "INVALID_RESPONSE");
1199
+ }
1200
+ }
1201
+ for await (const frame of sseFrames(body, signal, idleMs)) {
1202
+ let value;
1203
+ try {
1204
+ value = JSON.parse(frame.data);
1205
+ } catch {
1206
+ throw new LlmError7("OpenDesign\uFF1A\u65E0\u6548\u7684\u6D41\u5F0F JSON", "INVALID_RESPONSE");
1207
+ }
1208
+ if (!record(value)) throw new LlmError7("OpenDesign\uFF1A\u6D41\u5F0F\u4E8B\u4EF6\u4E0D\u662F\u5BF9\u8C61", "INVALID_RESPONSE");
1209
+ const type = typeof value.type === "string" && value.type !== "" ? value.type : frame.event;
1210
+ if (!type) throw new LlmError7("OpenDesign\uFF1A\u6D41\u5F0F\u4E8B\u4EF6\u7F3A\u5C11\u7C7B\u578B", "INVALID_RESPONSE");
1211
+ const out = [];
1212
+ switch (type) {
1213
+ case "message_start": {
1214
+ const message = record(value.message) ? value.message : {};
1215
+ const counters = record(message.usage) ? message.usage : {};
1216
+ raw.input = num(counters.input_tokens) ?? raw.input;
1217
+ raw.output = num(counters.output_tokens) ?? raw.output;
1218
+ raw.cacheRead = num(counters.cache_read_input_tokens) ?? raw.cacheRead;
1219
+ raw.cacheWrite = num(counters.cache_creation_input_tokens) ?? raw.cacheWrite;
1220
+ break;
1221
+ }
1222
+ case "content_block_start": {
1223
+ const index = value.index;
1224
+ if (typeof index !== "number" || !Number.isSafeInteger(index) || index < 0) throw new LlmError7("OpenDesign\uFF1A\u6D41\u5F0F\u5185\u5BB9\u5757\u7D22\u5F15\u65E0\u6548", "INVALID_RESPONSE");
1225
+ const block = record(value.content_block) ? value.content_block : {};
1226
+ const kind = typeof block.type === "string" ? block.type : "";
1227
+ const state = { index: -1, kind: "skip", text: "", id: "", name: "", args: "", started: false };
1228
+ if (kind === "text") state.kind = "text";
1229
+ else if (kind === "thinking") state.kind = "reasoning";
1230
+ else if (kind === "tool_use") state.kind = "tool";
1231
+ if (state.kind === "tool") {
1232
+ state.id = typeof block.id === "string" ? block.id : "";
1233
+ state.name = typeof block.name === "string" ? block.name : "";
1234
+ sawTool = true;
1235
+ }
1236
+ if (state.kind === "text" && typeof block.text === "string" && block.text !== "") state.text = block.text;
1237
+ if (state.kind === "reasoning" && typeof block.thinking === "string" && block.thinking !== "") state.text = block.thinking;
1238
+ blocks.set(index, state);
1239
+ if (state.kind === "tool") {
1240
+ assign(state);
1241
+ state.started = true;
1242
+ out.push({ type: "block-start", index: state.index, blockType: "tool-call" });
1243
+ if (state.id) out.push({ type: "tool-call-delta", index: state.index, id: ToolCallId2(state.id), ...state.name ? { name: state.name } : {}, argumentsDelta: "" });
1244
+ } else if ((state.kind === "text" || state.kind === "reasoning") && state.text !== "") {
1245
+ assign(state);
1246
+ state.started = true;
1247
+ out.push({ type: "block-start", index: state.index, blockType: state.kind === "text" ? "text" : "reasoning" });
1248
+ out.push(state.kind === "text" ? { type: "text-delta", index: state.index, text: state.text } : { type: "reasoning-delta", index: state.index, text: state.text });
1249
+ }
1250
+ break;
1251
+ }
1252
+ case "content_block_delta": {
1253
+ const state = stateAt(value.index);
1254
+ const delta = record(value.delta) ? value.delta : {};
1255
+ const deltaType = typeof delta.type === "string" ? delta.type : "";
1256
+ if (deltaType === "text_delta") {
1257
+ const text2 = typeof delta.text === "string" ? delta.text : "";
1258
+ if (!text2) break;
1259
+ assign(state);
1260
+ if (!state.started) {
1261
+ state.started = true;
1262
+ out.push({ type: "block-start", index: state.index, blockType: "text" });
1263
+ }
1264
+ state.text += text2;
1265
+ out.push({ type: "text-delta", index: state.index, text: text2 });
1266
+ break;
1267
+ }
1268
+ if (deltaType === "thinking_delta") {
1269
+ const text2 = typeof delta.thinking === "string" ? delta.thinking : "";
1270
+ if (!text2) break;
1271
+ assign(state);
1272
+ if (!state.started) {
1273
+ state.started = true;
1274
+ out.push({ type: "block-start", index: state.index, blockType: "reasoning" });
1275
+ }
1276
+ state.text += text2;
1277
+ out.push({ type: "reasoning-delta", index: state.index, text: text2 });
1278
+ break;
1279
+ }
1280
+ if (deltaType === "input_json_delta") {
1281
+ if (state.kind !== "tool") throw new LlmError7("OpenDesign\uFF1A\u5DE5\u5177\u53C2\u6570\u5206\u7247\u843D\u5728\u975E\u5DE5\u5177\u5757\u4E0A", "INVALID_RESPONSE");
1282
+ const part = typeof delta.partial_json === "string" ? delta.partial_json : "";
1283
+ state.args += part;
1284
+ if (state.id) out.push({ type: "tool-call-delta", index: state.index, id: ToolCallId2(state.id), ...state.name ? { name: state.name } : {}, argumentsDelta: part });
1285
+ break;
1286
+ }
1287
+ break;
1288
+ }
1289
+ case "message_delta": {
1290
+ const delta = record(value.delta) ? value.delta : {};
1291
+ if (typeof delta.stop_reason === "string") stopReason = delta.stop_reason;
1292
+ const counters = record(value.usage) ? value.usage : {};
1293
+ raw.output = num(counters.output_tokens) ?? raw.output;
1294
+ raw.input = num(counters.input_tokens) ?? raw.input;
1295
+ raw.cacheRead = num(counters.cache_read_input_tokens) ?? raw.cacheRead;
1296
+ raw.cacheWrite = num(counters.cache_creation_input_tokens) ?? raw.cacheWrite;
1297
+ break;
1298
+ }
1299
+ case "message_stop": {
1300
+ const reason = reasonFromStop(stopReason);
1301
+ if (reason.kind === "tool-calls" && !sawTool) throw new LlmError7("OpenDesign\uFF1A\u54CD\u5E94\u58F0\u660E\u5DE5\u5177\u8C03\u7528\uFF0C\u4F46\u672A\u8FD4\u56DE\u4EFB\u4F55\u5DE5\u5177\u5757", "INVALID_RESPONSE");
1302
+ if (!count2) throw new LlmError7("OpenDesign\uFF1A\u6A21\u578B\u8FD4\u56DE\u7A7A\u54CD\u5E94", "EMPTY_RESPONSE");
1303
+ usage = anthropicUsage(raw) ?? usage;
1304
+ out.push(...close());
1305
+ if (usage) out.push({ type: "usage", usage });
1306
+ finish = reason;
1307
+ out.push({ type: "finish", reason });
1308
+ break;
1309
+ }
1310
+ case "error":
1311
+ throw anthropicError(void 0, value);
1312
+ case "ping":
1313
+ default:
1314
+ break;
1315
+ }
1316
+ for (const chunk of out) yield chunk;
1317
+ if (finish) break;
1318
+ }
1319
+ signal.throwIfAborted();
1320
+ if (!finish) throw new LlmError7("OpenDesign\uFF1A\u54CD\u5E94\u6D41\u63D0\u524D\u4E2D\u65AD\uFF0C\u672A\u6536\u5230\u7ED3\u675F\u4E8B\u4EF6", "INVALID_RESPONSE");
1321
+ }
1322
+ var messages = {
1323
+ id: "messages",
1324
+ build: buildMessagesRequest,
1325
+ stream: streamMessages
1326
+ };
1327
+
1328
+ // src/protocols/responses.ts
1329
+ import {
1330
+ LlmError as LlmError8,
1331
+ ToolCallId as ToolCallId3
1332
+ } from "@deepseek-ai/dsh-llm";
1333
+ var REASONING_EFFORTS2 = ["none", "minimal", "low", "medium", "high", "xhigh", "max"];
1334
+ function userContent(text2, images) {
1335
+ if (!images.length) return text2;
1336
+ return [
1337
+ ...text2 ? [{ type: "input_text", text: text2 }] : [],
1338
+ ...images.map((url) => ({ type: "input_image", image_url: url, detail: "auto" }))
1339
+ ];
1340
+ }
1341
+ function buildResponsesRequest(ctx) {
1342
+ const { options, model } = ctx;
1343
+ const instructions = [];
1344
+ const input = [];
1345
+ const lookup = ctx.images.size ? (ref) => ctx.images.get(ref.attachmentId) : void 0;
1346
+ let lifted = [];
1347
+ const flushLifted = () => {
1348
+ if (!lifted.length) return;
1349
+ input.push({ role: "user", content: userContent("(Image content from the preceding tool result.)", lifted) });
1350
+ lifted = [];
1351
+ };
1352
+ for (const message of options.messages) {
1353
+ if (message.role !== "tool") flushLifted();
1354
+ const projected = projectContent(message.content, message.role === "user" || message.role === "tool" ? lookup : void 0);
1355
+ switch (message.role) {
1356
+ case "system":
1357
+ case "developer":
1358
+ if (projected.text) instructions.push(projected.text);
1359
+ break;
1360
+ case "user":
1361
+ if (projected.text || projected.images.length) input.push({ role: "user", content: userContent(projected.text, projected.images) });
1362
+ break;
1363
+ case "assistant": {
1364
+ if (projected.text) input.push({ role: "assistant", content: projected.text });
1365
+ for (const block of message.content) {
1366
+ if (block.type === "tool-call") input.push({ type: "function_call", call_id: block.id, name: block.name, arguments: block.arguments });
1367
+ }
1368
+ break;
1369
+ }
1370
+ case "tool": {
1371
+ input.push({
1372
+ type: "function_call_output",
1373
+ call_id: message.toolCallId,
1374
+ output: projected.text || (projected.images.length ? "(Image content follows in the next user message.)" : "")
1375
+ });
1376
+ lifted.push(...projected.images);
1377
+ break;
1378
+ }
1379
+ }
1380
+ }
1381
+ flushLifted();
1382
+ if (options.system?.trim()) instructions.unshift(options.system);
1383
+ const body = { model: model.id, input, stream: true, store: false };
1384
+ if (instructions.length) body.instructions = instructions.join("\n\n");
1385
+ if (options.tools?.length) body.tools = options.tools.map((tool) => ({ type: "function", name: tool.name, description: tool.description, parameters: tool.parameters }));
1386
+ if (options.temperature !== void 0) body.temperature = options.temperature;
1387
+ if (ctx.topP !== void 0) body.top_p = ctx.topP;
1388
+ if (options.maxTokens !== void 0) {
1389
+ if (!Number.isSafeInteger(options.maxTokens) || options.maxTokens <= 0) throw new LlmError8("maxTokens \u5FC5\u987B\u662F\u6B63\u6574\u6570", "INVALID_REQUEST");
1390
+ body.max_output_tokens = options.maxTokens;
1391
+ }
1392
+ if (options.reasoningEffort !== void 0) {
1393
+ const effort2 = String(options.reasoningEffort);
1394
+ if (!REASONING_EFFORTS2.includes(effort2)) throw new LlmError8(`Responses \u534F\u8BAE\u4E0D\u63A5\u53D7\u601D\u8003\u6DF1\u5EA6\u300C${effort2}\u300D`, "UNSUPPORTED_OPTION");
1395
+ body.reasoning = { effort: effort2, summary: "auto" };
1396
+ }
1397
+ if (options.stop?.length) throw new LlmError8("Responses \u534F\u8BAE\u4E0D\u652F\u6301 stop \u5E8F\u5217", "UNSUPPORTED_OPTION");
1398
+ return { url: inferenceEndpoint(ctx.baseURL, "responses"), headers: streamHeaders(ctx.apiKey), body };
1399
+ }
1400
+ function str(value) {
1401
+ return typeof value === "string" && value !== "" ? value : void 0;
1402
+ }
1403
+ function int(value) {
1404
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : void 0;
1405
+ }
1406
+ function responseUsage(response) {
1407
+ if (!record(response.usage)) return void 0;
1408
+ const usage = response.usage;
1409
+ const input = record(usage.input_tokens_details) ? usage.input_tokens_details : {};
1410
+ const output = record(usage.output_tokens_details) ? usage.output_tokens_details : {};
1411
+ return openAiUsage({
1412
+ input: typeof usage.input_tokens === "number" ? usage.input_tokens : void 0,
1413
+ output: typeof usage.output_tokens === "number" ? usage.output_tokens : void 0,
1414
+ cacheRead: typeof input.cached_tokens === "number" ? input.cached_tokens : void 0,
1415
+ cacheWrite: typeof input.cache_write_tokens === "number" ? input.cache_write_tokens : void 0,
1416
+ reasoning: typeof output.reasoning_tokens === "number" ? output.reasoning_tokens : void 0
1417
+ });
1418
+ }
1419
+ async function* streamResponses(body, signal, idleMs) {
1420
+ let nextIndex = 0;
1421
+ let count2 = 0;
1422
+ let usage;
1423
+ let finish;
1424
+ const texts = /* @__PURE__ */ new Map();
1425
+ const reasonings = /* @__PURE__ */ new Map();
1426
+ const calls = /* @__PURE__ */ new Map();
1427
+ const keyByOutput = /* @__PURE__ */ new Map();
1428
+ function textState(key) {
1429
+ let state = texts.get(key);
1430
+ if (!state) {
1431
+ state = { index: nextIndex++, text: "", started: false };
1432
+ texts.set(key, state);
1433
+ count2++;
1434
+ }
1435
+ return state;
1436
+ }
1437
+ function reasoningState(key) {
1438
+ let state = reasonings.get(key);
1439
+ if (!state) {
1440
+ state = { index: nextIndex++, text: "", started: false };
1441
+ reasonings.set(key, state);
1442
+ count2++;
1443
+ }
1444
+ return state;
1445
+ }
1446
+ function appendText(state, delta, out) {
1447
+ if (!state.started) {
1448
+ state.started = true;
1449
+ out.push({ type: "block-start", index: state.index, blockType: "text" });
1450
+ }
1451
+ state.text += delta;
1452
+ out.push({ type: "text-delta", index: state.index, text: delta });
1453
+ }
1454
+ function appendReasoning(state, delta, out) {
1455
+ if (!state.started) {
1456
+ state.started = true;
1457
+ out.push({ type: "block-start", index: state.index, blockType: "reasoning" });
1458
+ }
1459
+ state.text += delta;
1460
+ out.push({ type: "reasoning-delta", index: state.index, text: delta });
1461
+ }
1462
+ function callState(key, item, out) {
1463
+ let call = calls.get(key);
1464
+ if (!call) {
1465
+ call = { index: nextIndex++, id: "", name: "", args: "", started: false };
1466
+ calls.set(key, call);
1467
+ count2++;
1468
+ out?.push({ type: "block-start", index: call.index, blockType: "tool-call" });
1469
+ }
1470
+ if (item) {
1471
+ const id = str(item.call_id) ?? str(item.id);
1472
+ if (id && !call.id) call.id = id;
1473
+ const name2 = str(item.name);
1474
+ if (name2) call.name = name2;
1475
+ }
1476
+ return call;
1477
+ }
1478
+ function flushCall(call, delta, out) {
1479
+ if (!call.id) return;
1480
+ const name2 = call.name ? { name: call.name } : {};
1481
+ if (!call.started) {
1482
+ call.started = true;
1483
+ out.push({ type: "tool-call-delta", index: call.index, id: ToolCallId3(call.id), ...name2, argumentsDelta: call.args });
1484
+ return;
1485
+ }
1486
+ out.push({ type: "tool-call-delta", index: call.index, id: ToolCallId3(call.id), ...name2, argumentsDelta: delta });
1487
+ }
1488
+ function keyFor(value, kind, outputIndex) {
1489
+ const itemId = str(value.item_id) ?? (outputIndex === void 0 ? void 0 : keyByOutput.get(outputIndex));
1490
+ return `${kind}:${itemId ?? `idx:${outputIndex ?? -1}`}`;
1491
+ }
1492
+ function hydrate(output, out) {
1493
+ if (!Array.isArray(output)) return;
1494
+ for (const item of output) {
1495
+ if (!record(item)) continue;
1496
+ if (item.type === "function_call") {
1497
+ const itemId = str(item.id) ?? str(item.call_id);
1498
+ const callId = str(item.call_id) ?? itemId;
1499
+ if (!callId || [...calls.values()].some((call2) => call2.id === callId)) continue;
1500
+ const call = callState(`call:${itemId ?? callId}`, item, out);
1501
+ call.args = str(item.arguments) ?? call.args;
1502
+ flushCall(call, call.args, out);
1503
+ continue;
1504
+ }
1505
+ if (item.type === "message") {
1506
+ const key = `text:${str(item.id) ?? "unknown"}:0`;
1507
+ if (texts.has(key)) continue;
1508
+ const content2 = Array.isArray(item.content) ? item.content : [];
1509
+ const text2 = content2.map((part) => record(part) && (part.type === "output_text" || part.type === "refusal") ? str(part.text) ?? "" : "").join("");
1510
+ if (!text2) continue;
1511
+ appendText(textState(key), text2, out);
1512
+ continue;
1513
+ }
1514
+ if (item.type === "reasoning") {
1515
+ const key = `reasoning:${str(item.id) ?? "unknown"}:s0`;
1516
+ if (reasonings.has(key)) continue;
1517
+ const summary = Array.isArray(item.summary) ? item.summary : [];
1518
+ const text2 = summary.map((part) => record(part) ? str(part.text) ?? "" : "").join("");
1519
+ if (!text2) continue;
1520
+ appendReasoning(reasoningState(key), text2, out);
1521
+ }
1522
+ }
1523
+ }
1524
+ function close() {
1525
+ const out = [];
1526
+ for (const state of texts.values()) if (state.started) out.push({ type: "block-end", index: state.index, block: { type: "text", text: state.text } });
1527
+ for (const state of reasonings.values()) if (state.started) out.push({ type: "block-end", index: state.index, block: { type: "reasoning", text: state.text } });
1528
+ for (const call of calls.values()) {
1529
+ if (!call.id || !call.name) throw new LlmError8("OpenDesign\uFF1A\u5DE5\u5177\u8C03\u7528\u7F3A\u5C11\u6807\u8BC6\u6216\u540D\u79F0", "INVALID_RESPONSE");
1530
+ out.push({ type: "block-end", index: call.index, block: { type: "tool-call", id: ToolCallId3(call.id), name: call.name, arguments: call.args } });
1531
+ }
1532
+ return out.sort((a, b) => ("index" in a ? a.index : 0) - ("index" in b ? b.index : 0));
1533
+ }
1534
+ function reasonOf(type, response) {
1535
+ if (type === "response.incomplete") {
1536
+ const details = record(response.incomplete_details) ? response.incomplete_details : {};
1537
+ const why = str(details.reason) ?? "";
1538
+ if (why === "max_tokens" || why === "max_output_tokens") return { kind: "max-tokens" };
1539
+ if (why === "content_filter") throw new LlmError8("OpenDesign\uFF1A\u6A21\u578B\u54CD\u5E94\u88AB\u5185\u5BB9\u8FC7\u6EE4", "INVALID_RESPONSE");
1540
+ throw new LlmError8("OpenDesign\uFF1A\u6A21\u578B\u54CD\u5E94\u672A\u5B8C\u6210\uFF0C\u4E0A\u6E38\u4E2D\u65AD", "SERVER");
1541
+ }
1542
+ const output = Array.isArray(response.output) ? response.output : [];
1543
+ return output.some((item) => record(item) && item.type === "function_call") || calls.size > 0 ? { kind: "tool-calls" } : { kind: "stop" };
1544
+ }
1545
+ for await (const frame of sseFrames(body, signal, idleMs)) {
1546
+ if (frame.data.trim() === "[DONE]") break;
1547
+ let value;
1548
+ try {
1549
+ value = JSON.parse(frame.data);
1550
+ } catch {
1551
+ throw new LlmError8("OpenDesign\uFF1A\u65E0\u6548\u7684\u6D41\u5F0F JSON", "INVALID_RESPONSE");
1552
+ }
1553
+ if (!record(value)) throw new LlmError8("OpenDesign\uFF1A\u6D41\u5F0F\u4E8B\u4EF6\u4E0D\u662F\u5BF9\u8C61", "INVALID_RESPONSE");
1554
+ const type = str(value.type) ?? frame.event;
1555
+ if (!type) throw new LlmError8("OpenDesign\uFF1A\u6D41\u5F0F\u4E8B\u4EF6\u7F3A\u5C11\u7C7B\u578B", "INVALID_RESPONSE");
1556
+ const out = [];
1557
+ switch (type) {
1558
+ case "response.output_item.added": {
1559
+ const outputIndex = int(value.output_index);
1560
+ const item = record(value.item) ? value.item : void 0;
1561
+ if (item && outputIndex !== void 0) {
1562
+ const itemId = str(item.id) ?? str(item.call_id);
1563
+ if (itemId) keyByOutput.set(outputIndex, itemId);
1564
+ }
1565
+ if (item?.type === "function_call") callState(keyFor({ item: value.item }, "call", outputIndex), item, out);
1566
+ break;
1567
+ }
1568
+ case "response.output_text.delta":
1569
+ case "response.output_text.done": {
1570
+ if (type.endsWith(".done")) break;
1571
+ const delta = str(value.delta);
1572
+ if (!delta) break;
1573
+ const outputIndex = int(value.output_index);
1574
+ const contentIndex = int(value.content_index) ?? 0;
1575
+ const itemId = str(value.item_id) ?? (outputIndex === void 0 ? void 0 : keyByOutput.get(outputIndex));
1576
+ appendText(textState(`text:${itemId ?? `idx:${outputIndex ?? -1}`}:${contentIndex}`), delta, out);
1577
+ break;
1578
+ }
1579
+ case "response.refusal.delta": {
1580
+ const delta = str(value.delta);
1581
+ if (!delta) break;
1582
+ const outputIndex = int(value.output_index);
1583
+ const contentIndex = int(value.content_index) ?? 0;
1584
+ const itemId = str(value.item_id) ?? (outputIndex === void 0 ? void 0 : keyByOutput.get(outputIndex));
1585
+ appendText(textState(`text:${itemId ?? `idx:${outputIndex ?? -1}`}:${contentIndex}`), delta, out);
1586
+ break;
1587
+ }
1588
+ case "response.reasoning_summary_text.delta": {
1589
+ const delta = str(value.delta);
1590
+ if (!delta) break;
1591
+ const key = keyFor(value, "reasoning", int(value.output_index));
1592
+ appendReasoning(reasoningState(`${key}:s${int(value.summary_index) ?? 0}`), delta, out);
1593
+ break;
1594
+ }
1595
+ case "response.reasoning_text.delta": {
1596
+ const delta = str(value.delta);
1597
+ if (!delta) break;
1598
+ const key = keyFor(value, "reasoning", int(value.output_index));
1599
+ appendReasoning(reasoningState(`${key}:t`), delta, out);
1600
+ break;
1601
+ }
1602
+ case "response.function_call_arguments.delta": {
1603
+ const call = callState(keyFor(value, "call", int(value.output_index)), void 0, out);
1604
+ const delta = str(value.delta) ?? "";
1605
+ call.args += delta;
1606
+ flushCall(call, delta, out);
1607
+ break;
1608
+ }
1609
+ case "response.output_item.done": {
1610
+ const item = record(value.item) ? value.item : void 0;
1611
+ if (!item) break;
1612
+ const outputIndex = int(value.output_index);
1613
+ if (item.type === "function_call") {
1614
+ const call = callState(keyFor(value, "call", outputIndex), item, out);
1615
+ const args = str(item.arguments);
1616
+ if (args !== void 0 && call.args === "") {
1617
+ call.args = args;
1618
+ flushCall(call, args, out);
1619
+ } else flushCall(call, "", out);
1620
+ } else if (item.type === "message") {
1621
+ const key = `text:${str(item.id) ?? `idx:${outputIndex ?? -1}`}:0`;
1622
+ if (!texts.has(key)) {
1623
+ const content2 = Array.isArray(item.content) ? item.content : [];
1624
+ const text2 = content2.map((part) => record(part) && (part.type === "output_text" || part.type === "refusal") ? str(part.text) ?? "" : "").join("");
1625
+ if (text2) appendText(textState(key), text2, out);
1626
+ }
1627
+ } else if (item.type === "reasoning") {
1628
+ const key = `reasoning:${str(item.id) ?? `idx:${outputIndex ?? -1}`}:s0`;
1629
+ if (!reasonings.has(key)) {
1630
+ const summary = Array.isArray(item.summary) ? item.summary : [];
1631
+ const text2 = summary.map((part) => record(part) ? str(part.text) ?? "" : "").join("");
1632
+ if (text2) appendReasoning(reasoningState(key), text2, out);
1633
+ }
1634
+ }
1635
+ break;
1636
+ }
1637
+ case "response.completed":
1638
+ case "response.incomplete":
1639
+ case "response.failed": {
1640
+ const response = record(value.response) ? value.response : {};
1641
+ if (type === "response.failed") throw inBandError(record(response.error) ? response.error : {}, "SERVER");
1642
+ usage = responseUsage(response) ?? usage;
1643
+ hydrate(response.output, out);
1644
+ const reason = reasonOf(type, response);
1645
+ if (reason.kind === "tool-calls" && calls.size === 0) throw new LlmError8("OpenDesign\uFF1A\u54CD\u5E94\u58F0\u660E\u5DE5\u5177\u8C03\u7528\uFF0C\u4F46\u672A\u8FD4\u56DE\u4EFB\u4F55\u5DE5\u5177\u5757", "INVALID_RESPONSE");
1646
+ if (!count2) throw new LlmError8("OpenDesign\uFF1A\u6A21\u578B\u8FD4\u56DE\u7A7A\u54CD\u5E94", "EMPTY_RESPONSE");
1647
+ out.push(...close());
1648
+ if (usage) out.push({ type: "usage", usage });
1649
+ finish = reason;
1650
+ out.push({ type: "finish", reason });
1651
+ break;
1652
+ }
1653
+ case "error":
1654
+ throw inBandError(value);
1655
+ default:
1656
+ break;
1657
+ }
1658
+ for (const chunk of out) yield chunk;
1659
+ if (finish) break;
1660
+ }
1661
+ signal.throwIfAborted();
1662
+ if (!finish) throw new LlmError8("OpenDesign\uFF1A\u54CD\u5E94\u6D41\u63D0\u524D\u4E2D\u65AD\uFF0C\u672A\u6536\u5230\u7ED3\u675F\u4E8B\u4EF6", "INVALID_RESPONSE");
1663
+ }
1664
+ var responses = {
1665
+ id: "responses",
1666
+ build: buildResponsesRequest,
1667
+ stream: streamResponses
1668
+ };
1669
+
1670
+ // src/protocols/index.ts
1671
+ var PROTOCOLS = {
1672
+ "chat-completions": chatCompletions,
1673
+ responses,
1674
+ messages
1675
+ };
1676
+ function protocolFor(id) {
1677
+ const protocol = PROTOCOLS[id];
1678
+ if (!protocol) throw new LlmError9(`\u672A\u77E5\u7684\u63A8\u7406\u534F\u8BAE\uFF1A${String(id)}`, "INVALID_REQUEST");
1679
+ return protocol;
1680
+ }
1681
+
701
1682
  // src/adapter.ts
702
1683
  function group(provider) {
703
1684
  if (provider === PLAN_PROVIDER) return "plan";
704
1685
  if (provider === PAID_PROVIDER) return "paid";
705
- throw new LlmError3("\u672A\u77E5\u7684 CoRouter Provider", "INVALID_REQUEST");
1686
+ throw new LlmError10("\u672A\u77E5\u7684 CoRouter Provider", "INVALID_REQUEST");
706
1687
  }
707
- function modelOf(provider, id) {
708
- const model = findModel(id);
709
- if (!model || model.group !== group(provider)) throw new LlmError3("\u8BE5\u6A21\u578B\u4E0D\u5C5E\u4E8E\u5F53\u524D CoRouter Provider\uFF1B\u8BF7\u5728\u6A21\u578B\u9009\u62E9\u5668\u4E2D\u9009\u62E9\u5BF9\u5E94\u901A\u9053", "INVALID_REQUEST");
710
- return model;
1688
+ function outputReserve(model, protocol) {
1689
+ const limit = model.maxOutputTokens ?? (protocol === "messages" ? 4096 : void 0);
1690
+ return limit === void 0 ? void 0 : model.contextWindow === void 0 ? limit : Math.min(limit, Math.floor(model.contextWindow / 4));
711
1691
  }
712
- function outputReserve(model) {
713
- return model.maxOutputTokens === void 0 ? void 0 : model.contextWindow === void 0 ? model.maxOutputTokens : Math.min(model.maxOutputTokens, Math.floor(model.contextWindow / 4));
714
- }
715
- function metadata(provider, row) {
716
- const reserve = outputReserve(row);
1692
+ function metadata(provider, row, config) {
1693
+ const setting = config.modelSettings?.[row.id];
1694
+ const reserve = setting?.maxTokens ?? outputReserve(row, config.apiProtocol ?? "chat-completions");
1695
+ const efforts = row.reasoningEfforts ?? (setting?.reasoningEffort ? [setting.reasoningEffort] : void 0);
717
1696
  return {
718
1697
  provider,
719
1698
  id: row.id,
@@ -721,7 +1700,10 @@ function metadata(provider, row) {
721
1700
  ...row.inputModalities ? { inputModalities: row.inputModalities } : {},
722
1701
  ...row.contextWindow === void 0 ? {} : { context: { contextWindow: row.contextWindow - (reserve ?? 0) } },
723
1702
  ...reserve === void 0 ? {} : { defaultMaxTokens: reserve },
724
- ...row.reasoningEfforts?.length ? { reasoning: { efforts: row.reasoningEfforts.map((id) => ({ id: ReasoningEffortId(id), name: { none: "\u5173\u95ED", minimal: "\u6700\u4F4E", low: "\u4F4E", medium: "\u4E2D", high: "\u9AD8", xhigh: "\u8F83\u9AD8", ultra: "\u6781\u9AD8", max: "\u6700\u9AD8" }[id] ?? id })) } } : {}
1703
+ ...efforts?.length ? { reasoning: {
1704
+ efforts: efforts.map((id) => ({ id: ReasoningEffortId(id), name: { none: "\u5173\u95ED", minimal: "\u6700\u4F4E", low: "\u4F4E", medium: "\u4E2D", high: "\u9AD8", xhigh: "\u8F83\u9AD8", ultra: "\u6781\u9AD8", max: "\u6700\u9AD8" }[id] ?? id })),
1705
+ ...setting?.reasoningEffort ? { defaultEffort: ReasoningEffortId(setting.reasoningEffort) } : {}
1706
+ } } : {}
725
1707
  };
726
1708
  }
727
1709
  var CoRouterAdapter = class extends LlmAdapter {
@@ -732,67 +1714,101 @@ var CoRouterAdapter = class extends LlmAdapter {
732
1714
  options;
733
1715
  #lifetime = new AbortController();
734
1716
  dispose() {
735
- this.#lifetime.abort(new LlmError3("CoRouter \u63D2\u4EF6\u5DF2\u5378\u8F7D", "ABORTED"));
1717
+ this.#lifetime.abort(new LlmError10("CoRouter \u63D2\u4EF6\u5DF2\u5378\u8F7D", "ABORTED"));
736
1718
  }
737
1719
  providerInfo(provider) {
738
1720
  return { id: provider, name: group(provider) === "plan" ? "OpenDesign CoRouter \xB7 \u5957\u9910\u8D60\u9001" : "OpenDesign CoRouter \xB7 \u4ED8\u8D39\u6A21\u578B" };
739
1721
  }
1722
+ modelOf(provider, id, config) {
1723
+ const row = (this.options.models?.() ?? MODELS).find((candidate) => candidate.id === id);
1724
+ if (!row || row.group !== group(provider)) throw new LlmError10("\u8BE5\u6A21\u578B\u4E0D\u5C5E\u4E8E\u5F53\u524D CoRouter Provider\uFF1B\u8BF7\u5237\u65B0\u6A21\u578B\u5217\u8868\u5E76\u9009\u62E9\u5BF9\u5E94\u901A\u9053", "INVALID_REQUEST");
1725
+ if (!isModelEnabled(id, config.modelSettings)) throw new LlmError10("\u8BE5\u6A21\u578B\u5DF2\u5728 CoRouter \u8BBE\u7F6E\u4E2D\u505C\u7528", "MODEL_DISABLED");
1726
+ const configured = applyModelSettings(row, config.modelSettings?.[id]);
1727
+ const output = config.modelSettings?.[id]?.maxTokens;
1728
+ if (output !== void 0 && configured.contextWindow !== void 0 && output >= configured.contextWindow) throw new LlmError10("\u6A21\u578B\u6700\u5927\u8F93\u51FA\u5FC5\u987B\u5C0F\u4E8E\u4E0A\u4E0B\u6587\u9884\u7B97\uFF0C\u8BF7\u8C03\u6574\u6A21\u578B\u53C2\u6570", "INVALID_REQUEST");
1729
+ return configured;
1730
+ }
740
1731
  async listModels(provider) {
741
- return modelsFor(group(provider)).map((row) => ({ provider, id: row.id, name: row.name, ...row.inputModalities ? { inputModalities: row.inputModalities } : {} }));
1732
+ const config = this.options.config(), selectedGroup = group(provider);
1733
+ return (this.options.models?.() ?? MODELS).filter((row) => row.group === selectedGroup && isModelEnabled(row.id, config.modelSettings)).map((row) => ({ provider, id: row.id, name: row.name, ...row.inputModalities ? { inputModalities: row.inputModalities } : {} }));
742
1734
  }
743
1735
  async resolveModel(provider, model, signal) {
744
1736
  signal?.throwIfAborted();
745
- return metadata(provider, modelOf(provider, model));
1737
+ const config = this.options.config();
1738
+ return metadata(provider, this.modelOf(provider, model, config), config);
746
1739
  }
747
1740
  async prepareCall(provider, model, signal) {
748
1741
  signal?.throwIfAborted();
749
- const config = this.options.config(), row = modelOf(provider, model);
750
- return { model: metadata(provider, row), stream: (options) => this.#stream(options, config, row) };
1742
+ const config = this.options.config(), row = this.modelOf(provider, model, config);
1743
+ return { model: metadata(provider, row, config), stream: (options) => this.#stream(options, config, row) };
751
1744
  }
752
1745
  stream(options) {
753
- return this.#stream(options, this.options.config(), modelOf(options.provider, options.model));
1746
+ const config = this.options.config();
1747
+ return this.#stream(options, config, this.modelOf(options.provider, options.model, config));
754
1748
  }
755
1749
  async *#stream(options, config, row) {
756
1750
  const signal = AbortSignal.any([combinedSignal(options.signal, config.requestTimeoutMs), this.#lifetime.signal]);
757
1751
  signal.throwIfAborted();
758
1752
  const raw = await this.options.apiKey(config.apiKeyEnv);
759
- if (!raw) throw new LlmError3("\u672A\u914D\u7F6E CoRouter API Key\uFF1B\u8BF7\u5728\u63D2\u4EF6\u8BBE\u7F6E\u4E2D\u4FDD\u5B58\u6216\u8BBE\u7F6E\u5BF9\u5E94\u73AF\u5883\u53D8\u91CF", "MISSING_CREDENTIAL");
1753
+ if (!raw) throw new LlmError10("\u672A\u914D\u7F6E CoRouter API Key\uFF1B\u8BF7\u5728\u63D2\u4EF6\u8BBE\u7F6E\u4E2D\u4FDD\u5B58\u6216\u8BBE\u7F6E\u5BF9\u5E94\u73AF\u5883\u53D8\u91CF", "MISSING_CREDENTIAL");
760
1754
  const key = assertUsableApiKey(raw, "dsh-opendesign-corouter", config.apiKeyEnv);
1755
+ const setting = config.modelSettings?.[row.id] ?? {};
1756
+ const protocolId = config.apiProtocol ?? "chat-completions";
761
1757
  const images = /* @__PURE__ */ new Map();
762
- if (row.inputModalities?.includes("image") && this.options.imageDataUrl) for (const message of options.messages.filter((row2) => row2.role === "user" || row2.role === "tool")) for (const block of message.content) {
763
- if (block.type !== "image" || block.offloaded || images.has(block.attachment.attachmentId)) continue;
764
- const url = await this.options.imageDataUrl(block.attachment, signal);
765
- if (url) images.set(block.attachment.attachmentId, url);
1758
+ const attemptedImages = /* @__PURE__ */ new Set();
1759
+ const unreadableImages = /* @__PURE__ */ new Set();
1760
+ if (row.inputModalities?.includes("image") && this.options.imageDataUrl) for (const message of options.messages.filter((item) => item.role === "user" || item.role === "tool")) for (const block of message.content) {
1761
+ if (block.type !== "image" || block.offloaded || attemptedImages.has(block.attachment.attachmentId)) continue;
1762
+ attemptedImages.add(block.attachment.attachmentId);
1763
+ try {
1764
+ const url = await this.options.imageDataUrl(block.attachment, signal);
1765
+ signal.throwIfAborted();
1766
+ if (url) images.set(block.attachment.attachmentId, url);
1767
+ else unreadableImages.add(block.attachment.attachmentId);
1768
+ } catch {
1769
+ signal.throwIfAborted();
1770
+ unreadableImages.add(block.attachment.attachmentId);
1771
+ }
766
1772
  }
767
- const messages = chatMessages(options, (ref) => images.get(ref.attachmentId), row.id.startsWith("deepseek-") || row.id.startsWith("kimi-"));
1773
+ const projectedOptions = unreadableImages.size === 0 ? options : {
1774
+ ...options,
1775
+ messages: options.messages.map((message) => message.role !== "user" && message.role !== "tool" ? message : {
1776
+ ...message,
1777
+ content: message.content.map((block) => block.type === "image" && !block.offloaded && unreadableImages.has(block.attachment.attachmentId) ? { type: "text", text: `[Image ${JSON.stringify(block.attachment.name ?? "attachment")} could not be read; attachment ${block.attachment.attachmentId}. The model's image capability has not changed.]` } : block)
1778
+ })
1779
+ };
1780
+ const messages2 = chatMessages(projectedOptions, (ref) => images.get(ref.attachmentId), row.id.startsWith("deepseek-") || row.id.startsWith("kimi-"));
768
1781
  const tools = chatTools(options);
769
- const body = { model: row.id, messages, stream: true, stream_options: { include_usage: true }, ...tools ? { tools } : {} };
770
- if (options.temperature !== void 0) body.temperature = options.temperature;
771
- const requested = options.maxTokens ?? outputReserve(row);
772
- if (requested !== void 0) {
773
- if (!Number.isInteger(requested) || requested <= 0) throw new LlmError3("maxTokens \u5FC5\u987B\u662F\u6B63\u6574\u6570", "INVALID_REQUEST");
774
- const capped = Math.min(requested, row.maxOutputTokens ?? requested);
775
- const room = row.contextWindow === void 0 ? capped : row.contextWindow - estimateTokens({ messages, tools });
776
- if (room < 1) throw new LlmError3("\u5F53\u524D\u8F93\u5165\u6CA1\u6709\u4E3A\u8F93\u51FA\u7559\u4E0B\u4E0A\u4E0B\u6587\u7A7A\u95F4", "CONTEXT_WINDOW_EXCEEDED");
777
- body.max_tokens = Math.min(capped, Math.floor(room));
778
- }
779
- if (options.reasoningEffort !== void 0) {
780
- if (row.reasoningEfforts && !row.reasoningEfforts.includes(options.reasoningEffort)) throw new LlmError3("\u8BE5\u6A21\u578B\u4E0D\u652F\u6301\u6240\u9009\u601D\u8003\u6DF1\u5EA6", "UNSUPPORTED_OPTION");
781
- body.reasoning_effort = options.reasoningEffort;
782
- }
783
- if (options.stop?.length) body.stop = options.stop;
1782
+ let maxTokens = options.maxTokens ?? setting.maxTokens ?? outputReserve(row, protocolId);
1783
+ if (maxTokens !== void 0) {
1784
+ if (!Number.isSafeInteger(maxTokens) || maxTokens <= 0) throw new LlmError10("maxTokens \u5FC5\u987B\u662F\u6B63\u6574\u6570", "INVALID_REQUEST");
1785
+ const capped = Math.min(maxTokens, row.maxOutputTokens ?? maxTokens);
1786
+ const room = row.contextWindow === void 0 ? capped : row.contextWindow - estimateTokens({ messages: messages2, tools });
1787
+ if (room < 1) throw new LlmError10("\u5F53\u524D\u8F93\u5165\u6CA1\u6709\u4E3A\u8F93\u51FA\u7559\u4E0B\u4E0A\u4E0B\u6587\u7A7A\u95F4", "CONTEXT_WINDOW_EXCEEDED");
1788
+ maxTokens = Math.min(capped, Math.floor(room));
1789
+ }
1790
+ const effort2 = options.reasoningEffort ?? (setting.reasoningEffort ? ReasoningEffortId(setting.reasoningEffort) : void 0);
1791
+ if (effort2 !== void 0 && row.reasoningEfforts && !row.reasoningEfforts.includes(effort2)) throw new LlmError10("\u8BE5\u6A21\u578B\u4E0D\u652F\u6301\u6240\u9009\u601D\u8003\u6DF1\u5EA6", "UNSUPPORTED_OPTION");
1792
+ const effective = {
1793
+ ...projectedOptions,
1794
+ ...(options.temperature ?? setting.temperature) === void 0 ? {} : { temperature: options.temperature ?? setting.temperature },
1795
+ ...maxTokens === void 0 ? {} : { maxTokens },
1796
+ ...effort2 === void 0 ? {} : { reasoningEffort: effort2 }
1797
+ };
1798
+ const protocol = protocolFor(protocolId);
1799
+ const request = protocol.build({ baseURL: config.baseURL, apiKey: key, model: row, options: effective, images, ...setting.topP === void 0 ? {} : { topP: setting.topP } });
784
1800
  let response;
785
1801
  try {
786
- response = await (this.options.fetch ?? fetch)(`${config.baseURL}/chat/completions`, {
1802
+ response = await (this.options.fetch ?? fetch)(request.url, {
787
1803
  method: "POST",
788
- headers: { ...attributionHeaders(), authorization: `Bearer ${key}`, "content-type": "application/json", accept: "text/event-stream" },
789
- body: JSON.stringify(body),
1804
+ headers: request.headers,
1805
+ body: JSON.stringify(request.body),
790
1806
  signal,
791
1807
  redirect: "error"
792
1808
  });
793
1809
  } catch {
794
1810
  if (options.signal?.aborted || this.#lifetime.signal.aborted) signal.throwIfAborted();
795
- throw new LlmError3(signal.aborted ? "CoRouter \u8BF7\u6C42\u8D85\u8FC7\u603B\u8D85\u65F6" : "\u65E0\u6CD5\u8FDE\u63A5 CoRouter \u63A8\u7406\u670D\u52A1", signal.aborted ? "TIMEOUT" : "SERVER");
1811
+ throw new LlmError10(signal.aborted ? "CoRouter \u8BF7\u6C42\u8D85\u8FC7\u603B\u8D85\u65F6" : "\u65E0\u6CD5\u8FDE\u63A5 CoRouter \u63A8\u7406\u670D\u52A1", signal.aborted ? "TIMEOUT" : "SERVER");
796
1812
  }
797
1813
  if (!response.ok) {
798
1814
  let error = {};
@@ -820,25 +1836,152 @@ var CoRouterAdapter = class extends LlmAdapter {
820
1836
  }
821
1837
  throw gatewayError(response.status, error);
822
1838
  }
823
- if (!response.body) throw new LlmError3("CoRouter \u8FD4\u56DE\u7A7A\u54CD\u5E94", "EMPTY_RESPONSE");
1839
+ if (!response.body) throw new LlmError10("CoRouter \u8FD4\u56DE\u7A7A\u54CD\u5E94", "EMPTY_RESPONSE");
824
1840
  const tracker = new EmptyTurnTracker({ enabled: row.group === "plan" && config.emptyTurnGuard && Boolean(options.tools?.length), onEmptyTurn: () => this.options.onEmptyTurn?.() });
825
1841
  try {
826
- for await (const chunk of consumeSse(response.body, signal, config.streamIdleTimeoutMs)) {
1842
+ for await (const chunk of protocol.stream(response.body, signal, config.streamIdleTimeoutMs)) {
827
1843
  tracker.observe(chunk);
828
- if (chunk.type === "finish" && row.group === "plan" && config.emptyTurnGuard) {
829
- const reason = tracker.resolve(chunk.reason);
830
- yield { ...chunk, reason };
831
- } else yield chunk;
1844
+ if (chunk.type === "finish" && row.group === "plan" && config.emptyTurnGuard) yield { ...chunk, reason: tracker.resolve(chunk.reason) };
1845
+ else yield chunk;
832
1846
  }
833
1847
  } catch (error) {
834
1848
  if (options.signal?.aborted || this.#lifetime.signal.aborted) signal.throwIfAborted();
835
- if (signal.aborted) throw new LlmError3("CoRouter \u8BF7\u6C42\u8D85\u8FC7\u603B\u8D85\u65F6", "TIMEOUT");
836
- if (error instanceof LlmError3) throw error;
837
- throw new LlmError3("CoRouter \u54CD\u5E94\u6D41\u8FDE\u63A5\u4E2D\u65AD", "SERVER");
1849
+ if (signal.aborted) throw new LlmError10("CoRouter \u8BF7\u6C42\u8D85\u8FC7\u603B\u8D85\u65F6", "TIMEOUT");
1850
+ if (error instanceof LlmError10) throw error;
1851
+ throw new LlmError10("CoRouter \u54CD\u5E94\u6D41\u8FDE\u63A5\u4E2D\u65AD", "SERVER");
838
1852
  }
839
1853
  }
840
1854
  };
841
1855
 
1856
+ // src/model-discovery.ts
1857
+ import { attributionHeaders as attributionHeaders2 } from "@deepseek-ai/dsh-llm";
1858
+ var DISCOVERY_TIMEOUT_MS = 2e4;
1859
+ var MAX_DISCOVERY_BODY_BYTES = 2 * 1024 * 1024;
1860
+ var MAX_MODELS = 512;
1861
+ var MAX_MODEL_NAME_LENGTH = 128;
1862
+ var MODEL_ID2 = /^[A-Za-z0-9._:/-]{1,128}$/u;
1863
+ var MEDIA_MODEL_ID = /^(?:gpt-image-|nano-banana-|seedream-|imagen-|veo-|doubao-seedance-|sora-)/u;
1864
+ var MESSAGES2 = {
1865
+ url: "\u6A21\u578B\u53D1\u73B0\u5730\u5740\u65E0\u6548\uFF1A\u5FC5\u987B\u662F\u7EDD\u5BF9 HTTP(S) \u5730\u5740\uFF0C\u4E14\u4E0D\u542B\u51ED\u636E\u3001\u67E5\u8BE2\u4E32\u6216\u7247\u6BB5",
1866
+ credential: "\u6A21\u578B\u53D1\u73B0\u51ED\u636E\u7F3A\u5931\uFF0C\u6216\u542B\u6709\u4E0D\u53EF\u7528\u4E8E HTTP \u8BF7\u6C42\u5934\u7684\u5B57\u7B26",
1867
+ network: "\u65E0\u6CD5\u8FDE\u63A5\u6A21\u578B\u53D1\u73B0\u670D\u52A1",
1868
+ timeout: "\u6A21\u578B\u53D1\u73B0\u8BF7\u6C42\u8D85\u8FC7 20 \u79D2\u65F6\u9650",
1869
+ aborted: "\u6A21\u578B\u53D1\u73B0\u8BF7\u6C42\u5DF2\u53D6\u6D88",
1870
+ auth: "\u6A21\u578B\u53D1\u73B0\u8BA4\u8BC1\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u51ED\u636E",
1871
+ permission: "\u6A21\u578B\u53D1\u73B0\u51ED\u636E\u65E0\u6743\u8BFB\u53D6\u6A21\u578B\u5217\u8868",
1872
+ "rate-limit": "\u6A21\u578B\u53D1\u73B0\u8BF7\u6C42\u88AB\u9650\u6D41\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5",
1873
+ server: "\u6A21\u578B\u53D1\u73B0\u670D\u52A1\u6682\u65F6\u4E0D\u53EF\u7528",
1874
+ response: "\u6A21\u578B\u53D1\u73B0\u54CD\u5E94\u4E0D\u7B26\u5408 OpenAI \u6A21\u578B\u5217\u8868\u683C\u5F0F"
1875
+ };
1876
+ var ModelDiscoveryError = class extends Error {
1877
+ constructor(kind, status) {
1878
+ super(MESSAGES2[kind]);
1879
+ this.kind = kind;
1880
+ this.status = status;
1881
+ this.name = "ModelDiscoveryError";
1882
+ }
1883
+ kind;
1884
+ status;
1885
+ };
1886
+ function statusKind(status) {
1887
+ if (status === 401) return "auth";
1888
+ if (status === 403) return "permission";
1889
+ if (status === 429) return "rate-limit";
1890
+ if (status === 408 || status === 504) return "timeout";
1891
+ if (status >= 500) return "server";
1892
+ return "response";
1893
+ }
1894
+ async function boundedJson(response, signal, deadline) {
1895
+ const reader = response.body?.getReader();
1896
+ if (!reader) throw new ModelDiscoveryError("response");
1897
+ const declared = Number(response.headers.get("content-length"));
1898
+ if (Number.isFinite(declared) && declared > MAX_DISCOVERY_BODY_BYTES) {
1899
+ await reader.cancel().catch(() => void 0);
1900
+ reader.releaseLock();
1901
+ throw new ModelDiscoveryError("response");
1902
+ }
1903
+ let size = 0;
1904
+ let raw = "";
1905
+ const decoder = new TextDecoder();
1906
+ try {
1907
+ while (true) {
1908
+ const chunk = await reader.read();
1909
+ if (chunk.done) break;
1910
+ size += chunk.value.byteLength;
1911
+ if (size > MAX_DISCOVERY_BODY_BYTES) throw new ModelDiscoveryError("response");
1912
+ raw += decoder.decode(chunk.value, { stream: true });
1913
+ }
1914
+ raw += decoder.decode();
1915
+ } catch (error) {
1916
+ if (signal?.aborted) throw new ModelDiscoveryError("aborted");
1917
+ if (deadline.aborted) throw new ModelDiscoveryError("timeout");
1918
+ if (error instanceof ModelDiscoveryError) throw error;
1919
+ throw new ModelDiscoveryError("response");
1920
+ } finally {
1921
+ await reader.cancel().catch(() => void 0);
1922
+ reader.releaseLock();
1923
+ }
1924
+ try {
1925
+ return JSON.parse(raw);
1926
+ } catch {
1927
+ throw new ModelDiscoveryError("response");
1928
+ }
1929
+ }
1930
+ function normalize(body) {
1931
+ if (!record(body) || !Array.isArray(body.data)) throw new ModelDiscoveryError("response");
1932
+ if (body.data.length > MAX_MODELS) throw new ModelDiscoveryError("response");
1933
+ const seen = /* @__PURE__ */ new Set();
1934
+ const rows = [];
1935
+ for (const value of body.data) {
1936
+ if (!record(value) || typeof value.id !== "string" || !MODEL_ID2.test(value.id) || ["__proto__", "constructor", "prototype"].includes(value.id)) continue;
1937
+ if (MEDIA_MODEL_ID.test(value.id) || seen.has(value.id)) continue;
1938
+ seen.add(value.id);
1939
+ const known = findModel(value.id);
1940
+ if (known) {
1941
+ rows.push({ ...known });
1942
+ continue;
1943
+ }
1944
+ const offered = typeof value.name === "string" ? value.name.trim() : "";
1945
+ const name2 = offered !== "" && offered.length <= MAX_MODEL_NAME_LENGTH ? offered : value.id;
1946
+ rows.push({ id: value.id, name: name2, group: "paid" });
1947
+ }
1948
+ return rows;
1949
+ }
1950
+ async function fetchModelCatalog(openAiBaseURL2, key, signal, fetcher) {
1951
+ let base;
1952
+ try {
1953
+ base = openAiBaseURL(assertBaseURL(openAiBaseURL2));
1954
+ } catch {
1955
+ throw new ModelDiscoveryError("url");
1956
+ }
1957
+ let credential;
1958
+ try {
1959
+ credential = safeSecret(key);
1960
+ } catch {
1961
+ throw new ModelDiscoveryError("credential");
1962
+ }
1963
+ const request = fetcher ?? fetch;
1964
+ const deadline = combinedSignal(signal, DISCOVERY_TIMEOUT_MS);
1965
+ let response;
1966
+ try {
1967
+ response = await request(`${base}/models`, {
1968
+ method: "GET",
1969
+ headers: { ...attributionHeaders2(), authorization: `Bearer ${credential}`, accept: "application/json" },
1970
+ signal: deadline,
1971
+ redirect: "error"
1972
+ });
1973
+ } catch {
1974
+ if (signal?.aborted) throw new ModelDiscoveryError("aborted");
1975
+ if (deadline.aborted) throw new ModelDiscoveryError("timeout");
1976
+ throw new ModelDiscoveryError("network");
1977
+ }
1978
+ if (!response.ok) {
1979
+ await response.body?.cancel().catch(() => void 0);
1980
+ throw new ModelDiscoveryError(statusKind(response.status), response.status);
1981
+ }
1982
+ return normalize(await boundedJson(response, signal, deadline));
1983
+ }
1984
+
842
1985
  // src/auth.ts
843
1986
  import { readFile as fsReadFile } from "node:fs/promises";
844
1987
  import { homedir as osHomedir } from "node:os";
@@ -1311,7 +2454,7 @@ async function defaultLaunch(signal) {
1311
2454
  }
1312
2455
 
1313
2456
  // src/quota.ts
1314
- import { attributionHeaders as attributionHeaders2 } from "@deepseek-ai/dsh-llm";
2457
+ import { attributionHeaders as attributionHeaders3 } from "@deepseek-ai/dsh-llm";
1315
2458
  var API_BASE = "https://amr-api.open-design.ai/api/v1";
1316
2459
  var CURRENT_URL = `${API_BASE}/workspaces/current`;
1317
2460
  var SUMMARY_URL = `${API_BASE}/billing/summary`;
@@ -1328,7 +2471,7 @@ var ConsoleError = class extends Error {
1328
2471
  async function consoleJson(url, session, signal, fetcher = fetch) {
1329
2472
  let response;
1330
2473
  try {
1331
- response = await fetcher(url, { headers: { ...attributionHeaders2(), accept: "application/json", ...session.headers }, signal: combinedSignal(signal, 2e4), redirect: "error" });
2474
+ response = await fetcher(url, { headers: { ...attributionHeaders3(), accept: "application/json", ...session.headers }, signal: combinedSignal(signal, 2e4), redirect: "error" });
1332
2475
  } catch {
1333
2476
  throw new ConsoleError("network");
1334
2477
  }
@@ -1357,16 +2500,23 @@ async function consoleJson(url, session, signal, fetcher = fetch) {
1357
2500
  await reader.cancel().catch(() => void 0);
1358
2501
  reader.releaseLock();
1359
2502
  }
2503
+ let body;
1360
2504
  try {
1361
- return JSON.parse(raw);
2505
+ body = JSON.parse(raw);
1362
2506
  } catch {
1363
2507
  throw new ConsoleError("response");
1364
2508
  }
2509
+ if (record(body) && (body.code === 401 || body.code === "401")) throw new ConsoleError("auth", 401);
2510
+ if (record(body) && (body.code === 403 || body.code === "403")) throw new ConsoleError("permission", 403);
2511
+ return body;
1365
2512
  }
1366
2513
  function number(value, negative = false) {
1367
2514
  const n = typeof value === "number" ? value : typeof value === "string" && /^-?\d+(\.\d+)?$/u.test(value.trim()) ? Number(value) : NaN;
1368
2515
  return Number.isFinite(n) && (negative || n >= 0) ? n : void 0;
1369
2516
  }
2517
+ function iso(value) {
2518
+ return typeof value === "string" && Number.isFinite(Date.parse(value)) ? new Date(value).toISOString() : void 0;
2519
+ }
1370
2520
  function normalizeQuota(body, summary) {
1371
2521
  if (!record(body) || !(typeof body.eligible === "boolean" || typeof body.tier === "string" || body.tier === null || Array.isArray(body.windows))) throw new ConsoleError("response");
1372
2522
  const windows = [];
@@ -1374,9 +2524,19 @@ function normalizeQuota(body, summary) {
1374
2524
  if (!record(row)) continue;
1375
2525
  const seconds = number(row.durationSeconds), used = number(row.usedCredits), limit = number(row.limitCredits), remaining = number(row.remainingCredits);
1376
2526
  if (seconds === void 0 || seconds <= 0 || used === void 0 || limit === void 0 || limit <= 0) continue;
1377
- windows.push({ durationSeconds: seconds, used, limit, percent: Math.min(100, used / limit * 100), ...remaining === void 0 ? {} : { remaining }, ...typeof row.resetsAt === "string" && Number.isFinite(Date.parse(row.resetsAt)) ? { resetsAt: row.resetsAt } : {} });
2527
+ const resetsAt = iso(row.resetsAt), windowStart = iso(row.windowStart);
2528
+ const resetMode = typeof row.resetMode === "string" && row.resetMode.trim() ? row.resetMode.trim() : void 0;
2529
+ windows.push({ durationSeconds: seconds, used, limit, percent: Math.min(100, used / limit * 100), ...remaining === void 0 ? {} : { remaining }, ...resetsAt === void 0 ? {} : { resetsAt }, ...windowStart === void 0 ? {} : { windowStart }, ...resetMode === void 0 ? {} : { resetMode } });
1378
2530
  }
1379
- return { windows, balances: normalizeBalances(summary), ...typeof body.tier === "string" && body.tier ? { tier: body.tier } : {}, ...typeof body.eligible === "boolean" ? { eligible: body.eligible } : {} };
2531
+ const membershipTier = record(summary) && typeof summary.membershipTier === "string" ? summary.membershipTier.trim() : "";
2532
+ const tier = typeof body.tier === "string" ? body.tier.trim() : body.tier === void 0 && body.eligible !== false ? membershipTier : "";
2533
+ const eligible = typeof body.eligible === "boolean" ? body.eligible : body.tier === null ? false : void 0;
2534
+ return { windows, balances: normalizeBalances(summary), ...normalizeSubscription(summary), ...tier ? { tier } : {}, ...eligible === void 0 ? {} : { eligible } };
2535
+ }
2536
+ function normalizeSubscription(summary) {
2537
+ if (!record(summary)) return {};
2538
+ const start = iso(summary.subscriptionCurrentPeriodStart), end = iso(summary.subscriptionCurrentPeriodEnd);
2539
+ return { ...start === void 0 ? {} : { subscriptionPeriodStart: start }, ...end === void 0 ? {} : { subscriptionPeriodEnd: end } };
1380
2540
  }
1381
2541
  function normalizeBalances(summary) {
1382
2542
  if (!record(summary)) return {};
@@ -1404,12 +2564,14 @@ async function readQuota(session, signal, fetcher = fetch) {
1404
2564
  } catch (error) {
1405
2565
  const balances = normalizeBalances(summary);
1406
2566
  if (!Object.keys(balances).length) throw error;
1407
- return { ...common, state: "partial", windows: [], balances, note: "\u94B1\u5305\u5DF2\u8BFB\u53D6\uFF1B\u5957\u9910\u8D60\u9001\u989D\u5EA6\u6682\u4E0D\u53EF\u786E\u8BA4" };
2567
+ return { ...common, state: "partial", windows: [], balances, ...normalizeSubscription(summary), note: "\u94B1\u5305\u5DF2\u8BFB\u53D6\uFF1B\u5957\u9910\u8D60\u9001\u989D\u5EA6\u6682\u4E0D\u53EF\u786E\u8BA4" };
1408
2568
  }
1409
- const missingWindows = normalized.eligible === true && !normalized.windows.length;
2569
+ const missingWindows = normalized.eligible !== false && !normalized.windows.length;
2570
+ const noSubscription = normalized.eligible === false && !normalized.windows.length;
1410
2571
  const missingBalances = personal && Object.keys(normalized.balances).length < 3;
1411
2572
  const partial = missingWindows || missingBalances || !personal;
1412
- return { ...common, ...normalized, state: partial ? "partial" : "ok", ...partial ? { note: !personal ? "\u56E2\u961F\u6216\u672A\u77E5\u5DE5\u4F5C\u533A\uFF1A\u8D26\u53F7\u94B1\u5305\u53E3\u5F84\u672A\u6838\u5B9E\uFF0C\u6682\u4E0D\u663E\u793A" : missingWindows ? "\u5957\u9910\u5B58\u5728\uFF0C\u4F46\u989D\u5EA6\u7A97\u53E3\u6682\u4E0D\u53EF\u786E\u8BA4" : "\u90E8\u5206\u94B1\u5305\u4F59\u989D\u6682\u4E0D\u53EF\u786E\u8BA4" } : {} };
2573
+ const note = !personal ? "\u56E2\u961F\u6216\u672A\u77E5\u5DE5\u4F5C\u533A\uFF1A\u8D26\u53F7\u94B1\u5305\u53E3\u5F84\u672A\u6838\u5B9E\uFF0C\u6682\u4E0D\u663E\u793A" : missingWindows ? "\u5957\u9910\u989D\u5EA6\u7A97\u53E3\u6682\u4E0D\u53EF\u786E\u8BA4" : noSubscription ? "\u8BE5\u8D26\u53F7\u6CA1\u6709 Design Plan \u8BA2\u9605\uFF1B\u94B1\u5305\u4F59\u989D\u7167\u5E38\u663E\u793A" : missingBalances ? "\u90E8\u5206\u94B1\u5305\u4F59\u989D\u6682\u4E0D\u53EF\u786E\u8BA4" : void 0;
2574
+ return { ...common, ...normalized, state: partial ? "partial" : "ok", ...note === void 0 ? {} : { note } };
1413
2575
  }
1414
2576
 
1415
2577
  // src/desktop-quota.ts
@@ -1442,7 +2604,8 @@ async function readDesktopQuota(session, signal, runner = runVela, fetcher = fet
1442
2604
  if (!record(workspace) || typeof workspace.workspaceId !== "string" || !/^[A-Za-z0-9_-]{1,64}$/u.test(workspace.workspaceId)) throw new ConsoleError("response");
1443
2605
  const funding2 = await runner(["billing", "funding-check", "--workspace-id", workspace.workspaceId, "--format", "json"], deadline, session.profile);
1444
2606
  if (!record(funding2) || !record(funding2.codingPlan)) throw new ConsoleError("response");
1445
- return { ...normalizeQuota(funding2.codingPlan), ...typeof funding2.balanceUsd === "string" ? { balance: funding2.balanceUsd } : {} };
2607
+ const generatedAt = typeof funding2.generatedAt === "string" && Number.isFinite(Date.parse(funding2.generatedAt)) ? new Date(funding2.generatedAt).toISOString() : void 0;
2608
+ return { ...normalizeQuota(funding2.codingPlan), ...typeof funding2.balanceUsd === "string" ? { balance: funding2.balanceUsd } : {}, ...generatedAt === void 0 ? {} : { generatedAt } };
1446
2609
  })();
1447
2610
  const [wallet, funding] = await Promise.allSettled([
1448
2611
  consoleJson(`${session.apiUrl}/api/v1/wallet/balance`, session, deadline, fetcher),
@@ -1452,21 +2615,24 @@ async function readDesktopQuota(session, signal, runner = runVela, fetcher = fet
1452
2615
  const cliBalances = funding.status === "fulfilled" && "balance" in funding.value ? normalizeBalances({ balanceUsd: funding.value.balance }) : {};
1453
2616
  const windows = funding.status === "fulfilled" ? funding.value.windows : [];
1454
2617
  const balances = { ...cliBalances, ...walletBalances };
1455
- if (!windows.length && !Object.keys(balances).length) {
2618
+ const eligible = funding.status === "fulfilled" ? funding.value.eligible : void 0;
2619
+ const noSubscription = funding.status === "fulfilled" && eligible === false && windows.length === 0;
2620
+ if (!windows.length && !Object.keys(balances).length && !noSubscription) {
1456
2621
  if (wallet.status === "rejected" && wallet.reason instanceof ConsoleError && (wallet.reason.kind === "auth" || wallet.reason.kind === "permission")) throw wallet.reason;
1457
2622
  throw new ConsoleError("network");
1458
2623
  }
1459
2624
  const tier = (funding.status === "fulfilled" ? funding.value.tier : void 0) ?? session.plan;
1460
- const eligible = funding.status === "fulfilled" ? funding.value.eligible : void 0;
2625
+ const updatedAt = funding.status === "fulfilled" && funding.value.generatedAt || (/* @__PURE__ */ new Date()).toISOString();
2626
+ const note = noSubscription ? "\u684C\u9762\u767B\u5F55\u6001\u663E\u793A\u8BE5\u8D26\u53F7\u6CA1\u6709 Design Plan \u8BA2\u9605\uFF1B\u672A\u8BFB\u5230\u7684\u5206\u6C60\u4FDD\u6301\u672A\u77E5\u3002\u5982\u9700\u5B8C\u6574\u5206\u6C60\uFF0C\u8BF7\u5728\u8FDE\u63A5\u9875\u767B\u5F55\u5E76\u5173\u95ED\u201C\u4F18\u5148\u684C\u9762\u767B\u5F55\u6001\u201D\u3002" : !windows.length ? "\u684C\u9762\u767B\u5F55\u6001\u672A\u8FD4\u56DE\u5957\u9910\u7A97\u53E3\uFF1B\u5DF2\u8BFB\u5230\u7684\u94B1\u5305\u4F59\u989D\u7167\u5E38\u663E\u793A\uFF0C\u672A\u8BFB\u5230\u7684\u5206\u6C60\u4FDD\u6301\u672A\u77E5\u3002\u5982\u9700\u5B8C\u6574\u5206\u6C60\uFF0C\u8BF7\u5728\u8FDE\u63A5\u9875\u767B\u5F55\u5E76\u5173\u95ED\u201C\u4F18\u5148\u684C\u9762\u767B\u5F55\u6001\u201D\u3002" : "\u684C\u9762\u767B\u5F55\u6001\u90E8\u5206\u63A5\u53E3\u6743\u9650\u53D7\u9650\uFF1B\u672A\u8BFB\u5230\u7684\u5206\u6C60\u663E\u793A\u672A\u77E5\u3002\u5982\u9700\u5B8C\u6574\u5206\u6C60\uFF0C\u8BF7\u5728\u8FDE\u63A5\u9875\u767B\u5F55\u5E76\u5173\u95ED\u201C\u4F18\u5148\u684C\u9762\u767B\u5F55\u6001\u201D\u3002";
1461
2627
  return {
1462
2628
  state: "partial",
1463
2629
  source: "desktop",
1464
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2630
+ updatedAt,
1465
2631
  windows,
1466
2632
  balances,
1467
2633
  ...tier ? { tier } : {},
1468
2634
  ...eligible === void 0 ? {} : { eligible },
1469
- note: "\u684C\u9762\u767B\u5F55\u6001\u90E8\u5206\u63A5\u53E3\u6743\u9650\u53D7\u9650\uFF1B\u672A\u8BFB\u5230\u7684\u5206\u6C60\u663E\u793A\u672A\u77E5\u3002\u5982\u9700\u5B8C\u6574\u5206\u6C60\uFF0C\u8BF7\u5728\u8FDE\u63A5\u9875\u767B\u5F55\u5E76\u5173\u95ED\u201C\u4F18\u5148\u684C\u9762\u767B\u5F55\u6001\u201D\u3002"
2635
+ note
1470
2636
  };
1471
2637
  }
1472
2638
 
@@ -1478,12 +2644,12 @@ var MAX_ENV_REF = 128;
1478
2644
  var MAX_TEXT = 4096;
1479
2645
  var MAX_MESSAGE = 1024;
1480
2646
  var MAX_TIER = 256;
1481
- var MAX_MODELS = 512;
2647
+ var MAX_MODELS2 = 512;
1482
2648
  var MAX_WINDOWS = 64;
1483
2649
  var MAX_API_KEY = 16384;
1484
2650
  var HTTP_ASCII = /^[\x21-\x7e]+$/u;
1485
2651
  var ENV_REF = /^[A-Za-z_][A-Za-z0-9_]*$/u;
1486
- var MODEL_ID = /^[A-Za-z0-9._:-]{1,128}$/u;
2652
+ var MODEL_ID3 = /^[A-Za-z0-9._:/-]{1,128}$/u;
1487
2653
  var LOGIN_PHASES = ["idle", "launching", "waiting", "verifying", "success", "cancelled", "error"];
1488
2654
  var QUOTA_STATES = ["ok", "partial", "need-login", "error"];
1489
2655
  var SESSION_SOURCES = ["desktop", "login", "environment"];
@@ -1497,7 +2663,7 @@ function isoTime(value, label) {
1497
2663
  if (!Number.isFinite(Date.parse(time))) throw new Error(`${label} \u5FC5\u987B\u662F ISO \u65F6\u95F4\u5B57\u7B26\u4E32`);
1498
2664
  return time;
1499
2665
  }
1500
- function flag(value, label) {
2666
+ function flag2(value, label) {
1501
2667
  if (typeof value !== "boolean") throw new Error(`${label} \u5FC5\u987B\u662F\u5E03\u5C14\u503C`);
1502
2668
  return value;
1503
2669
  }
@@ -1509,6 +2675,15 @@ function amount(value, label, negative = false) {
1509
2675
  if (typeof value !== "number" || !Number.isFinite(value) || !negative && value < 0) throw new Error(`${label} \u5FC5\u987B\u662F${negative ? "" : "\u975E\u8D1F"}\u6709\u9650\u6570\u5B57`);
1510
2676
  return value;
1511
2677
  }
2678
+ function positiveInteger(value, label, max) {
2679
+ const result = count(value, label);
2680
+ if (result === 0 || result > max) throw new Error(`${label} \u8D85\u51FA\u8303\u56F4`);
2681
+ return result;
2682
+ }
2683
+ function parseEfforts(value) {
2684
+ if (!Array.isArray(value) || value.length > 16) throw new Error("\u6A21\u578B\u601D\u8003\u6863\u4F4D\u683C\u5F0F\u65E0\u6548");
2685
+ return value.map((item) => text(item, "\u601D\u8003\u6863\u4F4D", 32));
2686
+ }
1512
2687
  function member(value, allowed, label) {
1513
2688
  const hit = allowed.find((candidate) => candidate === value);
1514
2689
  if (hit === void 0) throw new Error(`${label} \u4E0D\u5728\u5141\u8BB8\u7684\u53D6\u503C\u5185`);
@@ -1526,7 +2701,9 @@ function parseQuotaWindow(value) {
1526
2701
  limit,
1527
2702
  ...value.remaining === void 0 ? {} : { remaining: amount(value.remaining, "\u5269\u4F59\u989D\u5EA6") },
1528
2703
  percent: Math.min(100, Math.max(0, amount(value.percent, "\u5DF2\u7528\u767E\u5206\u6BD4"))),
1529
- ...value.resetsAt === void 0 ? {} : { resetsAt: isoTime(value.resetsAt, "\u989D\u5EA6\u91CD\u7F6E\u65F6\u95F4") }
2704
+ ...value.resetsAt === void 0 ? {} : { resetsAt: isoTime(value.resetsAt, "\u989D\u5EA6\u91CD\u7F6E\u65F6\u95F4") },
2705
+ ...value.windowStart === void 0 ? {} : { windowStart: isoTime(value.windowStart, "\u989D\u5EA6\u7A97\u53E3\u5F00\u59CB\u65F6\u95F4") },
2706
+ ...value.resetMode === void 0 ? {} : { resetMode: text(value.resetMode, "\u989D\u5EA6\u5237\u65B0\u65B9\u5F0F", 128) }
1530
2707
  };
1531
2708
  }
1532
2709
  function parseQuotaSnapshot(value) {
@@ -1539,8 +2716,14 @@ function parseQuotaSnapshot(value) {
1539
2716
  state: member(value.state, QUOTA_STATES, "\u989D\u5EA6\u72B6\u6001"),
1540
2717
  ...value.source === void 0 ? {} : { source: member(value.source, SESSION_SOURCES, "\u989D\u5EA6\u6765\u6E90") },
1541
2718
  updatedAt: isoTime(value.updatedAt, "\u989D\u5EA6\u66F4\u65B0\u65F6\u95F4"),
2719
+ ...value.subscriptionPeriodStart === void 0 ? {} : { subscriptionPeriodStart: isoTime(value.subscriptionPeriodStart, "\u8BA2\u9605\u5468\u671F\u5F00\u59CB\u65F6\u95F4") },
2720
+ ...value.subscriptionPeriodEnd === void 0 ? {} : { subscriptionPeriodEnd: isoTime(value.subscriptionPeriodEnd, "\u8BA2\u9605\u5468\u671F\u7ED3\u675F\u65F6\u95F4") },
2721
+ ...value.nextRefreshAt === void 0 ? {} : { nextRefreshAt: isoTime(value.nextRefreshAt, "\u4E0B\u6B21\u81EA\u52A8\u5237\u65B0\u65F6\u95F4") },
2722
+ ...value.stale === void 0 ? {} : { stale: flag2(value.stale, "\u5386\u53F2\u989D\u5EA6\u8BFB\u6570") },
2723
+ ...value.refreshFailures === void 0 ? {} : { refreshFailures: count(value.refreshFailures, "\u8FDE\u7EED\u5237\u65B0\u5931\u8D25\u6B21\u6570") },
2724
+ ...value.autoRefreshPaused === void 0 ? {} : { autoRefreshPaused: flag2(value.autoRefreshPaused, "\u81EA\u52A8\u5237\u65B0\u6682\u505C\u72B6\u6001") },
1542
2725
  ...value.tier === void 0 ? {} : { tier: text(value.tier, "\u5957\u9910\u540D", MAX_TIER) },
1543
- ...value.eligible === void 0 ? {} : { eligible: flag(value.eligible, "\u5957\u9910\u8D44\u683C") },
2726
+ ...value.eligible === void 0 ? {} : { eligible: flag2(value.eligible, "\u5957\u9910\u8D44\u683C") },
1544
2727
  windows: windows.map(parseQuotaWindow),
1545
2728
  balances: {
1546
2729
  ...balances.subscriptionUsd === void 0 ? {} : { subscriptionUsd: amount(balances.subscriptionUsd, "\u8BA2\u9605\u4F59\u989D") },
@@ -1565,11 +2748,15 @@ function parseLoginState(value) {
1565
2748
  function parseStatusModel(value) {
1566
2749
  if (!record(value)) throw new Error("\u6A21\u578B\u6761\u76EE\u5FC5\u987B\u662F\u5BF9\u8C61");
1567
2750
  const id = text(value.id, "\u6A21\u578B id", 128);
1568
- if (!MODEL_ID.test(id)) throw new Error("\u6A21\u578B id \u542B\u4E0D\u5141\u8BB8\u7684\u5B57\u7B26");
2751
+ if (!MODEL_ID3.test(id)) throw new Error("\u6A21\u578B id \u542B\u4E0D\u5141\u8BB8\u7684\u5B57\u7B26");
1569
2752
  return {
1570
2753
  id,
1571
2754
  name: text(value.name, "\u6A21\u578B\u540D", 128),
1572
- group: member(value.group, MODEL_GROUPS, "\u6A21\u578B\u5206\u7EC4")
2755
+ group: member(value.group, MODEL_GROUPS, "\u6A21\u578B\u5206\u7EC4"),
2756
+ ...value.enabled === void 0 ? {} : { enabled: flag2(value.enabled, "\u6A21\u578B\u542F\u7528\u72B6\u6001") },
2757
+ ...value.contextWindow === void 0 ? {} : { contextWindow: positiveInteger(value.contextWindow, "\u4E0A\u4E0B\u6587\u9884\u7B97", 1e8) },
2758
+ ...value.maxOutputTokens === void 0 ? {} : { maxOutputTokens: positiveInteger(value.maxOutputTokens, "\u6700\u5927\u8F93\u51FA", 1e7) },
2759
+ ...value.reasoningEfforts === void 0 ? {} : { reasoningEfforts: parseEfforts(value.reasoningEfforts) }
1573
2760
  };
1574
2761
  }
1575
2762
  function parseStatusConfig(value) {
@@ -1577,16 +2764,21 @@ function parseStatusConfig(value) {
1577
2764
  const apiKeyEnv = text(value.apiKeyEnv, "\u51ED\u636E\u5F15\u7528\u540D", MAX_ENV_REF);
1578
2765
  if (!ENV_REF.test(apiKeyEnv)) throw new Error("\u51ED\u636E\u5F15\u7528\u540D\u5FC5\u987B\u662F\u5408\u6CD5\u6807\u8BC6\u7B26");
1579
2766
  return {
1580
- emptyTurnGuard: flag(value.emptyTurnGuard, "\u7A7A\u8F6E\u4FDD\u62A4\u5F00\u5173"),
1581
- useDesktopCredential: flag(value.useDesktopCredential, "\u684C\u9762\u767B\u5F55\u6001\u5F00\u5173"),
2767
+ emptyTurnGuard: flag2(value.emptyTurnGuard, "\u7A7A\u8F6E\u4FDD\u62A4\u5F00\u5173"),
2768
+ useDesktopCredential: flag2(value.useDesktopCredential, "\u684C\u9762\u767B\u5F55\u6001\u5F00\u5173"),
1582
2769
  baseURL: text(value.baseURL, "\u63A8\u7406\u5730\u5740", MAX_BASE_URL),
1583
- apiKeyEnv
2770
+ apiKeyEnv,
2771
+ ...value.apiProtocol === void 0 ? {} : { apiProtocol: member(value.apiProtocol, API_PROTOCOL_IDS, "\u63A8\u7406\u534F\u8BAE") },
2772
+ ...value.modelSettings === void 0 ? {} : { modelSettings: parseModelSettingsMap(value.modelSettings) },
2773
+ ...value.requestTimeoutMs === void 0 ? {} : { requestTimeoutMs: positiveInteger(value.requestTimeoutMs, "\u8BF7\u6C42\u8D85\u65F6", 36e5) },
2774
+ ...value.streamIdleTimeoutMs === void 0 ? {} : { streamIdleTimeoutMs: positiveInteger(value.streamIdleTimeoutMs, "\u6D41\u7A7A\u95F2\u8D85\u65F6", 18e5) },
2775
+ ...value.quotaRefreshSeconds === void 0 ? {} : { quotaRefreshSeconds: positiveInteger(value.quotaRefreshSeconds, "\u81EA\u52A8\u5237\u65B0\u95F4\u9694", 3600) }
1584
2776
  };
1585
2777
  }
1586
2778
  function parseStatus(value) {
1587
2779
  if (!record(value)) throw new Error("\u72B6\u6001\u5FC5\u987B\u662F\u5BF9\u8C61");
1588
2780
  const models = value.models;
1589
- if (!Array.isArray(models) || models.length > MAX_MODELS) throw new Error(`\u6A21\u578B\u5217\u8868\u5FC5\u987B\u662F\u6570\u7EC4\uFF08\u6700\u591A ${MAX_MODELS} \u9879\uFF09`);
2781
+ if (!Array.isArray(models) || models.length > MAX_MODELS2) throw new Error(`\u6A21\u578B\u5217\u8868\u5FC5\u987B\u662F\u6570\u7EC4\uFF08\u6700\u591A ${MAX_MODELS2} \u9879\uFF09`);
1590
2782
  const ids = /* @__PURE__ */ new Set();
1591
2783
  const parsed = models.map(parseStatusModel);
1592
2784
  for (const model of parsed) {
@@ -1595,13 +2787,16 @@ function parseStatus(value) {
1595
2787
  }
1596
2788
  return {
1597
2789
  revision: text(value.revision, "\u914D\u7F6E\u7248\u672C", MAX_REVISION),
1598
- apiKeyConfigured: flag(value.apiKeyConfigured, "\u51ED\u636E\u662F\u5426\u5C31\u7EEA"),
2790
+ apiKeyConfigured: flag2(value.apiKeyConfigured, "\u51ED\u636E\u662F\u5426\u5C31\u7EEA"),
1599
2791
  ...value.sessionSource === void 0 ? {} : { sessionSource: member(value.sessionSource, SESSION_SOURCES, "\u767B\u5F55\u6001\u6765\u6E90") },
1600
2792
  quota: parseQuotaSnapshot(value.quota),
1601
2793
  login: parseLoginState(value.login),
1602
2794
  config: parseStatusConfig(value.config),
1603
2795
  guardedTurns: count(value.guardedTurns, "\u5DF2\u62E6\u622A\u7A7A\u8F6E\u6570"),
1604
- models: parsed
2796
+ models: parsed,
2797
+ ...value.modelsSource === void 0 ? {} : { modelsSource: member(value.modelsSource, ["builtin", "openai"], "\u6A21\u578B\u76EE\u5F55\u6765\u6E90") },
2798
+ ...value.modelsUpdatedAt === void 0 ? {} : { modelsUpdatedAt: isoTime(value.modelsUpdatedAt, "\u6A21\u578B\u66F4\u65B0\u65F6\u95F4") },
2799
+ ...value.modelsError === void 0 ? {} : { modelsError: text(value.modelsError, "\u6A21\u578B\u66F4\u65B0\u63D0\u793A", MAX_MESSAGE) }
1605
2800
  };
1606
2801
  }
1607
2802
  function parseStartLoginRequest(value) {
@@ -1632,8 +2827,8 @@ function grantOf(value) {
1632
2827
  if (value?.kind !== "grant" || !record(value.payload) || typeof value.payload.cookie !== "string") return;
1633
2828
  return { cookie: value.payload.cookie, ...typeof value.payload.expiresAt === "number" ? { expiresAt: value.payload.expiresAt } : {} };
1634
2829
  }
1635
- var _saveApiKey_dec, _cancelLogin_dec, _loginStatus_dec, _startLogin_dec, _refreshQuota_dec, _status_dec, _a, _init;
1636
- var OpenDesignCoRouterService = class extends (_a = TypertRemoteService, _status_dec = [Remote("status")], _refreshQuota_dec = [Remote("refreshQuota")], _startLogin_dec = [Remote("startLogin")], _loginStatus_dec = [Remote("loginStatus")], _cancelLogin_dec = [Remote("cancelLogin")], _saveApiKey_dec = [Remote("saveApiKey")], _a) {
2830
+ var _saveApiKey_dec, _cancelLogin_dec, _loginStatus_dec, _startLogin_dec, _refreshQuotaNow_dec, _refreshQuota_dec, _refreshModels_dec, _status_dec, _a, _init;
2831
+ var OpenDesignCoRouterService = class extends (_a = TypertRemoteService, _status_dec = [Remote("status")], _refreshModels_dec = [Remote("refreshModels")], _refreshQuota_dec = [Remote("refreshQuota")], _refreshQuotaNow_dec = [Remote("refreshQuotaNow")], _startLogin_dec = [Remote("startLogin")], _loginStatus_dec = [Remote("loginStatus")], _cancelLogin_dec = [Remote("cancelLogin")], _saveApiKey_dec = [Remote("saveApiKey")], _a) {
1637
2832
  constructor(ctx, config, dependencies = { resolveSession, readQuota, readDesktopQuota }) {
1638
2833
  super(ctx, "openDesignCoRouter");
1639
2834
  this.dependencies = dependencies;
@@ -1646,10 +2841,18 @@ var OpenDesignCoRouterService = class extends (_a = TypertRemoteService, _status
1646
2841
  __publicField(this, "_quota", needLogin());
1647
2842
  __publicField(this, "_quotaAt", 0);
1648
2843
  __publicField(this, "_quotaConfig", "");
2844
+ __publicField(this, "_quotaIdentity");
1649
2845
  __publicField(this, "_quotaEpoch", 0);
1650
2846
  __publicField(this, "_quotaPending");
1651
2847
  __publicField(this, "_quotaController");
1652
2848
  __publicField(this, "guardedTurns", 0);
2849
+ __publicField(this, "_models", MODELS);
2850
+ __publicField(this, "_modelsUpdatedAt");
2851
+ __publicField(this, "_modelsError");
2852
+ __publicField(this, "_modelsFingerprint", "");
2853
+ __publicField(this, "_modelsEpoch", 0);
2854
+ __publicField(this, "_modelsPending");
2855
+ __publicField(this, "_modelsController");
1653
2856
  this._config = config;
1654
2857
  this._login = new ConsoleLogin({
1655
2858
  capture: async (expectedRevision) => {
@@ -1683,6 +2886,9 @@ var OpenDesignCoRouterService = class extends (_a = TypertRemoteService, _status
1683
2886
  });
1684
2887
  }
1685
2888
  dependencies;
2889
+ modelCatalog() {
2890
+ return this._models;
2891
+ }
1686
2892
  _credentials() {
1687
2893
  const service = this.ctx.get("credentials");
1688
2894
  if (!service) throw new RemoteError("opendesign/credentials-unavailable", "DSH \u51ED\u636E\u670D\u52A1\u4E0D\u53EF\u7528\uFF1B\u53EF\u5148\u4F7F\u7528\u542F\u52A8\u73AF\u5883\u53D8\u91CF", void 0);
@@ -1699,6 +2905,18 @@ var OpenDesignCoRouterService = class extends (_a = TypertRemoteService, _status
1699
2905
  const credentials = this.ctx.get("credentials"), config = this._config();
1700
2906
  const [apiRecord, sessionRecord, key] = await Promise.all([credentials?.readRecord(API_KEY), credentials?.readRecord(SESSION), this.resolveApiKey(config.apiKeyEnv)]);
1701
2907
  const state = { apiRecord, sessionRecord, key, config };
2908
+ const modelsFingerprint = hash({ key, baseURL: config.baseURL });
2909
+ if (modelsFingerprint !== this._modelsFingerprint) {
2910
+ const changed = this._modelsUpdatedAt !== void 0;
2911
+ this._modelsFingerprint = modelsFingerprint;
2912
+ this._modelsEpoch++;
2913
+ this._modelsController?.abort();
2914
+ this._modelsPending = void 0;
2915
+ this._models = MODELS;
2916
+ this._modelsUpdatedAt = void 0;
2917
+ this._modelsError = void 0;
2918
+ if (changed) this.dependencies.modelsChanged?.();
2919
+ }
1702
2920
  const fingerprint = hash(state);
1703
2921
  if (fingerprint !== this._fingerprint) {
1704
2922
  this._fingerprint = fingerprint;
@@ -1716,12 +2934,13 @@ var OpenDesignCoRouterService = class extends (_a = TypertRemoteService, _status
1716
2934
  this._quotaAt = 0;
1717
2935
  this._quotaEpoch++;
1718
2936
  this._quota = needLogin();
2937
+ this._quotaIdentity = void 0;
1719
2938
  this._quotaController?.abort();
1720
2939
  this._quotaPending = void 0;
1721
2940
  }
1722
2941
  async status() {
1723
2942
  const state = await this._state();
1724
- const quotaConfig = hash({ desktop: state.config.useDesktopCredential, session: state.sessionRecord });
2943
+ const quotaConfig = hash({ desktop: state.config.useDesktopCredential, session: state.sessionRecord, refreshSeconds: state.config.quotaRefreshSeconds });
1725
2944
  if (this._quotaConfig !== quotaConfig) {
1726
2945
  this._quotaConfig = quotaConfig;
1727
2946
  this._invalidateQuota();
@@ -1734,24 +2953,78 @@ var OpenDesignCoRouterService = class extends (_a = TypertRemoteService, _status
1734
2953
  login: this._login.status(),
1735
2954
  config: state.config,
1736
2955
  guardedTurns: this.guardedTurns,
1737
- models: MODELS.map(({ id, name: name2, group: group2 }) => ({ id, name: name2, group: group2 }))
2956
+ models: this._models.map(({ id, name: name2, group: group2, contextWindow, maxOutputTokens, reasoningEfforts }) => ({
2957
+ id,
2958
+ name: name2,
2959
+ group: group2,
2960
+ enabled: isModelEnabled(id, state.config.modelSettings),
2961
+ ...contextWindow === void 0 ? {} : { contextWindow },
2962
+ ...maxOutputTokens === void 0 ? {} : { maxOutputTokens },
2963
+ ...reasoningEfforts === void 0 ? {} : { reasoningEfforts }
2964
+ })),
2965
+ modelsSource: this._modelsUpdatedAt ? "openai" : "builtin",
2966
+ ...this._modelsUpdatedAt ? { modelsUpdatedAt: this._modelsUpdatedAt } : {},
2967
+ ...this._modelsError ? { modelsError: this._modelsError } : {}
1738
2968
  });
1739
2969
  }
2970
+ async refreshModels() {
2971
+ const state = await this._state();
2972
+ if (this._modelsPending) return this._modelsPending;
2973
+ const key = state.key;
2974
+ if (!key?.trim()) {
2975
+ this._modelsError = "\u8BF7\u5148\u914D\u7F6E API Key\uFF0C\u518D\u4ECE OpenAI \u7AEF\u53E3\u62C9\u53D6\u6A21\u578B\u3002";
2976
+ return this.status();
2977
+ }
2978
+ const epoch = this._modelsEpoch;
2979
+ const controller = new AbortController();
2980
+ this._modelsController = controller;
2981
+ const signal = AbortSignal.any([controller.signal, this._lifetime.signal]);
2982
+ const run = (async () => {
2983
+ try {
2984
+ const models = await (this.dependencies.fetchModels ?? fetchModelCatalog)(state.config.baseURL, key, signal);
2985
+ await this._state();
2986
+ if (signal.aborted || epoch !== this._modelsEpoch) return this.status();
2987
+ this._models = models;
2988
+ this._modelsUpdatedAt = (/* @__PURE__ */ new Date()).toISOString();
2989
+ this._modelsError = void 0;
2990
+ this.dependencies.modelsChanged?.();
2991
+ } catch (error) {
2992
+ if (!signal.aborted && epoch === this._modelsEpoch) this._modelsError = error instanceof ModelDiscoveryError ? error.message : "\u6A21\u578B\u5217\u8868\u66F4\u65B0\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002";
2993
+ }
2994
+ return this.status();
2995
+ })();
2996
+ this._modelsPending = run;
2997
+ try {
2998
+ return await run;
2999
+ } finally {
3000
+ if (this._modelsPending === run) this._modelsPending = void 0;
3001
+ }
3002
+ }
1740
3003
  async refreshQuota() {
3004
+ return this._refreshQuota(false);
3005
+ }
3006
+ async refreshQuotaNow() {
3007
+ return this._refreshQuota(true);
3008
+ }
3009
+ async _refreshQuota(force) {
1741
3010
  const state = await this._state();
1742
- const fingerprint = hash({ desktop: state.config.useDesktopCredential, session: state.sessionRecord });
3011
+ const fingerprint = hash({ desktop: state.config.useDesktopCredential, session: state.sessionRecord, refreshSeconds: state.config.quotaRefreshSeconds });
1743
3012
  if (fingerprint !== this._quotaConfig) {
1744
3013
  this._quotaConfig = fingerprint;
1745
3014
  this._invalidateQuota();
1746
3015
  }
1747
3016
  if (this._quotaPending) return this._quotaPending;
1748
- const cacheMs = this._quota.state === "error" || this._quota.state === "need-login" ? 5e3 : 3e4;
1749
- if (Date.now() - this._quotaAt < cacheMs) return this._quota;
3017
+ if (!force) {
3018
+ if (this._quota.autoRefreshPaused) return this._quota;
3019
+ const next = this._quota.nextRefreshAt ? Date.parse(this._quota.nextRefreshAt) : this._quotaAt + 5e3;
3020
+ if (Date.now() < next) return this._quota;
3021
+ }
1750
3022
  const epoch = this._quotaEpoch;
1751
3023
  const controller = new AbortController();
1752
3024
  this._quotaController = controller;
1753
3025
  const signal = AbortSignal.any([controller.signal, this._lifetime.signal]);
1754
- const run = this._readQuota(epoch, signal);
3026
+ const failures = force ? 0 : this._quota.refreshFailures ?? 0;
3027
+ const run = this._readQuota(epoch, signal, failures);
1755
3028
  this._quotaPending = run;
1756
3029
  void run.finally(() => {
1757
3030
  if (this._quotaPending === run) this._quotaPending = void 0;
@@ -1759,9 +3032,11 @@ var OpenDesignCoRouterService = class extends (_a = TypertRemoteService, _status
1759
3032
  });
1760
3033
  return run;
1761
3034
  }
1762
- async _readQuota(epoch, signal) {
3035
+ async _readQuota(epoch, signal, failures) {
1763
3036
  const config = this._config();
1764
3037
  let result;
3038
+ let identity;
3039
+ let refreshFailed = false;
1765
3040
  try {
1766
3041
  const authDeps = {
1767
3042
  useDesktop: config.useDesktopCredential,
@@ -1772,11 +3047,23 @@ var OpenDesignCoRouterService = class extends (_a = TypertRemoteService, _status
1772
3047
  }
1773
3048
  };
1774
3049
  const auth = await this.dependencies.resolveSession(authDeps);
3050
+ if (auth.state === "ready") identity = hash(auth.session);
1775
3051
  if (auth.state === "need-login") result = needLogin();
1776
3052
  else if (auth.session.source !== "desktop") result = await this.dependencies.readQuota(auth.session, signal);
1777
3053
  else {
1778
3054
  try {
1779
3055
  result = await this.dependencies.readDesktopQuota(auth.session, signal);
3056
+ if (result.state === "partial") {
3057
+ const fallback = await this.dependencies.resolveSession({ ...authDeps, useDesktop: false });
3058
+ if (fallback.state === "ready") {
3059
+ try {
3060
+ const complete = await this.dependencies.readQuota(fallback.session, signal);
3061
+ if (complete.state === "ok" && (complete.windows.length > 0 || complete.eligible === false)) result = { ...complete, note: `\u684C\u9762\u767B\u5F55\u6001\u4EC5\u63D0\u4F9B\u90E8\u5206\u989D\u5EA6\uFF1B\u5F53\u524D\u663E\u793A\u72EC\u7ACB\u767B\u5F55\u8D26\u53F7\u7684\u5B8C\u6574\u989D\u5EA6\u3002${complete.note ? ` ${complete.note}` : ""}` };
3062
+ } catch (error) {
3063
+ if (signal.aborted) throw error;
3064
+ }
3065
+ }
3066
+ }
1780
3067
  } catch (error) {
1781
3068
  if (!(error instanceof ConsoleError) || !["auth", "permission"].includes(error.kind)) throw error;
1782
3069
  const fallback = await this.dependencies.resolveSession({ ...authDeps, useDesktop: false });
@@ -1785,11 +3072,32 @@ var OpenDesignCoRouterService = class extends (_a = TypertRemoteService, _status
1785
3072
  }
1786
3073
  }
1787
3074
  } catch (error) {
1788
- result = { ...needLogin(), state: error instanceof ConsoleError && error.kind === "auth" ? "need-login" : "error", note: error instanceof ConsoleError ? error.message : "\u989D\u5EA6\u8BFB\u53D6\u6682\u65F6\u5931\u8D25\uFF1B\u8BF7\u68C0\u67E5\u8FDE\u63A5\u6216\u91CD\u65B0\u767B\u5F55" };
3075
+ const authFailure = error instanceof ConsoleError && error.kind === "auth";
3076
+ refreshFailed = !authFailure;
3077
+ const hasReadings = this._quota.windows.length > 0 || Object.keys(this._quota.balances).length > 0;
3078
+ if (!authFailure && identity !== void 0 && identity === this._quotaIdentity && hasReadings) {
3079
+ result = { ...this._quota, state: "partial", stale: true, note: "\u989D\u5EA6\u5237\u65B0\u5931\u8D25\uFF0C\u5F53\u524D\u663E\u793A\u4E0A\u6B21\u6210\u529F\u8BFB\u6570\u3002" };
3080
+ } else {
3081
+ result = { ...needLogin(), state: authFailure ? "need-login" : "error", note: error instanceof ConsoleError ? error.message : "\u989D\u5EA6\u8BFB\u53D6\u6682\u65F6\u5931\u8D25\uFF1B\u8BF7\u68C0\u67E5\u8FDE\u63A5\u6216\u91CD\u65B0\u767B\u5F55" };
3082
+ }
1789
3083
  }
1790
3084
  if (!signal.aborted && epoch === this._quotaEpoch) {
1791
- this._quota = result;
1792
3085
  this._quotaAt = Date.now();
3086
+ if (refreshFailed) {
3087
+ const count2 = failures + 1;
3088
+ const paused = count2 >= 6;
3089
+ const { nextRefreshAt: _previousPlan, ...previous } = result;
3090
+ result = {
3091
+ ...previous,
3092
+ refreshFailures: count2,
3093
+ autoRefreshPaused: paused,
3094
+ ...paused ? { note: `${result.note ?? "\u989D\u5EA6\u5237\u65B0\u5931\u8D25"} \u81EA\u52A8\u91CD\u8BD5\u5DF2\u6682\u505C\uFF0C\u8BF7\u70B9\u51FB\u5237\u65B0\u989D\u5EA6\u91CD\u8BD5\u3002` } : { nextRefreshAt: new Date(this._quotaAt + Math.min(6e4, 5e3 * 2 ** (count2 - 1))).toISOString() }
3095
+ };
3096
+ } else if (result.state !== "need-login") {
3097
+ result = { ...result, nextRefreshAt: new Date(this._quotaAt + (config.quotaRefreshSeconds ?? 60) * 1e3).toISOString() };
3098
+ }
3099
+ this._quota = result;
3100
+ this._quotaIdentity = identity;
1793
3101
  }
1794
3102
  return epoch === this._quotaEpoch ? result : this._quota;
1795
3103
  }
@@ -1819,7 +3127,9 @@ var OpenDesignCoRouterService = class extends (_a = TypertRemoteService, _status
1819
3127
  };
1820
3128
  _init = __decoratorStart(_a);
1821
3129
  __decorateElement(_init, 1, "status", _status_dec, OpenDesignCoRouterService);
3130
+ __decorateElement(_init, 1, "refreshModels", _refreshModels_dec, OpenDesignCoRouterService);
1822
3131
  __decorateElement(_init, 1, "refreshQuota", _refreshQuota_dec, OpenDesignCoRouterService);
3132
+ __decorateElement(_init, 1, "refreshQuotaNow", _refreshQuotaNow_dec, OpenDesignCoRouterService);
1823
3133
  __decorateElement(_init, 1, "startLogin", _startLogin_dec, OpenDesignCoRouterService);
1824
3134
  __decorateElement(_init, 1, "loginStatus", _loginStatus_dec, OpenDesignCoRouterService);
1825
3135
  __decorateElement(_init, 1, "cancelLogin", _cancelLogin_dec, OpenDesignCoRouterService);
@@ -1829,9 +3139,12 @@ function apply(ctx, config) {
1829
3139
  const current = () => readConfig(config);
1830
3140
  current();
1831
3141
  ctx.inject(["settings"], (scope) => scope.effect(() => scope.settings.configure({ auto: false }, ctx.fiber)));
1832
- const service = new OpenDesignCoRouterService(ctx, current);
3142
+ let modelsChanged = () => {
3143
+ };
3144
+ const service = new OpenDesignCoRouterService(ctx, current, { resolveSession, readQuota, readDesktopQuota, modelsChanged: () => modelsChanged() });
1833
3145
  const adapter = new CoRouterAdapter({
1834
3146
  config: current,
3147
+ models: () => service.modelCatalog(),
1835
3148
  apiKey: (ref) => service.resolveApiKey(ref),
1836
3149
  onEmptyTurn: () => {
1837
3150
  service.guardedTurns++;
@@ -1843,7 +3156,23 @@ function apply(ctx, config) {
1843
3156
  return `data:${image.ref.mediaType};base64,${Buffer.from(image.data).toString("base64")}`;
1844
3157
  }
1845
3158
  });
1846
- ctx.llm.registerAdapter([PLAN_PROVIDER, PAID_PROVIDER], adapter);
3159
+ const registration = ctx.llm.registerAdapter([PLAN_PROVIDER, PAID_PROVIDER], adapter);
3160
+ modelsChanged = () => registration.replace([PLAN_PROVIDER, PAID_PROVIDER]);
3161
+ let configFingerprint = hash(current());
3162
+ ctx.on("loader/volatile-update", () => {
3163
+ const next = hash(current());
3164
+ if (next === configFingerprint) return;
3165
+ configFingerprint = next;
3166
+ modelsChanged();
3167
+ });
3168
+ ctx.llm.registerModelDiscovery(SETTINGS_NS, async (request, signal) => {
3169
+ if (request.provider !== void 0 && ![PLAN_PROVIDER, PAID_PROVIDER].includes(request.provider)) throw new LlmError11("\u4E0D\u662F\u672C\u63D2\u4EF6\u7684\u6A21\u578B\u901A\u9053", "INVALID_REQUEST");
3170
+ const config2 = current();
3171
+ const key = request.apiKey ?? await service.resolveApiKey(config2.apiKeyEnv);
3172
+ if (!key) throw new LlmError11("\u8BF7\u5148\u914D\u7F6E CoRouter API Key", "MISSING_CREDENTIAL");
3173
+ const models = await fetchModelCatalog(config2.baseURL, key, signal);
3174
+ return models.map((row) => ({ id: row.id, name: row.name, ...row.contextWindow === void 0 ? {} : { contextWindow: row.contextWindow }, ...row.maxOutputTokens === void 0 ? {} : { maxTokens: row.maxOutputTokens }, ...row.inputModalities ? { inputModalities: row.inputModalities } : {} }));
3175
+ });
1847
3176
  ctx.llm.registerConfigurableProviders([
1848
3177
  { provider: PLAN_PROVIDER, displayName: "OpenDesign CoRouter \xB7 \u5957\u9910\u8D60\u9001", settingsNs: SETTINGS_NS, settingsPath: [], defaultBaseURL: current().baseURL },
1849
3178
  { provider: PAID_PROVIDER, displayName: "OpenDesign CoRouter \xB7 \u4ED8\u8D39\u6A21\u578B", settingsNs: SETTINGS_NS, settingsPath: [], defaultBaseURL: current().baseURL }