dsh-opendesign-corouter 0.1.3 → 0.1.5
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 +22 -1
- package/lib/adapter.d.ts +1 -1
- package/lib/auth.d.ts +18 -3
- package/lib/browser-open.d.ts +104 -0
- package/lib/client.d.ts +14 -1
- package/lib/client.js +122 -15
- package/lib/device-auth.d.ts +36 -0
- package/lib/generated/typert.host.js +39 -1
- package/lib/generated/typert.remote-client.d.ts +2 -1
- package/lib/generated/typert.remote-client.js +39 -1
- package/lib/index.d.ts +10 -2
- package/lib/index.js +800 -277
- package/lib/index.js.map +4 -4
- package/lib/login.d.ts +31 -133
- package/lib/quota.d.ts +6 -0
- package/lib/remote.d.ts +11 -0
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -1752,8 +1752,8 @@ var CoRouterAdapter = class extends LlmAdapter {
|
|
|
1752
1752
|
async *#stream(options, config, row) {
|
|
1753
1753
|
const signal = AbortSignal.any([combinedSignal(options.signal, config.requestTimeoutMs), this.#lifetime.signal]);
|
|
1754
1754
|
signal.throwIfAborted();
|
|
1755
|
-
const raw = await this.options.apiKey(config.apiKeyEnv);
|
|
1756
|
-
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");
|
|
1757
1757
|
const key = assertUsableApiKey(raw, "dsh-opendesign-corouter", config.apiKeyEnv);
|
|
1758
1758
|
const setting = config.modelSettings?.[row.id] ?? {};
|
|
1759
1759
|
const protocolId = config.apiProtocol ?? "chat-completions";
|
|
@@ -2005,7 +2005,7 @@ function isOfficialApiUrl(raw) {
|
|
|
2005
2005
|
if (url.username || url.password) return false;
|
|
2006
2006
|
if (url.search || url.hash) return false;
|
|
2007
2007
|
if (url.pathname !== "/" && url.pathname !== "") return false;
|
|
2008
|
-
return url.hostname.toLowerCase() === API_HOST;
|
|
2008
|
+
return url.hostname.toLowerCase() === API_HOST && !url.port;
|
|
2009
2009
|
}
|
|
2010
2010
|
function normalizeApiUrl(raw) {
|
|
2011
2011
|
const value = typeof raw === "string" ? raw.trim() : "";
|
|
@@ -2080,6 +2080,24 @@ async function readDesktopCredential(deps = {}) {
|
|
|
2080
2080
|
}
|
|
2081
2081
|
return parseDesktopConfigText(text2, profile);
|
|
2082
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
|
+
}
|
|
2083
2101
|
function cookieHeader(raw) {
|
|
2084
2102
|
const value = raw.trim();
|
|
2085
2103
|
if (!value || !value.includes("=")) return void 0;
|
|
@@ -2100,6 +2118,10 @@ function desktopSession(credential, apiUrl = credential.apiUrl ?? DEFAULT_API_UR
|
|
|
2100
2118
|
...credential.plan ? { plan: credential.plan } : {}
|
|
2101
2119
|
};
|
|
2102
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
|
+
}
|
|
2103
2125
|
function cookieSession(cookie, source, apiUrl = DEFAULT_API_URL) {
|
|
2104
2126
|
const header = cookieHeader(cookie);
|
|
2105
2127
|
if (!header) return void 0;
|
|
@@ -2140,10 +2162,15 @@ async function resolveSession(deps = {}) {
|
|
|
2140
2162
|
} catch {
|
|
2141
2163
|
grant = void 0;
|
|
2142
2164
|
}
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
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
|
+
}
|
|
2147
2174
|
}
|
|
2148
2175
|
}
|
|
2149
2176
|
if (deps.resolveRef) {
|
|
@@ -2164,61 +2191,207 @@ async function resolveSession(deps = {}) {
|
|
|
2164
2191
|
|
|
2165
2192
|
// src/login.ts
|
|
2166
2193
|
import { randomUUID } from "node:crypto";
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
var
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
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.5";
|
|
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");
|
|
2174
2240
|
return {
|
|
2175
|
-
|
|
2176
|
-
|
|
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))
|
|
2177
2247
|
};
|
|
2178
2248
|
}
|
|
2179
|
-
function
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
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"));
|
|
2186
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
|
+
}
|
|
2187
2361
|
}
|
|
2362
|
+
|
|
2363
|
+
// src/login.ts
|
|
2188
2364
|
var LoginError = class extends Error {
|
|
2189
2365
|
constructor(code) {
|
|
2190
|
-
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");
|
|
2191
2367
|
this.code = code;
|
|
2192
2368
|
}
|
|
2193
2369
|
code;
|
|
2194
2370
|
};
|
|
2195
2371
|
var ACTIVE = /* @__PURE__ */ new Set(["launching", "waiting", "verifying"]);
|
|
2196
|
-
var
|
|
2197
|
-
var
|
|
2198
|
-
var FATAL_COMMIT_CODES = /* @__PURE__ */ new Set(["opendesign/revision-conflict", "opendesign/invalid-request", "opendesign/config", "opendesign/login-disposed"]);
|
|
2199
|
-
var CHROME_LAUNCH_OPTIONS = {
|
|
2200
|
-
channel: "chrome",
|
|
2201
|
-
headless: false,
|
|
2202
|
-
ignoreDefaultArgs: ["--enable-automation"],
|
|
2203
|
-
args: ["--disable-blink-features=AutomationControlled"]
|
|
2204
|
-
};
|
|
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"]);
|
|
2205
2374
|
var ConsoleLogin = class {
|
|
2206
|
-
job = null;
|
|
2207
|
-
disposed = false;
|
|
2208
|
-
callbacks;
|
|
2209
|
-
options;
|
|
2210
|
-
now;
|
|
2211
|
-
delay;
|
|
2212
2375
|
constructor(callbacks, options = {}) {
|
|
2213
2376
|
this.callbacks = callbacks;
|
|
2214
2377
|
this.options = options;
|
|
2215
2378
|
this.now = options.now ?? Date.now;
|
|
2216
2379
|
this.delay = options.delay ?? sleep;
|
|
2380
|
+
this.fetcher = options.fetcher ?? fetch;
|
|
2217
2381
|
}
|
|
2218
|
-
|
|
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. */
|
|
2219
2390
|
status() {
|
|
2220
2391
|
if (!this.job) return { jobId: null, state: "idle" };
|
|
2221
|
-
const
|
|
2392
|
+
const job = this.job;
|
|
2393
|
+
if (job.status.state === "waiting" && this.now() >= job.deadline) this.expire(job);
|
|
2394
|
+
const s = job.status;
|
|
2222
2395
|
return {
|
|
2223
2396
|
jobId: s.jobId,
|
|
2224
2397
|
state: s.state,
|
|
@@ -2226,241 +2399,450 @@ var ConsoleLogin = class {
|
|
|
2226
2399
|
...s.finishedAt ? { finishedAt: s.finishedAt } : {},
|
|
2227
2400
|
...s.message ? { message: s.message } : {},
|
|
2228
2401
|
...s.errorCode ? { errorCode: s.errorCode } : {},
|
|
2229
|
-
...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
|
+
} : {}
|
|
2230
2407
|
};
|
|
2231
2408
|
}
|
|
2232
|
-
/**
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2409
|
+
/** Host-only: the live approval link, so the Host can open it in a bounded window. */
|
|
2410
|
+
activationLink() {
|
|
2411
|
+
const status = this.status();
|
|
2412
|
+
return status.state === "waiting" ? status.activationUrl : void 0;
|
|
2413
|
+
}
|
|
2414
|
+
/** Synchronous/non-blocking and idempotent while capture/network/commit is pending. */
|
|
2237
2415
|
start(request) {
|
|
2238
2416
|
if (this.disposed) throw new LoginError("opendesign/login-disposed");
|
|
2417
|
+
this.status();
|
|
2239
2418
|
if (this.job && ACTIVE.has(this.job.status.state)) return this.status();
|
|
2419
|
+
const started = this.now();
|
|
2420
|
+
const requested = this.options.timeoutMs;
|
|
2421
|
+
const timeoutMs = typeof requested === "number" && Number.isFinite(requested) && requested > 0 ? Math.min(MAX_TIMEOUT_MS, requested) : MAX_TIMEOUT_MS;
|
|
2240
2422
|
const job = {
|
|
2241
2423
|
id: randomUUID(),
|
|
2242
|
-
startedAt: new Date(this.now()).toISOString(),
|
|
2243
2424
|
controller: new AbortController(),
|
|
2244
|
-
|
|
2245
|
-
status: { jobId:
|
|
2425
|
+
deadline: started + timeoutMs,
|
|
2426
|
+
status: { jobId: null, state: "launching", startedAt: new Date(started).toISOString(), message: "\u6B63\u5728\u7533\u8BF7\u5B98\u65B9\u8BBE\u5907\u6388\u6743\u7801" },
|
|
2246
2427
|
captured: void 0,
|
|
2247
|
-
capturedAt: void 0,
|
|
2248
|
-
verifying: false,
|
|
2249
2428
|
committing: false,
|
|
2250
|
-
cancelRequested: false
|
|
2429
|
+
cancelRequested: false,
|
|
2430
|
+
settled: false
|
|
2251
2431
|
};
|
|
2252
2432
|
job.status.jobId = job.id;
|
|
2253
|
-
job.status.startedAt = job.startedAt;
|
|
2254
2433
|
this.job = job;
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2434
|
+
this.armExpiry(job);
|
|
2435
|
+
void Promise.resolve().then(async () => {
|
|
2436
|
+
if (!this.alive(job)) return;
|
|
2437
|
+
try {
|
|
2438
|
+
job.captured = await this.callbacks.capture(request.expectedRevision);
|
|
2439
|
+
} catch (error) {
|
|
2440
|
+
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");
|
|
2441
|
+
return;
|
|
2442
|
+
}
|
|
2443
|
+
if (this.alive(job)) await this.run(job);
|
|
2444
|
+
}).catch((error) => {
|
|
2445
|
+
if (this.alive(job)) this.finish(job, "error", failureMessage(error), error instanceof DeviceAuthError ? error.code : "opendesign/login-network");
|
|
2263
2446
|
});
|
|
2264
2447
|
return this.status();
|
|
2265
2448
|
}
|
|
2266
|
-
/**
|
|
2267
|
-
* Abandon the attempt. While `commit` is in flight this only aborts the signal and records
|
|
2268
|
-
* the request: the Host's atomic store decides, because the commit is the linearization
|
|
2269
|
-
* point. A cancel that wins that race reports `cancelled`; one that loses reports `success`
|
|
2270
|
-
* and the cookie really is stored.
|
|
2271
|
-
*/
|
|
2272
2449
|
cancel(request) {
|
|
2273
|
-
|
|
2274
|
-
if (!job || job.status.jobId !== request.jobId) return this.status();
|
|
2275
|
-
this.requestCancellation(job, "\u767B\u5F55\u5DF2\u53D6\u6D88\uFF0C\u672A\u4FDD\u5B58 Cookie");
|
|
2450
|
+
if (this.job?.id === request.jobId) this.requestCancellation(this.job, "\u767B\u5F55\u5DF2\u53D6\u6D88\uFF0C\u672A\u4FDD\u5B58\u6388\u6743");
|
|
2276
2451
|
return this.status();
|
|
2277
2452
|
}
|
|
2278
|
-
/** Plugin teardown: abort the attempt and close the browser. */
|
|
2279
2453
|
dispose() {
|
|
2280
|
-
const job = this.job;
|
|
2281
|
-
if (job) this.requestCancellation(job, "\u670D\u52A1\u5DF2\u5378\u8F7D\uFF0C\u767B\u5F55\u5DF2\u53D6\u6D88");
|
|
2282
2454
|
this.disposed = true;
|
|
2455
|
+
const job = this.job;
|
|
2456
|
+
if (job) {
|
|
2457
|
+
this.requestCancellation(job, "\u670D\u52A1\u5DF2\u5378\u8F7D\uFF0C\u767B\u5F55\u5DF2\u53D6\u6D88");
|
|
2458
|
+
this.settle(job);
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
alive(job) {
|
|
2462
|
+
return this.job === job && !this.disposed && !job.controller.signal.aborted && ACTIVE.has(job.status.state);
|
|
2463
|
+
}
|
|
2464
|
+
stopTimer(job) {
|
|
2465
|
+
if (job.timer !== void 0) {
|
|
2466
|
+
clearTimeout(job.timer);
|
|
2467
|
+
job.timer = void 0;
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2470
|
+
armExpiry(job) {
|
|
2471
|
+
this.stopTimer(job);
|
|
2472
|
+
job.timer = setTimeout(() => this.expire(job), Math.max(1, job.deadline - this.now()));
|
|
2473
|
+
job.timer.unref?.();
|
|
2474
|
+
}
|
|
2475
|
+
clearCode(job) {
|
|
2476
|
+
delete job.status.activationUrl;
|
|
2477
|
+
delete job.status.userCode;
|
|
2478
|
+
delete job.status.expiresAt;
|
|
2479
|
+
}
|
|
2480
|
+
expire(job) {
|
|
2481
|
+
if (job.committing) this.requestCancellation(job, "\u6388\u6743\u5DF2\u8FC7\u671F\uFF0C\u672A\u4FDD\u5B58\u6388\u6743");
|
|
2482
|
+
else this.finish(job, "error", "\u8BBE\u5907\u6388\u6743\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u5F00\u59CB", "opendesign/login-expired");
|
|
2283
2483
|
}
|
|
2284
|
-
/** Every cancellation source must let an in-flight atomic store decide the final outcome. */
|
|
2285
2484
|
requestCancellation(job, message) {
|
|
2286
2485
|
if (this.job !== job || !ACTIVE.has(job.status.state)) return;
|
|
2486
|
+
this.clearCode(job);
|
|
2287
2487
|
if (!job.committing) {
|
|
2288
2488
|
this.finish(job, "cancelled", message);
|
|
2289
2489
|
return;
|
|
2290
2490
|
}
|
|
2291
2491
|
if (!job.cancelRequested) {
|
|
2292
2492
|
job.cancellationMessage = message;
|
|
2293
|
-
job.status.message = "\u6B63\u5728\u786E\u8BA4\
|
|
2493
|
+
job.status.message = "\u6B63\u5728\u786E\u8BA4\u6388\u6743\u4FDD\u5B58\u7ED3\u679C";
|
|
2294
2494
|
}
|
|
2295
2495
|
job.cancelRequested = true;
|
|
2296
2496
|
this.stopTimer(job);
|
|
2297
2497
|
job.controller.abort();
|
|
2298
|
-
void this.close(job);
|
|
2299
|
-
}
|
|
2300
|
-
alive(job) {
|
|
2301
|
-
if (this.disposed || this.job !== job) return false;
|
|
2302
|
-
if (job.controller.signal.aborted && !job.committing) return false;
|
|
2303
|
-
return job.status.state !== "success" && job.status.state !== "cancelled" && job.status.state !== "error";
|
|
2304
|
-
}
|
|
2305
|
-
stopTimer(job) {
|
|
2306
|
-
if (job.timer) {
|
|
2307
|
-
clearTimeout(job.timer);
|
|
2308
|
-
job.timer = void 0;
|
|
2309
|
-
}
|
|
2310
2498
|
}
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
job.browser = void 0;
|
|
2314
|
-
try {
|
|
2315
|
-
await browser?.close();
|
|
2316
|
-
} catch {
|
|
2317
|
-
}
|
|
2318
|
-
}
|
|
2319
|
-
finish(job, state, message, errorCode2) {
|
|
2320
|
-
if (this.job !== job) return;
|
|
2321
|
-
if (job.status.state === "success" || job.status.state === "cancelled" || job.status.state === "error") return;
|
|
2499
|
+
finish(job, state, message, errorCode) {
|
|
2500
|
+
if (this.job !== job || !ACTIVE.has(job.status.state)) return;
|
|
2322
2501
|
job.status.state = state;
|
|
2323
2502
|
job.status.finishedAt = new Date(this.now()).toISOString();
|
|
2324
2503
|
job.status.message = message;
|
|
2325
|
-
if (
|
|
2326
|
-
job
|
|
2504
|
+
if (errorCode) job.status.errorCode = errorCode;
|
|
2505
|
+
this.clearCode(job);
|
|
2327
2506
|
this.stopTimer(job);
|
|
2328
2507
|
job.controller.abort();
|
|
2329
|
-
|
|
2508
|
+
this.settle(job);
|
|
2330
2509
|
}
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
if (!browser) return null;
|
|
2336
|
-
job.browser = browser;
|
|
2337
|
-
browser.on?.("disconnected", () => this.requestCancellation(job, "\u767B\u5F55\u7A97\u53E3\u5DF2\u5173\u95ED\uFF0C\u672A\u4FDD\u5B58 Cookie"));
|
|
2338
|
-
return browser;
|
|
2339
|
-
}
|
|
2340
|
-
async run(job) {
|
|
2341
|
-
const site = this.options.site ?? consoleSite();
|
|
2342
|
-
const browser = await this.resolveBrowser(job);
|
|
2343
|
-
if (!browser) return;
|
|
2344
|
-
if (!this.alive(job)) {
|
|
2345
|
-
await this.close(job);
|
|
2346
|
-
return;
|
|
2347
|
-
}
|
|
2348
|
-
const context = await browser.newContext();
|
|
2349
|
-
if (!this.alive(job)) {
|
|
2350
|
-
await this.close(job);
|
|
2351
|
-
return;
|
|
2352
|
-
}
|
|
2353
|
-
const page = await context.newPage();
|
|
2354
|
-
page.on?.("close", () => this.requestCancellation(job, "\u767B\u5F55\u7A97\u53E3\u5DF2\u5173\u95ED\uFF0C\u672A\u4FDD\u5B58 Cookie"));
|
|
2355
|
-
if (!this.alive(job)) {
|
|
2356
|
-
await this.close(job);
|
|
2357
|
-
return;
|
|
2358
|
-
}
|
|
2510
|
+
/** Report one terminal transition per job, exactly once. */
|
|
2511
|
+
settle(job) {
|
|
2512
|
+
if (job.settled) return;
|
|
2513
|
+
job.settled = true;
|
|
2359
2514
|
try {
|
|
2360
|
-
|
|
2515
|
+
this.options.onSettled?.();
|
|
2361
2516
|
} catch {
|
|
2362
|
-
if (!this.alive(job)) return;
|
|
2363
|
-
try {
|
|
2364
|
-
await page.goto(site.origin, { waitUntil: "domcontentloaded", timeout: 3e4 });
|
|
2365
|
-
} catch {
|
|
2366
|
-
job.status.browserOpenFailed = true;
|
|
2367
|
-
}
|
|
2368
2517
|
}
|
|
2518
|
+
}
|
|
2519
|
+
async run(job) {
|
|
2520
|
+
const signal = job.controller.signal;
|
|
2521
|
+
const raw = await postDeviceJson(this.fetcher, DEVICE_AUTH_ENDPOINT, {
|
|
2522
|
+
// The third-party protocol's official-CLI version is NOT our version. Compatibility
|
|
2523
|
+
// of this actual plugin version requires a real-account check outside offline tests.
|
|
2524
|
+
cliVersion: DEVICE_AUTH_PLUGIN_VERSION,
|
|
2525
|
+
host: "DSH CoRouter",
|
|
2526
|
+
profile: "prod"
|
|
2527
|
+
}, signal, job.deadline - this.now());
|
|
2369
2528
|
if (!this.alive(job)) return;
|
|
2529
|
+
const device = parseDeviceAuthorization(raw);
|
|
2530
|
+
job.deadline = Math.min(job.deadline, device.expiresAt);
|
|
2531
|
+
if (this.now() >= job.deadline) {
|
|
2532
|
+
this.expire(job);
|
|
2533
|
+
return;
|
|
2534
|
+
}
|
|
2535
|
+
this.armExpiry(job);
|
|
2370
2536
|
job.status.state = "waiting";
|
|
2371
|
-
job.status.message = "\u8BF7\u5728\
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2537
|
+
job.status.message = "\u8BF7\u5728\u6388\u6743\u7A97\u53E3\u6216\u5E38\u7528\u6D4F\u89C8\u5668\u6253\u5F00\u5B98\u65B9\u6388\u6743\u9875\uFF0C\u767B\u5F55\u5E76\u786E\u8BA4\u8BBE\u5907\u7801";
|
|
2538
|
+
job.status.activationUrl = device.activationUrl;
|
|
2539
|
+
job.status.userCode = device.userCode;
|
|
2540
|
+
job.status.expiresAt = new Date(job.deadline).toISOString();
|
|
2375
2541
|
while (this.alive(job)) {
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
if (!this.alive(job)) return;
|
|
2398
|
-
failures += 1;
|
|
2399
|
-
nextAttempt = this.now() + Math.max(5e3, Math.min(6e4, 2e3 * 2 ** Math.min(failures, 5)), retryAfterMs(error));
|
|
2400
|
-
job.status.state = "waiting";
|
|
2401
|
-
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";
|
|
2402
|
-
} finally {
|
|
2403
|
-
job.verifying = false;
|
|
2404
|
-
}
|
|
2405
|
-
}
|
|
2542
|
+
await this.delay(Math.min(device.pollMs, Math.max(1, job.deadline - this.now())), signal);
|
|
2543
|
+
if (!this.alive(job)) return;
|
|
2544
|
+
if (this.now() >= job.deadline) {
|
|
2545
|
+
this.expire(job);
|
|
2546
|
+
return;
|
|
2547
|
+
}
|
|
2548
|
+
const result = parseDevicePoll(await postDeviceJson(
|
|
2549
|
+
this.fetcher,
|
|
2550
|
+
`${DEVICE_AUTH_ENDPOINT}/${device.deviceId}/token`,
|
|
2551
|
+
{ deviceSecret: device.deviceSecret },
|
|
2552
|
+
signal,
|
|
2553
|
+
job.deadline - this.now()
|
|
2554
|
+
));
|
|
2555
|
+
if (!this.alive(job)) return;
|
|
2556
|
+
if (this.now() >= job.deadline) {
|
|
2557
|
+
this.expire(job);
|
|
2558
|
+
return;
|
|
2559
|
+
}
|
|
2560
|
+
if (result.state === "success") {
|
|
2561
|
+
await this.commit(job, result.tokens);
|
|
2562
|
+
return;
|
|
2406
2563
|
}
|
|
2407
|
-
await this.delay(pollMs, job.controller.signal);
|
|
2408
2564
|
}
|
|
2409
2565
|
}
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
* `retry` when the store rejected the write transiently, so the caller decides when to try
|
|
2413
|
-
* again instead of claiming a success that did not happen.
|
|
2414
|
-
*/
|
|
2415
|
-
async commit(job, session) {
|
|
2416
|
-
if (job.committing || !this.alive(job)) return "cancelled";
|
|
2566
|
+
async commit(job, tokens) {
|
|
2567
|
+
if (!this.alive(job) || job.committing) return;
|
|
2417
2568
|
job.committing = true;
|
|
2569
|
+
this.clearCode(job);
|
|
2418
2570
|
job.status.state = "verifying";
|
|
2419
|
-
job.status.message = "\
|
|
2571
|
+
job.status.message = "\u5B98\u65B9\u6388\u6743\u5DF2\u5B8C\u6210\uFF0C\u6B63\u5728\u4FDD\u5B58\u6388\u6743";
|
|
2420
2572
|
try {
|
|
2421
|
-
await this.callbacks.commit(
|
|
2573
|
+
await this.callbacks.commit(tokens, job.captured, job.controller.signal);
|
|
2422
2574
|
job.committing = false;
|
|
2423
|
-
this.finish(job, "success", "\
|
|
2424
|
-
return "success";
|
|
2575
|
+
this.finish(job, "success", "\u8BBE\u5907\u6388\u6743\u5DF2\u5B8C\u6210\u5E76\u4FDD\u5B58\uFF1B\u989D\u5EA6\u72B6\u6001\u53E6\u884C\u663E\u793A");
|
|
2425
2576
|
} catch (error) {
|
|
2426
2577
|
job.committing = false;
|
|
2427
|
-
if (job.cancelRequested || job.controller.signal.aborted)
|
|
2428
|
-
|
|
2429
|
-
return "cancelled";
|
|
2430
|
-
}
|
|
2431
|
-
if (FATAL_COMMIT_CODES.has(errorCode(error) ?? "")) {
|
|
2432
|
-
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));
|
|
2433
|
-
return "error";
|
|
2434
|
-
}
|
|
2435
|
-
job.status.state = "waiting";
|
|
2436
|
-
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";
|
|
2437
|
-
return "retry";
|
|
2578
|
+
if (job.cancelRequested || job.controller.signal.aborted) this.finish(job, "cancelled", job.cancellationMessage ?? "\u767B\u5F55\u5DF2\u53D6\u6D88\uFF0C\u672A\u4FDD\u5B58\u6388\u6743");
|
|
2579
|
+
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");
|
|
2438
2580
|
}
|
|
2439
2581
|
}
|
|
2440
2582
|
};
|
|
2441
|
-
function
|
|
2442
|
-
if (error && typeof error === "object" && "code" in error && typeof error.code === "string") return error.code;
|
|
2583
|
+
function hostCode(error) {
|
|
2584
|
+
if (error !== null && typeof error === "object" && "code" in error && typeof error.code === "string" && SAFE_HOST_CODES.has(error.code)) return error.code;
|
|
2443
2585
|
return void 0;
|
|
2444
2586
|
}
|
|
2445
|
-
function
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
if (
|
|
2449
|
-
|
|
2450
|
-
return
|
|
2451
|
-
}
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2587
|
+
function failureMessage(error) {
|
|
2588
|
+
const code = error instanceof DeviceAuthError ? error.code : "";
|
|
2589
|
+
if (code === "opendesign/login-expired") return "\u8BBE\u5907\u6388\u6743\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u5F00\u59CB";
|
|
2590
|
+
if (code === "opendesign/login-denied") return "\u8BBE\u5907\u6388\u6743\u88AB\u62D2\u7EDD\uFF0C\u8BF7\u91CD\u65B0\u5F00\u59CB";
|
|
2591
|
+
if (code === "opendesign/login-request-timeout") return "\u8BBE\u5907\u6388\u6743\u8BF7\u6C42\u8D85\u65F6\uFF0C\u8BF7\u91CD\u65B0\u5F00\u59CB";
|
|
2592
|
+
return "\u8BBE\u5907\u6388\u6743\u8BF7\u6C42\u5931\u8D25\uFF0C\u8BF7\u91CD\u65B0\u5F00\u59CB";
|
|
2593
|
+
}
|
|
2594
|
+
|
|
2595
|
+
// src/browser-open.ts
|
|
2596
|
+
var ACTIVATION_NAVIGATE_TIMEOUT_MS = 15e3;
|
|
2597
|
+
var ACTIVATION_OPEN_BUDGET_MS = 2e4;
|
|
2598
|
+
var ACTIVATION_CLOSE_TIMEOUT_MS = 5e3;
|
|
2599
|
+
function chromeLaunchOptions() {
|
|
2600
|
+
return {
|
|
2601
|
+
channel: "chrome",
|
|
2602
|
+
headless: false,
|
|
2603
|
+
chromiumSandbox: true,
|
|
2604
|
+
ignoreDefaultArgs: ["--enable-automation"],
|
|
2605
|
+
args: ["--disable-blink-features=AutomationControlled"]
|
|
2606
|
+
};
|
|
2607
|
+
}
|
|
2608
|
+
var ActivationLauncher = class {
|
|
2609
|
+
browser;
|
|
2610
|
+
page;
|
|
2611
|
+
pending;
|
|
2612
|
+
disposal;
|
|
2613
|
+
/** In-flight window closure, shared so disposal can join it. */
|
|
2614
|
+
closing;
|
|
2615
|
+
/** Closures of handles that were never adopted; disposal joins them too. */
|
|
2616
|
+
tracked = [];
|
|
2617
|
+
/** Chrome starts in flight, so disposal waits for the handle they will produce. */
|
|
2618
|
+
starting = [];
|
|
2619
|
+
disposed = false;
|
|
2620
|
+
/** Bumped by `close()`: an attempt from an older session must not resurrect a window. */
|
|
2621
|
+
session = 0;
|
|
2622
|
+
options;
|
|
2623
|
+
constructor(options = {}) {
|
|
2624
|
+
this.options = options;
|
|
2625
|
+
}
|
|
2626
|
+
/**
|
|
2627
|
+
* Show `url` in the temporary window, reusing one already open. Concurrent calls share
|
|
2628
|
+
* a single attempt: the caller's button stays honest about what actually happened.
|
|
2629
|
+
*/
|
|
2630
|
+
async open(url) {
|
|
2631
|
+
if (validateActivationUrl(url) === null) return { code: "invalid-url", opened: false };
|
|
2632
|
+
if (this.disposed) return { code: "failed", opened: false };
|
|
2633
|
+
if (this.pending) return this.pending;
|
|
2634
|
+
const attempt = this.attempt(url);
|
|
2635
|
+
this.pending = attempt;
|
|
2636
|
+
try {
|
|
2637
|
+
return await attempt;
|
|
2638
|
+
} finally {
|
|
2639
|
+
if (this.pending === attempt) this.pending = void 0;
|
|
2640
|
+
}
|
|
2641
|
+
}
|
|
2642
|
+
/**
|
|
2643
|
+
* Close the temporary window and stay usable: a cancelled or expired job takes its
|
|
2644
|
+
* window away, while the next login may open a new one.
|
|
2645
|
+
*/
|
|
2646
|
+
async close() {
|
|
2647
|
+
this.session++;
|
|
2648
|
+
await this.closeCurrent();
|
|
2649
|
+
}
|
|
2650
|
+
/**
|
|
2651
|
+
* Retire the launcher for good. Cleanup is best-effort and bounded: it joins in-flight
|
|
2652
|
+
* starts and closures, but a browser that ignores `close()` may outlive the 5 s wait.
|
|
2653
|
+
*/
|
|
2654
|
+
async dispose() {
|
|
2655
|
+
this.disposed = true;
|
|
2656
|
+
this.session++;
|
|
2657
|
+
if (this.disposal) return this.disposal;
|
|
2658
|
+
this.disposal = (async () => {
|
|
2659
|
+
await this.bounded(Date.now() + ACTIVATION_CLOSE_TIMEOUT_MS, async () => {
|
|
2660
|
+
await Promise.allSettled(this.starting);
|
|
2661
|
+
});
|
|
2662
|
+
await this.closing;
|
|
2663
|
+
await this.closeCurrent();
|
|
2664
|
+
await this.bounded(Date.now() + ACTIVATION_CLOSE_TIMEOUT_MS, async () => {
|
|
2665
|
+
await Promise.all(this.tracked);
|
|
2666
|
+
});
|
|
2667
|
+
})();
|
|
2668
|
+
return this.disposal;
|
|
2669
|
+
}
|
|
2670
|
+
/**
|
|
2671
|
+
* Close the window this launcher owns right now. Ownership is taken before the async
|
|
2672
|
+
* close, so a job that adopts a new window afterwards is not affected by this closure.
|
|
2673
|
+
* The closure promise is shared so a later `dispose()` joins it.
|
|
2674
|
+
*/
|
|
2675
|
+
async closeCurrent() {
|
|
2676
|
+
if (this.closing === void 0) {
|
|
2677
|
+
const browser = this.browser;
|
|
2678
|
+
this.browser = void 0;
|
|
2679
|
+
this.page = void 0;
|
|
2680
|
+
if (browser === void 0) return;
|
|
2681
|
+
this.closing = closeBounded(browser);
|
|
2682
|
+
}
|
|
2683
|
+
const closing = this.closing;
|
|
2684
|
+
try {
|
|
2685
|
+
await closing;
|
|
2686
|
+
} finally {
|
|
2687
|
+
if (this.closing === closing) this.closing = void 0;
|
|
2688
|
+
}
|
|
2689
|
+
}
|
|
2690
|
+
async attempt(url) {
|
|
2691
|
+
const session = this.session;
|
|
2692
|
+
const budget = this.options.timeoutMs ?? ACTIVATION_OPEN_BUDGET_MS;
|
|
2693
|
+
const deadline = Date.now() + budget;
|
|
2694
|
+
try {
|
|
2695
|
+
if (this.options.available?.() === false) return { code: "unavailable", opened: false };
|
|
2696
|
+
const browser = await this.ensureBrowser(session, deadline);
|
|
2697
|
+
if (browser === void 0) return { code: this.stale(session) ? "failed" : "unavailable", opened: false };
|
|
2698
|
+
if (this.stale(session)) {
|
|
2699
|
+
await this.bounded(deadline, () => this.closeCurrent());
|
|
2700
|
+
return { code: "failed", opened: false };
|
|
2701
|
+
}
|
|
2702
|
+
const page = await this.ensurePage(browser, session, deadline);
|
|
2703
|
+
if (page === void 0) {
|
|
2704
|
+
await this.bounded(deadline, () => this.closeCurrent());
|
|
2705
|
+
return { code: "failed", opened: false };
|
|
2706
|
+
}
|
|
2707
|
+
const remaining = Math.max(1, Math.min(ACTIVATION_NAVIGATE_TIMEOUT_MS, deadline - Date.now()));
|
|
2708
|
+
await page.goto(url, { waitUntil: "domcontentloaded", timeout: remaining });
|
|
2709
|
+
if (this.stale(session)) {
|
|
2710
|
+
await this.bounded(deadline, () => this.closeCurrent());
|
|
2711
|
+
return { code: "failed", opened: false };
|
|
2712
|
+
}
|
|
2713
|
+
return { code: "opened", opened: true };
|
|
2714
|
+
} catch {
|
|
2715
|
+
await this.bounded(deadline, () => this.closeCurrent());
|
|
2716
|
+
return { code: "failed", opened: false };
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
2719
|
+
/** The job that started this attempt ended, or the launcher retired. */
|
|
2720
|
+
stale(session) {
|
|
2721
|
+
return this.disposed || session !== this.session;
|
|
2722
|
+
}
|
|
2723
|
+
async ensureBrowser(session, deadline) {
|
|
2724
|
+
const open = this.browser;
|
|
2725
|
+
if (open !== void 0) {
|
|
2726
|
+
try {
|
|
2727
|
+
if (open.isConnected()) return open;
|
|
2728
|
+
} catch {
|
|
2729
|
+
}
|
|
2730
|
+
await this.bounded(deadline, () => this.closeCurrent());
|
|
2731
|
+
}
|
|
2732
|
+
for (let round = 0; round < 2; round++) {
|
|
2733
|
+
await this.bounded(deadline, async () => {
|
|
2734
|
+
await Promise.allSettled([this.closing, ...this.tracked, ...this.starting]);
|
|
2735
|
+
});
|
|
2736
|
+
if (this.stale(session) || Date.now() >= deadline) return void 0;
|
|
2737
|
+
if (this.closing === void 0 && this.tracked.length === 0 && this.starting.length === 0) break;
|
|
2738
|
+
}
|
|
2739
|
+
if (this.stale(session) || Date.now() >= deadline) return void 0;
|
|
2740
|
+
const launch = this.options.launch ?? defaultLaunch;
|
|
2741
|
+
const starting = launch();
|
|
2742
|
+
this.starting.push(starting);
|
|
2743
|
+
const browser = await this.bounded(deadline, () => starting);
|
|
2744
|
+
void starting.then((handle) => {
|
|
2745
|
+
if (handle !== browser) void this.trackClose(handle);
|
|
2746
|
+
}).catch(() => void 0).finally(() => {
|
|
2747
|
+
this.starting = this.starting.filter((item) => item !== starting);
|
|
2748
|
+
});
|
|
2749
|
+
if (browser === void 0) return void 0;
|
|
2750
|
+
if (this.stale(session)) {
|
|
2751
|
+
void this.trackClose(browser);
|
|
2752
|
+
return void 0;
|
|
2753
|
+
}
|
|
2754
|
+
this.adopt(browser);
|
|
2755
|
+
return browser;
|
|
2756
|
+
}
|
|
2757
|
+
async ensurePage(browser, session, deadline) {
|
|
2758
|
+
if (this.page !== void 0) return this.page;
|
|
2759
|
+
const page = await this.bounded(deadline, () => browser.newPage(), (late) => {
|
|
2760
|
+
void this.trackPageClose(late);
|
|
2761
|
+
});
|
|
2762
|
+
if (page === void 0) return void 0;
|
|
2763
|
+
if (this.stale(session) || this.browser !== browser) {
|
|
2764
|
+
await this.bounded(deadline, () => this.closeCurrent());
|
|
2765
|
+
return void 0;
|
|
2766
|
+
}
|
|
2767
|
+
this.page = page;
|
|
2768
|
+
return page;
|
|
2769
|
+
}
|
|
2770
|
+
/** A handle that arrived too late is closed, and still counts as work disposal joins. */
|
|
2771
|
+
trackClose(browser) {
|
|
2772
|
+
const closing = closeBounded(browser);
|
|
2773
|
+
this.tracked.push(closing);
|
|
2774
|
+
void closing.finally(() => {
|
|
2775
|
+
this.tracked = this.tracked.filter((item) => item !== closing);
|
|
2776
|
+
});
|
|
2777
|
+
return closing;
|
|
2778
|
+
}
|
|
2779
|
+
trackPageClose(page) {
|
|
2780
|
+
try {
|
|
2781
|
+
return Promise.resolve(page.close?.()).catch(() => void 0);
|
|
2782
|
+
} catch {
|
|
2783
|
+
return Promise.resolve();
|
|
2784
|
+
}
|
|
2785
|
+
}
|
|
2786
|
+
adopt(browser) {
|
|
2787
|
+
this.browser = browser;
|
|
2788
|
+
browser.on?.("disconnected", () => {
|
|
2789
|
+
if (this.browser === browser) {
|
|
2790
|
+
this.browser = void 0;
|
|
2791
|
+
this.page = void 0;
|
|
2792
|
+
}
|
|
2793
|
+
});
|
|
2794
|
+
}
|
|
2795
|
+
/**
|
|
2796
|
+
* Bound one launch step by the attempt's shared deadline. A step that settles only
|
|
2797
|
+
* after that deadline still hands its handle back: dropping it would leak a Chrome
|
|
2798
|
+
* process nobody can close.
|
|
2799
|
+
*/
|
|
2800
|
+
async bounded(deadline, start, discardOnLate) {
|
|
2801
|
+
let timer;
|
|
2802
|
+
let expired = false;
|
|
2803
|
+
const work = (async () => {
|
|
2804
|
+
try {
|
|
2805
|
+
return await start();
|
|
2806
|
+
} catch {
|
|
2807
|
+
return void 0;
|
|
2808
|
+
}
|
|
2809
|
+
})().then((value) => {
|
|
2810
|
+
if (expired && value !== void 0) discardOnLate?.(value);
|
|
2811
|
+
return value;
|
|
2812
|
+
});
|
|
2813
|
+
const timeout = new Promise((resolve) => {
|
|
2814
|
+
timer = setTimeout(() => {
|
|
2815
|
+
expired = true;
|
|
2816
|
+
resolve(void 0);
|
|
2817
|
+
}, Math.max(1, deadline - Date.now()));
|
|
2818
|
+
});
|
|
2819
|
+
try {
|
|
2820
|
+
return await Promise.race([work, timeout]);
|
|
2821
|
+
} finally {
|
|
2822
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
2823
|
+
}
|
|
2824
|
+
}
|
|
2825
|
+
};
|
|
2826
|
+
function closeBounded(browser) {
|
|
2827
|
+
let timer;
|
|
2828
|
+
return Promise.race([
|
|
2829
|
+
(async () => {
|
|
2830
|
+
try {
|
|
2831
|
+
await browser.close();
|
|
2832
|
+
} catch {
|
|
2833
|
+
}
|
|
2834
|
+
})(),
|
|
2835
|
+
new Promise((resolve) => {
|
|
2836
|
+
timer = setTimeout(resolve, ACTIVATION_CLOSE_TIMEOUT_MS);
|
|
2837
|
+
})
|
|
2838
|
+
]).finally(() => {
|
|
2839
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
2462
2840
|
});
|
|
2463
2841
|
}
|
|
2842
|
+
async function defaultLaunch() {
|
|
2843
|
+
const playwright = await import("playwright-core");
|
|
2844
|
+
return playwright.chromium.launch(chromeLaunchOptions());
|
|
2845
|
+
}
|
|
2464
2846
|
|
|
2465
2847
|
// src/quota.ts
|
|
2466
2848
|
import { attributionHeaders as attributionHeaders3 } from "@deepseek-ai/dsh-llm";
|
|
@@ -2468,6 +2850,7 @@ var API_BASE = "https://amr-api.open-design.ai/api/v1";
|
|
|
2468
2850
|
var CURRENT_URL = `${API_BASE}/workspaces/current`;
|
|
2469
2851
|
var SUMMARY_URL = `${API_BASE}/billing/summary`;
|
|
2470
2852
|
var usageUrl = (workspace) => `${API_BASE}/workspaces/${encodeURIComponent(workspace)}/billing/coding-plan-usage`;
|
|
2853
|
+
var preflightUrl = (workspace) => `${API_BASE}/workspaces/${encodeURIComponent(workspace)}/billing/preflight`;
|
|
2471
2854
|
var ConsoleError = class extends Error {
|
|
2472
2855
|
constructor(kind, status) {
|
|
2473
2856
|
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");
|
|
@@ -2478,46 +2861,90 @@ var ConsoleError = class extends Error {
|
|
|
2478
2861
|
status;
|
|
2479
2862
|
};
|
|
2480
2863
|
async function consoleJson(url, session, signal, fetcher = fetch) {
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
await response.body?.cancel();
|
|
2489
|
-
throw new ConsoleError(response.status === 401 ? "auth" : "permission", response.status);
|
|
2490
|
-
}
|
|
2491
|
-
if (!response.ok) {
|
|
2492
|
-
await response.body?.cancel();
|
|
2493
|
-
throw new ConsoleError(response.status === 429 ? "rate-limit" : "network", response.status);
|
|
2494
|
-
}
|
|
2495
|
-
const reader = response.body?.getReader();
|
|
2496
|
-
if (!reader) throw new ConsoleError("response");
|
|
2497
|
-
let size = 0, raw = "";
|
|
2498
|
-
const decoder = new TextDecoder();
|
|
2499
|
-
try {
|
|
2500
|
-
while (true) {
|
|
2501
|
-
const chunk = await reader.read();
|
|
2502
|
-
if (chunk.done) break;
|
|
2503
|
-
size += chunk.value.byteLength;
|
|
2504
|
-
if (size > 2 * 1024 * 1024) throw new ConsoleError("response");
|
|
2505
|
-
raw += decoder.decode(chunk.value, { stream: true });
|
|
2864
|
+
signal?.throwIfAborted();
|
|
2865
|
+
if (session.credentialType === "device") {
|
|
2866
|
+
let target;
|
|
2867
|
+
try {
|
|
2868
|
+
target = new URL(url);
|
|
2869
|
+
} catch {
|
|
2870
|
+
throw new ConsoleError("response");
|
|
2506
2871
|
}
|
|
2507
|
-
|
|
2508
|
-
} finally {
|
|
2509
|
-
await reader.cancel().catch(() => void 0);
|
|
2510
|
-
reader.releaseLock();
|
|
2872
|
+
if (target.origin !== "https://amr-api.open-design.ai" || target.username || target.password) throw new ConsoleError("response");
|
|
2511
2873
|
}
|
|
2512
|
-
|
|
2874
|
+
const requestSignal = combinedSignal(signal, 2e4);
|
|
2875
|
+
let rejectAbort = () => void 0;
|
|
2876
|
+
const aborted = new Promise((_resolve, reject) => {
|
|
2877
|
+
rejectAbort = reject;
|
|
2878
|
+
});
|
|
2879
|
+
const onAbort = () => rejectAbort(new ConsoleError("network"));
|
|
2880
|
+
requestSignal.addEventListener("abort", onAbort, { once: true });
|
|
2881
|
+
const read = async () => {
|
|
2882
|
+
const response = await fetcher(url, { headers: { ...attributionHeaders3(), accept: "application/json", ...session.headers }, signal: requestSignal, redirect: "error", credentials: "omit" });
|
|
2883
|
+
const cancelBody = () => {
|
|
2884
|
+
void response.body?.cancel().catch(() => void 0);
|
|
2885
|
+
};
|
|
2886
|
+
if (requestSignal.aborted) {
|
|
2887
|
+
cancelBody();
|
|
2888
|
+
throw new ConsoleError("network");
|
|
2889
|
+
}
|
|
2890
|
+
if (response.redirected || response.url && response.url !== url) {
|
|
2891
|
+
cancelBody();
|
|
2892
|
+
throw new ConsoleError("response");
|
|
2893
|
+
}
|
|
2894
|
+
if (response.status === 401 || response.status === 403) {
|
|
2895
|
+
cancelBody();
|
|
2896
|
+
throw new ConsoleError(response.status === 401 ? "auth" : "permission", response.status);
|
|
2897
|
+
}
|
|
2898
|
+
if (!response.ok) {
|
|
2899
|
+
cancelBody();
|
|
2900
|
+
throw new ConsoleError(response.status === 429 ? "rate-limit" : "network", response.status);
|
|
2901
|
+
}
|
|
2902
|
+
const declared = response.headers.get("content-length");
|
|
2903
|
+
if (declared !== null && (!/^\d+$/u.test(declared) || Number(declared) > 2 * 1024 * 1024)) {
|
|
2904
|
+
cancelBody();
|
|
2905
|
+
throw new ConsoleError("response");
|
|
2906
|
+
}
|
|
2907
|
+
const reader = response.body?.getReader();
|
|
2908
|
+
if (!reader) throw new ConsoleError("response");
|
|
2909
|
+
const cancelReader = () => {
|
|
2910
|
+
void reader.cancel().catch(() => void 0);
|
|
2911
|
+
};
|
|
2912
|
+
requestSignal.addEventListener("abort", cancelReader, { once: true });
|
|
2913
|
+
let size = 0, raw = "";
|
|
2914
|
+
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
2915
|
+
try {
|
|
2916
|
+
while (true) {
|
|
2917
|
+
const chunk = await reader.read();
|
|
2918
|
+
if (requestSignal.aborted) throw new ConsoleError("network");
|
|
2919
|
+
if (chunk.done) break;
|
|
2920
|
+
size += chunk.value.byteLength;
|
|
2921
|
+
if (size > 2 * 1024 * 1024) throw new ConsoleError("response");
|
|
2922
|
+
raw += decoder.decode(chunk.value, { stream: true });
|
|
2923
|
+
}
|
|
2924
|
+
raw += decoder.decode();
|
|
2925
|
+
} finally {
|
|
2926
|
+
requestSignal.removeEventListener("abort", cancelReader);
|
|
2927
|
+
cancelReader();
|
|
2928
|
+
reader.releaseLock();
|
|
2929
|
+
}
|
|
2930
|
+
let body;
|
|
2931
|
+
try {
|
|
2932
|
+
body = JSON.parse(raw);
|
|
2933
|
+
} catch {
|
|
2934
|
+
throw new ConsoleError("response");
|
|
2935
|
+
}
|
|
2936
|
+
if (record(body) && (body.code === 401 || body.code === "401")) throw new ConsoleError("auth", 401);
|
|
2937
|
+
if (record(body) && (body.code === 403 || body.code === "403")) throw new ConsoleError("permission", 403);
|
|
2938
|
+
return body;
|
|
2939
|
+
};
|
|
2513
2940
|
try {
|
|
2514
|
-
|
|
2515
|
-
} catch {
|
|
2516
|
-
|
|
2941
|
+
return await Promise.race([read(), aborted]);
|
|
2942
|
+
} catch (error) {
|
|
2943
|
+
signal?.throwIfAborted();
|
|
2944
|
+
throw error instanceof ConsoleError ? error : new ConsoleError("network");
|
|
2945
|
+
} finally {
|
|
2946
|
+
requestSignal.removeEventListener("abort", onAbort);
|
|
2517
2947
|
}
|
|
2518
|
-
if (record(body) && (body.code === 401 || body.code === "401")) throw new ConsoleError("auth", 401);
|
|
2519
|
-
if (record(body) && (body.code === 403 || body.code === "403")) throw new ConsoleError("permission", 403);
|
|
2520
|
-
return body;
|
|
2521
2948
|
}
|
|
2522
2949
|
function number(value, negative = false) {
|
|
2523
2950
|
const n = typeof value === "number" ? value : typeof value === "string" && /^-?\d+(\.\d+)?$/u.test(value.trim()) ? Number(value) : NaN;
|
|
@@ -2555,7 +2982,51 @@ function normalizeBalances(summary) {
|
|
|
2555
2982
|
const totalUsd = number(summary.balanceUsd, true) ?? number(summary.totalAvailableCreditsUsd, true) ?? (divisor !== void 0 && total !== void 0 ? total / divisor : void 0);
|
|
2556
2983
|
return { ...divisor !== void 0 && subscription !== void 0 ? { subscriptionUsd: subscription / divisor } : {}, ...divisor !== void 0 && recharge !== void 0 ? { rechargeUsd: recharge / divisor } : {}, ...totalUsd !== void 0 ? { totalUsd } : {} };
|
|
2557
2984
|
}
|
|
2985
|
+
async function readDeviceQuota(session, signal, fetcher = fetch) {
|
|
2986
|
+
const common = { source: session.source, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
2987
|
+
let current;
|
|
2988
|
+
try {
|
|
2989
|
+
current = await consoleJson(CURRENT_URL, session, signal, fetcher);
|
|
2990
|
+
} catch (error) {
|
|
2991
|
+
signal?.throwIfAborted();
|
|
2992
|
+
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" };
|
|
2993
|
+
throw error;
|
|
2994
|
+
}
|
|
2995
|
+
if (!record(current)) throw new ConsoleError("response");
|
|
2996
|
+
const workspaceId = typeof current.activeWorkspaceId === "string" && current.activeWorkspaceId.trim() ? current.activeWorkspaceId : current.workspaceId;
|
|
2997
|
+
if (typeof workspaceId !== "string" || !/^[A-Za-z0-9_-]{1,64}$/u.test(workspaceId)) throw new ConsoleError("response");
|
|
2998
|
+
const personal = current.workspaceType === "personal";
|
|
2999
|
+
const [preflight, billing] = await Promise.allSettled([
|
|
3000
|
+
consoleJson(preflightUrl(workspaceId), session, signal, fetcher),
|
|
3001
|
+
personal ? consoleJson(SUMMARY_URL, session, signal, fetcher) : Promise.resolve(void 0)
|
|
3002
|
+
]);
|
|
3003
|
+
signal?.throwIfAborted();
|
|
3004
|
+
for (const item of [preflight, billing]) if (item.status === "rejected" && item.reason instanceof ConsoleError && item.reason.kind === "auth") throw item.reason;
|
|
3005
|
+
const summary = billing.status === "fulfilled" ? billing.value : void 0;
|
|
3006
|
+
const flight = preflight.status === "fulfilled" && record(preflight.value) ? preflight.value : void 0;
|
|
3007
|
+
let normalized;
|
|
3008
|
+
try {
|
|
3009
|
+
normalized = normalizeQuota(flight?.codingPlan, personal ? summary : void 0);
|
|
3010
|
+
} catch (error) {
|
|
3011
|
+
if (!(error instanceof ConsoleError)) throw error;
|
|
3012
|
+
}
|
|
3013
|
+
const balances = personal ? normalizeBalances(summary) : {};
|
|
3014
|
+
const total = personal ? number(flight?.balanceUsd, true) : void 0;
|
|
3015
|
+
if (total !== void 0) balances.totalUsd = total;
|
|
3016
|
+
const permission = [preflight, billing].some((item) => item.status === "rejected" && item.reason instanceof ConsoleError && item.reason.kind === "permission");
|
|
3017
|
+
if (!normalized && !Object.keys(balances).length && !permission) {
|
|
3018
|
+
if (preflight.status === "rejected") throw preflight.reason;
|
|
3019
|
+
throw new ConsoleError("response");
|
|
3020
|
+
}
|
|
3021
|
+
const windows = normalized?.windows ?? [];
|
|
3022
|
+
const missingWindows = normalized?.eligible !== false && !windows.length;
|
|
3023
|
+
const missingBalances = personal && Object.keys(balances).length < 3;
|
|
3024
|
+
const partial = missingWindows || missingBalances || !personal || permission;
|
|
3025
|
+
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;
|
|
3026
|
+
return { ...common, ...normalized, ...normalizeSubscription(personal ? summary : void 0), windows, balances, state: partial ? "partial" : "ok", ...note ? { note } : {} };
|
|
3027
|
+
}
|
|
2558
3028
|
async function readQuota(session, signal, fetcher = fetch) {
|
|
3029
|
+
if (session.credentialType === "device") return readDeviceQuota(session, signal, fetcher);
|
|
2559
3030
|
const current = await consoleJson(CURRENT_URL, session, signal, fetcher);
|
|
2560
3031
|
if (!record(current) || typeof current.workspaceId !== "string" || !/^[A-Za-z0-9_-]{1,64}$/u.test(current.workspaceId)) throw new ConsoleError("response");
|
|
2561
3032
|
const personal = current.workspaceType === "personal";
|
|
@@ -2660,6 +3131,7 @@ var MAX_API_KEY = 16384;
|
|
|
2660
3131
|
var HTTP_ASCII = /^[\x21-\x7e]+$/u;
|
|
2661
3132
|
var ENV_REF = /^[A-Za-z_][A-Za-z0-9_]*$/u;
|
|
2662
3133
|
var MODEL_ID2 = /^[A-Za-z0-9._:/-]{1,128}$/u;
|
|
3134
|
+
var ACTIVATION_OPEN_CODES = ["opened", "no-link", "invalid-url", "unavailable", "failed"];
|
|
2663
3135
|
var LOGIN_PHASES = ["idle", "launching", "waiting", "verifying", "success", "cancelled", "error"];
|
|
2664
3136
|
var QUOTA_STATES = ["ok", "partial", "need-login", "error"];
|
|
2665
3137
|
var SESSION_SOURCES = ["desktop", "login", "environment"];
|
|
@@ -2747,9 +3219,19 @@ function parseLoginState(value) {
|
|
|
2747
3219
|
if (!record(value)) throw new Error("\u767B\u5F55\u72B6\u6001\u5FC5\u987B\u662F\u5BF9\u8C61");
|
|
2748
3220
|
const jobId = value.jobId;
|
|
2749
3221
|
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");
|
|
3222
|
+
const state = member(value.state, LOGIN_PHASES, "\u767B\u5F55\u9636\u6BB5");
|
|
3223
|
+
let approval = {};
|
|
3224
|
+
if (state === "waiting" && (value.activationUrl !== void 0 || value.userCode !== void 0 || value.expiresAt !== void 0)) {
|
|
3225
|
+
const userCode = text(value.userCode, "\u8BBE\u5907\u6388\u6743\u7801", 64);
|
|
3226
|
+
if (!/^[A-Za-z0-9-]+$/u.test(userCode)) throw new Error("\u8BBE\u5907\u6388\u6743\u7801\u683C\u5F0F\u65E0\u6548");
|
|
3227
|
+
const activationUrl = validateActivationUrl(value.activationUrl, userCode);
|
|
3228
|
+
if (activationUrl === null) throw new Error("\u8BBE\u5907\u6388\u6743\u94FE\u63A5\u65E0\u6548");
|
|
3229
|
+
approval = { activationUrl, userCode, expiresAt: isoTime(value.expiresAt, "\u8BBE\u5907\u6388\u6743\u5230\u671F\u65F6\u95F4") };
|
|
3230
|
+
}
|
|
2750
3231
|
return {
|
|
2751
3232
|
jobId: typeof jobId === "string" && jobId.length > 0 ? text(jobId, "\u767B\u5F55\u4EFB\u52A1 id", MAX_JOB_ID) : null,
|
|
2752
|
-
state
|
|
3233
|
+
state,
|
|
3234
|
+
...approval,
|
|
2753
3235
|
...value.message === void 0 ? {} : { message: text(value.message, "\u767B\u5F55\u8BF4\u660E", MAX_MESSAGE) },
|
|
2754
3236
|
...value.startedAt === void 0 ? {} : { startedAt: isoTime(value.startedAt, "\u767B\u5F55\u5F00\u59CB\u65F6\u95F4") },
|
|
2755
3237
|
...value.finishedAt === void 0 ? {} : { finishedAt: isoTime(value.finishedAt, "\u767B\u5F55\u7ED3\u675F\u65F6\u95F4") }
|
|
@@ -2825,6 +3307,10 @@ function parseSaveApiKeyRequest(value) {
|
|
|
2825
3307
|
if (key.length > MAX_API_KEY) throw new Error(`API Key \u4E0D\u80FD\u8D85\u8FC7 ${MAX_API_KEY} \u5B57\u7B26`);
|
|
2826
3308
|
return { value: key, expectedRevision: text(value.expectedRevision, "\u914D\u7F6E\u7248\u672C", MAX_REVISION) };
|
|
2827
3309
|
}
|
|
3310
|
+
function parseActivationOpenState(value) {
|
|
3311
|
+
if (!record(value)) throw new Error("\u6388\u6743\u9875\u6253\u5F00\u7ED3\u679C\u5FC5\u987B\u662F\u5BF9\u8C61");
|
|
3312
|
+
return { code: member(value.code, ACTIVATION_OPEN_CODES, "\u6388\u6743\u9875\u6253\u5F00\u7ED3\u679C"), opened: flag(value.opened, "\u6388\u6743\u9875\u662F\u5426\u5DF2\u6253\u5F00") };
|
|
3313
|
+
}
|
|
2828
3314
|
|
|
2829
3315
|
// src/index.ts
|
|
2830
3316
|
var name = "llm-opendesign-corouter";
|
|
@@ -2834,17 +3320,20 @@ var SESSION = credentialKey(name, "console");
|
|
|
2834
3320
|
var hash = (value) => createHash("sha256").update(JSON.stringify(value) ?? "").digest("hex");
|
|
2835
3321
|
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" });
|
|
2836
3322
|
function grantOf(value) {
|
|
2837
|
-
if (value?.kind !== "grant" || !record(value.payload)
|
|
3323
|
+
if (value?.kind !== "grant" || !record(value.payload)) return;
|
|
3324
|
+
if (value.payload.kind === "device") return deviceGrant(value.payload);
|
|
3325
|
+
if (typeof value.payload.cookie !== "string") return;
|
|
2838
3326
|
return { cookie: value.payload.cookie, ...typeof value.payload.expiresAt === "number" ? { expiresAt: value.payload.expiresAt } : {} };
|
|
2839
3327
|
}
|
|
2840
|
-
var _saveApiKey_dec, _cancelLogin_dec, _loginStatus_dec, _startLogin_dec, _refreshQuotaNow_dec, _refreshQuota_dec, _refreshModels_dec, _status_dec, _a, _init;
|
|
2841
|
-
var OpenDesignCoRouterService = class extends (_a = TypertRemoteService, _status_dec = [Remote("status")], _refreshModels_dec = [Remote("refreshModels")], _refreshQuota_dec = [Remote("refreshQuota")], _refreshQuotaNow_dec = [Remote("refreshQuotaNow")], _startLogin_dec = [Remote("startLogin")], _loginStatus_dec = [Remote("loginStatus")], _cancelLogin_dec = [Remote("cancelLogin")], _saveApiKey_dec = [Remote("saveApiKey")], _a) {
|
|
3328
|
+
var _saveApiKey_dec, _openActivationPage_dec, _cancelLogin_dec, _loginStatus_dec, _startLogin_dec, _refreshQuotaNow_dec, _refreshQuota_dec, _refreshModels_dec, _status_dec, _a, _init;
|
|
3329
|
+
var OpenDesignCoRouterService = class extends (_a = TypertRemoteService, _status_dec = [Remote("status")], _refreshModels_dec = [Remote("refreshModels")], _refreshQuota_dec = [Remote("refreshQuota")], _refreshQuotaNow_dec = [Remote("refreshQuotaNow")], _startLogin_dec = [Remote("startLogin")], _loginStatus_dec = [Remote("loginStatus")], _cancelLogin_dec = [Remote("cancelLogin")], _openActivationPage_dec = [Remote("openActivationPage")], _saveApiKey_dec = [Remote("saveApiKey")], _a) {
|
|
2842
3330
|
constructor(ctx, config, dependencies = { resolveSession, readQuota, readDesktopQuota }) {
|
|
2843
3331
|
super(ctx, "openDesignCoRouter");
|
|
2844
3332
|
this.dependencies = dependencies;
|
|
2845
3333
|
__runInitializers(_init, 5, this);
|
|
2846
3334
|
__publicField(this, "_config");
|
|
2847
3335
|
__publicField(this, "_login");
|
|
3336
|
+
__publicField(this, "_activation");
|
|
2848
3337
|
__publicField(this, "_lifetime", new AbortController());
|
|
2849
3338
|
__publicField(this, "_revision", randomUUID2());
|
|
2850
3339
|
__publicField(this, "_fingerprint", "");
|
|
@@ -2871,13 +3360,7 @@ var OpenDesignCoRouterService = class extends (_a = TypertRemoteService, _status
|
|
|
2871
3360
|
this._credentials();
|
|
2872
3361
|
return { revision: expectedRevision, sessionHash: hash(state.sessionRecord), configHash: hash(state.config) };
|
|
2873
3362
|
},
|
|
2874
|
-
|
|
2875
|
-
const session = cookieSession(cookie, "login");
|
|
2876
|
-
if (!session) throw new ConsoleError("auth");
|
|
2877
|
-
const quota = await this.dependencies.readQuota(session, signal);
|
|
2878
|
-
return { balanceOk: true, coverage: quota.state === "ok" ? "complete" : "partial", windows: quota.windows };
|
|
2879
|
-
},
|
|
2880
|
-
commit: async (cookie, captured, signal, expiresAt) => {
|
|
3363
|
+
commit: async (tokens, captured, signal) => {
|
|
2881
3364
|
if (!record(captured) || typeof captured.revision !== "string" || typeof captured.sessionHash !== "string" || typeof captured.configHash !== "string") throw this._conflict();
|
|
2882
3365
|
await this._state();
|
|
2883
3366
|
this._expect(captured.revision);
|
|
@@ -2885,14 +3368,27 @@ var OpenDesignCoRouterService = class extends (_a = TypertRemoteService, _status
|
|
|
2885
3368
|
await this._credentials().modifyRecord(SESSION, async (current) => {
|
|
2886
3369
|
signal.throwIfAborted();
|
|
2887
3370
|
if (hash(current) !== captured.sessionHash || hash(this._config()) !== captured.configHash) throw this._conflict();
|
|
2888
|
-
|
|
3371
|
+
const grant = deviceGrant({ kind: "device", ...tokens });
|
|
3372
|
+
if (!grant) throw new ConsoleError("auth");
|
|
3373
|
+
return { kind: "grant", payload: { ...grant, savedAt: (/* @__PURE__ */ new Date()).toISOString() } };
|
|
2889
3374
|
});
|
|
2890
3375
|
this._invalidateQuota();
|
|
3376
|
+
this._modelsFingerprint = "";
|
|
3377
|
+
this._invalidateModels();
|
|
3378
|
+
}
|
|
3379
|
+
}, {
|
|
3380
|
+
...dependencies.loginOptions,
|
|
3381
|
+
// The Host owns the temporary window, so it closes it on every terminal transition —
|
|
3382
|
+
// including expiry, which happens on a timer even when no UI is polling.
|
|
3383
|
+
onSettled: () => {
|
|
3384
|
+
void this._activation.close();
|
|
2891
3385
|
}
|
|
2892
3386
|
});
|
|
3387
|
+
this._activation = new ActivationLauncher(dependencies.activationOptions);
|
|
2893
3388
|
ctx.effect(() => () => {
|
|
2894
3389
|
this._lifetime.abort();
|
|
2895
3390
|
this._login.dispose();
|
|
3391
|
+
return this._activation.dispose();
|
|
2896
3392
|
});
|
|
2897
3393
|
}
|
|
2898
3394
|
dependencies;
|
|
@@ -2904,28 +3400,43 @@ var OpenDesignCoRouterService = class extends (_a = TypertRemoteService, _status
|
|
|
2904
3400
|
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);
|
|
2905
3401
|
return service;
|
|
2906
3402
|
}
|
|
2907
|
-
async
|
|
3403
|
+
async _key(ref, baseURL, apiRecord, sessionRecord) {
|
|
2908
3404
|
const credentials = this.ctx.get("credentials");
|
|
2909
3405
|
const ambient = credentials ? (await credentials.resolve(credentialRef(ref)))?.value : launchEnvironmentOf(this.ctx).get(ref)?.value;
|
|
2910
3406
|
if (ambient?.trim()) return ambient;
|
|
2911
|
-
|
|
2912
|
-
|
|
3407
|
+
if (apiRecord?.kind === "api-key" && apiRecord.key?.trim()) return apiRecord.key;
|
|
3408
|
+
const grant = grantOf(sessionRecord);
|
|
3409
|
+
return isOfficialRuntimeUrl(baseURL) && grant && "kind" in grant && grant.kind === "device" ? grant.runtimeKey : void 0;
|
|
3410
|
+
}
|
|
3411
|
+
async resolveApiKey(ref, baseURL = this._config().baseURL) {
|
|
3412
|
+
const credentials = this.ctx.get("credentials");
|
|
3413
|
+
const [apiRecord, sessionRecord] = await Promise.all([credentials?.readRecord(API_KEY), credentials?.readRecord(SESSION)]);
|
|
3414
|
+
return this._key(ref, baseURL, apiRecord, sessionRecord);
|
|
3415
|
+
}
|
|
3416
|
+
_invalidateModels() {
|
|
3417
|
+
const changed = this._modelsUpdatedAt !== void 0;
|
|
3418
|
+
this._modelsEpoch++;
|
|
3419
|
+
this._modelsController?.abort();
|
|
3420
|
+
this._modelsPending = void 0;
|
|
3421
|
+
this._models = MODELS;
|
|
3422
|
+
this._modelsUpdatedAt = void 0;
|
|
3423
|
+
this._modelsError = void 0;
|
|
3424
|
+
if (changed) {
|
|
3425
|
+
try {
|
|
3426
|
+
this.dependencies.modelsChanged?.();
|
|
3427
|
+
} catch {
|
|
3428
|
+
}
|
|
3429
|
+
}
|
|
2913
3430
|
}
|
|
2914
3431
|
async _state() {
|
|
2915
3432
|
const credentials = this.ctx.get("credentials"), config = this._config();
|
|
2916
|
-
const [apiRecord, sessionRecord
|
|
3433
|
+
const [apiRecord, sessionRecord] = await Promise.all([credentials?.readRecord(API_KEY), credentials?.readRecord(SESSION)]);
|
|
3434
|
+
const key = await this._key(config.apiKeyEnv, config.baseURL, apiRecord, sessionRecord);
|
|
2917
3435
|
const state = { apiRecord, sessionRecord, key, config };
|
|
2918
3436
|
const modelsFingerprint = hash({ key, baseURL: config.baseURL });
|
|
2919
3437
|
if (modelsFingerprint !== this._modelsFingerprint) {
|
|
2920
|
-
const changed = this._modelsUpdatedAt !== void 0;
|
|
2921
3438
|
this._modelsFingerprint = modelsFingerprint;
|
|
2922
|
-
this.
|
|
2923
|
-
this._modelsController?.abort();
|
|
2924
|
-
this._modelsPending = void 0;
|
|
2925
|
-
this._models = MODELS;
|
|
2926
|
-
this._modelsUpdatedAt = void 0;
|
|
2927
|
-
this._modelsError = void 0;
|
|
2928
|
-
if (changed) this.dependencies.modelsChanged?.();
|
|
3439
|
+
this._invalidateModels();
|
|
2929
3440
|
}
|
|
2930
3441
|
const fingerprint = hash(state);
|
|
2931
3442
|
if (fingerprint !== this._fingerprint) {
|
|
@@ -3048,9 +3559,10 @@ var OpenDesignCoRouterService = class extends (_a = TypertRemoteService, _status
|
|
|
3048
3559
|
let identity;
|
|
3049
3560
|
let refreshFailed = false;
|
|
3050
3561
|
try {
|
|
3562
|
+
const grant = grantOf(await this.ctx.get("credentials")?.readRecord(SESSION));
|
|
3051
3563
|
const authDeps = {
|
|
3052
3564
|
useDesktop: config.useDesktopCredential,
|
|
3053
|
-
readGrant: async () =>
|
|
3565
|
+
readGrant: async () => grant,
|
|
3054
3566
|
resolveRef: async (ref) => {
|
|
3055
3567
|
const credentials = this.ctx.get("credentials");
|
|
3056
3568
|
return credentials ? (await credentials.resolve(credentialRef(ref)))?.value : launchEnvironmentOf(this.ctx).get(ref)?.value;
|
|
@@ -3068,7 +3580,10 @@ var OpenDesignCoRouterService = class extends (_a = TypertRemoteService, _status
|
|
|
3068
3580
|
if (fallback.state === "ready") {
|
|
3069
3581
|
try {
|
|
3070
3582
|
const complete = await this.dependencies.readQuota(fallback.session, signal);
|
|
3071
|
-
if (complete.state === "ok" && (complete.windows.length > 0 || complete.eligible === false))
|
|
3583
|
+
if (complete.state === "ok" && (complete.windows.length > 0 || complete.eligible === false)) {
|
|
3584
|
+
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}` : ""}` };
|
|
3585
|
+
identity = hash(fallback.session);
|
|
3586
|
+
}
|
|
3072
3587
|
} catch (error) {
|
|
3073
3588
|
if (signal.aborted) throw error;
|
|
3074
3589
|
}
|
|
@@ -3078,6 +3593,7 @@ var OpenDesignCoRouterService = class extends (_a = TypertRemoteService, _status
|
|
|
3078
3593
|
if (!(error instanceof ConsoleError) || !["auth", "permission"].includes(error.kind)) throw error;
|
|
3079
3594
|
const fallback = await this.dependencies.resolveSession({ ...authDeps, useDesktop: false });
|
|
3080
3595
|
if (fallback.state !== "ready") throw new ConsoleError("auth");
|
|
3596
|
+
identity = hash(fallback.session);
|
|
3081
3597
|
result = await this.dependencies.readQuota(fallback.session, signal);
|
|
3082
3598
|
}
|
|
3083
3599
|
}
|
|
@@ -3124,6 +3640,12 @@ var OpenDesignCoRouterService = class extends (_a = TypertRemoteService, _status
|
|
|
3124
3640
|
cancelLogin(input) {
|
|
3125
3641
|
return parseLoginState(this._login.cancel(parseCancelLoginRequest(input)));
|
|
3126
3642
|
}
|
|
3643
|
+
async openActivationPage() {
|
|
3644
|
+
const url = this._login.activationLink();
|
|
3645
|
+
if (url === void 0) return { code: "no-link", opened: false };
|
|
3646
|
+
const result = await this._activation.open(url);
|
|
3647
|
+
return parseActivationOpenState({ code: result.code, opened: result.opened });
|
|
3648
|
+
}
|
|
3127
3649
|
async saveApiKey(input) {
|
|
3128
3650
|
const request = parseSaveApiKeyRequest(input), state = await this._state();
|
|
3129
3651
|
this._expect(request.expectedRevision);
|
|
@@ -3143,6 +3665,7 @@ __decorateElement(_init, 1, "refreshQuotaNow", _refreshQuotaNow_dec, OpenDesignC
|
|
|
3143
3665
|
__decorateElement(_init, 1, "startLogin", _startLogin_dec, OpenDesignCoRouterService);
|
|
3144
3666
|
__decorateElement(_init, 1, "loginStatus", _loginStatus_dec, OpenDesignCoRouterService);
|
|
3145
3667
|
__decorateElement(_init, 1, "cancelLogin", _cancelLogin_dec, OpenDesignCoRouterService);
|
|
3668
|
+
__decorateElement(_init, 1, "openActivationPage", _openActivationPage_dec, OpenDesignCoRouterService);
|
|
3146
3669
|
__decorateElement(_init, 1, "saveApiKey", _saveApiKey_dec, OpenDesignCoRouterService);
|
|
3147
3670
|
__decoratorMetadata(_init, OpenDesignCoRouterService);
|
|
3148
3671
|
function apply(ctx, config) {
|
|
@@ -3155,7 +3678,7 @@ function apply(ctx, config) {
|
|
|
3155
3678
|
const adapter = new CoRouterAdapter({
|
|
3156
3679
|
config: current,
|
|
3157
3680
|
models: () => service.modelCatalog(),
|
|
3158
|
-
apiKey: (ref) => service.resolveApiKey(ref),
|
|
3681
|
+
apiKey: (ref, baseURL) => service.resolveApiKey(ref, baseURL),
|
|
3159
3682
|
onEmptyTurn: () => {
|
|
3160
3683
|
service.guardedTurns++;
|
|
3161
3684
|
},
|
|
@@ -3178,7 +3701,7 @@ function apply(ctx, config) {
|
|
|
3178
3701
|
ctx.llm.registerModelDiscovery(SETTINGS_NS, async (request, signal) => {
|
|
3179
3702
|
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");
|
|
3180
3703
|
const config2 = current();
|
|
3181
|
-
const key = request.apiKey ?? await service.resolveApiKey(config2.apiKeyEnv);
|
|
3704
|
+
const key = request.apiKey ?? await service.resolveApiKey(config2.apiKeyEnv, config2.baseURL);
|
|
3182
3705
|
if (!key) throw new LlmError11("\u8BF7\u5148\u914D\u7F6E CoRouter API Key", "MISSING_CREDENTIAL");
|
|
3183
3706
|
const models = await fetchModelCatalog(config2.baseURL, key, signal);
|
|
3184
3707
|
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 } : {} }));
|