dsh-opendesign-corouter 0.1.2 → 0.1.4

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
@@ -228,96 +228,59 @@ function inferenceEndpoint(baseURL, protocol) {
228
228
  return baseURL.trim() === "" ? suffix : `${openAiBaseURL(baseURL.trim())}${suffix}`;
229
229
  }
230
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
231
  // 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
232
  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
233
  function parseModelSettingsMap(value) {
303
- if (!record(value)) throw new Error("\u6A21\u578B\u8BBE\u7F6E\u5FC5\u987B\u662F\u5BF9\u8C61");
234
+ if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error("\u6A21\u578B\u8BBE\u7F6E\u5FC5\u987B\u662F\u5BF9\u8C61");
304
235
  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`);
236
+ if (keys.length > 512) throw new Error("\u6A21\u578B\u8BBE\u7F6E\u6700\u591A\u5141\u8BB8 512 \u4E2A\u6A21\u578B");
237
+ const fields = /* @__PURE__ */ new Set(["enabled", "temperature", "topP", "maxTokens", "contextWindow", "reasoningEffort"]);
238
+ const efforts = ["none", "minimal", "low", "medium", "high", "xhigh", "ultra", "max"];
306
239
  const out = {};
307
240
  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]);
241
+ if (["__proto__", "constructor", "prototype"].includes(id)) throw new Error("\u6A21\u578B\u8BBE\u7F6E\u4E0D\u5141\u8BB8\u4FDD\u7559\u5B57\u6BB5\u540D");
242
+ if (!/^[A-Za-z0-9._:/-]{1,128}$/u.test(id)) throw new Error("\u6A21\u578B ID \u5FC5\u987B\u662F 1\u2013128 \u4F4D\u5B57\u6BCD\u3001\u6570\u5B57\u6216 . _ : / -");
243
+ const entry = value[id];
244
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) throw new Error("\u6BCF\u4E2A\u6A21\u578B\u7684\u8BBE\u7F6E\u5FC5\u987B\u662F\u5BF9\u8C61");
245
+ const settings = entry;
246
+ for (const field of Object.keys(settings)) {
247
+ if (!fields.has(field)) throw new Error("\u6A21\u578B\u8BBE\u7F6E\u542B\u6709\u4E0D\u652F\u6301\u7684\u5B57\u6BB5");
248
+ }
249
+ const parsed = {};
250
+ if (settings.enabled !== void 0) {
251
+ if (typeof settings.enabled !== "boolean") throw new Error("\u6A21\u578B\u8BBE\u7F6E\u7684 enabled \u5FC5\u987B\u662F\u5E03\u5C14\u503C");
252
+ parsed.enabled = settings.enabled;
253
+ }
254
+ for (const [field, max] of [["temperature", 2], ["topP", 1]]) {
255
+ const item = settings[field];
256
+ if (item === void 0) continue;
257
+ if (typeof item !== "number" || !Number.isFinite(item) || item < 0 || item > max) throw new Error(`\u6A21\u578B\u8BBE\u7F6E\u7684 ${field} \u5FC5\u987B\u662F 0 \u5230 ${max} \u4E4B\u95F4\u7684\u6709\u9650\u6570\u5B57`);
258
+ parsed[field] = item;
259
+ }
260
+ for (const [field, max] of [["maxTokens", 1e7], ["contextWindow", 1e8]]) {
261
+ const item = settings[field];
262
+ if (item === void 0) continue;
263
+ if (typeof item !== "number" || !Number.isSafeInteger(item) || item < 1 || item > max) throw new Error(`\u6A21\u578B\u8BBE\u7F6E\u7684 ${field} \u5FC5\u987B\u662F 1 \u5230 ${max} \u4E4B\u95F4\u7684\u6574\u6570`);
264
+ parsed[field] = item;
265
+ }
266
+ if (settings.reasoningEffort !== void 0) {
267
+ if (typeof settings.reasoningEffort !== "string" || !efforts.includes(settings.reasoningEffort)) throw new Error("\u6A21\u578B\u8BBE\u7F6E\u7684 reasoningEffort \u4E0D\u5728\u5141\u8BB8\u7684\u53D6\u503C\u5185");
268
+ parsed.reasoningEffort = settings.reasoningEffort;
269
+ }
270
+ if (parsed.maxTokens !== void 0 && parsed.contextWindow !== void 0 && parsed.maxTokens >= parsed.contextWindow) throw new Error("\u6A21\u578B\u8BBE\u7F6E\u7684 maxTokens \u5FC5\u987B\u5C0F\u4E8E contextWindow");
271
+ out[id] = parsed;
311
272
  }
312
273
  return out;
313
274
  }
314
275
  function applyModelSettings(row, settings) {
315
276
  const { contextWindow, maxTokens } = settings ?? {};
316
277
  if (contextWindow === void 0 && maxTokens === void 0) return { ...row };
278
+ if (contextWindow !== void 0) parseModelSettingsMap({ model: { contextWindow } });
279
+ if (maxTokens !== void 0) parseModelSettingsMap({ model: { maxTokens } });
317
280
  return {
318
281
  ...row,
319
- ...contextWindow === void 0 ? {} : { contextWindow: capacity(contextWindow, MAX_CONTEXT_WINDOW, "contextWindow") },
320
- ...maxTokens === void 0 ? {} : { maxOutputTokens: capacity(maxTokens, MAX_TOKENS, "maxTokens") }
282
+ ...contextWindow === void 0 ? {} : { contextWindow },
283
+ ...maxTokens === void 0 ? {} : { maxOutputTokens: maxTokens }
321
284
  };
322
285
  }
323
286
  function isModelEnabled(id, settings = {}) {
@@ -331,10 +294,10 @@ var PLAN_PROVIDER = "opendesign-corouter-plan";
331
294
  var PAID_PROVIDER = "opendesign-corouter-paid";
332
295
  var DEFAULT_BASE_URL = "https://amr-link.open-design.ai/v1";
333
296
  var Config = Schema.object({
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(),
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(),
297
+ apiKeyEnv: Schema.string().max(128).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(),
298
+ baseURL: Schema.transform(Schema.string(), assertBaseURL).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
299
  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(),
300
+ modelSettings: Schema.transform(Schema.dict(Schema.any()), parseModelSettingsMap).default({}).description("\u9010\u6A21\u578B\u542F\u7528\u72B6\u6001\u548C\u8BF7\u6C42\u53C2\u6570").volatile(),
338
301
  useDesktopCredential: Schema.boolean().default(true).description("\u4F18\u5148\u8BFB\u53D6 OpenDesign \u684C\u9762\u7AEF\u767B\u5F55\u6001\u67E5\u8BE2\u989D\u5EA6").volatile(),
339
302
  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
303
  quotaRefreshSeconds: Schema.natural().min(5).max(3600).default(60).description("\u989D\u5EA6\u81EA\u52A8\u5237\u65B0\u95F4\u9694\uFF08\u79D2\uFF09").volatile(),
@@ -342,6 +305,7 @@ var Config = Schema.object({
342
305
  streamIdleTimeoutMs: Schema.natural().min(1e3).max(18e5).default(3e5).description("\u6D41\u5F0F\u54CD\u5E94\u65E0\u6570\u636E\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09").volatile()
343
306
  });
344
307
  function assertBaseURL(raw) {
308
+ if (raw.length > 2048) throw new Error("\u63A8\u7406 Base URL \u6700\u591A\u5141\u8BB8 2048 \u5B57\u7B26");
345
309
  let url;
346
310
  try {
347
311
  url = new URL(raw);
@@ -350,7 +314,9 @@ function assertBaseURL(raw) {
350
314
  }
351
315
  if (!["https:", "http:"].includes(url.protocol) || url.username || url.password || url.search || url.hash) throw new Error("\u63A8\u7406 Base URL \u4E0D\u53EF\u5305\u542B\u51ED\u636E\u3001\u67E5\u8BE2\u4E32\u6216\u7247\u6BB5");
352
316
  if (url.protocol === "http:" && !["localhost", "127.0.0.1", "[::1]"].includes(url.hostname)) throw new Error("\u975E\u672C\u673A\u63A8\u7406\u5730\u5740\u5FC5\u987B\u4F7F\u7528 HTTPS");
353
- return url.href.replace(/\/+$/u, "");
317
+ const normalized = url.href.replace(/\/+$/u, "");
318
+ if (normalized.length > 2048) throw new Error("\u63A8\u7406 Base URL \u6700\u591A\u5141\u8BB8 2048 \u5B57\u7B26");
319
+ return normalized;
354
320
  }
355
321
  function readConfig(config) {
356
322
  return {
@@ -462,6 +428,35 @@ function estimateTokens(value) {
462
428
  // src/stream.ts
463
429
  import { LlmError as LlmError4, ToolCallId } from "@deepseek-ai/dsh-llm";
464
430
 
431
+ // src/util.ts
432
+ function record(value) {
433
+ return value !== null && typeof value === "object" && !Array.isArray(value);
434
+ }
435
+ function finite(value) {
436
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
437
+ }
438
+ function safeSecret(raw) {
439
+ const value = raw.trim();
440
+ 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");
441
+ return value;
442
+ }
443
+ function combinedSignal(signal, timeoutMs) {
444
+ const deadline = AbortSignal.timeout(timeoutMs);
445
+ return signal ? AbortSignal.any([signal, deadline]) : deadline;
446
+ }
447
+ function sleep(ms, signal) {
448
+ return new Promise((resolve) => {
449
+ if (signal.aborted) return resolve();
450
+ const done = () => {
451
+ clearTimeout(timer);
452
+ signal.removeEventListener("abort", done);
453
+ resolve();
454
+ };
455
+ const timer = setTimeout(done, ms);
456
+ signal.addEventListener("abort", done, { once: true });
457
+ });
458
+ }
459
+
465
460
  // src/protocols/errors.ts
466
461
  import {
467
462
  ACCOUNT_QUOTA_EXCEEDED_CODE,
@@ -1130,12 +1125,12 @@ function buildMessagesRequest(ctx) {
1130
1125
  if (ctx.topP !== void 0) body.top_p = ctx.topP;
1131
1126
  if (options.stop?.length) body.stop_sequences = options.stop;
1132
1127
  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)) {
1128
+ const effort = String(options.reasoningEffort);
1129
+ if (effort === "none") body.thinking = { type: "disabled" };
1130
+ else if (THINKING_EFFORTS.includes(effort)) {
1136
1131
  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");
1132
+ body.output_config = { effort };
1133
+ } else throw new LlmError7(`Anthropic Messages \u534F\u8BAE\u4E0D\u63A5\u53D7\u601D\u8003\u6DF1\u5EA6\u300C${effort}\u300D`, "UNSUPPORTED_OPTION");
1139
1134
  }
1140
1135
  return { url: messagesURL(ctx.baseURL), headers: streamHeaders(ctx.apiKey, { "anthropic-version": ANTHROPIC_VERSION }), body };
1141
1136
  }
@@ -1330,7 +1325,7 @@ import {
1330
1325
  LlmError as LlmError8,
1331
1326
  ToolCallId as ToolCallId3
1332
1327
  } from "@deepseek-ai/dsh-llm";
1333
- var REASONING_EFFORTS2 = ["none", "minimal", "low", "medium", "high", "xhigh", "max"];
1328
+ var REASONING_EFFORTS = ["none", "minimal", "low", "medium", "high", "xhigh", "max"];
1334
1329
  function userContent(text2, images) {
1335
1330
  if (!images.length) return text2;
1336
1331
  return [
@@ -1390,9 +1385,9 @@ function buildResponsesRequest(ctx) {
1390
1385
  body.max_output_tokens = options.maxTokens;
1391
1386
  }
1392
1387
  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" };
1388
+ const effort = String(options.reasoningEffort);
1389
+ if (!REASONING_EFFORTS.includes(effort)) throw new LlmError8(`Responses \u534F\u8BAE\u4E0D\u63A5\u53D7\u601D\u8003\u6DF1\u5EA6\u300C${effort}\u300D`, "UNSUPPORTED_OPTION");
1390
+ body.reasoning = { effort, summary: "auto" };
1396
1391
  }
1397
1392
  if (options.stop?.length) throw new LlmError8("Responses \u534F\u8BAE\u4E0D\u652F\u6301 stop \u5E8F\u5217", "UNSUPPORTED_OPTION");
1398
1393
  return { url: inferenceEndpoint(ctx.baseURL, "responses"), headers: streamHeaders(ctx.apiKey), body };
@@ -1485,6 +1480,18 @@ async function* streamResponses(body, signal, idleMs) {
1485
1480
  }
1486
1481
  out.push({ type: "tool-call-delta", index: call.index, id: ToolCallId3(call.id), ...name2, argumentsDelta: delta });
1487
1482
  }
1483
+ function completeCall(call, item, out) {
1484
+ const args = item.arguments;
1485
+ let delta = "";
1486
+ if (args !== void 0) {
1487
+ if (typeof args !== "string" || !args.startsWith(call.args)) {
1488
+ throw new LlmError8("OpenDesign\uFF1A\u5DE5\u5177\u8C03\u7528\u5B8C\u6574\u53C2\u6570\u4E0E\u6D41\u5F0F\u589E\u91CF\u4E0D\u4E00\u81F4", "INVALID_RESPONSE");
1489
+ }
1490
+ delta = args.slice(call.args.length);
1491
+ call.args = args;
1492
+ }
1493
+ flushCall(call, delta, out);
1494
+ }
1488
1495
  function keyFor(value, kind, outputIndex) {
1489
1496
  const itemId = str(value.item_id) ?? (outputIndex === void 0 ? void 0 : keyByOutput.get(outputIndex));
1490
1497
  return `${kind}:${itemId ?? `idx:${outputIndex ?? -1}`}`;
@@ -1496,10 +1503,10 @@ async function* streamResponses(body, signal, idleMs) {
1496
1503
  if (item.type === "function_call") {
1497
1504
  const itemId = str(item.id) ?? str(item.call_id);
1498
1505
  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);
1506
+ if (!callId) continue;
1507
+ const existing = [...calls].find(([, call2]) => call2.id === callId);
1508
+ const call = callState(existing?.[0] ?? `call:${itemId ?? callId}`, item, out);
1509
+ completeCall(call, item, out);
1503
1510
  continue;
1504
1511
  }
1505
1512
  if (item.type === "message") {
@@ -1612,11 +1619,7 @@ async function* streamResponses(body, signal, idleMs) {
1612
1619
  const outputIndex = int(value.output_index);
1613
1620
  if (item.type === "function_call") {
1614
1621
  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);
1622
+ completeCall(call, item, out);
1620
1623
  } else if (item.type === "message") {
1621
1624
  const key = `text:${str(item.id) ?? `idx:${outputIndex ?? -1}`}:0`;
1622
1625
  if (!texts.has(key)) {
@@ -1749,8 +1752,8 @@ var CoRouterAdapter = class extends LlmAdapter {
1749
1752
  async *#stream(options, config, row) {
1750
1753
  const signal = AbortSignal.any([combinedSignal(options.signal, config.requestTimeoutMs), this.#lifetime.signal]);
1751
1754
  signal.throwIfAborted();
1752
- const raw = await this.options.apiKey(config.apiKeyEnv);
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");
1755
+ const raw = await this.options.apiKey(config.apiKeyEnv, config.baseURL);
1756
+ if (!raw) throw new LlmError10("\u672A\u914D\u7F6E\u5F53\u524D\u7F51\u5173\u7684\u63A8\u7406\u51ED\u636E\uFF1B\u8BF7\u5B8C\u6210\u8BBE\u5907\u6388\u6743\u6216\u914D\u7F6E\u5BF9\u5E94 API Key", "MISSING_CREDENTIAL");
1754
1757
  const key = assertUsableApiKey(raw, "dsh-opendesign-corouter", config.apiKeyEnv);
1755
1758
  const setting = config.modelSettings?.[row.id] ?? {};
1756
1759
  const protocolId = config.apiProtocol ?? "chat-completions";
@@ -1787,13 +1790,13 @@ var CoRouterAdapter = class extends LlmAdapter {
1787
1790
  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
1791
  maxTokens = Math.min(capped, Math.floor(room));
1789
1792
  }
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");
1793
+ const effort = options.reasoningEffort ?? (setting.reasoningEffort ? ReasoningEffortId(setting.reasoningEffort) : void 0);
1794
+ if (effort !== void 0 && row.reasoningEfforts && !row.reasoningEfforts.includes(effort)) throw new LlmError10("\u8BE5\u6A21\u578B\u4E0D\u652F\u6301\u6240\u9009\u601D\u8003\u6DF1\u5EA6", "UNSUPPORTED_OPTION");
1792
1795
  const effective = {
1793
1796
  ...projectedOptions,
1794
1797
  ...(options.temperature ?? setting.temperature) === void 0 ? {} : { temperature: options.temperature ?? setting.temperature },
1795
1798
  ...maxTokens === void 0 ? {} : { maxTokens },
1796
- ...effort2 === void 0 ? {} : { reasoningEffort: effort2 }
1799
+ ...effort === void 0 ? {} : { reasoningEffort: effort }
1797
1800
  };
1798
1801
  const protocol = protocolFor(protocolId);
1799
1802
  const request = protocol.build({ baseURL: config.baseURL, apiKey: key, model: row, options: effective, images, ...setting.topP === void 0 ? {} : { topP: setting.topP } });
@@ -1859,7 +1862,7 @@ var DISCOVERY_TIMEOUT_MS = 2e4;
1859
1862
  var MAX_DISCOVERY_BODY_BYTES = 2 * 1024 * 1024;
1860
1863
  var MAX_MODELS = 512;
1861
1864
  var MAX_MODEL_NAME_LENGTH = 128;
1862
- var MODEL_ID2 = /^[A-Za-z0-9._:/-]{1,128}$/u;
1865
+ var MODEL_ID = /^[A-Za-z0-9._:/-]{1,128}$/u;
1863
1866
  var MEDIA_MODEL_ID = /^(?:gpt-image-|nano-banana-|seedream-|imagen-|veo-|doubao-seedance-|sora-)/u;
1864
1867
  var MESSAGES2 = {
1865
1868
  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",
@@ -1933,7 +1936,7 @@ function normalize(body) {
1933
1936
  const seen = /* @__PURE__ */ new Set();
1934
1937
  const rows = [];
1935
1938
  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;
1939
+ if (!record(value) || typeof value.id !== "string" || !MODEL_ID.test(value.id) || ["__proto__", "constructor", "prototype"].includes(value.id)) continue;
1937
1940
  if (MEDIA_MODEL_ID.test(value.id) || seen.has(value.id)) continue;
1938
1941
  seen.add(value.id);
1939
1942
  const known = findModel(value.id);
@@ -2002,7 +2005,7 @@ function isOfficialApiUrl(raw) {
2002
2005
  if (url.username || url.password) return false;
2003
2006
  if (url.search || url.hash) return false;
2004
2007
  if (url.pathname !== "/" && url.pathname !== "") return false;
2005
- return url.hostname.toLowerCase() === API_HOST;
2008
+ return url.hostname.toLowerCase() === API_HOST && !url.port;
2006
2009
  }
2007
2010
  function normalizeApiUrl(raw) {
2008
2011
  const value = typeof raw === "string" ? raw.trim() : "";
@@ -2077,6 +2080,24 @@ async function readDesktopCredential(deps = {}) {
2077
2080
  }
2078
2081
  return parseDesktopConfigText(text2, profile);
2079
2082
  }
2083
+ function deviceGrant(value) {
2084
+ if (!record(value) || value.kind !== "device" || typeof value.controlKey !== "string" || typeof value.runtimeKey !== "string") return;
2085
+ try {
2086
+ const controlKey = safeSecret(value.controlKey), runtimeKey = safeSecret(value.runtimeKey);
2087
+ if ([controlKey, runtimeKey].some((key) => key.length > 8192 || /\s/u.test(key))) return;
2088
+ return { kind: "device", controlKey, runtimeKey, ...typeof value.savedAt === "string" ? { savedAt: value.savedAt } : {} };
2089
+ } catch {
2090
+ return void 0;
2091
+ }
2092
+ }
2093
+ function isOfficialRuntimeUrl(raw) {
2094
+ try {
2095
+ const url = new URL(raw);
2096
+ return url.origin === "https://amr-link.open-design.ai" && !url.username && !url.password && !url.search && !url.hash;
2097
+ } catch {
2098
+ return false;
2099
+ }
2100
+ }
2080
2101
  function cookieHeader(raw) {
2081
2102
  const value = raw.trim();
2082
2103
  if (!value || !value.includes("=")) return void 0;
@@ -2097,6 +2118,10 @@ function desktopSession(credential, apiUrl = credential.apiUrl ?? DEFAULT_API_UR
2097
2118
  ...credential.plan ? { plan: credential.plan } : {}
2098
2119
  };
2099
2120
  }
2121
+ function deviceSession(tokens) {
2122
+ const controlKey = typeof tokens === "string" ? tokens : tokens.controlKey;
2123
+ return { headers: { authorization: `Bearer ${safeSecret(controlKey)}` }, source: "login", via: "login", apiUrl: DEFAULT_API_URL, credentialType: "device" };
2124
+ }
2100
2125
  function cookieSession(cookie, source, apiUrl = DEFAULT_API_URL) {
2101
2126
  const header = cookieHeader(cookie);
2102
2127
  if (!header) return void 0;
@@ -2137,10 +2162,15 @@ async function resolveSession(deps = {}) {
2137
2162
  } catch {
2138
2163
  grant = void 0;
2139
2164
  }
2140
- const expiry = typeof grant?.expiresAt === "number" && Number.isFinite(grant.expiresAt) ? grant.expiresAt : 0;
2141
- if (grant && (expiry === 0 || expiry > now())) {
2142
- const session = cookieSession(grant.cookie, "login");
2143
- if (session) return { state: "ready", session, warnings };
2165
+ if (grant && "kind" in grant && grant.kind === "device") {
2166
+ const device = deviceGrant(grant);
2167
+ if (device) return { state: "ready", session: deviceSession(device), warnings };
2168
+ } else if (grant && "cookie" in grant) {
2169
+ const expiry = typeof grant.expiresAt === "number" && Number.isFinite(grant.expiresAt) ? grant.expiresAt : 0;
2170
+ if (expiry === 0 || expiry > now()) {
2171
+ const session = cookieSession(grant.cookie, "login");
2172
+ if (session) return { state: "ready", session, warnings };
2173
+ }
2144
2174
  }
2145
2175
  }
2146
2176
  if (deps.resolveRef) {
@@ -2161,61 +2191,207 @@ async function resolveSession(deps = {}) {
2161
2191
 
2162
2192
  // src/login.ts
2163
2193
  import { randomUUID } from "node:crypto";
2164
- var CONSOLE_ORIGIN = "https://open-design.ai";
2165
- var CONSOLE_DASHBOARD_URL = `${CONSOLE_ORIGIN}/cloud/dashboard`;
2166
- var API_COOKIE_URL = "https://amr-api.open-design.ai/api/v1/workspaces/current";
2167
- function extractConsoleSession(cookies, now = Date.now()) {
2168
- const usable = cookies.filter((c) => typeof c.name === "string" && c.name.length > 0 && typeof c.value === "string" && c.value.length > 0 && (c.expires === void 0 || c.expires === -1 || c.expires * 1e3 > now));
2169
- if (!usable.length) return null;
2170
- const declared = usable.map((c) => c.expires).filter((expires) => typeof expires === "number" && Number.isFinite(expires) && expires > 0);
2194
+
2195
+ // src/device-auth.ts
2196
+ var DEVICE_AUTH_ENDPOINT = "https://amr-api.open-design.ai/api/v1/cli/device-authorizations";
2197
+ var DEVICE_AUTH_PLUGIN_VERSION = "0.1.4";
2198
+ var DEVICE_REQUEST_TIMEOUT_MS = 2e4;
2199
+ var DEVICE_RESPONSE_MAX_BYTES = 65536;
2200
+ var DEVICE_POLL_MIN_MS = 1e3;
2201
+ var DEVICE_POLL_MAX_MS = 3e5;
2202
+ var DeviceAuthError = class extends Error {
2203
+ constructor(code) {
2204
+ super("\u8BBE\u5907\u6388\u6743\u672A\u5B8C\u6210\uFF0C\u8BF7\u91CD\u65B0\u5F00\u59CB");
2205
+ this.code = code;
2206
+ }
2207
+ code;
2208
+ };
2209
+ function validateActivationUrl(value, userCode) {
2210
+ if (typeof value !== "string" || value.length > 512 || !/^https:\/\/open-design\.ai\/cloud\/cli\/activate(?:\?[^#]*)?$/u.test(value) || /[\s\\]/u.test(value)) return null;
2211
+ try {
2212
+ const url = new URL(value);
2213
+ if (url.origin !== "https://open-design.ai" || url.pathname !== "/cloud/cli/activate" || url.username || url.password || url.hash) return null;
2214
+ const entries = [...url.searchParams];
2215
+ if (entries.length > 1) return null;
2216
+ if (entries.length) {
2217
+ const entry = entries[0];
2218
+ if (!["code", "user_code"].includes(entry[0]) || !validUserCode(entry[1]) || userCode !== void 0 && entry[1] !== userCode) return null;
2219
+ }
2220
+ return url.href;
2221
+ } catch {
2222
+ return null;
2223
+ }
2224
+ }
2225
+ function object(value) {
2226
+ return value !== null && typeof value === "object" && !Array.isArray(value);
2227
+ }
2228
+ function validUserCode(value) {
2229
+ return typeof value === "string" && /^[a-z0-9][a-z0-9-]{3,31}$/iu.test(value);
2230
+ }
2231
+ function secret(value) {
2232
+ return typeof value === "string" && value.length > 0 && value.length <= 8192 && /^[\x21-\x7e]+$/u.test(value);
2233
+ }
2234
+ function parseDeviceAuthorization(value) {
2235
+ if (!object(value) || typeof value.deviceId !== "string" || !/^[a-z0-9_-]{1,200}$/iu.test(value.deviceId) || !secret(value.deviceSecret) || !validUserCode(value.userCode)) throw new DeviceAuthError("opendesign/login-response");
2236
+ const activationUrl = validateActivationUrl(value.activationUrl, value.userCode);
2237
+ if (!activationUrl || value.userCode.toLowerCase().includes(value.deviceSecret.toLowerCase()) || decodeURIComponent(activationUrl).toLowerCase().includes(value.deviceSecret.toLowerCase())) throw new DeviceAuthError("opendesign/login-response");
2238
+ const expiresAt = typeof value.expiresAt === "string" && /^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d{1,3})?(?:Z|[+-]\d\d:\d\d)$/u.test(value.expiresAt) ? Date.parse(value.expiresAt) : NaN;
2239
+ if (!Number.isFinite(expiresAt) || typeof value.pollIntervalSeconds !== "number" || !Number.isFinite(value.pollIntervalSeconds) || value.pollIntervalSeconds <= 0) throw new DeviceAuthError("opendesign/login-response");
2171
2240
  return {
2172
- cookie: usable.map((c) => `${c.name}=${c.value}`).join("; "),
2173
- expiresAt: declared.length ? Math.min(...declared) * 1e3 : 0
2241
+ deviceId: value.deviceId,
2242
+ deviceSecret: value.deviceSecret,
2243
+ userCode: value.userCode,
2244
+ activationUrl,
2245
+ expiresAt,
2246
+ pollMs: Math.max(DEVICE_POLL_MIN_MS, Math.min(DEVICE_POLL_MAX_MS, value.pollIntervalSeconds * 1e3))
2174
2247
  };
2175
2248
  }
2176
- function consoleSite(overrides = {}) {
2177
- const origin = overrides.origin ?? CONSOLE_ORIGIN;
2178
- return {
2179
- origin,
2180
- loginUrl: overrides.loginUrl ?? (origin === CONSOLE_ORIGIN ? CONSOLE_DASHBOARD_URL : `${origin}/cloud/dashboard`),
2181
- cookieUrls: overrides.cookieUrls ?? [API_COOKIE_URL],
2182
- extract: extractConsoleSession
2249
+ function parseDevicePoll(value) {
2250
+ if (!object(value)) throw new DeviceAuthError("opendesign/login-response");
2251
+ const state = typeof value.status === "string" ? value.status.toLowerCase() : "";
2252
+ if (["expired", "cancelled", "canceled", "denied", "rejected", "failed", "error"].includes(state)) throw new DeviceAuthError(state === "expired" ? "opendesign/login-expired" : "opendesign/login-denied");
2253
+ if (value.error !== void 0 && value.error !== null && value.error !== "") throw new DeviceAuthError("opendesign/login-response");
2254
+ const user = object(value.user) ? value.user : {};
2255
+ const topPresent = value.controlKey !== void 0 || value.runtimeKey !== void 0;
2256
+ const source = topPresent ? value : user;
2257
+ if (source.controlKey !== void 0 || source.runtimeKey !== void 0) {
2258
+ if (!secret(source.controlKey) || !secret(source.runtimeKey)) throw new DeviceAuthError("opendesign/login-incomplete");
2259
+ if (topPresent && (user.controlKey !== void 0 && user.controlKey !== source.controlKey || user.runtimeKey !== void 0 && user.runtimeKey !== source.runtimeKey)) throw new DeviceAuthError("opendesign/login-response");
2260
+ return { state: "success", tokens: { controlKey: source.controlKey, runtimeKey: source.runtimeKey } };
2261
+ }
2262
+ if (state === "pending" || state === "authorization_pending") return { state: "pending" };
2263
+ throw new DeviceAuthError("opendesign/login-response");
2264
+ }
2265
+ function validRequestUrl(value) {
2266
+ return value === DEVICE_AUTH_ENDPOINT || /^https:\/\/amr-api\.open-design\.ai\/api\/v1\/cli\/device-authorizations\/[a-zA-Z0-9_-]{1,200}\/token$/u.test(value);
2267
+ }
2268
+ async function postDeviceJson(fetcher, url, body, signal, timeoutMs = DEVICE_REQUEST_TIMEOUT_MS) {
2269
+ if (!validRequestUrl(url)) throw new DeviceAuthError("opendesign/login-url");
2270
+ signal.throwIfAborted();
2271
+ const controller = new AbortController();
2272
+ let timedOut = false;
2273
+ let timer;
2274
+ let rejectAbort = () => void 0;
2275
+ const aborted = new Promise((_resolve, reject) => {
2276
+ rejectAbort = reject;
2277
+ });
2278
+ const cancel = () => {
2279
+ controller.abort();
2280
+ rejectAbort(new DeviceAuthError(timedOut ? "opendesign/login-request-timeout" : "opendesign/login-cancelled"));
2183
2281
  };
2282
+ signal.addEventListener("abort", cancel, { once: true });
2283
+ timer = setTimeout(() => {
2284
+ timedOut = true;
2285
+ cancel();
2286
+ }, Math.max(1, Math.min(DEVICE_REQUEST_TIMEOUT_MS, timeoutMs)));
2287
+ const read = async () => {
2288
+ let response;
2289
+ try {
2290
+ response = await fetcher(url, {
2291
+ method: "POST",
2292
+ redirect: "error",
2293
+ credentials: "omit",
2294
+ referrerPolicy: "no-referrer",
2295
+ headers: { accept: "application/json", "content-type": "application/json" },
2296
+ body: JSON.stringify(body),
2297
+ signal: controller.signal
2298
+ });
2299
+ } catch {
2300
+ throw new DeviceAuthError("opendesign/login-network");
2301
+ }
2302
+ if (controller.signal.aborted) {
2303
+ void response.body?.cancel().catch(() => void 0);
2304
+ throw new DeviceAuthError("opendesign/login-cancelled");
2305
+ }
2306
+ const rejectResponse = (code) => {
2307
+ void response.body?.cancel().catch(() => void 0);
2308
+ throw new DeviceAuthError(code);
2309
+ };
2310
+ if (response.redirected || response.url && response.url !== url || response.status >= 300 && response.status < 400) return rejectResponse("opendesign/login-redirect");
2311
+ if (response.status === 401 || response.status === 403) return rejectResponse("opendesign/login-denied");
2312
+ if (response.status === 404 || response.status === 410) return rejectResponse("opendesign/login-expired");
2313
+ if (!response.ok) return rejectResponse("opendesign/login-network");
2314
+ const declared = response.headers.get("content-length");
2315
+ if (declared !== null && (!/^\d+$/u.test(declared) || Number(declared) > DEVICE_RESPONSE_MAX_BYTES)) return rejectResponse("opendesign/login-response-size");
2316
+ if (!response.body) throw new DeviceAuthError("opendesign/login-response");
2317
+ const reader = response.body.getReader();
2318
+ const stopReading = () => {
2319
+ void reader.cancel().catch(() => void 0);
2320
+ };
2321
+ controller.signal.addEventListener("abort", stopReading, { once: true });
2322
+ const chunks = [];
2323
+ let length = 0;
2324
+ try {
2325
+ for (; ; ) {
2326
+ const part = await reader.read();
2327
+ if (controller.signal.aborted) throw new DeviceAuthError("opendesign/login-cancelled");
2328
+ if (part.done) break;
2329
+ length += part.value.byteLength;
2330
+ if (length > DEVICE_RESPONSE_MAX_BYTES) {
2331
+ stopReading();
2332
+ throw new DeviceAuthError("opendesign/login-response-size");
2333
+ }
2334
+ chunks.push(part.value);
2335
+ }
2336
+ const bytes = new Uint8Array(length);
2337
+ let offset = 0;
2338
+ for (const chunk of chunks) {
2339
+ bytes.set(chunk, offset);
2340
+ offset += chunk.length;
2341
+ }
2342
+ try {
2343
+ return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
2344
+ } catch {
2345
+ throw new DeviceAuthError("opendesign/login-response");
2346
+ }
2347
+ } finally {
2348
+ controller.signal.removeEventListener("abort", stopReading);
2349
+ reader.releaseLock();
2350
+ }
2351
+ };
2352
+ try {
2353
+ return await Promise.race([read(), aborted]);
2354
+ } catch (error) {
2355
+ if (error instanceof DeviceAuthError) throw error;
2356
+ throw new DeviceAuthError("opendesign/login-network");
2357
+ } finally {
2358
+ if (timer !== void 0) clearTimeout(timer);
2359
+ signal.removeEventListener("abort", cancel);
2360
+ }
2184
2361
  }
2362
+
2363
+ // src/login.ts
2185
2364
  var LoginError = class extends Error {
2186
2365
  constructor(code) {
2187
- super(code === "opendesign/login-state" ? "\u767B\u5F55\u4EFB\u52A1\u4E0D\u5B58\u5728\u6216\u72B6\u6001\u5DF2\u6539\u53D8" : code === "opendesign/login-disposed" ? "\u767B\u5F55\u670D\u52A1\u5DF2\u5378\u8F7D" : "\u65E0\u6CD5\u6355\u83B7\u5F53\u524D\u8BBE\u7F6E\u72B6\u6001\uFF0C\u767B\u5F55\u5DF2\u53D6\u6D88");
2366
+ super(code === "opendesign/login-disposed" ? "\u767B\u5F55\u670D\u52A1\u5DF2\u5378\u8F7D" : "\u767B\u5F55\u4EFB\u52A1\u4E0D\u5B58\u5728\u6216\u72B6\u6001\u5DF2\u6539\u53D8");
2188
2367
  this.code = code;
2189
2368
  }
2190
2369
  code;
2191
2370
  };
2192
2371
  var ACTIVE = /* @__PURE__ */ new Set(["launching", "waiting", "verifying"]);
2193
- var DEFAULT_TIMEOUT_MS = 3e5;
2194
- var DEFAULT_POLL_MS = 1e3;
2195
- var FATAL_COMMIT_CODES = /* @__PURE__ */ new Set(["opendesign/revision-conflict", "opendesign/invalid-request", "opendesign/config", "opendesign/login-disposed"]);
2196
- var CHROME_LAUNCH_OPTIONS = {
2197
- channel: "chrome",
2198
- headless: false,
2199
- ignoreDefaultArgs: ["--enable-automation"],
2200
- args: ["--disable-blink-features=AutomationControlled"]
2201
- };
2372
+ var MAX_TIMEOUT_MS = 3e5;
2373
+ var SAFE_HOST_CODES = /* @__PURE__ */ new Set(["opendesign/revision-conflict", "opendesign/invalid-request", "opendesign/config", "opendesign/login-disposed"]);
2202
2374
  var ConsoleLogin = class {
2203
- job = null;
2204
- disposed = false;
2205
- callbacks;
2206
- options;
2207
- now;
2208
- delay;
2209
2375
  constructor(callbacks, options = {}) {
2210
2376
  this.callbacks = callbacks;
2211
2377
  this.options = options;
2212
2378
  this.now = options.now ?? Date.now;
2213
2379
  this.delay = options.delay ?? sleep;
2380
+ this.fetcher = options.fetcher ?? fetch;
2214
2381
  }
2215
- /** Idempotent, credential-free view of the current attempt. */
2382
+ callbacks;
2383
+ options;
2384
+ job = null;
2385
+ disposed = false;
2386
+ now;
2387
+ delay;
2388
+ fetcher;
2389
+ /** Explicit public allowlist. Codes are never available after waiting or expiry. */
2216
2390
  status() {
2217
2391
  if (!this.job) return { jobId: null, state: "idle" };
2218
- const s = this.job.status;
2392
+ const job = this.job;
2393
+ if (job.status.state === "waiting" && this.now() >= job.deadline) this.expire(job);
2394
+ const s = job.status;
2219
2395
  return {
2220
2396
  jobId: s.jobId,
2221
2397
  state: s.state,
@@ -2223,234 +2399,177 @@ var ConsoleLogin = class {
2223
2399
  ...s.finishedAt ? { finishedAt: s.finishedAt } : {},
2224
2400
  ...s.message ? { message: s.message } : {},
2225
2401
  ...s.errorCode ? { errorCode: s.errorCode } : {},
2226
- ...s.browserOpenFailed ? { browserOpenFailed: true } : {}
2402
+ ...s.state === "waiting" && this.now() < job.deadline ? {
2403
+ ...s.activationUrl ? { activationUrl: s.activationUrl } : {},
2404
+ ...s.userCode ? { userCode: s.userCode } : {},
2405
+ ...s.expiresAt ? { expiresAt: s.expiresAt } : {}
2406
+ } : {}
2227
2407
  };
2228
2408
  }
2229
- /**
2230
- * Queue one attempt and return immediately: the RPC that starts a login must not wait on a
2231
- * browser window or on a human. Re-entrant calls while an attempt is live return that
2232
- * attempt's status instead of opening a second browser.
2233
- */
2409
+ /** Synchronous/non-blocking and idempotent while capture/network/commit is pending. */
2234
2410
  start(request) {
2235
2411
  if (this.disposed) throw new LoginError("opendesign/login-disposed");
2412
+ this.status();
2236
2413
  if (this.job && ACTIVE.has(this.job.status.state)) return this.status();
2414
+ const started = this.now();
2415
+ const requested = this.options.timeoutMs;
2416
+ const timeoutMs = typeof requested === "number" && Number.isFinite(requested) && requested > 0 ? Math.min(MAX_TIMEOUT_MS, requested) : MAX_TIMEOUT_MS;
2237
2417
  const job = {
2238
2418
  id: randomUUID(),
2239
- startedAt: new Date(this.now()).toISOString(),
2240
2419
  controller: new AbortController(),
2241
- expectedRevision: request.expectedRevision,
2242
- status: { jobId: "", state: "launching", message: "\u6B63\u5728\u6253\u5F00\u4E34\u65F6 Chrome \u767B\u5F55\u7A97\u53E3" },
2420
+ deadline: started + timeoutMs,
2421
+ status: { jobId: null, state: "launching", startedAt: new Date(started).toISOString(), message: "\u6B63\u5728\u7533\u8BF7\u5B98\u65B9\u8BBE\u5907\u6388\u6743\u7801" },
2243
2422
  captured: void 0,
2244
- capturedAt: void 0,
2245
- verifying: false,
2246
2423
  committing: false,
2247
2424
  cancelRequested: false
2248
2425
  };
2249
2426
  job.status.jobId = job.id;
2250
- job.status.startedAt = job.startedAt;
2251
2427
  this.job = job;
2252
- job.timer = setTimeout(() => this.finish(job, "cancelled", "\u767B\u5F55\u5DF2\u8D85\u8FC7\u4E94\u5206\u949F\uFF0C\u8BF7\u91CD\u65B0\u5F00\u59CB"), this.options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
2253
- job.timer.unref?.();
2254
- job.capturedAt = Promise.resolve().then(() => this.callbacks.capture(job.expectedRevision)).then((captured) => {
2255
- job.captured = captured;
2256
- return captured;
2257
- });
2258
- void job.capturedAt.then(() => this.run(job)).catch((error) => {
2259
- if (this.alive(job)) this.finish(job, "error", "\u767B\u5F55\u65E0\u6CD5\u5F00\u59CB\uFF0C\u8BF7\u91CD\u8BD5", errorCode(error) ?? "opendesign/login-capture");
2428
+ this.armExpiry(job);
2429
+ void Promise.resolve().then(async () => {
2430
+ if (!this.alive(job)) return;
2431
+ try {
2432
+ job.captured = await this.callbacks.capture(request.expectedRevision);
2433
+ } catch (error) {
2434
+ if (this.alive(job)) this.finish(job, "error", "\u65E0\u6CD5\u6355\u83B7\u5F53\u524D\u8BBE\u7F6E\u72B6\u6001\uFF0C\u8BF7\u91CD\u65B0\u8BFB\u53D6\u540E\u91CD\u8BD5", hostCode(error) ?? "opendesign/login-capture");
2435
+ return;
2436
+ }
2437
+ if (this.alive(job)) await this.run(job);
2438
+ }).catch((error) => {
2439
+ if (this.alive(job)) this.finish(job, "error", failureMessage(error), error instanceof DeviceAuthError ? error.code : "opendesign/login-network");
2260
2440
  });
2261
2441
  return this.status();
2262
2442
  }
2263
- /**
2264
- * Abandon the attempt. While `commit` is in flight this only aborts the signal and records
2265
- * the request: the Host's atomic store decides, because the commit is the linearization
2266
- * point. A cancel that wins that race reports `cancelled`; one that loses reports `success`
2267
- * and the cookie really is stored.
2268
- */
2269
2443
  cancel(request) {
2270
- const job = this.job;
2271
- if (!job || job.status.jobId !== request.jobId) return this.status();
2272
- if (job.committing) {
2273
- job.cancelRequested = true;
2274
- job.controller.abort();
2275
- return this.status();
2276
- }
2277
- if (ACTIVE.has(job.status.state)) this.finish(job, "cancelled", "\u767B\u5F55\u5DF2\u53D6\u6D88\uFF0C\u672A\u4FDD\u5B58 Cookie");
2444
+ if (this.job?.id === request.jobId) this.requestCancellation(this.job, "\u767B\u5F55\u5DF2\u53D6\u6D88\uFF0C\u672A\u4FDD\u5B58\u6388\u6743");
2278
2445
  return this.status();
2279
2446
  }
2280
- /** Plugin teardown: abort the attempt and close the browser. */
2281
2447
  dispose() {
2282
- const job = this.job;
2283
- if (job && job.committing) {
2284
- job.cancelRequested = true;
2285
- job.controller.abort();
2286
- } else if (job && ACTIVE.has(job.status.state)) {
2287
- this.finish(job, "cancelled", "\u670D\u52A1\u5DF2\u5378\u8F7D\uFF0C\u767B\u5F55\u5DF2\u53D6\u6D88");
2288
- }
2289
2448
  this.disposed = true;
2449
+ if (this.job) this.requestCancellation(this.job, "\u670D\u52A1\u5DF2\u5378\u8F7D\uFF0C\u767B\u5F55\u5DF2\u53D6\u6D88");
2290
2450
  }
2291
2451
  alive(job) {
2292
- if (this.disposed || this.job !== job) return false;
2293
- if (job.controller.signal.aborted && !job.committing) return false;
2294
- return job.status.state !== "success" && job.status.state !== "cancelled" && job.status.state !== "error";
2452
+ return this.job === job && !this.disposed && !job.controller.signal.aborted && ACTIVE.has(job.status.state);
2295
2453
  }
2296
2454
  stopTimer(job) {
2297
- if (job.timer) {
2455
+ if (job.timer !== void 0) {
2298
2456
  clearTimeout(job.timer);
2299
2457
  job.timer = void 0;
2300
2458
  }
2301
2459
  }
2302
- async close(job) {
2303
- const browser = job.browser;
2304
- job.browser = void 0;
2305
- try {
2306
- await browser?.close();
2307
- } catch {
2460
+ armExpiry(job) {
2461
+ this.stopTimer(job);
2462
+ job.timer = setTimeout(() => this.expire(job), Math.max(1, job.deadline - this.now()));
2463
+ job.timer.unref?.();
2464
+ }
2465
+ clearCode(job) {
2466
+ delete job.status.activationUrl;
2467
+ delete job.status.userCode;
2468
+ delete job.status.expiresAt;
2469
+ }
2470
+ expire(job) {
2471
+ if (job.committing) this.requestCancellation(job, "\u6388\u6743\u5DF2\u8FC7\u671F\uFF0C\u672A\u4FDD\u5B58\u6388\u6743");
2472
+ else this.finish(job, "error", "\u8BBE\u5907\u6388\u6743\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u5F00\u59CB", "opendesign/login-expired");
2473
+ }
2474
+ requestCancellation(job, message) {
2475
+ if (this.job !== job || !ACTIVE.has(job.status.state)) return;
2476
+ this.clearCode(job);
2477
+ if (!job.committing) {
2478
+ this.finish(job, "cancelled", message);
2479
+ return;
2308
2480
  }
2481
+ if (!job.cancelRequested) {
2482
+ job.cancellationMessage = message;
2483
+ job.status.message = "\u6B63\u5728\u786E\u8BA4\u6388\u6743\u4FDD\u5B58\u7ED3\u679C";
2484
+ }
2485
+ job.cancelRequested = true;
2486
+ this.stopTimer(job);
2487
+ job.controller.abort();
2309
2488
  }
2310
- finish(job, state, message, errorCode2) {
2311
- if (this.job !== job) return;
2312
- if (job.status.state === "success" || job.status.state === "cancelled" || job.status.state === "error") return;
2489
+ finish(job, state, message, errorCode) {
2490
+ if (this.job !== job || !ACTIVE.has(job.status.state)) return;
2313
2491
  job.status.state = state;
2314
2492
  job.status.finishedAt = new Date(this.now()).toISOString();
2315
2493
  job.status.message = message;
2316
- if (errorCode2) job.status.errorCode = errorCode2;
2317
- job.secret = void 0;
2494
+ if (errorCode) job.status.errorCode = errorCode;
2495
+ this.clearCode(job);
2318
2496
  this.stopTimer(job);
2319
2497
  job.controller.abort();
2320
- void this.close(job);
2321
- }
2322
- async resolveBrowser(job) {
2323
- if (!this.alive(job)) return null;
2324
- const launch = this.options.launch ?? defaultLaunch;
2325
- const browser = await launch(job.controller.signal);
2326
- if (!browser) return null;
2327
- job.browser = browser;
2328
- browser.on?.("disconnected", () => this.finish(job, "cancelled", "\u767B\u5F55\u7A97\u53E3\u5DF2\u5173\u95ED\uFF0C\u672A\u4FDD\u5B58 Cookie"));
2329
- return browser;
2330
2498
  }
2331
2499
  async run(job) {
2332
- const site = this.options.site ?? consoleSite();
2333
- const browser = await this.resolveBrowser(job);
2334
- if (!browser) return;
2335
- if (!this.alive(job)) {
2336
- await this.close(job);
2337
- return;
2338
- }
2339
- const context = await browser.newContext();
2340
- if (!this.alive(job)) {
2341
- await this.close(job);
2342
- return;
2343
- }
2344
- const page = await context.newPage();
2345
- page.on?.("close", () => this.finish(job, "cancelled", "\u767B\u5F55\u7A97\u53E3\u5DF2\u5173\u95ED\uFF0C\u672A\u4FDD\u5B58 Cookie"));
2346
- if (!this.alive(job)) {
2347
- await this.close(job);
2500
+ const signal = job.controller.signal;
2501
+ const raw = await postDeviceJson(this.fetcher, DEVICE_AUTH_ENDPOINT, {
2502
+ // The third-party protocol's official-CLI version is NOT our version. Compatibility
2503
+ // of this actual plugin version requires a real-account check outside offline tests.
2504
+ cliVersion: DEVICE_AUTH_PLUGIN_VERSION,
2505
+ host: "DSH CoRouter",
2506
+ profile: "prod"
2507
+ }, signal, job.deadline - this.now());
2508
+ if (!this.alive(job)) return;
2509
+ const device = parseDeviceAuthorization(raw);
2510
+ job.deadline = Math.min(job.deadline, device.expiresAt);
2511
+ if (this.now() >= job.deadline) {
2512
+ this.expire(job);
2348
2513
  return;
2349
2514
  }
2350
- try {
2351
- await page.goto(site.loginUrl, { waitUntil: "domcontentloaded", timeout: 3e4 });
2352
- } catch {
2353
- if (!this.alive(job)) return;
2354
- try {
2355
- await page.goto(site.origin, { waitUntil: "domcontentloaded", timeout: 3e4 });
2356
- } catch {
2357
- job.status.browserOpenFailed = true;
2358
- }
2359
- }
2360
- if (!this.alive(job)) return;
2515
+ this.armExpiry(job);
2361
2516
  job.status.state = "waiting";
2362
- job.status.message = "\u8BF7\u5728\u5B98\u65B9\u7A97\u53E3\u81EA\u884C\u5B8C\u6210\u767B\u5F55\uFF1B\u8D26\u6237\u9A8C\u8BC1\u901A\u8FC7\u540E\u4FDD\u5B58\uFF0C\u989D\u5EA6\u72B6\u6001\u53E6\u884C\u663E\u793A";
2363
- let nextAttempt = 0;
2364
- let failures = 0;
2365
- const pollMs = this.options.pollMs ?? DEFAULT_POLL_MS;
2517
+ job.status.message = "\u8BF7\u5728\u5E38\u7528\u6D4F\u89C8\u5668\u6253\u5F00\u5B98\u65B9\u6388\u6743\u9875\uFF0C\u4F7F\u7528\u5DF2\u4FDD\u5B58\u7684\u5BC6\u7801\u767B\u5F55\u5E76\u786E\u8BA4\u8BBE\u5907\u7801";
2518
+ job.status.activationUrl = device.activationUrl;
2519
+ job.status.userCode = device.userCode;
2520
+ job.status.expiresAt = new Date(job.deadline).toISOString();
2366
2521
  while (this.alive(job)) {
2367
- if (!job.verifying) {
2368
- const session = site.extract(await context.cookies(site.cookieUrls), this.now());
2369
- if (!this.alive(job)) return;
2370
- if (session && this.now() >= nextAttempt) {
2371
- job.verifying = true;
2372
- job.status.state = "verifying";
2373
- job.status.message = "\u6B63\u5728\u9A8C\u8BC1\u767B\u5F55\u548C\u8BA2\u9605\u989D\u5EA6\uFF08\u5C1A\u672A\u4FDD\u5B58\uFF09";
2374
- try {
2375
- const verified = await this.callbacks.verify(session.cookie, job.controller.signal);
2376
- if (!this.alive(job)) return;
2377
- if (verified.usageError || !verified.balanceOk) {
2378
- job.status.state = "waiting";
2379
- job.status.message = "\u5DF2\u8BFB\u53D6\u8D26\u6237\uFF0C\u4F46\u8BA2\u9605\u989D\u5EA6\u5C1A\u672A\u5B8C\u6574\u9A8C\u8BC1\uFF1B\u5C1A\u672A\u4FDD\u5B58\uFF0C\u8BF7\u786E\u8BA4\u5957\u9910\u6216\u7EE7\u7EED\u767B\u5F55";
2380
- nextAttempt = this.now() + 15e3;
2381
- } else {
2382
- const outcome = await this.commit(job, session);
2383
- if (outcome !== "retry") return;
2384
- failures += 1;
2385
- nextAttempt = this.now() + Math.max(5e3, Math.min(6e4, 2e3 * 2 ** Math.min(failures, 5)));
2386
- }
2387
- } catch (error) {
2388
- if (!this.alive(job)) return;
2389
- failures += 1;
2390
- nextAttempt = this.now() + Math.max(5e3, Math.min(6e4, 2e3 * 2 ** Math.min(failures, 5)), retryAfterMs(error));
2391
- job.status.state = "waiting";
2392
- job.status.message = errorCode(error) === "opendesign/auth" ? "\u767B\u5F55\u5C1A\u672A\u751F\u6548\uFF0C\u8BF7\u7EE7\u7EED\u5728\u5B98\u65B9\u7A97\u53E3\u5B8C\u6210\u767B\u5F55\uFF1B\u672A\u4FDD\u5B58" : "\u989D\u5EA6\u9A8C\u8BC1\u6682\u672A\u6210\u529F\uFF0C\u5C06\u9650\u9891\u91CD\u8BD5\uFF1B\u672A\u4FDD\u5B58";
2393
- } finally {
2394
- job.verifying = false;
2395
- }
2396
- }
2522
+ await this.delay(Math.min(device.pollMs, Math.max(1, job.deadline - this.now())), signal);
2523
+ if (!this.alive(job)) return;
2524
+ if (this.now() >= job.deadline) {
2525
+ this.expire(job);
2526
+ return;
2527
+ }
2528
+ const result = parseDevicePoll(await postDeviceJson(
2529
+ this.fetcher,
2530
+ `${DEVICE_AUTH_ENDPOINT}/${device.deviceId}/token`,
2531
+ { deviceSecret: device.deviceSecret },
2532
+ signal,
2533
+ job.deadline - this.now()
2534
+ ));
2535
+ if (!this.alive(job)) return;
2536
+ if (this.now() >= job.deadline) {
2537
+ this.expire(job);
2538
+ return;
2539
+ }
2540
+ if (result.state === "success") {
2541
+ await this.commit(job, result.tokens);
2542
+ return;
2397
2543
  }
2398
- await this.delay(pollMs, job.controller.signal);
2399
2544
  }
2400
2545
  }
2401
- /**
2402
- * Persist the verified session; `commit` resolving is the linearization point. Returns
2403
- * `retry` when the store rejected the write transiently, so the caller decides when to try
2404
- * again instead of claiming a success that did not happen.
2405
- */
2406
- async commit(job, session) {
2407
- if (job.committing || !this.alive(job)) return "cancelled";
2546
+ async commit(job, tokens) {
2547
+ if (!this.alive(job) || job.committing) return;
2408
2548
  job.committing = true;
2549
+ this.clearCode(job);
2409
2550
  job.status.state = "verifying";
2410
- job.status.message = "\u767B\u5F55\u5DF2\u9A8C\u8BC1\uFF0C\u6B63\u5728\u4FDD\u5B58\u4F1A\u8BDD";
2551
+ job.status.message = "\u5B98\u65B9\u6388\u6743\u5DF2\u5B8C\u6210\uFF0C\u6B63\u5728\u4FDD\u5B58\u6388\u6743";
2411
2552
  try {
2412
- await this.callbacks.commit(session.cookie, job.captured, job.controller.signal, session.expiresAt);
2553
+ await this.callbacks.commit(tokens, job.captured, job.controller.signal);
2413
2554
  job.committing = false;
2414
- this.finish(job, "success", "\u767B\u5F55\u53CA\u8BA2\u9605\u989D\u5EA6\u5DF2\u9A8C\u8BC1\uFF0CCookie \u5DF2\u4FDD\u5B58");
2415
- return "success";
2555
+ this.finish(job, "success", "\u8BBE\u5907\u6388\u6743\u5DF2\u5B8C\u6210\u5E76\u4FDD\u5B58\uFF1B\u989D\u5EA6\u72B6\u6001\u53E6\u884C\u663E\u793A");
2416
2556
  } catch (error) {
2417
2557
  job.committing = false;
2418
- if (job.cancelRequested || job.controller.signal.aborted) {
2419
- this.finish(job, "cancelled", "\u767B\u5F55\u5DF2\u53D6\u6D88\uFF0C\u672A\u4FDD\u5B58 Cookie");
2420
- return "cancelled";
2421
- }
2422
- if (FATAL_COMMIT_CODES.has(errorCode(error) ?? "")) {
2423
- this.finish(job, "error", "\u8BBE\u7F6E\u5DF2\u6539\u53D8\u6216\u65E0\u6CD5\u4FDD\u5B58\uFF0C\u8BF7\u91CD\u65B0\u8BFB\u53D6\u540E\u91CD\u8BD5", errorCode(error));
2424
- return "error";
2425
- }
2426
- job.status.state = "waiting";
2427
- job.status.message = "\u767B\u5F55\u5DF2\u9A8C\u8BC1\uFF0C\u4F46\u4FDD\u5B58\u6682\u672A\u6210\u529F\uFF0C\u5C06\u9650\u9891\u91CD\u8BD5\uFF1B\u5C1A\u672A\u4FDD\u5B58";
2428
- return "retry";
2558
+ if (job.cancelRequested || job.controller.signal.aborted) this.finish(job, "cancelled", job.cancellationMessage ?? "\u767B\u5F55\u5DF2\u53D6\u6D88\uFF0C\u672A\u4FDD\u5B58\u6388\u6743");
2559
+ else this.finish(job, "error", hostCode(error) ? "\u8BBE\u7F6E\u5DF2\u6539\u53D8\u6216\u65E0\u6CD5\u4FDD\u5B58\uFF0C\u8BF7\u91CD\u65B0\u8BFB\u53D6\u540E\u91CD\u8BD5" : "\u6388\u6743\u4FDD\u5B58\u5931\u8D25\uFF0C\u8BF7\u91CD\u65B0\u5F00\u59CB", hostCode(error) ?? "opendesign/login-commit");
2429
2560
  }
2430
2561
  }
2431
2562
  };
2432
- function errorCode(error) {
2433
- if (error && typeof error === "object" && "code" in error && typeof error.code === "string") return error.code;
2563
+ function hostCode(error) {
2564
+ if (error !== null && typeof error === "object" && "code" in error && typeof error.code === "string" && SAFE_HOST_CODES.has(error.code)) return error.code;
2434
2565
  return void 0;
2435
2566
  }
2436
- function retryAfterMs(error) {
2437
- if (!error || typeof error !== "object") return 0;
2438
- const details = error.details;
2439
- if (!details || typeof details !== "object") return 0;
2440
- const value = details.retryAfterMs;
2441
- return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.min(3e5, value) : 0;
2442
- }
2443
- async function defaultLaunch(signal) {
2444
- if (signal.aborted) return null;
2445
- const { chromium } = await import("playwright-core");
2446
- if (signal.aborted) return null;
2447
- return await chromium.launch({
2448
- channel: CHROME_LAUNCH_OPTIONS.channel,
2449
- headless: CHROME_LAUNCH_OPTIONS.headless,
2450
- // Copied so Playwright, which wants mutable arrays, cannot freeze on the readonly literals.
2451
- ignoreDefaultArgs: [...CHROME_LAUNCH_OPTIONS.ignoreDefaultArgs],
2452
- args: [...CHROME_LAUNCH_OPTIONS.args]
2453
- });
2567
+ function failureMessage(error) {
2568
+ const code = error instanceof DeviceAuthError ? error.code : "";
2569
+ if (code === "opendesign/login-expired") return "\u8BBE\u5907\u6388\u6743\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u5F00\u59CB";
2570
+ if (code === "opendesign/login-denied") return "\u8BBE\u5907\u6388\u6743\u88AB\u62D2\u7EDD\uFF0C\u8BF7\u91CD\u65B0\u5F00\u59CB";
2571
+ if (code === "opendesign/login-request-timeout") return "\u8BBE\u5907\u6388\u6743\u8BF7\u6C42\u8D85\u65F6\uFF0C\u8BF7\u91CD\u65B0\u5F00\u59CB";
2572
+ return "\u8BBE\u5907\u6388\u6743\u8BF7\u6C42\u5931\u8D25\uFF0C\u8BF7\u91CD\u65B0\u5F00\u59CB";
2454
2573
  }
2455
2574
 
2456
2575
  // src/quota.ts
@@ -2459,6 +2578,7 @@ var API_BASE = "https://amr-api.open-design.ai/api/v1";
2459
2578
  var CURRENT_URL = `${API_BASE}/workspaces/current`;
2460
2579
  var SUMMARY_URL = `${API_BASE}/billing/summary`;
2461
2580
  var usageUrl = (workspace) => `${API_BASE}/workspaces/${encodeURIComponent(workspace)}/billing/coding-plan-usage`;
2581
+ var preflightUrl = (workspace) => `${API_BASE}/workspaces/${encodeURIComponent(workspace)}/billing/preflight`;
2462
2582
  var ConsoleError = class extends Error {
2463
2583
  constructor(kind, status) {
2464
2584
  super(kind === "auth" ? "OpenDesign \u767B\u5F55\u5DF2\u5931\u6548\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55" : kind === "permission" ? "OpenDesign \u5F53\u524D\u767B\u5F55\u6001\u65E0\u6743\u8BFB\u53D6\u8BE5\u989D\u5EA6\u5C42" : kind === "rate-limit" ? "OpenDesign \u989D\u5EA6\u63A5\u53E3\u9650\u6D41\uFF0C\u8BF7\u7A0D\u540E\u5237\u65B0" : kind === "response" ? "OpenDesign \u989D\u5EA6\u63A5\u53E3\u8FD4\u56DE\u683C\u5F0F\u53D1\u751F\u53D8\u5316" : "OpenDesign \u989D\u5EA6\u6682\u65F6\u65E0\u6CD5\u8BFB\u53D6");
@@ -2469,46 +2589,90 @@ var ConsoleError = class extends Error {
2469
2589
  status;
2470
2590
  };
2471
2591
  async function consoleJson(url, session, signal, fetcher = fetch) {
2472
- let response;
2473
- try {
2474
- response = await fetcher(url, { headers: { ...attributionHeaders3(), accept: "application/json", ...session.headers }, signal: combinedSignal(signal, 2e4), redirect: "error" });
2475
- } catch {
2476
- throw new ConsoleError("network");
2477
- }
2478
- if (response.status === 401 || response.status === 403) {
2479
- await response.body?.cancel();
2480
- throw new ConsoleError(response.status === 401 ? "auth" : "permission", response.status);
2481
- }
2482
- if (!response.ok) {
2483
- await response.body?.cancel();
2484
- throw new ConsoleError(response.status === 429 ? "rate-limit" : "network", response.status);
2485
- }
2486
- const reader = response.body?.getReader();
2487
- if (!reader) throw new ConsoleError("response");
2488
- let size = 0, raw = "";
2489
- const decoder = new TextDecoder();
2490
- try {
2491
- while (true) {
2492
- const chunk = await reader.read();
2493
- if (chunk.done) break;
2494
- size += chunk.value.byteLength;
2495
- if (size > 2 * 1024 * 1024) throw new ConsoleError("response");
2496
- raw += decoder.decode(chunk.value, { stream: true });
2592
+ signal?.throwIfAborted();
2593
+ if (session.credentialType === "device") {
2594
+ let target;
2595
+ try {
2596
+ target = new URL(url);
2597
+ } catch {
2598
+ throw new ConsoleError("response");
2497
2599
  }
2498
- raw += decoder.decode();
2499
- } finally {
2500
- await reader.cancel().catch(() => void 0);
2501
- reader.releaseLock();
2600
+ if (target.origin !== "https://amr-api.open-design.ai" || target.username || target.password) throw new ConsoleError("response");
2502
2601
  }
2503
- let body;
2602
+ const requestSignal = combinedSignal(signal, 2e4);
2603
+ let rejectAbort = () => void 0;
2604
+ const aborted = new Promise((_resolve, reject) => {
2605
+ rejectAbort = reject;
2606
+ });
2607
+ const onAbort = () => rejectAbort(new ConsoleError("network"));
2608
+ requestSignal.addEventListener("abort", onAbort, { once: true });
2609
+ const read = async () => {
2610
+ const response = await fetcher(url, { headers: { ...attributionHeaders3(), accept: "application/json", ...session.headers }, signal: requestSignal, redirect: "error", credentials: "omit" });
2611
+ const cancelBody = () => {
2612
+ void response.body?.cancel().catch(() => void 0);
2613
+ };
2614
+ if (requestSignal.aborted) {
2615
+ cancelBody();
2616
+ throw new ConsoleError("network");
2617
+ }
2618
+ if (response.redirected || response.url && response.url !== url) {
2619
+ cancelBody();
2620
+ throw new ConsoleError("response");
2621
+ }
2622
+ if (response.status === 401 || response.status === 403) {
2623
+ cancelBody();
2624
+ throw new ConsoleError(response.status === 401 ? "auth" : "permission", response.status);
2625
+ }
2626
+ if (!response.ok) {
2627
+ cancelBody();
2628
+ throw new ConsoleError(response.status === 429 ? "rate-limit" : "network", response.status);
2629
+ }
2630
+ const declared = response.headers.get("content-length");
2631
+ if (declared !== null && (!/^\d+$/u.test(declared) || Number(declared) > 2 * 1024 * 1024)) {
2632
+ cancelBody();
2633
+ throw new ConsoleError("response");
2634
+ }
2635
+ const reader = response.body?.getReader();
2636
+ if (!reader) throw new ConsoleError("response");
2637
+ const cancelReader = () => {
2638
+ void reader.cancel().catch(() => void 0);
2639
+ };
2640
+ requestSignal.addEventListener("abort", cancelReader, { once: true });
2641
+ let size = 0, raw = "";
2642
+ const decoder = new TextDecoder("utf-8", { fatal: true });
2643
+ try {
2644
+ while (true) {
2645
+ const chunk = await reader.read();
2646
+ if (requestSignal.aborted) throw new ConsoleError("network");
2647
+ if (chunk.done) break;
2648
+ size += chunk.value.byteLength;
2649
+ if (size > 2 * 1024 * 1024) throw new ConsoleError("response");
2650
+ raw += decoder.decode(chunk.value, { stream: true });
2651
+ }
2652
+ raw += decoder.decode();
2653
+ } finally {
2654
+ requestSignal.removeEventListener("abort", cancelReader);
2655
+ cancelReader();
2656
+ reader.releaseLock();
2657
+ }
2658
+ let body;
2659
+ try {
2660
+ body = JSON.parse(raw);
2661
+ } catch {
2662
+ throw new ConsoleError("response");
2663
+ }
2664
+ if (record(body) && (body.code === 401 || body.code === "401")) throw new ConsoleError("auth", 401);
2665
+ if (record(body) && (body.code === 403 || body.code === "403")) throw new ConsoleError("permission", 403);
2666
+ return body;
2667
+ };
2504
2668
  try {
2505
- body = JSON.parse(raw);
2506
- } catch {
2507
- throw new ConsoleError("response");
2669
+ return await Promise.race([read(), aborted]);
2670
+ } catch (error) {
2671
+ signal?.throwIfAborted();
2672
+ throw error instanceof ConsoleError ? error : new ConsoleError("network");
2673
+ } finally {
2674
+ requestSignal.removeEventListener("abort", onAbort);
2508
2675
  }
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;
2512
2676
  }
2513
2677
  function number(value, negative = false) {
2514
2678
  const n = typeof value === "number" ? value : typeof value === "string" && /^-?\d+(\.\d+)?$/u.test(value.trim()) ? Number(value) : NaN;
@@ -2546,7 +2710,51 @@ function normalizeBalances(summary) {
2546
2710
  const totalUsd = number(summary.balanceUsd, true) ?? number(summary.totalAvailableCreditsUsd, true) ?? (divisor !== void 0 && total !== void 0 ? total / divisor : void 0);
2547
2711
  return { ...divisor !== void 0 && subscription !== void 0 ? { subscriptionUsd: subscription / divisor } : {}, ...divisor !== void 0 && recharge !== void 0 ? { rechargeUsd: recharge / divisor } : {}, ...totalUsd !== void 0 ? { totalUsd } : {} };
2548
2712
  }
2713
+ async function readDeviceQuota(session, signal, fetcher = fetch) {
2714
+ const common = { source: session.source, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
2715
+ let current;
2716
+ try {
2717
+ current = await consoleJson(CURRENT_URL, session, signal, fetcher);
2718
+ } catch (error) {
2719
+ signal?.throwIfAborted();
2720
+ if (error instanceof ConsoleError && error.kind === "permission") return { ...common, state: "partial", windows: [], balances: {}, note: "\u5F53\u524D\u8BBE\u5907\u6388\u6743\u65E0\u6743\u8BFB\u53D6\u5DE5\u4F5C\u533A\uFF1B\u989D\u5EA6\u6682\u4E0D\u53EF\u786E\u8BA4" };
2721
+ throw error;
2722
+ }
2723
+ if (!record(current)) throw new ConsoleError("response");
2724
+ const workspaceId = typeof current.activeWorkspaceId === "string" && current.activeWorkspaceId.trim() ? current.activeWorkspaceId : current.workspaceId;
2725
+ if (typeof workspaceId !== "string" || !/^[A-Za-z0-9_-]{1,64}$/u.test(workspaceId)) throw new ConsoleError("response");
2726
+ const personal = current.workspaceType === "personal";
2727
+ const [preflight, billing] = await Promise.allSettled([
2728
+ consoleJson(preflightUrl(workspaceId), session, signal, fetcher),
2729
+ personal ? consoleJson(SUMMARY_URL, session, signal, fetcher) : Promise.resolve(void 0)
2730
+ ]);
2731
+ signal?.throwIfAborted();
2732
+ for (const item of [preflight, billing]) if (item.status === "rejected" && item.reason instanceof ConsoleError && item.reason.kind === "auth") throw item.reason;
2733
+ const summary = billing.status === "fulfilled" ? billing.value : void 0;
2734
+ const flight = preflight.status === "fulfilled" && record(preflight.value) ? preflight.value : void 0;
2735
+ let normalized;
2736
+ try {
2737
+ normalized = normalizeQuota(flight?.codingPlan, personal ? summary : void 0);
2738
+ } catch (error) {
2739
+ if (!(error instanceof ConsoleError)) throw error;
2740
+ }
2741
+ const balances = personal ? normalizeBalances(summary) : {};
2742
+ const total = personal ? number(flight?.balanceUsd, true) : void 0;
2743
+ if (total !== void 0) balances.totalUsd = total;
2744
+ const permission = [preflight, billing].some((item) => item.status === "rejected" && item.reason instanceof ConsoleError && item.reason.kind === "permission");
2745
+ if (!normalized && !Object.keys(balances).length && !permission) {
2746
+ if (preflight.status === "rejected") throw preflight.reason;
2747
+ throw new ConsoleError("response");
2748
+ }
2749
+ const windows = normalized?.windows ?? [];
2750
+ const missingWindows = normalized?.eligible !== false && !windows.length;
2751
+ const missingBalances = personal && Object.keys(balances).length < 3;
2752
+ const partial = missingWindows || missingBalances || !personal || permission;
2753
+ 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" : missingBalances ? "\u90E8\u5206\u94B1\u5305\u4F59\u989D\u6682\u4E0D\u53EF\u786E\u8BA4" : permission ? "\u90E8\u5206\u989D\u5EA6\u5C42\u65E0\u6743\u8BFB\u53D6" : normalized?.eligible === false ? "\u8BE5\u8D26\u53F7\u6CA1\u6709 Design Plan \u8BA2\u9605\uFF1B\u94B1\u5305\u4F59\u989D\u7167\u5E38\u663E\u793A" : void 0;
2754
+ return { ...common, ...normalized, ...normalizeSubscription(personal ? summary : void 0), windows, balances, state: partial ? "partial" : "ok", ...note ? { note } : {} };
2755
+ }
2549
2756
  async function readQuota(session, signal, fetcher = fetch) {
2757
+ if (session.credentialType === "device") return readDeviceQuota(session, signal, fetcher);
2550
2758
  const current = await consoleJson(CURRENT_URL, session, signal, fetcher);
2551
2759
  if (!record(current) || typeof current.workspaceId !== "string" || !/^[A-Za-z0-9_-]{1,64}$/u.test(current.workspaceId)) throw new ConsoleError("response");
2552
2760
  const personal = current.workspaceType === "personal";
@@ -2611,6 +2819,7 @@ async function readDesktopQuota(session, signal, runner = runVela, fetcher = fet
2611
2819
  consoleJson(`${session.apiUrl}/api/v1/wallet/balance`, session, deadline, fetcher),
2612
2820
  cli
2613
2821
  ]);
2822
+ if (wallet.status === "rejected" && wallet.reason instanceof ConsoleError && wallet.reason.kind === "auth") throw wallet.reason;
2614
2823
  const walletBalances = wallet.status === "fulfilled" ? normalizeBalances(wallet.value) : {};
2615
2824
  const cliBalances = funding.status === "fulfilled" && "balance" in funding.value ? normalizeBalances({ balanceUsd: funding.value.balance }) : {};
2616
2825
  const windows = funding.status === "fulfilled" ? funding.value.windows : [];
@@ -2623,7 +2832,7 @@ async function readDesktopQuota(session, signal, runner = runVela, fetcher = fet
2623
2832
  }
2624
2833
  const tier = (funding.status === "fulfilled" ? funding.value.tier : void 0) ?? session.plan;
2625
2834
  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";
2835
+ 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\u53EF\u5728\u8FDE\u63A5\u9875\u5B8C\u6210\u72EC\u7ACB\u767B\u5F55\uFF0C\u63D2\u4EF6\u4F1A\u4F18\u5148\u91C7\u7528\u53EF\u8BFB\u53D6\u7684\u5B8C\u6574\u989D\u5EA6\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\u53EF\u5728\u8FDE\u63A5\u9875\u5B8C\u6210\u72EC\u7ACB\u767B\u5F55\uFF0C\u63D2\u4EF6\u4F1A\u4F18\u5148\u91C7\u7528\u53EF\u8BFB\u53D6\u7684\u5B8C\u6574\u989D\u5EA6\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\u53EF\u5728\u8FDE\u63A5\u9875\u5B8C\u6210\u72EC\u7ACB\u767B\u5F55\uFF0C\u63D2\u4EF6\u4F1A\u4F18\u5148\u91C7\u7528\u53EF\u8BFB\u53D6\u7684\u5B8C\u6574\u989D\u5EA6\u3002";
2627
2836
  return {
2628
2837
  state: "partial",
2629
2838
  source: "desktop",
@@ -2649,7 +2858,7 @@ var MAX_WINDOWS = 64;
2649
2858
  var MAX_API_KEY = 16384;
2650
2859
  var HTTP_ASCII = /^[\x21-\x7e]+$/u;
2651
2860
  var ENV_REF = /^[A-Za-z_][A-Za-z0-9_]*$/u;
2652
- var MODEL_ID3 = /^[A-Za-z0-9._:/-]{1,128}$/u;
2861
+ var MODEL_ID2 = /^[A-Za-z0-9._:/-]{1,128}$/u;
2653
2862
  var LOGIN_PHASES = ["idle", "launching", "waiting", "verifying", "success", "cancelled", "error"];
2654
2863
  var QUOTA_STATES = ["ok", "partial", "need-login", "error"];
2655
2864
  var SESSION_SOURCES = ["desktop", "login", "environment"];
@@ -2663,7 +2872,7 @@ function isoTime(value, label) {
2663
2872
  if (!Number.isFinite(Date.parse(time))) throw new Error(`${label} \u5FC5\u987B\u662F ISO \u65F6\u95F4\u5B57\u7B26\u4E32`);
2664
2873
  return time;
2665
2874
  }
2666
- function flag2(value, label) {
2875
+ function flag(value, label) {
2667
2876
  if (typeof value !== "boolean") throw new Error(`${label} \u5FC5\u987B\u662F\u5E03\u5C14\u503C`);
2668
2877
  return value;
2669
2878
  }
@@ -2719,11 +2928,11 @@ function parseQuotaSnapshot(value) {
2719
2928
  ...value.subscriptionPeriodStart === void 0 ? {} : { subscriptionPeriodStart: isoTime(value.subscriptionPeriodStart, "\u8BA2\u9605\u5468\u671F\u5F00\u59CB\u65F6\u95F4") },
2720
2929
  ...value.subscriptionPeriodEnd === void 0 ? {} : { subscriptionPeriodEnd: isoTime(value.subscriptionPeriodEnd, "\u8BA2\u9605\u5468\u671F\u7ED3\u675F\u65F6\u95F4") },
2721
2930
  ...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") },
2931
+ ...value.stale === void 0 ? {} : { stale: flag(value.stale, "\u5386\u53F2\u989D\u5EA6\u8BFB\u6570") },
2723
2932
  ...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") },
2933
+ ...value.autoRefreshPaused === void 0 ? {} : { autoRefreshPaused: flag(value.autoRefreshPaused, "\u81EA\u52A8\u5237\u65B0\u6682\u505C\u72B6\u6001") },
2725
2934
  ...value.tier === void 0 ? {} : { tier: text(value.tier, "\u5957\u9910\u540D", MAX_TIER) },
2726
- ...value.eligible === void 0 ? {} : { eligible: flag2(value.eligible, "\u5957\u9910\u8D44\u683C") },
2935
+ ...value.eligible === void 0 ? {} : { eligible: flag(value.eligible, "\u5957\u9910\u8D44\u683C") },
2727
2936
  windows: windows.map(parseQuotaWindow),
2728
2937
  balances: {
2729
2938
  ...balances.subscriptionUsd === void 0 ? {} : { subscriptionUsd: amount(balances.subscriptionUsd, "\u8BA2\u9605\u4F59\u989D") },
@@ -2737,9 +2946,19 @@ function parseLoginState(value) {
2737
2946
  if (!record(value)) throw new Error("\u767B\u5F55\u72B6\u6001\u5FC5\u987B\u662F\u5BF9\u8C61");
2738
2947
  const jobId = value.jobId;
2739
2948
  if (jobId !== null && jobId !== void 0 && typeof jobId !== "string") throw new Error("\u767B\u5F55\u4EFB\u52A1 id \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u6216 null");
2949
+ const state = member(value.state, LOGIN_PHASES, "\u767B\u5F55\u9636\u6BB5");
2950
+ let approval = {};
2951
+ if (state === "waiting" && (value.activationUrl !== void 0 || value.userCode !== void 0 || value.expiresAt !== void 0)) {
2952
+ const userCode = text(value.userCode, "\u8BBE\u5907\u6388\u6743\u7801", 64);
2953
+ if (!/^[A-Za-z0-9-]+$/u.test(userCode)) throw new Error("\u8BBE\u5907\u6388\u6743\u7801\u683C\u5F0F\u65E0\u6548");
2954
+ const activationUrl = validateActivationUrl(value.activationUrl, userCode);
2955
+ if (activationUrl === null) throw new Error("\u8BBE\u5907\u6388\u6743\u94FE\u63A5\u65E0\u6548");
2956
+ approval = { activationUrl, userCode, expiresAt: isoTime(value.expiresAt, "\u8BBE\u5907\u6388\u6743\u5230\u671F\u65F6\u95F4") };
2957
+ }
2740
2958
  return {
2741
2959
  jobId: typeof jobId === "string" && jobId.length > 0 ? text(jobId, "\u767B\u5F55\u4EFB\u52A1 id", MAX_JOB_ID) : null,
2742
- state: member(value.state, LOGIN_PHASES, "\u767B\u5F55\u9636\u6BB5"),
2960
+ state,
2961
+ ...approval,
2743
2962
  ...value.message === void 0 ? {} : { message: text(value.message, "\u767B\u5F55\u8BF4\u660E", MAX_MESSAGE) },
2744
2963
  ...value.startedAt === void 0 ? {} : { startedAt: isoTime(value.startedAt, "\u767B\u5F55\u5F00\u59CB\u65F6\u95F4") },
2745
2964
  ...value.finishedAt === void 0 ? {} : { finishedAt: isoTime(value.finishedAt, "\u767B\u5F55\u7ED3\u675F\u65F6\u95F4") }
@@ -2748,12 +2967,12 @@ function parseLoginState(value) {
2748
2967
  function parseStatusModel(value) {
2749
2968
  if (!record(value)) throw new Error("\u6A21\u578B\u6761\u76EE\u5FC5\u987B\u662F\u5BF9\u8C61");
2750
2969
  const id = text(value.id, "\u6A21\u578B id", 128);
2751
- if (!MODEL_ID3.test(id)) throw new Error("\u6A21\u578B id \u542B\u4E0D\u5141\u8BB8\u7684\u5B57\u7B26");
2970
+ if (!MODEL_ID2.test(id)) throw new Error("\u6A21\u578B id \u542B\u4E0D\u5141\u8BB8\u7684\u5B57\u7B26");
2752
2971
  return {
2753
2972
  id,
2754
2973
  name: text(value.name, "\u6A21\u578B\u540D", 128),
2755
2974
  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") },
2975
+ ...value.enabled === void 0 ? {} : { enabled: flag(value.enabled, "\u6A21\u578B\u542F\u7528\u72B6\u6001") },
2757
2976
  ...value.contextWindow === void 0 ? {} : { contextWindow: positiveInteger(value.contextWindow, "\u4E0A\u4E0B\u6587\u9884\u7B97", 1e8) },
2758
2977
  ...value.maxOutputTokens === void 0 ? {} : { maxOutputTokens: positiveInteger(value.maxOutputTokens, "\u6700\u5927\u8F93\u51FA", 1e7) },
2759
2978
  ...value.reasoningEfforts === void 0 ? {} : { reasoningEfforts: parseEfforts(value.reasoningEfforts) }
@@ -2764,8 +2983,8 @@ function parseStatusConfig(value) {
2764
2983
  const apiKeyEnv = text(value.apiKeyEnv, "\u51ED\u636E\u5F15\u7528\u540D", MAX_ENV_REF);
2765
2984
  if (!ENV_REF.test(apiKeyEnv)) throw new Error("\u51ED\u636E\u5F15\u7528\u540D\u5FC5\u987B\u662F\u5408\u6CD5\u6807\u8BC6\u7B26");
2766
2985
  return {
2767
- emptyTurnGuard: flag2(value.emptyTurnGuard, "\u7A7A\u8F6E\u4FDD\u62A4\u5F00\u5173"),
2768
- useDesktopCredential: flag2(value.useDesktopCredential, "\u684C\u9762\u767B\u5F55\u6001\u5F00\u5173"),
2986
+ emptyTurnGuard: flag(value.emptyTurnGuard, "\u7A7A\u8F6E\u4FDD\u62A4\u5F00\u5173"),
2987
+ useDesktopCredential: flag(value.useDesktopCredential, "\u684C\u9762\u767B\u5F55\u6001\u5F00\u5173"),
2769
2988
  baseURL: text(value.baseURL, "\u63A8\u7406\u5730\u5740", MAX_BASE_URL),
2770
2989
  apiKeyEnv,
2771
2990
  ...value.apiProtocol === void 0 ? {} : { apiProtocol: member(value.apiProtocol, API_PROTOCOL_IDS, "\u63A8\u7406\u534F\u8BAE") },
@@ -2787,7 +3006,7 @@ function parseStatus(value) {
2787
3006
  }
2788
3007
  return {
2789
3008
  revision: text(value.revision, "\u914D\u7F6E\u7248\u672C", MAX_REVISION),
2790
- apiKeyConfigured: flag2(value.apiKeyConfigured, "\u51ED\u636E\u662F\u5426\u5C31\u7EEA"),
3009
+ apiKeyConfigured: flag(value.apiKeyConfigured, "\u51ED\u636E\u662F\u5426\u5C31\u7EEA"),
2791
3010
  ...value.sessionSource === void 0 ? {} : { sessionSource: member(value.sessionSource, SESSION_SOURCES, "\u767B\u5F55\u6001\u6765\u6E90") },
2792
3011
  quota: parseQuotaSnapshot(value.quota),
2793
3012
  login: parseLoginState(value.login),
@@ -2824,7 +3043,9 @@ var SESSION = credentialKey(name, "console");
2824
3043
  var hash = (value) => createHash("sha256").update(JSON.stringify(value) ?? "").digest("hex");
2825
3044
  var needLogin = () => ({ state: "need-login", updatedAt: (/* @__PURE__ */ new Date()).toISOString(), windows: [], balances: {}, note: "\u5C1A\u672A\u8BFB\u53D6\u989D\u5EA6\uFF1B\u53EF\u4F7F\u7528\u684C\u9762\u767B\u5F55\u6001\u6216\u5728\u8FDE\u63A5\u9875\u767B\u5F55" });
2826
3045
  function grantOf(value) {
2827
- if (value?.kind !== "grant" || !record(value.payload) || typeof value.payload.cookie !== "string") return;
3046
+ if (value?.kind !== "grant" || !record(value.payload)) return;
3047
+ if (value.payload.kind === "device") return deviceGrant(value.payload);
3048
+ if (typeof value.payload.cookie !== "string") return;
2828
3049
  return { cookie: value.payload.cookie, ...typeof value.payload.expiresAt === "number" ? { expiresAt: value.payload.expiresAt } : {} };
2829
3050
  }
2830
3051
  var _saveApiKey_dec, _cancelLogin_dec, _loginStatus_dec, _startLogin_dec, _refreshQuotaNow_dec, _refreshQuota_dec, _refreshModels_dec, _status_dec, _a, _init;
@@ -2861,13 +3082,7 @@ var OpenDesignCoRouterService = class extends (_a = TypertRemoteService, _status
2861
3082
  this._credentials();
2862
3083
  return { revision: expectedRevision, sessionHash: hash(state.sessionRecord), configHash: hash(state.config) };
2863
3084
  },
2864
- verify: async (cookie, signal) => {
2865
- const session = cookieSession(cookie, "login");
2866
- if (!session) throw new ConsoleError("auth");
2867
- const quota = await this.dependencies.readQuota(session, signal);
2868
- return { balanceOk: true, coverage: quota.state === "ok" ? "complete" : "partial", windows: quota.windows };
2869
- },
2870
- commit: async (cookie, captured, signal, expiresAt) => {
3085
+ commit: async (tokens, captured, signal) => {
2871
3086
  if (!record(captured) || typeof captured.revision !== "string" || typeof captured.sessionHash !== "string" || typeof captured.configHash !== "string") throw this._conflict();
2872
3087
  await this._state();
2873
3088
  this._expect(captured.revision);
@@ -2875,11 +3090,15 @@ var OpenDesignCoRouterService = class extends (_a = TypertRemoteService, _status
2875
3090
  await this._credentials().modifyRecord(SESSION, async (current) => {
2876
3091
  signal.throwIfAborted();
2877
3092
  if (hash(current) !== captured.sessionHash || hash(this._config()) !== captured.configHash) throw this._conflict();
2878
- return { kind: "grant", payload: { cookie, expiresAt, savedAt: (/* @__PURE__ */ new Date()).toISOString() } };
3093
+ const grant = deviceGrant({ kind: "device", ...tokens });
3094
+ if (!grant) throw new ConsoleError("auth");
3095
+ return { kind: "grant", payload: { ...grant, savedAt: (/* @__PURE__ */ new Date()).toISOString() } };
2879
3096
  });
2880
3097
  this._invalidateQuota();
3098
+ this._modelsFingerprint = "";
3099
+ this._invalidateModels();
2881
3100
  }
2882
- });
3101
+ }, dependencies.loginOptions);
2883
3102
  ctx.effect(() => () => {
2884
3103
  this._lifetime.abort();
2885
3104
  this._login.dispose();
@@ -2894,28 +3113,43 @@ var OpenDesignCoRouterService = class extends (_a = TypertRemoteService, _status
2894
3113
  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);
2895
3114
  return service;
2896
3115
  }
2897
- async resolveApiKey(ref) {
3116
+ async _key(ref, baseURL, apiRecord, sessionRecord) {
2898
3117
  const credentials = this.ctx.get("credentials");
2899
3118
  const ambient = credentials ? (await credentials.resolve(credentialRef(ref)))?.value : launchEnvironmentOf(this.ctx).get(ref)?.value;
2900
3119
  if (ambient?.trim()) return ambient;
2901
- const saved = await credentials?.readRecord(API_KEY);
2902
- return saved?.kind === "api-key" ? saved.key : void 0;
3120
+ if (apiRecord?.kind === "api-key" && apiRecord.key?.trim()) return apiRecord.key;
3121
+ const grant = grantOf(sessionRecord);
3122
+ return isOfficialRuntimeUrl(baseURL) && grant && "kind" in grant && grant.kind === "device" ? grant.runtimeKey : void 0;
3123
+ }
3124
+ async resolveApiKey(ref, baseURL = this._config().baseURL) {
3125
+ const credentials = this.ctx.get("credentials");
3126
+ const [apiRecord, sessionRecord] = await Promise.all([credentials?.readRecord(API_KEY), credentials?.readRecord(SESSION)]);
3127
+ return this._key(ref, baseURL, apiRecord, sessionRecord);
3128
+ }
3129
+ _invalidateModels() {
3130
+ const changed = this._modelsUpdatedAt !== void 0;
3131
+ this._modelsEpoch++;
3132
+ this._modelsController?.abort();
3133
+ this._modelsPending = void 0;
3134
+ this._models = MODELS;
3135
+ this._modelsUpdatedAt = void 0;
3136
+ this._modelsError = void 0;
3137
+ if (changed) {
3138
+ try {
3139
+ this.dependencies.modelsChanged?.();
3140
+ } catch {
3141
+ }
3142
+ }
2903
3143
  }
2904
3144
  async _state() {
2905
3145
  const credentials = this.ctx.get("credentials"), config = this._config();
2906
- const [apiRecord, sessionRecord, key] = await Promise.all([credentials?.readRecord(API_KEY), credentials?.readRecord(SESSION), this.resolveApiKey(config.apiKeyEnv)]);
3146
+ const [apiRecord, sessionRecord] = await Promise.all([credentials?.readRecord(API_KEY), credentials?.readRecord(SESSION)]);
3147
+ const key = await this._key(config.apiKeyEnv, config.baseURL, apiRecord, sessionRecord);
2907
3148
  const state = { apiRecord, sessionRecord, key, config };
2908
3149
  const modelsFingerprint = hash({ key, baseURL: config.baseURL });
2909
3150
  if (modelsFingerprint !== this._modelsFingerprint) {
2910
- const changed = this._modelsUpdatedAt !== void 0;
2911
3151
  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?.();
3152
+ this._invalidateModels();
2919
3153
  }
2920
3154
  const fingerprint = hash(state);
2921
3155
  if (fingerprint !== this._fingerprint) {
@@ -3038,9 +3272,10 @@ var OpenDesignCoRouterService = class extends (_a = TypertRemoteService, _status
3038
3272
  let identity;
3039
3273
  let refreshFailed = false;
3040
3274
  try {
3275
+ const grant = grantOf(await this.ctx.get("credentials")?.readRecord(SESSION));
3041
3276
  const authDeps = {
3042
3277
  useDesktop: config.useDesktopCredential,
3043
- readGrant: async () => grantOf(await this.ctx.get("credentials")?.readRecord(SESSION)),
3278
+ readGrant: async () => grant,
3044
3279
  resolveRef: async (ref) => {
3045
3280
  const credentials = this.ctx.get("credentials");
3046
3281
  return credentials ? (await credentials.resolve(credentialRef(ref)))?.value : launchEnvironmentOf(this.ctx).get(ref)?.value;
@@ -3058,7 +3293,10 @@ var OpenDesignCoRouterService = class extends (_a = TypertRemoteService, _status
3058
3293
  if (fallback.state === "ready") {
3059
3294
  try {
3060
3295
  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}` : ""}` };
3296
+ if (complete.state === "ok" && (complete.windows.length > 0 || complete.eligible === false)) {
3297
+ 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}` : ""}` };
3298
+ identity = hash(fallback.session);
3299
+ }
3062
3300
  } catch (error) {
3063
3301
  if (signal.aborted) throw error;
3064
3302
  }
@@ -3068,6 +3306,7 @@ var OpenDesignCoRouterService = class extends (_a = TypertRemoteService, _status
3068
3306
  if (!(error instanceof ConsoleError) || !["auth", "permission"].includes(error.kind)) throw error;
3069
3307
  const fallback = await this.dependencies.resolveSession({ ...authDeps, useDesktop: false });
3070
3308
  if (fallback.state !== "ready") throw new ConsoleError("auth");
3309
+ identity = hash(fallback.session);
3071
3310
  result = await this.dependencies.readQuota(fallback.session, signal);
3072
3311
  }
3073
3312
  }
@@ -3145,7 +3384,7 @@ function apply(ctx, config) {
3145
3384
  const adapter = new CoRouterAdapter({
3146
3385
  config: current,
3147
3386
  models: () => service.modelCatalog(),
3148
- apiKey: (ref) => service.resolveApiKey(ref),
3387
+ apiKey: (ref, baseURL) => service.resolveApiKey(ref, baseURL),
3149
3388
  onEmptyTurn: () => {
3150
3389
  service.guardedTurns++;
3151
3390
  },
@@ -3168,7 +3407,7 @@ function apply(ctx, config) {
3168
3407
  ctx.llm.registerModelDiscovery(SETTINGS_NS, async (request, signal) => {
3169
3408
  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
3409
  const config2 = current();
3171
- const key = request.apiKey ?? await service.resolveApiKey(config2.apiKeyEnv);
3410
+ const key = request.apiKey ?? await service.resolveApiKey(config2.apiKeyEnv, config2.baseURL);
3172
3411
  if (!key) throw new LlmError11("\u8BF7\u5148\u914D\u7F6E CoRouter API Key", "MISSING_CREDENTIAL");
3173
3412
  const models = await fetchModelCatalog(config2.baseURL, key, signal);
3174
3413
  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 } : {} }));