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/README.md +6 -6
- package/SECURITY.md +3 -2
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/lib/adapter.d.ts +1 -1
- package/lib/auth.d.ts +18 -3
- package/lib/client.d.ts +12 -4
- package/lib/client.js +224 -117
- package/lib/device-auth.d.ts +34 -0
- package/lib/generated/typert.host.js +77 -57
- package/lib/generated/typert.remote-client.js +77 -57
- package/lib/index.d.ts +5 -1
- package/lib/index.js +628 -389
- package/lib/index.js.map +4 -4
- package/lib/login.d.ts +21 -131
- package/lib/model-settings.d.ts +3 -3
- package/lib/quota.d.ts +6 -0
- package/lib/remote.d.ts +4 -0
- package/package.json +2 -2
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 (
|
|
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 >
|
|
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 (
|
|
309
|
-
if (
|
|
310
|
-
|
|
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
|
|
320
|
-
...maxTokens === void 0 ? {} : { maxOutputTokens:
|
|
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(),
|
|
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()),
|
|
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
|
-
|
|
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
|
|
1134
|
-
if (
|
|
1135
|
-
else if (THINKING_EFFORTS.includes(
|
|
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
|
|
1138
|
-
} else throw new LlmError7(`Anthropic Messages \u534F\u8BAE\u4E0D\u63A5\u53D7\u601D\u8003\u6DF1\u5EA6\u300C${
|
|
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
|
|
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
|
|
1394
|
-
if (!
|
|
1395
|
-
body.reasoning = { effort
|
|
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
|
|
1500
|
-
const
|
|
1501
|
-
call
|
|
1502
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
1791
|
-
if (
|
|
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
|
-
...
|
|
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
|
|
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" || !
|
|
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
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
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
|
-
|
|
2165
|
-
|
|
2166
|
-
var
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
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
|
-
|
|
2173
|
-
|
|
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
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
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-
|
|
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
|
|
2194
|
-
var
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
2242
|
-
status: { jobId:
|
|
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
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2303
|
-
|
|
2304
|
-
job.
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
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,
|
|
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 (
|
|
2317
|
-
job
|
|
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
|
|
2333
|
-
const
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
if (!this.alive(job))
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
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
|
-
|
|
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\
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
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
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
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
|
-
|
|
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 = "\
|
|
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(
|
|
2553
|
+
await this.callbacks.commit(tokens, job.captured, job.controller.signal);
|
|
2413
2554
|
job.committing = false;
|
|
2414
|
-
this.finish(job, "success", "\
|
|
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
|
-
|
|
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
|
|
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
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
if (
|
|
2440
|
-
|
|
2441
|
-
return
|
|
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
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2506
|
-
} catch {
|
|
2507
|
-
|
|
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\
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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 (!
|
|
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:
|
|
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:
|
|
2768
|
-
useDesktopCredential:
|
|
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:
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
2902
|
-
|
|
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
|
|
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.
|
|
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 () =>
|
|
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))
|
|
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 } : {} }));
|