dsh-codex-subscription 2.1.0 → 2.1.1-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +9 -3
- package/README.md +9 -3
- package/THIRD_PARTY_NOTICES.md +2 -0
- package/compatibility.json +2 -1
- package/lib/client.js +2087 -1450
- package/lib/index.js +2183 -1479
- package/package.json +23 -17
package/lib/index.js
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
|
-
import { clientRequestSchema } from "@deepseek-ai/dsh-client-connection";
|
|
2
1
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
3
|
-
import {
|
|
2
|
+
import { closeOpenAICodexWebSocketSessions, resetOpenAICodexWebSocketDebugStats } from "@earendil-works/pi-ai/api/openai-codex-responses";
|
|
3
|
+
import { execFile, spawn } from "node:child_process";
|
|
4
|
+
import { request } from "node:https";
|
|
5
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
6
|
+
import { PassThrough, Readable } from "node:stream";
|
|
7
|
+
import { promisify } from "node:util";
|
|
8
|
+
import { HttpsProxyAgent } from "https-proxy-agent";
|
|
9
|
+
import WebSocket from "ws";
|
|
10
|
+
import { clientRequestSchema } from "@deepseek-ai/dsh-client-connection";
|
|
4
11
|
import { lstat, mkdir, open, readFile, rename, rm, stat } from "node:fs/promises";
|
|
12
|
+
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
5
13
|
import * as dshCredentials from "@deepseek-ai/dsh-credentials";
|
|
6
14
|
import { dshHomePath, resolveDshHome } from "@deepseek-ai/dsh-home-paths";
|
|
7
15
|
import { LlmError, createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
8
16
|
import { PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
|
|
9
17
|
import z from "@deepseek-ai/schemastery";
|
|
10
|
-
import { execFile, spawn } from "node:child_process";
|
|
11
|
-
import { request } from "node:https";
|
|
12
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
13
|
-
import { Readable } from "node:stream";
|
|
14
|
-
import { promisify } from "node:util";
|
|
15
|
-
import { HttpsProxyAgent } from "https-proxy-agent";
|
|
16
18
|
import { openaiCodexProvider as createOpenAICodexProvider } from "@earendil-works/pi-ai/providers/openai-codex";
|
|
17
19
|
import { createModels } from "@earendil-works/pi-ai";
|
|
18
20
|
import { WebError } from "@deepseek-ai/dsh-web";
|
|
@@ -286,6 +288,21 @@ const supportsCodexFastMode = (modelId) => typeof modelId === "string" && (/^gpt
|
|
|
286
288
|
//#endregion
|
|
287
289
|
//#region src/preference-fields.js
|
|
288
290
|
const PREFERENCE_FIELDS = Object.freeze({
|
|
291
|
+
compactionMode: {
|
|
292
|
+
choices: ["dsh", "cloud"],
|
|
293
|
+
default: "dsh",
|
|
294
|
+
error: "Invalid compaction mode"
|
|
295
|
+
},
|
|
296
|
+
connectionMode: {
|
|
297
|
+
choices: ["sse", "websocket"],
|
|
298
|
+
default: "sse",
|
|
299
|
+
error: "Invalid connection mode"
|
|
300
|
+
},
|
|
301
|
+
subagentBackend: {
|
|
302
|
+
choices: ["dsh", "codex"],
|
|
303
|
+
default: "dsh",
|
|
304
|
+
error: "Invalid subagent backend"
|
|
305
|
+
},
|
|
289
306
|
[QUICK_QUOTA_MODE_FIELD]: {
|
|
290
307
|
choices: [
|
|
291
308
|
"off",
|
|
@@ -330,1572 +347,2133 @@ const PREFERENCE_FIELDS = Object.freeze({
|
|
|
330
347
|
}
|
|
331
348
|
});
|
|
332
349
|
//#endregion
|
|
333
|
-
//#region src/
|
|
334
|
-
const
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
"
|
|
347
|
-
"
|
|
348
|
-
"reset-credit/inspect",
|
|
349
|
-
"reset-credit/prepare",
|
|
350
|
-
"reset-credit/consume",
|
|
351
|
-
"image/original/chunk",
|
|
352
|
-
"sketch/connect",
|
|
353
|
-
"sketch/poll",
|
|
354
|
-
"sketch/claim",
|
|
355
|
-
"sketch/result",
|
|
356
|
-
"sketch/disconnect"
|
|
357
|
-
]);
|
|
358
|
-
//#endregion
|
|
359
|
-
//#region src/subscription-transport.js
|
|
360
|
-
/** Exact routes stay inside DSH's authenticated /api bridge and body limit. */
|
|
361
|
-
function registerSubscriptionTransport(connection, handler) {
|
|
362
|
-
const disposers = [];
|
|
350
|
+
//#region src/oauth-network.js
|
|
351
|
+
const execFileAsync = promisify(execFile);
|
|
352
|
+
const CODEX_AUTH_HOST = "auth.openai.com";
|
|
353
|
+
const CODEX_SUBSCRIPTION_HOST = "chatgpt.com";
|
|
354
|
+
const CODEX_HOSTS = /* @__PURE__ */ new Set([CODEX_AUTH_HOST, CODEX_SUBSCRIPTION_HOST]);
|
|
355
|
+
const networkScope = new AsyncLocalStorage();
|
|
356
|
+
let activeScopes = 0;
|
|
357
|
+
let baseFetch;
|
|
358
|
+
let scopedFetch;
|
|
359
|
+
let baseWebSocket;
|
|
360
|
+
let scopedWebSocket;
|
|
361
|
+
let activeWebSocketScopes = 0;
|
|
362
|
+
function normalizeProxy(raw) {
|
|
363
|
+
if (typeof raw !== "string" || raw.trim() === "") return void 0;
|
|
364
|
+
const value = raw.trim().includes("://") ? raw.trim() : `http://${raw.trim()}`;
|
|
363
365
|
try {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
requestBody: "buffered",
|
|
370
|
-
async fetch(request) {
|
|
371
|
-
if (request.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase() !== "application/json") return new Response("content type must be application/json", { status: 415 });
|
|
372
|
-
let body;
|
|
373
|
-
try {
|
|
374
|
-
body = await request.json();
|
|
375
|
-
} catch {
|
|
376
|
-
return new Response("invalid JSON", { status: 400 });
|
|
377
|
-
}
|
|
378
|
-
const envelope = clientRequestSchema.safeParse(body);
|
|
379
|
-
if (!envelope.success || envelope.data.method !== method) return new Response("invalid RPC envelope", { status: 400 });
|
|
380
|
-
let result;
|
|
381
|
-
try {
|
|
382
|
-
request.signal.throwIfAborted();
|
|
383
|
-
result = await handler(endpoint, envelope.data.payload, request.signal);
|
|
384
|
-
} catch {
|
|
385
|
-
result = {
|
|
386
|
-
ok: false,
|
|
387
|
-
error: {
|
|
388
|
-
code: "internal",
|
|
389
|
-
message: "Subscription request failed",
|
|
390
|
-
details: { issues: [] }
|
|
391
|
-
}
|
|
392
|
-
};
|
|
393
|
-
}
|
|
394
|
-
return Response.json({
|
|
395
|
-
type: "server-response",
|
|
396
|
-
rpcId: envelope.data.rpcId,
|
|
397
|
-
result
|
|
398
|
-
});
|
|
399
|
-
}
|
|
400
|
-
}));
|
|
401
|
-
}
|
|
402
|
-
} catch (error) {
|
|
403
|
-
for (const dispose of disposers.reverse()) dispose();
|
|
404
|
-
throw error;
|
|
366
|
+
const url = new URL(value);
|
|
367
|
+
if (!["http:", "https:"].includes(url.protocol) || url.hostname === "") return void 0;
|
|
368
|
+
return url.toString();
|
|
369
|
+
} catch {
|
|
370
|
+
return;
|
|
405
371
|
}
|
|
406
|
-
return () => {
|
|
407
|
-
for (const dispose of disposers.reverse()) dispose();
|
|
408
|
-
};
|
|
409
372
|
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
373
|
+
function bypassesProxy(hostname, port, rawNoProxy) {
|
|
374
|
+
if (typeof rawNoProxy !== "string" || rawNoProxy.trim() === "") return false;
|
|
375
|
+
return rawNoProxy.split(/[\s,]+/u).some((raw) => {
|
|
376
|
+
const entry = raw.trim().toLowerCase();
|
|
377
|
+
if (entry === "*") return true;
|
|
378
|
+
if (entry === "") return false;
|
|
379
|
+
const match = /^(.*?)(?::(\d+))?$/u.exec(entry);
|
|
380
|
+
const host = match?.[1]?.replace(/^\./u, "");
|
|
381
|
+
const entryPort = match?.[2];
|
|
382
|
+
if (!host || entryPort && entryPort !== port) return false;
|
|
383
|
+
return hostname === host || hostname.endsWith(`.${host}`);
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
function proxyFromEnvironment(env = process.env, target = new URL(`https://${CODEX_AUTH_HOST}/`)) {
|
|
387
|
+
if (bypassesProxy(target.hostname.toLowerCase(), target.port || "443", env.NO_PROXY ?? env.no_proxy)) return void 0;
|
|
388
|
+
return normalizeProxy(env.HTTPS_PROXY ?? env.https_proxy ?? env.ALL_PROXY ?? env.all_proxy);
|
|
389
|
+
}
|
|
390
|
+
function selectWindowsProxy(value) {
|
|
391
|
+
if (typeof value !== "string") return void 0;
|
|
392
|
+
const entries = value.split(";").map((item) => item.trim()).filter(Boolean);
|
|
393
|
+
const https = entries.find((item) => /^https=/iu.test(item));
|
|
394
|
+
const http = entries.find((item) => /^http=/iu.test(item));
|
|
395
|
+
const selected = (https ?? http ?? entries.find((item) => !item.includes("=")))?.replace(/^[^=]+=/u, "");
|
|
396
|
+
return normalizeProxy(selected);
|
|
397
|
+
}
|
|
398
|
+
async function windowsSystemProxy(options = {}) {
|
|
399
|
+
const run = options.execFile ?? execFileAsync;
|
|
400
|
+
const reg = `${process.env.SystemRoot ?? "C:\\Windows"}\\System32\\reg.exe`;
|
|
401
|
+
const key = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings";
|
|
402
|
+
try {
|
|
403
|
+
const enabled = await run(reg, [
|
|
404
|
+
"query",
|
|
405
|
+
key,
|
|
406
|
+
"/v",
|
|
407
|
+
"ProxyEnable"
|
|
408
|
+
], {
|
|
409
|
+
windowsHide: true,
|
|
410
|
+
encoding: "utf8"
|
|
411
|
+
});
|
|
412
|
+
if (!/REG_DWORD\s+0x1\b/iu.test(enabled.stdout)) return void 0;
|
|
413
|
+
const configured = await run(reg, [
|
|
414
|
+
"query",
|
|
415
|
+
key,
|
|
416
|
+
"/v",
|
|
417
|
+
"ProxyServer"
|
|
418
|
+
], {
|
|
419
|
+
windowsHide: true,
|
|
420
|
+
encoding: "utf8"
|
|
421
|
+
});
|
|
422
|
+
return selectWindowsProxy(/^\s*ProxyServer\s+REG_\w+\s+(.+)$/imu.exec(configured.stdout)?.[1]);
|
|
423
|
+
} catch {
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
async function macSystemProxy(options = {}) {
|
|
428
|
+
const run = options.execFile ?? execFileAsync;
|
|
429
|
+
try {
|
|
430
|
+
const result = await run("/usr/sbin/scutil", ["--proxy"], { encoding: "utf8" });
|
|
431
|
+
if (!/^\s*HTTPSEnable\s*:\s*1\s*$/imu.test(result.stdout)) return void 0;
|
|
432
|
+
const host = /^\s*HTTPSProxy\s*:\s*(\S+)\s*$/imu.exec(result.stdout)?.[1];
|
|
433
|
+
const port = /^\s*HTTPSPort\s*:\s*(\d+)\s*$/imu.exec(result.stdout)?.[1];
|
|
434
|
+
return normalizeProxy(host && port ? `${host}:${port}` : void 0);
|
|
435
|
+
} catch {
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
async function resolveCodexOAuthProxy(options = {}) {
|
|
440
|
+
return (await resolveCodexProxy(options)).url;
|
|
441
|
+
}
|
|
442
|
+
async function resolveCodexProxy(options = {}) {
|
|
443
|
+
const target = options.target ?? new URL(`https://${CODEX_AUTH_HOST}/`);
|
|
444
|
+
const env = options.env ?? process.env;
|
|
445
|
+
if (bypassesProxy(target.hostname.toLowerCase(), target.port || "443", env.NO_PROXY ?? env.no_proxy)) return {
|
|
446
|
+
url: void 0,
|
|
447
|
+
source: "bypass"
|
|
417
448
|
};
|
|
418
|
-
const
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
return entry;
|
|
449
|
+
const envProxy = proxyFromEnvironment(env, target);
|
|
450
|
+
if (envProxy) return {
|
|
451
|
+
url: envProxy,
|
|
452
|
+
source: "environment"
|
|
423
453
|
};
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
if (!enabled()) return Promise.reject(Error("Sketch is disabled"));
|
|
504
|
-
const entry = sessions.get(sessionId);
|
|
505
|
-
if (!entry || now() - entry.seen > 1e4) return Promise.reject(Error("Switch to this session in DSH with sketch editing enabled"));
|
|
506
|
-
if (entry.tasks.size) return Promise.reject(Error("Another sketch operation is pending"));
|
|
507
|
-
if (JSON.stringify(request).length > 2e6) return Promise.reject(Error("Sketch batch is too large"));
|
|
508
|
-
return new Promise((resolve, reject) => {
|
|
509
|
-
const id = randomUUID();
|
|
510
|
-
const finish = (callback, value) => {
|
|
511
|
-
clearTimeout(timer);
|
|
512
|
-
signal?.removeEventListener("abort", abort);
|
|
513
|
-
entry.tasks.delete(id);
|
|
514
|
-
callback(value);
|
|
515
|
-
};
|
|
516
|
-
const cancel = (message) => {
|
|
517
|
-
if (entry.tasks.get(id)?.delivered) {
|
|
518
|
-
entry.cancelled.push(id);
|
|
519
|
-
if (entry.cancelled.length > 32) entry.cancelled.shift();
|
|
520
|
-
}
|
|
521
|
-
finish(reject, Error(message));
|
|
522
|
-
};
|
|
523
|
-
const abort = () => cancel("Sketch operation interrupted; inspect recentRequests before retrying");
|
|
524
|
-
const timer = setTimeout(() => cancel("Sketch response timed out; inspect recentRequests before retrying"), timeoutMs);
|
|
525
|
-
entry.tasks.set(id, {
|
|
526
|
-
request,
|
|
527
|
-
expiresAt: now() + timeoutMs,
|
|
528
|
-
delivered: false,
|
|
529
|
-
resolve: (value) => finish(resolve, value),
|
|
530
|
-
reject: (error) => finish(reject, error)
|
|
454
|
+
const platform = options.platform ?? process.platform;
|
|
455
|
+
const system = platform === "win32" ? await windowsSystemProxy(options) : platform === "darwin" ? await macSystemProxy(options) : void 0;
|
|
456
|
+
return system ? {
|
|
457
|
+
url: system,
|
|
458
|
+
source: "system"
|
|
459
|
+
} : {
|
|
460
|
+
url: void 0,
|
|
461
|
+
source: "direct"
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
function bodyBytes(body) {
|
|
465
|
+
if (body === void 0 || body === null) return void 0;
|
|
466
|
+
if (typeof body === "string") return Buffer.from(body);
|
|
467
|
+
if (body instanceof URLSearchParams) return Buffer.from(body.toString());
|
|
468
|
+
if (body instanceof Uint8Array) return Buffer.from(body);
|
|
469
|
+
throw new TypeError("Unsupported Codex OAuth request body");
|
|
470
|
+
}
|
|
471
|
+
function fetchThroughProxy(input, init, proxyUrl) {
|
|
472
|
+
const target = new URL(typeof input === "string" || input instanceof URL ? input : input.url);
|
|
473
|
+
const body = bodyBytes(init?.body);
|
|
474
|
+
const headers = new Headers(init?.headers);
|
|
475
|
+
if (body && !headers.has("content-length")) headers.set("content-length", String(body.byteLength));
|
|
476
|
+
return new Promise((resolve, reject) => {
|
|
477
|
+
const request$1 = request(target, {
|
|
478
|
+
method: init?.method ?? "GET",
|
|
479
|
+
headers: Object.fromEntries(headers.entries()),
|
|
480
|
+
agent: new HttpsProxyAgent(proxyUrl),
|
|
481
|
+
signal: init?.signal
|
|
482
|
+
}, (response) => {
|
|
483
|
+
const responseHeaders = new Headers();
|
|
484
|
+
for (const [name, value] of Object.entries(response.headers)) if (Array.isArray(value)) value.forEach((item) => responseHeaders.append(name, item));
|
|
485
|
+
else if (value !== void 0) responseHeaders.set(name, value);
|
|
486
|
+
const status = response.statusCode ?? 500;
|
|
487
|
+
const empty = init?.method === "HEAD" || [
|
|
488
|
+
204,
|
|
489
|
+
205,
|
|
490
|
+
304
|
|
491
|
+
].includes(status);
|
|
492
|
+
resolve(new Response(empty ? null : Readable.toWeb(response), {
|
|
493
|
+
status,
|
|
494
|
+
statusText: response.statusMessage,
|
|
495
|
+
headers: responseHeaders
|
|
496
|
+
}));
|
|
497
|
+
});
|
|
498
|
+
request$1.on("error", reject);
|
|
499
|
+
if (body) request$1.write(body);
|
|
500
|
+
request$1.end();
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
async function withCodexNetwork(run, options = {}) {
|
|
504
|
+
if (options.websocket && activeWebSocketScopes === 0) {
|
|
505
|
+
baseWebSocket = globalThis.WebSocket;
|
|
506
|
+
scopedWebSocket = new Proxy(baseWebSocket ?? WebSocket, { construct(target, args, newTarget) {
|
|
507
|
+
const scope = networkScope.getStore();
|
|
508
|
+
const url = new URL(String(args[0]));
|
|
509
|
+
if (!scope?.options.websocket || url.protocol !== "wss:" || url.hostname !== CODEX_SUBSCRIPTION_HOST) return Reflect.construct(target, args, newTarget);
|
|
510
|
+
const proxy = scope.options.websocketProxy;
|
|
511
|
+
return new WebSocket(args[0], {
|
|
512
|
+
...args[1],
|
|
513
|
+
...proxy ? { agent: new HttpsProxyAgent(proxy) } : {}
|
|
514
|
+
});
|
|
515
|
+
} });
|
|
516
|
+
globalThis.WebSocket = scopedWebSocket;
|
|
517
|
+
}
|
|
518
|
+
if (options.websocket) activeWebSocketScopes += 1;
|
|
519
|
+
if (activeScopes === 0) {
|
|
520
|
+
baseFetch = globalThis.fetch;
|
|
521
|
+
scopedFetch = async (input, init) => {
|
|
522
|
+
const scope = networkScope.getStore();
|
|
523
|
+
if (scope === void 0) return baseFetch(input, init);
|
|
524
|
+
const { options: scopedOptions, allowedHosts, resolved } = scope;
|
|
525
|
+
const proxyFetch = scopedOptions.fetchThroughProxy ?? fetchThroughProxy;
|
|
526
|
+
const target = new URL(typeof input === "string" || input instanceof URL ? input : input.url);
|
|
527
|
+
if (target.protocol !== "https:" || !allowedHosts.has(target.hostname)) return baseFetch(input, init);
|
|
528
|
+
let proxy = resolved.get(target.hostname);
|
|
529
|
+
if (proxy === void 0) {
|
|
530
|
+
proxy = resolveCodexProxy({
|
|
531
|
+
...scopedOptions,
|
|
532
|
+
target
|
|
531
533
|
});
|
|
532
|
-
|
|
533
|
-
|
|
534
|
+
resolved.set(target.hostname, proxy);
|
|
535
|
+
}
|
|
536
|
+
const route = await proxy;
|
|
537
|
+
scopedOptions.onRoute?.(route.source);
|
|
538
|
+
const response = await (route.url === void 0 ? baseFetch(input, init) : proxyFetch(input, init, route.url));
|
|
539
|
+
return scopedOptions.transformResponse?.(response, target) ?? response;
|
|
540
|
+
};
|
|
541
|
+
globalThis.fetch = scopedFetch;
|
|
542
|
+
}
|
|
543
|
+
activeScopes += 1;
|
|
544
|
+
const scope = {
|
|
545
|
+
options,
|
|
546
|
+
allowedHosts: options.hosts ?? CODEX_HOSTS,
|
|
547
|
+
resolved: /* @__PURE__ */ new Map()
|
|
548
|
+
};
|
|
549
|
+
try {
|
|
550
|
+
return await networkScope.run(scope, run);
|
|
551
|
+
} finally {
|
|
552
|
+
if (options.websocket && --activeWebSocketScopes === 0) {
|
|
553
|
+
if (globalThis.WebSocket === scopedWebSocket) globalThis.WebSocket = baseWebSocket;
|
|
554
|
+
baseWebSocket = void 0;
|
|
555
|
+
scopedWebSocket = void 0;
|
|
556
|
+
}
|
|
557
|
+
activeScopes -= 1;
|
|
558
|
+
if (activeScopes === 0) {
|
|
559
|
+
if (globalThis.fetch === scopedFetch) globalThis.fetch = baseFetch;
|
|
560
|
+
baseFetch = void 0;
|
|
561
|
+
scopedFetch = void 0;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
function classifyTransportError(error) {
|
|
566
|
+
const name = error?.name;
|
|
567
|
+
const code = String(error?.code ?? error?.cause?.code ?? "");
|
|
568
|
+
if (name === "AbortError" || name === "TimeoutError" || /ETIMEDOUT|UND_ERR_CONNECT_TIMEOUT/u.test(code)) return "timeout";
|
|
569
|
+
if (/ENOTFOUND|EAI_AGAIN/u.test(code)) return "dns";
|
|
570
|
+
if (/CERT_|TLS|SSL/u.test(code)) return "tls";
|
|
571
|
+
if (/ECONN|EPIPE|UND_ERR_SOCKET/u.test(code)) return "connection";
|
|
572
|
+
return "network";
|
|
573
|
+
}
|
|
574
|
+
const elapsedBucket = (elapsed) => elapsed < 1e3 ? "under-1s" : elapsed < 5e3 ? "1-5s" : elapsed < 15e3 ? "5-15s" : "over-15s";
|
|
575
|
+
function createCodexNetworkTransport(options = {}) {
|
|
576
|
+
const attempts = /* @__PURE__ */ new Map();
|
|
577
|
+
const now = options.now ?? Date.now;
|
|
578
|
+
const run = async (area, operation, connection = {}) => {
|
|
579
|
+
const startedAt = now();
|
|
580
|
+
let route = attempts.get(area)?.route ?? "direct";
|
|
581
|
+
let routed = false;
|
|
582
|
+
try {
|
|
583
|
+
const value = await withCodexNetwork(operation, {
|
|
584
|
+
...options,
|
|
585
|
+
...connection,
|
|
586
|
+
onRoute: (source) => {
|
|
587
|
+
route = source;
|
|
588
|
+
routed = true;
|
|
589
|
+
}
|
|
590
|
+
});
|
|
591
|
+
if (value instanceof Response && !value.ok) attempts.set(area, {
|
|
592
|
+
status: "failed",
|
|
593
|
+
stage: "http",
|
|
594
|
+
code: "http-error",
|
|
595
|
+
httpStatus: value.status,
|
|
596
|
+
route,
|
|
597
|
+
elapsed: elapsedBucket(now() - startedAt)
|
|
598
|
+
});
|
|
599
|
+
else if (routed || value instanceof Response) attempts.set(area, {
|
|
600
|
+
status: "ok",
|
|
601
|
+
route,
|
|
602
|
+
elapsed: elapsedBucket(now() - startedAt)
|
|
534
603
|
});
|
|
604
|
+
return value;
|
|
605
|
+
} catch (error) {
|
|
606
|
+
if (routed) attempts.set(area, {
|
|
607
|
+
status: "failed",
|
|
608
|
+
stage: "transport",
|
|
609
|
+
code: classifyTransportError(error),
|
|
610
|
+
route,
|
|
611
|
+
elapsed: elapsedBucket(now() - startedAt)
|
|
612
|
+
});
|
|
613
|
+
throw error;
|
|
614
|
+
}
|
|
615
|
+
};
|
|
616
|
+
return Object.freeze({
|
|
617
|
+
run,
|
|
618
|
+
fetch: (area, input, init) => run(area, () => globalThis.fetch(input, init)),
|
|
619
|
+
snapshot: () => Object.fromEntries([...attempts].map(([area, value]) => [area, { ...value }]))
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
//#endregion
|
|
623
|
+
//#region src/subscription-connection.js
|
|
624
|
+
function createSubscriptionConnection({ resolveMode = () => "sse", resolveProxy = resolveCodexOAuthProxy } = {}) {
|
|
625
|
+
const namespace = randomUUID();
|
|
626
|
+
const sessions = /* @__PURE__ */ new Set();
|
|
627
|
+
return {
|
|
628
|
+
async prepare(options = {}) {
|
|
629
|
+
if (resolveMode() !== "websocket") return { options: {
|
|
630
|
+
...options,
|
|
631
|
+
transport: "sse"
|
|
632
|
+
} };
|
|
633
|
+
const proxy = await resolveProxy({ target: new URL("https://chatgpt.com/") });
|
|
634
|
+
const sessionId = options.sessionId && `dsh-${createHash("sha256").update(JSON.stringify([
|
|
635
|
+
namespace,
|
|
636
|
+
options.sessionId,
|
|
637
|
+
options.apiKey,
|
|
638
|
+
proxy
|
|
639
|
+
])).digest("hex").slice(0, 56)}`;
|
|
640
|
+
if (sessionId) sessions.add(sessionId);
|
|
641
|
+
return {
|
|
642
|
+
options: {
|
|
643
|
+
...options,
|
|
644
|
+
sessionId,
|
|
645
|
+
transport: "websocket-cached",
|
|
646
|
+
websocketConnectTimeoutMs: 1e4,
|
|
647
|
+
env: {}
|
|
648
|
+
},
|
|
649
|
+
network: {
|
|
650
|
+
websocket: true,
|
|
651
|
+
websocketProxy: proxy
|
|
652
|
+
}
|
|
653
|
+
};
|
|
535
654
|
},
|
|
536
655
|
dispose() {
|
|
537
|
-
for (const
|
|
656
|
+
for (const session of sessions) {
|
|
657
|
+
closeOpenAICodexWebSocketSessions(session);
|
|
658
|
+
resetOpenAICodexWebSocketDebugStats(session);
|
|
659
|
+
}
|
|
538
660
|
sessions.clear();
|
|
539
661
|
}
|
|
540
662
|
};
|
|
541
663
|
}
|
|
542
664
|
//#endregion
|
|
543
|
-
//#region src/
|
|
544
|
-
|
|
545
|
-
const
|
|
546
|
-
const
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
x: {
|
|
555
|
-
...number,
|
|
556
|
-
required: true
|
|
557
|
-
},
|
|
558
|
-
y: {
|
|
559
|
-
...number,
|
|
560
|
-
required: true
|
|
561
|
-
}
|
|
562
|
-
})
|
|
563
|
-
};
|
|
564
|
-
const point = object({
|
|
565
|
-
x: {
|
|
566
|
-
...number,
|
|
567
|
-
required: true
|
|
568
|
-
},
|
|
569
|
-
y: {
|
|
570
|
-
...number,
|
|
571
|
-
required: true
|
|
665
|
+
//#region src/subscription-compaction.js
|
|
666
|
+
/** Experimental SSE bridge; callers must explicitly opt in. */
|
|
667
|
+
const clone$2 = (value) => JSON.parse(JSON.stringify(value));
|
|
668
|
+
const hash = (value) => createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
669
|
+
const key = "codexCompactionV1";
|
|
670
|
+
const historyHash = (messages) => hash(messages.map(({ source, ...message }) => ({
|
|
671
|
+
...message,
|
|
672
|
+
source: source && {
|
|
673
|
+
kind: source.kind,
|
|
674
|
+
provider: source.provider,
|
|
675
|
+
model: source.model
|
|
572
676
|
}
|
|
573
|
-
});
|
|
574
|
-
const
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
after: {
|
|
600
|
-
type: "integer",
|
|
601
|
-
description: "Layer add only: existing layer to insert after; defaults to active."
|
|
602
|
-
},
|
|
603
|
-
start: point,
|
|
604
|
-
segments: {
|
|
605
|
-
type: "array",
|
|
606
|
-
items: object({
|
|
607
|
-
control1: {
|
|
608
|
-
...point,
|
|
609
|
-
required: true
|
|
610
|
-
},
|
|
611
|
-
control2: {
|
|
612
|
-
...point,
|
|
613
|
-
required: true
|
|
614
|
-
},
|
|
615
|
-
end: {
|
|
616
|
-
...point,
|
|
617
|
-
required: true
|
|
618
|
-
}
|
|
619
|
-
})
|
|
620
|
-
},
|
|
621
|
-
layer: { type: "integer" },
|
|
622
|
-
shape: {
|
|
623
|
-
type: "string",
|
|
624
|
-
enum: [
|
|
625
|
-
"pen",
|
|
626
|
-
"line",
|
|
627
|
-
"arrow",
|
|
628
|
-
"text",
|
|
629
|
-
"rectangle",
|
|
630
|
-
"circle",
|
|
631
|
-
"ellipse",
|
|
632
|
-
"polygon",
|
|
633
|
-
"bezier",
|
|
634
|
-
"eraser"
|
|
635
|
-
]
|
|
636
|
-
},
|
|
637
|
-
...style,
|
|
638
|
-
action: {
|
|
639
|
-
type: "string",
|
|
640
|
-
enum: [
|
|
641
|
-
"update",
|
|
642
|
-
"duplicate",
|
|
643
|
-
"delete",
|
|
644
|
-
"add",
|
|
645
|
-
"select",
|
|
646
|
-
"rename",
|
|
647
|
-
"visible",
|
|
648
|
-
"up",
|
|
649
|
-
"down",
|
|
650
|
-
"clear"
|
|
651
|
-
]
|
|
652
|
-
},
|
|
653
|
-
value: string,
|
|
654
|
-
ratio: {
|
|
655
|
-
type: "string",
|
|
656
|
-
enum: [
|
|
657
|
-
"1:1",
|
|
658
|
-
"4:3",
|
|
659
|
-
"3:4",
|
|
660
|
-
"16:9",
|
|
661
|
-
"9:16"
|
|
662
|
-
]
|
|
663
|
-
},
|
|
664
|
-
patch: object(style),
|
|
665
|
-
transform: object({
|
|
666
|
-
dx: number,
|
|
667
|
-
dy: number,
|
|
668
|
-
scaleX: number,
|
|
669
|
-
scaleY: number
|
|
670
|
-
})
|
|
671
|
-
})
|
|
672
|
-
};
|
|
673
|
-
//#endregion
|
|
674
|
-
//#region src/sketch-agent-tool.js
|
|
675
|
-
function createSketchAgentTool(bridge, attachments) {
|
|
676
|
-
return defineTool({
|
|
677
|
-
name: "codex_sketch",
|
|
678
|
-
description: "Edit the sketch board in this session using native editable strokes and layers. Use for @sketch requests and explicit follow-up edits to that drawing. The toolbar pen button is for manual drawing; do not require the user to open it. Start with inspect for the runId, documentId, revision and command reference. Apply atomic batches, preview between stages, and call finish to save the finished draft and release the editing lock. save is only a checkpoint. finish returns an image only when the user enables experimental preview feedback. Closing the board does not stop drawing. If the user stops drawing, do not retry. Never generates AI images, sends messages or attaches images automatically. Inspect automatically opens the board in the currently viewed session. Do not ask the user to open it first. If the session is not visible in DSH, ask them to switch to it. On timeout inspect before retrying; reuse the exact requestId only for the same request.",
|
|
679
|
-
parameters: {
|
|
680
|
-
action: {
|
|
681
|
-
type: "string",
|
|
682
|
-
required: true,
|
|
683
|
-
enum: [
|
|
684
|
-
"inspect",
|
|
685
|
-
"apply",
|
|
686
|
-
"preview",
|
|
687
|
-
"save",
|
|
688
|
-
"finish"
|
|
689
|
-
]
|
|
690
|
-
},
|
|
691
|
-
runId: {
|
|
692
|
-
type: "string",
|
|
693
|
-
description: "From inspect; required for all other actions. Never reuse a stopped run."
|
|
694
|
-
},
|
|
695
|
-
documentId: {
|
|
696
|
-
type: "string",
|
|
697
|
-
description: "From inspect; required except for inspect."
|
|
698
|
-
},
|
|
699
|
-
revision: {
|
|
700
|
-
type: "integer",
|
|
701
|
-
description: "From latest response; required for apply/save/finish."
|
|
702
|
-
},
|
|
703
|
-
requestId: {
|
|
704
|
-
type: "string",
|
|
705
|
-
description: "Unique id for apply/save/finish; exact retries are deduplicated. After timeout inspect recentRequests before repeating a write."
|
|
706
|
-
},
|
|
707
|
-
commands: {
|
|
708
|
-
oneOf: [sketchCommandArray, { type: "string" }],
|
|
709
|
-
description: "Prefer a native command array. Legacy JSON string also accepted. Required for apply. Use named objects and update existing IDs; prefer Bezier start + segments (control1/control2/end) for curves, not hundreds of pen points."
|
|
710
|
-
},
|
|
711
|
-
name: {
|
|
712
|
-
type: "string",
|
|
713
|
-
description: "Draft name for save/finish."
|
|
714
|
-
},
|
|
715
|
-
offset: {
|
|
716
|
-
type: "integer",
|
|
717
|
-
description: "inspect only: object list offset, default 0. Follow nextOffset for further pages."
|
|
718
|
-
},
|
|
719
|
-
objectId: {
|
|
720
|
-
type: "string",
|
|
721
|
-
description: "inspect only: return full editable geometry for this object, in layer (defaults to active layer)."
|
|
722
|
-
},
|
|
723
|
-
layer: {
|
|
724
|
-
type: "integer",
|
|
725
|
-
description: "inspect only: layer containing objectId."
|
|
726
|
-
}
|
|
727
|
-
},
|
|
728
|
-
timeoutMs: 25e3,
|
|
729
|
-
isConcurrencySafe: () => false,
|
|
730
|
-
async execute(args, exec) {
|
|
731
|
-
const sessionId = exec.agent?.id;
|
|
732
|
-
if (typeof sessionId !== "string") throw Error("A session-owned sketch call is required");
|
|
733
|
-
const request = { ...args };
|
|
734
|
-
if (args.action === "apply") try {
|
|
735
|
-
request.commands = typeof args.commands === "string" ? JSON.parse(args.commands) : args.commands;
|
|
736
|
-
if (!Array.isArray(request.commands)) throw Error();
|
|
737
|
-
} catch {
|
|
738
|
-
throw Error("commands must be a native array or JSON array string");
|
|
677
|
+
})));
|
|
678
|
+
const MAX_BYTES = 2 * 1024 * 1024;
|
|
679
|
+
const MAX_AGE_MS = 10080 * 60 * 1e3;
|
|
680
|
+
function createCompactionBridge({ enabled = () => false, threshold = () => 1e5, accountScope, diagnostic = () => {}, now = Date.now }) {
|
|
681
|
+
const scope = new AsyncLocalStorage();
|
|
682
|
+
const wrapStream = (factory, options) => (async function* () {
|
|
683
|
+
if (!enabled() || options.provider !== "openai-codex") {
|
|
684
|
+
yield* factory(options);
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
const identity = await accountScope(options);
|
|
688
|
+
if (!identity) throw new Error("Compaction requires a stable account identity");
|
|
689
|
+
const state = {
|
|
690
|
+
identity: hash([identity, options.model]),
|
|
691
|
+
original: options.messages,
|
|
692
|
+
blocks: [],
|
|
693
|
+
suffix: void 0,
|
|
694
|
+
completed: false
|
|
695
|
+
};
|
|
696
|
+
let messages = options.messages;
|
|
697
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
698
|
+
const candidate = messages[i].source?.replayState?.response?.[key];
|
|
699
|
+
if (!candidate) continue;
|
|
700
|
+
if (Number.isFinite(candidate.createdAt) && candidate.createdAt <= now() && now() - candidate.createdAt <= MAX_AGE_MS && candidate.scope === state.identity && candidate.prefix === historyHash(messages.slice(0, i)) && candidate.content === hash(messages[i].content) && Array.isArray(candidate.items) && candidate.items[0]?.type === "compaction" && typeof candidate.items[0].encrypted_content === "string" && candidate.digest === hash(candidate.items) && JSON.stringify(candidate.items).length <= MAX_BYTES) {
|
|
701
|
+
state.suffix = clone$2(candidate.items);
|
|
702
|
+
messages = [...messages.slice(0, i + 1).filter((m) => m.role === "system"), ...messages.slice(i + 1)];
|
|
739
703
|
}
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
704
|
+
break;
|
|
705
|
+
}
|
|
706
|
+
const iterator = scope.run(state, () => factory({
|
|
707
|
+
...options,
|
|
708
|
+
messages
|
|
709
|
+
})[Symbol.asyncIterator]());
|
|
710
|
+
try {
|
|
711
|
+
while (true) {
|
|
712
|
+
const result = await scope.run(state, () => iterator.next());
|
|
713
|
+
if (result.done) break;
|
|
714
|
+
let event = result.value;
|
|
715
|
+
if (event.type === "block-end") state.blocks[event.index] = clone$2(event.block);
|
|
716
|
+
if (event.type === "finish") diagnostic({
|
|
717
|
+
completed: state.completed,
|
|
718
|
+
captured: state.captured?.length ?? 0,
|
|
719
|
+
replay: !!event.replayState
|
|
747
720
|
});
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
721
|
+
if (event.type === "finish" && ["stop", "tool-calls"].includes(event.reason?.kind) && !options.signal?.aborted && state.completed && state.captured?.length && event.replayState) {
|
|
722
|
+
const items = state.captured;
|
|
723
|
+
const checkpoint = {
|
|
724
|
+
createdAt: now(),
|
|
725
|
+
scope: state.identity,
|
|
726
|
+
prefix: historyHash(state.original),
|
|
727
|
+
content: hash(state.blocks.filter(Boolean)),
|
|
728
|
+
items,
|
|
729
|
+
digest: hash(items)
|
|
730
|
+
};
|
|
731
|
+
event = {
|
|
732
|
+
...event,
|
|
733
|
+
replayState: {
|
|
734
|
+
...event.replayState,
|
|
735
|
+
response: {
|
|
736
|
+
...event.replayState.response,
|
|
737
|
+
[key]: checkpoint
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
yield event;
|
|
753
743
|
}
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
output: {
|
|
757
|
-
schema: {
|
|
758
|
-
type: "object",
|
|
759
|
-
additionalProperties: true
|
|
760
|
-
},
|
|
761
|
-
render: (_args, value) => [{
|
|
762
|
-
type: "text",
|
|
763
|
-
text: JSON.stringify({
|
|
764
|
-
...value,
|
|
765
|
-
image: void 0
|
|
766
|
-
})
|
|
767
|
-
}, ...value.image ? [{
|
|
768
|
-
type: "image",
|
|
769
|
-
attachment: value.image
|
|
770
|
-
}] : []]
|
|
744
|
+
} finally {
|
|
745
|
+
await scope.run(state, () => iterator.return?.());
|
|
771
746
|
}
|
|
772
|
-
});
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
"
|
|
786
|
-
"cache-control": "no-store"
|
|
747
|
+
})();
|
|
748
|
+
return {
|
|
749
|
+
wrapAdapter(adapter) {
|
|
750
|
+
return new Proxy(adapter, { get(target, property) {
|
|
751
|
+
if (property === "stream") return (options) => wrapStream((o) => target.stream(o), options);
|
|
752
|
+
if (property === "prepareCall") return async (...args) => {
|
|
753
|
+
const call = await target.prepareCall(...args);
|
|
754
|
+
return {
|
|
755
|
+
...call,
|
|
756
|
+
stream: (options) => wrapStream((o) => call.stream(o), options)
|
|
757
|
+
};
|
|
758
|
+
};
|
|
759
|
+
const value = Reflect.get(target, property, target);
|
|
760
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
787
761
|
} });
|
|
762
|
+
},
|
|
763
|
+
preparePayload(payload, contextWindow) {
|
|
764
|
+
const state = scope.getStore();
|
|
765
|
+
if (!state) return payload;
|
|
766
|
+
const configured = threshold();
|
|
767
|
+
if (!Number.isSafeInteger(configured) || configured < 1e3) throw new Error("Invalid compaction threshold");
|
|
768
|
+
const limit = Number.isFinite(contextWindow) && contextWindow >= 2e3 ? Math.min(configured, Math.floor(contextWindow / 2)) : configured;
|
|
769
|
+
if (!Number.isSafeInteger(limit) || limit < 1e3) throw new Error("Invalid compaction threshold");
|
|
770
|
+
return {
|
|
771
|
+
...payload,
|
|
772
|
+
input: [...state.suffix ?? [], ...payload.input],
|
|
773
|
+
context_management: [{
|
|
774
|
+
type: "compaction",
|
|
775
|
+
compact_threshold: limit
|
|
776
|
+
}]
|
|
777
|
+
};
|
|
778
|
+
},
|
|
779
|
+
requestOptions(options) {
|
|
780
|
+
return scope.getStore() ? {
|
|
781
|
+
...options,
|
|
782
|
+
transport: "sse"
|
|
783
|
+
} : options;
|
|
784
|
+
},
|
|
785
|
+
networkOptions(options) {
|
|
786
|
+
const state = scope.getStore();
|
|
787
|
+
if (!state) return options;
|
|
788
|
+
return {
|
|
789
|
+
...options,
|
|
790
|
+
websocket: false,
|
|
791
|
+
transformResponse: (response, target) => {
|
|
792
|
+
if (target.hostname !== "chatgpt.com" || target.pathname !== "/backend-api/codex/responses" || !response.ok || !response.body) return response;
|
|
793
|
+
state.completed = false;
|
|
794
|
+
state.captured = void 0;
|
|
795
|
+
let buffer = "", oversized = false;
|
|
796
|
+
const decoder = new TextDecoder();
|
|
797
|
+
const observe = (line) => {
|
|
798
|
+
if (!line.startsWith("data:")) return;
|
|
799
|
+
const data = line.slice(5).trim();
|
|
800
|
+
if (data === "[DONE]") return;
|
|
801
|
+
let event;
|
|
802
|
+
try {
|
|
803
|
+
event = JSON.parse(data);
|
|
804
|
+
} catch {
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
if (event.type === "response.output_item.done") {
|
|
808
|
+
if (event.item?.type === "compaction") state.captured = [clone$2(event.item)];
|
|
809
|
+
else if (state.captured) state.captured.push(clone$2(event.item));
|
|
810
|
+
if (state.captured && JSON.stringify(state.captured).length > MAX_BYTES) {
|
|
811
|
+
state.captured = void 0;
|
|
812
|
+
oversized = true;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
if (event.type === "response.completed" && event.response?.status === "completed") state.completed = !oversized;
|
|
816
|
+
};
|
|
817
|
+
const stream = response.body.pipeThrough(new TransformStream({
|
|
818
|
+
transform(bytes, controller) {
|
|
819
|
+
controller.enqueue(bytes);
|
|
820
|
+
if (oversized) return;
|
|
821
|
+
buffer += decoder.decode(bytes, { stream: true });
|
|
822
|
+
let index;
|
|
823
|
+
while ((index = buffer.indexOf("\n")) >= 0) {
|
|
824
|
+
observe(buffer.slice(0, index).trimEnd());
|
|
825
|
+
buffer = buffer.slice(index + 1);
|
|
826
|
+
}
|
|
827
|
+
if (buffer.length > MAX_BYTES) {
|
|
828
|
+
buffer = "";
|
|
829
|
+
oversized = true;
|
|
830
|
+
state.captured = void 0;
|
|
831
|
+
}
|
|
832
|
+
},
|
|
833
|
+
flush() {
|
|
834
|
+
buffer += decoder.decode();
|
|
835
|
+
if (!oversized && buffer) observe(buffer.trimEnd());
|
|
836
|
+
}
|
|
837
|
+
}));
|
|
838
|
+
return new Response(stream, {
|
|
839
|
+
status: response.status,
|
|
840
|
+
statusText: response.statusText,
|
|
841
|
+
headers: response.headers
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
};
|
|
788
845
|
}
|
|
789
|
-
}
|
|
846
|
+
};
|
|
790
847
|
}
|
|
791
848
|
//#endregion
|
|
792
|
-
//#region src/
|
|
793
|
-
const
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
const
|
|
822
|
-
if (email === void 0) {
|
|
823
|
-
delete credential.email;
|
|
824
|
-
return credential;
|
|
825
|
-
}
|
|
826
|
-
return {
|
|
827
|
-
...credential,
|
|
828
|
-
email
|
|
829
|
-
};
|
|
830
|
-
}
|
|
831
|
-
function assertOAuthCredential$1(value) {
|
|
832
|
-
if (value === null || typeof value !== "object" || value.type !== "oauth" || typeof value.access !== "string" || value.access.length === 0 || typeof value.refresh !== "string" || value.refresh.length === 0 || typeof value.expires !== "number" || !Number.isFinite(value.expires)) throw new Error("Codex account vault received a malformed OAuth credential");
|
|
833
|
-
return clone$1(value);
|
|
834
|
-
}
|
|
835
|
-
function parseOAuthCredential$1(value) {
|
|
849
|
+
//#region src/rpc-contract.js
|
|
850
|
+
const RPC_ENDPOINTS = Object.freeze([
|
|
851
|
+
"status",
|
|
852
|
+
"login/start",
|
|
853
|
+
"login/status",
|
|
854
|
+
"login/submit",
|
|
855
|
+
"login/cancel",
|
|
856
|
+
"logout",
|
|
857
|
+
"account/select",
|
|
858
|
+
"account/remove",
|
|
859
|
+
"usage",
|
|
860
|
+
"diagnostics",
|
|
861
|
+
"preferences/status",
|
|
862
|
+
"preferences/models",
|
|
863
|
+
"preferences/update",
|
|
864
|
+
"reset-credit/inspect",
|
|
865
|
+
"reset-credit/prepare",
|
|
866
|
+
"reset-credit/consume",
|
|
867
|
+
"image/original/chunk",
|
|
868
|
+
"sketch/connect",
|
|
869
|
+
"sketch/poll",
|
|
870
|
+
"sketch/claim",
|
|
871
|
+
"sketch/result",
|
|
872
|
+
"sketch/disconnect"
|
|
873
|
+
]);
|
|
874
|
+
//#endregion
|
|
875
|
+
//#region src/subscription-transport.js
|
|
876
|
+
/** Exact routes stay inside DSH's authenticated /api bridge and body limit. */
|
|
877
|
+
function registerSubscriptionTransport(connection, handler) {
|
|
878
|
+
const disposers = [];
|
|
836
879
|
try {
|
|
837
|
-
|
|
880
|
+
for (const endpoint of RPC_ENDPOINTS) {
|
|
881
|
+
const method = `codex-subscription/${endpoint}`;
|
|
882
|
+
disposers.push(connection.fetch.register({
|
|
883
|
+
path: `/api/${method}`,
|
|
884
|
+
methods: ["POST"],
|
|
885
|
+
requestBody: "buffered",
|
|
886
|
+
async fetch(request) {
|
|
887
|
+
if (request.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase() !== "application/json") return new Response("content type must be application/json", { status: 415 });
|
|
888
|
+
let body;
|
|
889
|
+
try {
|
|
890
|
+
body = await request.json();
|
|
891
|
+
} catch {
|
|
892
|
+
return new Response("invalid JSON", { status: 400 });
|
|
893
|
+
}
|
|
894
|
+
const envelope = clientRequestSchema.safeParse(body);
|
|
895
|
+
if (!envelope.success || envelope.data.method !== method) return new Response("invalid RPC envelope", { status: 400 });
|
|
896
|
+
let result;
|
|
897
|
+
try {
|
|
898
|
+
request.signal.throwIfAborted();
|
|
899
|
+
result = await handler(endpoint, envelope.data.payload, request.signal);
|
|
900
|
+
} catch {
|
|
901
|
+
result = {
|
|
902
|
+
ok: false,
|
|
903
|
+
error: {
|
|
904
|
+
code: "internal",
|
|
905
|
+
message: "Subscription request failed",
|
|
906
|
+
details: { issues: [] }
|
|
907
|
+
}
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
return Response.json({
|
|
911
|
+
type: "server-response",
|
|
912
|
+
rpcId: envelope.data.rpcId,
|
|
913
|
+
result
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
}));
|
|
917
|
+
}
|
|
838
918
|
} catch (error) {
|
|
839
|
-
|
|
840
|
-
throw
|
|
919
|
+
for (const dispose of disposers.reverse()) dispose();
|
|
920
|
+
throw error;
|
|
841
921
|
}
|
|
922
|
+
return () => {
|
|
923
|
+
for (const dispose of disposers.reverse()) dispose();
|
|
924
|
+
};
|
|
842
925
|
}
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
const
|
|
853
|
-
|
|
854
|
-
|
|
926
|
+
//#endregion
|
|
927
|
+
//#region src/subagent-auth.js
|
|
928
|
+
const AUTH_ERROR = "Codex subscription authorization failed; check the selected account";
|
|
929
|
+
/** Keep refresh rotation in the plugin's existing serialized credential store. */
|
|
930
|
+
async function createSubagentTokens({ resolveAuth, store, refresh, signal }) {
|
|
931
|
+
signal.throwIfAborted();
|
|
932
|
+
await resolveAuth();
|
|
933
|
+
const initial = await store.read("openai-codex", { signal });
|
|
934
|
+
if (initial?.type !== "oauth" || !initial.accountId || !initial.access) throw new Error(AUTH_ERROR);
|
|
935
|
+
const accountId = initial.accountId;
|
|
936
|
+
let access = initial.access;
|
|
937
|
+
return async (previousAccountId, forceRefresh = false) => {
|
|
938
|
+
signal.throwIfAborted();
|
|
939
|
+
if (previousAccountId !== void 0 && previousAccountId !== accountId) throw new Error(AUTH_ERROR);
|
|
940
|
+
let credential;
|
|
941
|
+
if (previousAccountId !== void 0 || forceRefresh) {
|
|
942
|
+
const rejectedAccess = access;
|
|
943
|
+
credential = await store.modify("openai-codex", async (current) => {
|
|
944
|
+
if (current?.accountId !== accountId) throw new Error(AUTH_ERROR);
|
|
945
|
+
if (current.access !== rejectedAccess) return current;
|
|
946
|
+
const next = await refresh(current);
|
|
947
|
+
if (next?.accountId !== accountId) throw new Error(AUTH_ERROR);
|
|
948
|
+
return next;
|
|
949
|
+
}, { signal });
|
|
950
|
+
} else credential = await store.read("openai-codex", { signal });
|
|
951
|
+
signal.throwIfAborted();
|
|
952
|
+
if (credential?.accountId !== accountId || !credential.access) throw new Error(AUTH_ERROR);
|
|
953
|
+
access = credential.access;
|
|
855
954
|
return {
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
credential: sanitizeOAuthCredential(account.credential)
|
|
955
|
+
accessToken: access,
|
|
956
|
+
chatgptAccountId: accountId
|
|
859
957
|
};
|
|
860
|
-
});
|
|
861
|
-
if (!ids.has(record.payload.activeId)) throw new Error("Codex account vault active account is missing");
|
|
862
|
-
const legacyAccountId = record.payload.legacyAccountId;
|
|
863
|
-
if (legacyAccountId !== void 0 && !ids.has(legacyAccountId)) throw new Error("Codex account vault legacy account is missing");
|
|
864
|
-
return {
|
|
865
|
-
version: VERSION,
|
|
866
|
-
activeId: record.payload.activeId,
|
|
867
|
-
legacyAccountId,
|
|
868
|
-
accounts
|
|
869
958
|
};
|
|
870
959
|
}
|
|
871
|
-
const grant = (payload) => ({
|
|
872
|
-
kind: "grant",
|
|
873
|
-
payload
|
|
874
|
-
});
|
|
875
|
-
var PendingOAuthCredentialStore = class {
|
|
876
|
-
#credential;
|
|
877
|
-
async read(providerId) {
|
|
878
|
-
if (providerId !== "openai-codex") throw new Error("Pending Codex login received an unknown provider");
|
|
879
|
-
return clone$1(this.#credential);
|
|
880
|
-
}
|
|
881
|
-
async list() {
|
|
882
|
-
return this.#credential === void 0 ? [] : [{
|
|
883
|
-
providerId: "openai-codex",
|
|
884
|
-
type: "oauth"
|
|
885
|
-
}];
|
|
886
|
-
}
|
|
887
|
-
async modify(providerId, update) {
|
|
888
|
-
if (providerId !== "openai-codex") throw new Error("Pending Codex login received an unknown provider");
|
|
889
|
-
const next = await update(clone$1(this.#credential));
|
|
890
|
-
if (next !== void 0) this.#credential = sanitizeOAuthCredential(next);
|
|
891
|
-
return clone$1(this.#credential);
|
|
892
|
-
}
|
|
893
|
-
async delete(providerId) {
|
|
894
|
-
if (providerId !== "openai-codex") throw new Error("Pending Codex login received an unknown provider");
|
|
895
|
-
this.#credential = void 0;
|
|
896
|
-
}
|
|
897
|
-
credential() {
|
|
898
|
-
return clone$1(this.#credential);
|
|
899
|
-
}
|
|
900
|
-
};
|
|
901
960
|
/**
|
|
902
|
-
*
|
|
903
|
-
*
|
|
904
|
-
*
|
|
961
|
+
* Authenticate the official DSH provider's private app-server connection.
|
|
962
|
+
* DSH still owns framing, process containment, turns, tool approvals and disposal.
|
|
963
|
+
* Only the documented external-auth handshake and thread policy are adapted.
|
|
964
|
+
* No token is passed in argv, environment, logs or a second auth.json.
|
|
905
965
|
*/
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
}
|
|
922
|
-
async
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
ref,
|
|
928
|
-
credential: parseOAuthCredential$1(hit.value)
|
|
929
|
-
};
|
|
966
|
+
function authenticatedSubagentChild(child, { Transport, getTokens, thread, signal }) {
|
|
967
|
+
const stdin = new PassThrough();
|
|
968
|
+
const stdout = new PassThrough();
|
|
969
|
+
const host = new Transport(stdin, stdout);
|
|
970
|
+
const server = new Transport(child.stdout, child.stdin);
|
|
971
|
+
let initialized = false;
|
|
972
|
+
let authenticated;
|
|
973
|
+
let closed = false;
|
|
974
|
+
const close = () => {
|
|
975
|
+
if (closed) return;
|
|
976
|
+
closed = true;
|
|
977
|
+
host.close();
|
|
978
|
+
server.close();
|
|
979
|
+
stdin.destroy();
|
|
980
|
+
stdout.end();
|
|
981
|
+
};
|
|
982
|
+
const authorize = async (previousAccountId, refresh = false) => {
|
|
983
|
+
try {
|
|
984
|
+
return await getTokens(previousAccountId, refresh);
|
|
985
|
+
} catch {
|
|
986
|
+
throw new Error(AUTH_ERROR);
|
|
930
987
|
}
|
|
931
|
-
}
|
|
932
|
-
async
|
|
933
|
-
const
|
|
934
|
-
|
|
935
|
-
const legacy = await this.#legacyCredential();
|
|
936
|
-
if (legacy === void 0) return void 0;
|
|
937
|
-
const id = this.createId();
|
|
938
|
-
return assertVaultRecord(await this.credentials.modifyRecord(this.key, (current) => {
|
|
939
|
-
if (current !== void 0) return Promise.resolve(current);
|
|
940
|
-
return Promise.resolve(grant({
|
|
941
|
-
version: VERSION,
|
|
942
|
-
activeId: id,
|
|
943
|
-
legacyAccountId: id,
|
|
944
|
-
accounts: [{
|
|
945
|
-
id,
|
|
946
|
-
label: DEFAULT_LABEL,
|
|
947
|
-
credential: legacy.credential
|
|
948
|
-
}]
|
|
949
|
-
}));
|
|
950
|
-
}));
|
|
951
|
-
}
|
|
952
|
-
async #modifyPayload(update) {
|
|
953
|
-
await this.#ensurePayload();
|
|
954
|
-
let previousLegacy;
|
|
955
|
-
const payload = assertVaultRecord(await this.credentials.modifyRecord(this.key, async (current) => {
|
|
956
|
-
if (current === void 0) throw new Error("Codex account vault is not signed in");
|
|
957
|
-
const payload = assertVaultRecord(current);
|
|
958
|
-
previousLegacy = payload.accounts.find((account) => account.id === payload.legacyAccountId)?.credential;
|
|
959
|
-
const next = await update(clone$1(payload));
|
|
960
|
-
return grant(next);
|
|
961
|
-
}));
|
|
962
|
-
const legacy = payload.accounts.find((account) => account.id === payload.legacyAccountId)?.credential;
|
|
988
|
+
};
|
|
989
|
+
const login = () => authenticated ??= (async () => {
|
|
990
|
+
const tokens = await authorize();
|
|
991
|
+
signal.throwIfAborted();
|
|
963
992
|
try {
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
993
|
+
return await server.request("account/login/start", {
|
|
994
|
+
type: "chatgptAuthTokens",
|
|
995
|
+
...tokens
|
|
996
|
+
}, AbortSignal.any([signal, AbortSignal.timeout(1e4)]));
|
|
967
997
|
} catch {
|
|
968
|
-
|
|
998
|
+
throw new Error(AUTH_ERROR);
|
|
969
999
|
}
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
label: account.label,
|
|
979
|
-
active: account.id === payload.activeId,
|
|
980
|
-
expiresAt: account.credential.expires,
|
|
981
|
-
...account.credential.email === void 0 ? {} : { email: account.credential.email }
|
|
982
|
-
}));
|
|
983
|
-
});
|
|
984
|
-
}
|
|
985
|
-
readActive() {
|
|
986
|
-
return this.#enqueue(async () => {
|
|
987
|
-
const payload = await this.#ensurePayload();
|
|
988
|
-
return clone$1(payload?.accounts.find((account) => account.id === payload.activeId)?.credential);
|
|
989
|
-
});
|
|
990
|
-
}
|
|
991
|
-
activeId() {
|
|
992
|
-
return this.#enqueue(async () => (await this.#ensurePayload())?.activeId);
|
|
993
|
-
}
|
|
994
|
-
add(label, credential) {
|
|
995
|
-
return this.#enqueue(async () => {
|
|
996
|
-
const normalizedLabel = normalizeLabel(label);
|
|
997
|
-
const validated = sanitizeOAuthCredential(credential);
|
|
998
|
-
await this.#ensurePayload();
|
|
999
|
-
const id = this.createId();
|
|
1000
|
-
const account = (await this.#modifyPayload((current) => ({
|
|
1001
|
-
...current,
|
|
1002
|
-
activeId: id,
|
|
1003
|
-
accounts: [...current.accounts, {
|
|
1004
|
-
id,
|
|
1005
|
-
label: normalizedLabel,
|
|
1006
|
-
credential: validated
|
|
1007
|
-
}]
|
|
1008
|
-
}))).accounts.find((candidate) => candidate.id === id);
|
|
1009
|
-
return {
|
|
1010
|
-
id,
|
|
1011
|
-
label: account.label,
|
|
1012
|
-
active: true,
|
|
1013
|
-
expiresAt: account.credential.expires,
|
|
1014
|
-
...account.credential.email === void 0 ? {} : { email: account.credential.email }
|
|
1015
|
-
};
|
|
1016
|
-
});
|
|
1017
|
-
}
|
|
1018
|
-
select(id) {
|
|
1019
|
-
return this.#enqueue(async () => {
|
|
1020
|
-
await this.#modifyPayload((current) => {
|
|
1021
|
-
if (!current.accounts.some((account) => account.id === id)) throw new Error("Unknown Codex account");
|
|
1022
|
-
return {
|
|
1023
|
-
...current,
|
|
1024
|
-
activeId: id
|
|
1025
|
-
};
|
|
1026
|
-
});
|
|
1027
|
-
});
|
|
1028
|
-
}
|
|
1029
|
-
modifyActive(update) {
|
|
1030
|
-
return this.#enqueue(async () => {
|
|
1031
|
-
if (await this.#ensurePayload() === void 0) {
|
|
1032
|
-
const initial = await update(void 0);
|
|
1033
|
-
if (initial === void 0) return void 0;
|
|
1034
|
-
const credential = sanitizeOAuthCredential(initial);
|
|
1035
|
-
await this.credentials.set(this.legacyRef, JSON.stringify(credential));
|
|
1036
|
-
await this.#ensurePayload();
|
|
1037
|
-
return clone$1(credential);
|
|
1038
|
-
}
|
|
1039
|
-
let result;
|
|
1040
|
-
await this.#modifyPayload(async (current) => {
|
|
1041
|
-
const index = current.accounts.findIndex((account) => account.id === current.activeId);
|
|
1042
|
-
const previous = clone$1(current.accounts[index].credential);
|
|
1043
|
-
const next = await update(previous);
|
|
1044
|
-
if (next === void 0) {
|
|
1045
|
-
result = previous;
|
|
1046
|
-
return current;
|
|
1000
|
+
})();
|
|
1001
|
+
host.onRequest(async (method, params) => {
|
|
1002
|
+
if (method === "initialize") {
|
|
1003
|
+
const result = await server.request(method, {
|
|
1004
|
+
...params,
|
|
1005
|
+
capabilities: {
|
|
1006
|
+
...params.capabilities,
|
|
1007
|
+
experimentalApi: true
|
|
1047
1008
|
}
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
const accounts = [...current.accounts];
|
|
1051
|
-
accounts[index] = {
|
|
1052
|
-
...accounts[index],
|
|
1053
|
-
credential
|
|
1054
|
-
};
|
|
1055
|
-
result = clone$1(credential);
|
|
1056
|
-
return {
|
|
1057
|
-
...current,
|
|
1058
|
-
accounts
|
|
1059
|
-
};
|
|
1060
|
-
});
|
|
1009
|
+
}, signal);
|
|
1010
|
+
initialized = true;
|
|
1061
1011
|
return result;
|
|
1062
|
-
}
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
}
|
|
1086
|
-
};
|
|
1087
|
-
//#endregion
|
|
1088
|
-
//#region src/credential-store.js
|
|
1089
|
-
const PROVIDER$1 = "openai-codex";
|
|
1090
|
-
const abortIfNeeded = (options) => options?.signal?.throwIfAborted();
|
|
1091
|
-
const clone = (value) => value === void 0 ? void 0 : structuredClone(value);
|
|
1092
|
-
function assertProvider(providerId) {
|
|
1093
|
-
if (providerId !== PROVIDER$1) throw new Error(`Codex credential store does not own provider ${JSON.stringify(providerId)}`);
|
|
1012
|
+
}
|
|
1013
|
+
if (method === "thread/start") {
|
|
1014
|
+
if (!initialized) throw new Error("Codex initialization incomplete");
|
|
1015
|
+
await login();
|
|
1016
|
+
return server.request(method, {
|
|
1017
|
+
...params,
|
|
1018
|
+
...thread
|
|
1019
|
+
}, signal);
|
|
1020
|
+
}
|
|
1021
|
+
return server.request(method, params, signal);
|
|
1022
|
+
});
|
|
1023
|
+
host.onNotification((method, params) => server.notify(method, params));
|
|
1024
|
+
server.onRequest((method, params) => method === "account/chatgptAuthTokens/refresh" ? authorize(params.previousAccountId ?? void 0, true) : host.request(method, params, signal));
|
|
1025
|
+
server.onNotification((method, params) => host.notify(method, params));
|
|
1026
|
+
host.start();
|
|
1027
|
+
server.start();
|
|
1028
|
+
child.done.then(close, close);
|
|
1029
|
+
return new Proxy(child, { get(target, key) {
|
|
1030
|
+
if (key === "stdin") return stdin;
|
|
1031
|
+
if (key === "stdout") return stdout;
|
|
1032
|
+
const value = Reflect.get(target, key, target);
|
|
1033
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
1034
|
+
} });
|
|
1094
1035
|
}
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1036
|
+
//#endregion
|
|
1037
|
+
//#region src/subagent-backend.js
|
|
1038
|
+
const SUBAGENT_PROVIDER = "codex-subscription-subagent";
|
|
1039
|
+
const MODES = /* @__PURE__ */ new Set([
|
|
1040
|
+
"read-only",
|
|
1041
|
+
"workspace-write",
|
|
1042
|
+
"danger-full-access"
|
|
1043
|
+
]);
|
|
1044
|
+
function subagentThreadPolicy(parent, policy) {
|
|
1045
|
+
if (!MODES.has(policy?.mode)) throw new Error("DSH subagent sandbox policy is unavailable");
|
|
1046
|
+
const selected = parent.session.requestHeader?.()?.config ?? parent.options ?? {};
|
|
1047
|
+
const subscription = selected.provider === "openai-codex" && typeof selected.model === "string";
|
|
1048
|
+
return {
|
|
1049
|
+
model: subscription ? selected.model : "gpt-5.6-luna",
|
|
1050
|
+
modelProvider: "openai",
|
|
1051
|
+
approvalPolicy: "never",
|
|
1052
|
+
sandbox: policy.mode,
|
|
1053
|
+
config: { model_reasoning_effort: subscription ? selected.reasoningEffort ?? "low" : "low" }
|
|
1054
|
+
};
|
|
1099
1055
|
}
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
throw
|
|
1106
|
-
}
|
|
1056
|
+
/** Reuse the official DSH process/turn provider; keep only subscription auth here. */
|
|
1057
|
+
function createSubscriptionSubagent({ ctx, nativeHome, resolveAuth, store, refresh, loadRuntime }) {
|
|
1058
|
+
let runtime;
|
|
1059
|
+
const load = () => runtime ??= loadRuntime().catch((error) => {
|
|
1060
|
+
runtime = void 0;
|
|
1061
|
+
throw error;
|
|
1062
|
+
});
|
|
1063
|
+
const active = /* @__PURE__ */ new Set();
|
|
1064
|
+
let disposed = false;
|
|
1065
|
+
return {
|
|
1066
|
+
provider: {
|
|
1067
|
+
name: SUBAGENT_PROVIDER,
|
|
1068
|
+
capabilities: {
|
|
1069
|
+
agentOptions: false,
|
|
1070
|
+
outputSchema: false,
|
|
1071
|
+
depthLimit: false,
|
|
1072
|
+
toolFilter: false,
|
|
1073
|
+
persona: false
|
|
1074
|
+
},
|
|
1075
|
+
inheritsParentContext: false,
|
|
1076
|
+
async start(request) {
|
|
1077
|
+
if (disposed) throw new Error("Codex subagent is unavailable");
|
|
1078
|
+
const controller = new AbortController();
|
|
1079
|
+
active.add(controller);
|
|
1080
|
+
const signal = AbortSignal.any([request.signal, controller.signal]);
|
|
1081
|
+
let run;
|
|
1082
|
+
try {
|
|
1083
|
+
const thread = subagentThreadPolicy(request.parent, ctx.sandboxPolicy.resolve({ session: request.parent.session }));
|
|
1084
|
+
const getTokens = await createSubagentTokens({
|
|
1085
|
+
resolveAuth,
|
|
1086
|
+
store,
|
|
1087
|
+
refresh,
|
|
1088
|
+
signal
|
|
1089
|
+
});
|
|
1090
|
+
const { official, Transport } = await load();
|
|
1091
|
+
const proxy = await resolveCodexOAuthProxy({ target: new URL("https://chatgpt.com/") });
|
|
1092
|
+
signal.throwIfAborted();
|
|
1093
|
+
await mkdir(nativeHome, { recursive: true });
|
|
1094
|
+
const env = {
|
|
1095
|
+
CODEX_HOME: nativeHome,
|
|
1096
|
+
...proxy ? {
|
|
1097
|
+
HTTPS_PROXY: proxy,
|
|
1098
|
+
HTTP_PROXY: proxy,
|
|
1099
|
+
ALL_PROXY: proxy
|
|
1100
|
+
} : {}
|
|
1101
|
+
};
|
|
1102
|
+
let delegate;
|
|
1103
|
+
official.apply({
|
|
1104
|
+
subagents: { registerProvider(value) {
|
|
1105
|
+
delegate = value;
|
|
1106
|
+
} },
|
|
1107
|
+
subprocess: { spawn: (spec) => authenticatedSubagentChild(ctx.subprocess.spawn({
|
|
1108
|
+
...spec,
|
|
1109
|
+
env: {
|
|
1110
|
+
...spec.env,
|
|
1111
|
+
...env
|
|
1112
|
+
}
|
|
1113
|
+
}), {
|
|
1114
|
+
Transport,
|
|
1115
|
+
getTokens,
|
|
1116
|
+
thread,
|
|
1117
|
+
signal
|
|
1118
|
+
}) },
|
|
1119
|
+
logger: { warn: () => ctx.logger?.warn?.("Codex subscription subtask failed") }
|
|
1120
|
+
}, {
|
|
1121
|
+
model: thread.model,
|
|
1122
|
+
env,
|
|
1123
|
+
permissionMode: "never",
|
|
1124
|
+
disposeGraceMs: 1e3
|
|
1125
|
+
});
|
|
1126
|
+
run = await delegate.start({
|
|
1127
|
+
...request,
|
|
1128
|
+
signal
|
|
1129
|
+
});
|
|
1130
|
+
const result = run.result.finally(() => active.delete(controller));
|
|
1131
|
+
return {
|
|
1132
|
+
...run,
|
|
1133
|
+
result,
|
|
1134
|
+
async dispose() {
|
|
1135
|
+
controller.abort();
|
|
1136
|
+
await run.dispose();
|
|
1137
|
+
active.delete(controller);
|
|
1138
|
+
}
|
|
1139
|
+
};
|
|
1140
|
+
} catch (error) {
|
|
1141
|
+
controller.abort();
|
|
1142
|
+
await run?.dispose();
|
|
1143
|
+
active.delete(controller);
|
|
1144
|
+
throw error;
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
},
|
|
1148
|
+
prepare: load,
|
|
1149
|
+
dispose() {
|
|
1150
|
+
disposed = true;
|
|
1151
|
+
for (const controller of active) controller.abort();
|
|
1152
|
+
}
|
|
1153
|
+
};
|
|
1107
1154
|
}
|
|
1108
|
-
/**
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1155
|
+
/** Change only standard independent spawn tools; leave fork/custom tools untouched. */
|
|
1156
|
+
function createSubagentBackendSwitcher({ entries, prepare, persist }) {
|
|
1157
|
+
const originals = /* @__PURE__ */ new Map();
|
|
1158
|
+
let selected = "dsh";
|
|
1159
|
+
let tail = Promise.resolve();
|
|
1160
|
+
let disposed = false;
|
|
1161
|
+
const standard = (entry, config) => entry?.options?.name === "@deepseek-ai/dsh-tool-subagent" && config?.provider === "spawn" && !config.agentOptions && !config.persona && !config.toolFilter;
|
|
1162
|
+
const convert = (config) => ({
|
|
1163
|
+
...config,
|
|
1164
|
+
provider: SUBAGENT_PROVIDER,
|
|
1165
|
+
modelSelectionSettings: false,
|
|
1166
|
+
backgroundMode: "one-shot",
|
|
1167
|
+
maxDepth: "provider-managed"
|
|
1168
|
+
});
|
|
1169
|
+
const configure = (fiber, config) => {
|
|
1170
|
+
if (!standard(fiber.entry, config)) return config;
|
|
1171
|
+
if (!originals.has(fiber)) originals.set(fiber, { ...config });
|
|
1172
|
+
return selected === "codex" ? convert(config) : config;
|
|
1173
|
+
};
|
|
1174
|
+
const select = (mode) => {
|
|
1175
|
+
if (!["dsh", "codex"].includes(mode)) return Promise.reject(/* @__PURE__ */ new Error("Invalid subagent backend"));
|
|
1176
|
+
const next = tail.catch(() => {}).then(async () => {
|
|
1177
|
+
if (disposed) throw new Error("Subagent backend is unavailable");
|
|
1178
|
+
const all = [...entries()];
|
|
1179
|
+
for (const entry of all) if (entry.fiber && standard(entry, entry.fiber.config) && !originals.has(entry.fiber)) originals.set(entry.fiber, { ...entry.fiber.config });
|
|
1180
|
+
if (mode === "codex") {
|
|
1181
|
+
if (!originals.size && !all.some((entry) => standard(entry, entry.options?.config))) throw new Error("No standard DSH independent subagent tool is available");
|
|
1182
|
+
await prepare();
|
|
1183
|
+
}
|
|
1184
|
+
const changed = [];
|
|
1185
|
+
const previous = selected;
|
|
1186
|
+
selected = mode;
|
|
1187
|
+
try {
|
|
1188
|
+
for (const [fiber, original] of originals) {
|
|
1189
|
+
if (fiber.entry && fiber.entry.fiber !== fiber) {
|
|
1190
|
+
originals.delete(fiber);
|
|
1191
|
+
continue;
|
|
1192
|
+
}
|
|
1193
|
+
const before = { ...fiber.config };
|
|
1194
|
+
changed.push({
|
|
1195
|
+
fiber,
|
|
1196
|
+
before
|
|
1197
|
+
});
|
|
1198
|
+
await fiber.update(mode === "codex" ? convert(original) : original, true);
|
|
1199
|
+
}
|
|
1200
|
+
await persist?.(mode);
|
|
1201
|
+
} catch (error) {
|
|
1202
|
+
selected = previous;
|
|
1203
|
+
for (const { fiber, before } of changed.reverse()) await fiber.update(before, true);
|
|
1204
|
+
throw error;
|
|
1205
|
+
}
|
|
1135
1206
|
});
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
}
|
|
1149
|
-
let hit = await this.credentials.resolve(this.ref);
|
|
1150
|
-
if (hit?.value === void 0 || hit.value === "") for (const legacyRef of this.legacyRefs) {
|
|
1151
|
-
const legacy = await this.credentials.resolve(legacyRef);
|
|
1152
|
-
if (legacy?.value === void 0 || legacy.value === "") continue;
|
|
1153
|
-
const migrated = parseOAuthCredential(legacy.value);
|
|
1154
|
-
await this.credentials.set(this.ref, JSON.stringify(migrated));
|
|
1155
|
-
await this.credentials.unset(legacyRef);
|
|
1156
|
-
hit = { value: JSON.stringify(migrated) };
|
|
1157
|
-
break;
|
|
1207
|
+
tail = next;
|
|
1208
|
+
return next;
|
|
1209
|
+
};
|
|
1210
|
+
return {
|
|
1211
|
+
select,
|
|
1212
|
+
configure,
|
|
1213
|
+
async dispose() {
|
|
1214
|
+
await tail.catch(() => {});
|
|
1215
|
+
disposed = true;
|
|
1216
|
+
selected = "dsh";
|
|
1217
|
+
for (const [fiber, original] of originals) if (fiber.config?.provider === "codex-subscription-subagent") await fiber.update(original, true);
|
|
1218
|
+
originals.clear();
|
|
1158
1219
|
}
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
}
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1220
|
+
};
|
|
1221
|
+
}
|
|
1222
|
+
async function loadSubagentRuntime() {
|
|
1223
|
+
const [official, { JsonRpcLineTransport: Transport }] = await Promise.all([import("@deepseek-ai/dsh-subagent-codex"), import("@deepseek-ai/dsh-sdk-protocol")]);
|
|
1224
|
+
return {
|
|
1225
|
+
official,
|
|
1226
|
+
Transport
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1229
|
+
//#endregion
|
|
1230
|
+
//#region src/sketch-agent-bridge.js
|
|
1231
|
+
function createSketchAgentBridge({ enabled, now = Date.now, timeoutMs = 2e4 }) {
|
|
1232
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
1233
|
+
const fail = (entry, message) => {
|
|
1234
|
+
for (const task of entry.tasks.values()) task.reject(Error(message));
|
|
1235
|
+
entry.tasks.clear();
|
|
1236
|
+
};
|
|
1237
|
+
const find = (payload) => {
|
|
1238
|
+
const entry = sessions.get(payload.sessionId);
|
|
1239
|
+
if (!entry || entry.token !== payload.token || now() - entry.seen > 1e4) throw Error("Sketch connection expired");
|
|
1240
|
+
entry.seen = now();
|
|
1241
|
+
return entry;
|
|
1242
|
+
};
|
|
1243
|
+
return {
|
|
1244
|
+
async rpc(endpoint, payload) {
|
|
1245
|
+
try {
|
|
1246
|
+
if (!enabled()) throw Error("Sketch is disabled");
|
|
1247
|
+
if (!payload || typeof payload.sessionId !== "string" || !payload.sessionId.length || payload.sessionId.length > 200) throw Error("Invalid session");
|
|
1248
|
+
if (endpoint === "sketch/connect") {
|
|
1249
|
+
for (const [id, entry] of sessions) if (now() - entry.seen >= 1e4) {
|
|
1250
|
+
fail(entry, "Sketch connection expired");
|
|
1251
|
+
sessions.delete(id);
|
|
1252
|
+
}
|
|
1253
|
+
const previous = sessions.get(payload.sessionId);
|
|
1254
|
+
if (previous && now() - previous.seen < 1e4) throw Error("Another board is connected to this session");
|
|
1255
|
+
if (previous) fail(previous, "Sketch connection replaced");
|
|
1256
|
+
const entry = {
|
|
1257
|
+
token: randomUUID(),
|
|
1258
|
+
seen: now(),
|
|
1259
|
+
tasks: /* @__PURE__ */ new Map(),
|
|
1260
|
+
cancelled: []
|
|
1184
1261
|
};
|
|
1185
|
-
|
|
1186
|
-
return
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1262
|
+
sessions.set(payload.sessionId, entry);
|
|
1263
|
+
return {
|
|
1264
|
+
ok: true,
|
|
1265
|
+
value: { token: entry.token }
|
|
1266
|
+
};
|
|
1267
|
+
}
|
|
1268
|
+
const entry = find(payload);
|
|
1269
|
+
if (endpoint === "sketch/poll") return {
|
|
1270
|
+
ok: true,
|
|
1271
|
+
value: [...entry.cancelled.splice(0).map((id) => ({
|
|
1272
|
+
id,
|
|
1273
|
+
cancelled: true
|
|
1274
|
+
})), ...[...entry.tasks].filter(([, t]) => !t.delivered).map(([id, t]) => {
|
|
1275
|
+
t.delivered = true;
|
|
1276
|
+
return {
|
|
1277
|
+
id,
|
|
1278
|
+
request: t.request,
|
|
1279
|
+
expiresAt: t.expiresAt
|
|
1280
|
+
};
|
|
1281
|
+
})]
|
|
1282
|
+
};
|
|
1283
|
+
if (endpoint === "sketch/claim") {
|
|
1284
|
+
const task = entry.tasks.get(payload.id);
|
|
1285
|
+
return {
|
|
1286
|
+
ok: true,
|
|
1287
|
+
value: Boolean(task && task.delivered && task.expiresAt > now())
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
1290
|
+
if (endpoint === "sketch/disconnect") {
|
|
1291
|
+
fail(entry, "Sketch board closed");
|
|
1292
|
+
sessions.delete(payload.sessionId);
|
|
1293
|
+
return {
|
|
1294
|
+
ok: true,
|
|
1295
|
+
value: null
|
|
1296
|
+
};
|
|
1297
|
+
}
|
|
1298
|
+
if (endpoint === "sketch/result") {
|
|
1299
|
+
const task = entry.tasks.get(payload.id);
|
|
1300
|
+
if (task) {
|
|
1301
|
+
entry.tasks.delete(payload.id);
|
|
1302
|
+
payload.error ? task.reject(Error(String(payload.error).slice(0, 500))) : task.resolve(payload.value);
|
|
1303
|
+
}
|
|
1304
|
+
return {
|
|
1305
|
+
ok: true,
|
|
1306
|
+
value: null
|
|
1307
|
+
};
|
|
1308
|
+
}
|
|
1309
|
+
throw Error("Unknown sketch route");
|
|
1310
|
+
} catch (error) {
|
|
1311
|
+
return {
|
|
1312
|
+
ok: false,
|
|
1313
|
+
error: {
|
|
1314
|
+
code: "invalid-input",
|
|
1315
|
+
message: error.message,
|
|
1316
|
+
details: { issues: [] }
|
|
1317
|
+
}
|
|
1318
|
+
};
|
|
1190
1319
|
}
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1320
|
+
},
|
|
1321
|
+
request(sessionId, request, signal) {
|
|
1322
|
+
if (!enabled()) return Promise.reject(Error("Sketch is disabled"));
|
|
1323
|
+
const entry = sessions.get(sessionId);
|
|
1324
|
+
if (!entry || now() - entry.seen > 1e4) return Promise.reject(Error("Switch to this session in DSH with sketch editing enabled"));
|
|
1325
|
+
if (entry.tasks.size) return Promise.reject(Error("Another sketch operation is pending"));
|
|
1326
|
+
if (JSON.stringify(request).length > 2e6) return Promise.reject(Error("Sketch batch is too large"));
|
|
1327
|
+
return new Promise((resolve, reject) => {
|
|
1328
|
+
const id = randomUUID();
|
|
1329
|
+
const finish = (callback, value) => {
|
|
1330
|
+
clearTimeout(timer);
|
|
1331
|
+
signal?.removeEventListener("abort", abort);
|
|
1332
|
+
entry.tasks.delete(id);
|
|
1333
|
+
callback(value);
|
|
1334
|
+
};
|
|
1335
|
+
const cancel = (message) => {
|
|
1336
|
+
if (entry.tasks.get(id)?.delivered) {
|
|
1337
|
+
entry.cancelled.push(id);
|
|
1338
|
+
if (entry.cancelled.length > 32) entry.cancelled.shift();
|
|
1339
|
+
}
|
|
1340
|
+
finish(reject, Error(message));
|
|
1341
|
+
};
|
|
1342
|
+
const abort = () => cancel("Sketch operation interrupted; inspect recentRequests before retrying");
|
|
1343
|
+
const timer = setTimeout(() => cancel("Sketch response timed out; inspect recentRequests before retrying"), timeoutMs);
|
|
1344
|
+
entry.tasks.set(id, {
|
|
1345
|
+
request,
|
|
1346
|
+
expiresAt: now() + timeoutMs,
|
|
1347
|
+
delivered: false,
|
|
1348
|
+
resolve: (value) => finish(resolve, value),
|
|
1349
|
+
reject: (error) => finish(reject, error)
|
|
1350
|
+
});
|
|
1351
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
1352
|
+
if (signal?.aborted) abort();
|
|
1353
|
+
});
|
|
1354
|
+
},
|
|
1355
|
+
dispose() {
|
|
1356
|
+
for (const entry of sessions.values()) fail(entry, "Sketch service stopped");
|
|
1357
|
+
sessions.clear();
|
|
1358
|
+
}
|
|
1359
|
+
};
|
|
1360
|
+
}
|
|
1361
|
+
//#endregion
|
|
1362
|
+
//#region src/sketch-command-schema.js
|
|
1363
|
+
const number = { type: "number" };
|
|
1364
|
+
const string = { type: "string" };
|
|
1365
|
+
const object = (properties) => ({
|
|
1366
|
+
type: "object",
|
|
1367
|
+
additionalProperties: false,
|
|
1368
|
+
properties
|
|
1369
|
+
});
|
|
1370
|
+
const points = {
|
|
1371
|
+
type: "array",
|
|
1372
|
+
items: object({
|
|
1373
|
+
x: {
|
|
1374
|
+
...number,
|
|
1375
|
+
required: true
|
|
1376
|
+
},
|
|
1377
|
+
y: {
|
|
1378
|
+
...number,
|
|
1379
|
+
required: true
|
|
1380
|
+
}
|
|
1381
|
+
})
|
|
1382
|
+
};
|
|
1383
|
+
const point = object({
|
|
1384
|
+
x: {
|
|
1385
|
+
...number,
|
|
1386
|
+
required: true
|
|
1387
|
+
},
|
|
1388
|
+
y: {
|
|
1389
|
+
...number,
|
|
1390
|
+
required: true
|
|
1201
1391
|
}
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1392
|
+
});
|
|
1393
|
+
const style = {
|
|
1394
|
+
color: string,
|
|
1395
|
+
width: number,
|
|
1396
|
+
opacity: number,
|
|
1397
|
+
fill: { type: "boolean" },
|
|
1398
|
+
text: string,
|
|
1399
|
+
points
|
|
1400
|
+
};
|
|
1401
|
+
const sketchCommandArray = {
|
|
1402
|
+
type: "array",
|
|
1403
|
+
items: object({
|
|
1404
|
+
op: {
|
|
1405
|
+
type: "string",
|
|
1406
|
+
required: true,
|
|
1407
|
+
enum: [
|
|
1408
|
+
"stroke",
|
|
1409
|
+
"object",
|
|
1410
|
+
"layer",
|
|
1411
|
+
"resize"
|
|
1412
|
+
]
|
|
1413
|
+
},
|
|
1414
|
+
id: {
|
|
1415
|
+
oneOf: [{ type: "string" }, { type: "integer" }],
|
|
1416
|
+
description: "Object string ID. Layer add: optional NEW unique integer ID; other layer actions: existing layer ID."
|
|
1417
|
+
},
|
|
1418
|
+
after: {
|
|
1419
|
+
type: "integer",
|
|
1420
|
+
description: "Layer add only: existing layer to insert after; defaults to active."
|
|
1421
|
+
},
|
|
1422
|
+
start: point,
|
|
1423
|
+
segments: {
|
|
1424
|
+
type: "array",
|
|
1425
|
+
items: object({
|
|
1426
|
+
control1: {
|
|
1427
|
+
...point,
|
|
1428
|
+
required: true
|
|
1429
|
+
},
|
|
1430
|
+
control2: {
|
|
1431
|
+
...point,
|
|
1432
|
+
required: true
|
|
1433
|
+
},
|
|
1434
|
+
end: {
|
|
1435
|
+
...point,
|
|
1436
|
+
required: true
|
|
1437
|
+
}
|
|
1438
|
+
})
|
|
1439
|
+
},
|
|
1440
|
+
layer: { type: "integer" },
|
|
1441
|
+
shape: {
|
|
1442
|
+
type: "string",
|
|
1443
|
+
enum: [
|
|
1444
|
+
"pen",
|
|
1445
|
+
"line",
|
|
1446
|
+
"arrow",
|
|
1447
|
+
"text",
|
|
1448
|
+
"rectangle",
|
|
1449
|
+
"circle",
|
|
1450
|
+
"ellipse",
|
|
1451
|
+
"polygon",
|
|
1452
|
+
"bezier",
|
|
1453
|
+
"eraser",
|
|
1454
|
+
"triangle",
|
|
1455
|
+
"diamond",
|
|
1456
|
+
"star"
|
|
1457
|
+
]
|
|
1458
|
+
},
|
|
1459
|
+
...style,
|
|
1460
|
+
action: {
|
|
1461
|
+
type: "string",
|
|
1462
|
+
enum: [
|
|
1463
|
+
"update",
|
|
1464
|
+
"duplicate",
|
|
1465
|
+
"delete",
|
|
1466
|
+
"add",
|
|
1467
|
+
"select",
|
|
1468
|
+
"rename",
|
|
1469
|
+
"visible",
|
|
1470
|
+
"up",
|
|
1471
|
+
"down",
|
|
1472
|
+
"clear"
|
|
1473
|
+
]
|
|
1474
|
+
},
|
|
1475
|
+
value: string,
|
|
1476
|
+
ratio: {
|
|
1477
|
+
type: "string",
|
|
1478
|
+
enum: [
|
|
1479
|
+
"1:1",
|
|
1480
|
+
"4:3",
|
|
1481
|
+
"3:4",
|
|
1482
|
+
"16:9",
|
|
1483
|
+
"9:16"
|
|
1484
|
+
]
|
|
1485
|
+
},
|
|
1486
|
+
patch: object(style),
|
|
1487
|
+
transform: object({
|
|
1488
|
+
dx: number,
|
|
1489
|
+
dy: number,
|
|
1490
|
+
scaleX: number,
|
|
1491
|
+
scaleY: number
|
|
1492
|
+
})
|
|
1493
|
+
})
|
|
1494
|
+
};
|
|
1495
|
+
//#endregion
|
|
1496
|
+
//#region src/sketch-agent-tool.js
|
|
1497
|
+
function createSketchAgentTool(bridge, attachments) {
|
|
1498
|
+
return defineTool({
|
|
1499
|
+
name: "codex_sketch",
|
|
1500
|
+
description: "Edit the sketch board in this session using native editable strokes and layers. Use for @sketch requests and explicit follow-up edits to that drawing. The toolbar pen button is for manual drawing; do not require the user to open it. Start with inspect for the runId, documentId, revision and command reference. Apply atomic batches, preview between stages, and call finish to save the finished draft and release the editing lock. save is only a checkpoint. finish returns an image only when the user enables experimental preview feedback. Closing the board does not stop drawing. If the user stops drawing, do not retry. Never generates AI images, sends messages or attaches images automatically. Inspect automatically opens the board in the currently viewed session. Do not ask the user to open it first. If the session is not visible in DSH, ask them to switch to it. On timeout inspect before retrying; reuse the exact requestId only for the same request.",
|
|
1501
|
+
parameters: {
|
|
1502
|
+
action: {
|
|
1503
|
+
type: "string",
|
|
1504
|
+
required: true,
|
|
1505
|
+
enum: [
|
|
1506
|
+
"inspect",
|
|
1507
|
+
"apply",
|
|
1508
|
+
"preview",
|
|
1509
|
+
"save",
|
|
1510
|
+
"finish"
|
|
1511
|
+
]
|
|
1512
|
+
},
|
|
1513
|
+
runId: {
|
|
1514
|
+
type: "string",
|
|
1515
|
+
description: "From inspect; required for all other actions. Never reuse a stopped run."
|
|
1516
|
+
},
|
|
1517
|
+
documentId: {
|
|
1518
|
+
type: "string",
|
|
1519
|
+
description: "From inspect; required except for inspect."
|
|
1520
|
+
},
|
|
1521
|
+
revision: {
|
|
1522
|
+
type: "integer",
|
|
1523
|
+
description: "From latest response; required for apply/save/finish."
|
|
1524
|
+
},
|
|
1525
|
+
requestId: {
|
|
1526
|
+
type: "string",
|
|
1527
|
+
description: "Unique id for apply/save/finish; exact retries are deduplicated. After timeout inspect recentRequests before repeating a write."
|
|
1528
|
+
},
|
|
1529
|
+
commands: {
|
|
1530
|
+
oneOf: [sketchCommandArray, { type: "string" }],
|
|
1531
|
+
description: "Prefer a native command array. Legacy JSON string also accepted. Required for apply. Use named objects and update existing IDs; prefer Bezier start + segments (control1/control2/end) for curves, not hundreds of pen points."
|
|
1532
|
+
},
|
|
1533
|
+
name: {
|
|
1534
|
+
type: "string",
|
|
1535
|
+
description: "Draft name for save/finish."
|
|
1536
|
+
},
|
|
1537
|
+
offset: {
|
|
1538
|
+
type: "integer",
|
|
1539
|
+
description: "inspect only: object list offset, default 0. Follow nextOffset for further pages."
|
|
1540
|
+
},
|
|
1541
|
+
objectId: {
|
|
1542
|
+
type: "string",
|
|
1543
|
+
description: "inspect only: return full editable geometry for this object, in layer (defaults to active layer)."
|
|
1544
|
+
},
|
|
1545
|
+
layer: {
|
|
1546
|
+
type: "integer",
|
|
1547
|
+
description: "inspect only: layer containing objectId."
|
|
1208
1548
|
}
|
|
1209
|
-
await this.credentials.unset(this.ref);
|
|
1210
|
-
for (const legacyRef of this.legacyRefs) await this.credentials.unset(legacyRef);
|
|
1211
|
-
abortIfNeeded(options);
|
|
1212
|
-
}, options);
|
|
1213
|
-
}
|
|
1214
|
-
};
|
|
1215
|
-
/** Return only account state that is safe to expose to the browser client. */
|
|
1216
|
-
function createCodexAuthService(models, store, options = {}) {
|
|
1217
|
-
const runLogin = options.runLogin ?? ((run) => run());
|
|
1218
|
-
const accountVault = options.accountVault;
|
|
1219
|
-
const createLoginModels = options.createLoginModels;
|
|
1220
|
-
const createPendingStore = options.createPendingStore ?? (() => new PendingOAuthCredentialStore());
|
|
1221
|
-
return Object.freeze({
|
|
1222
|
-
async status(options) {
|
|
1223
|
-
const current = await store.read(PROVIDER$1, options);
|
|
1224
|
-
const accounts = await accountVault?.list();
|
|
1225
|
-
if (current === void 0) return {
|
|
1226
|
-
authenticated: false,
|
|
1227
|
-
provider: PROVIDER$1,
|
|
1228
|
-
...accounts === void 0 ? {} : { accounts }
|
|
1229
|
-
};
|
|
1230
|
-
return {
|
|
1231
|
-
authenticated: true,
|
|
1232
|
-
provider: PROVIDER$1,
|
|
1233
|
-
type: "oauth",
|
|
1234
|
-
expiresAt: current.expires,
|
|
1235
|
-
...accounts === void 0 ? {} : { accounts }
|
|
1236
|
-
};
|
|
1237
1549
|
},
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1550
|
+
timeoutMs: 25e3,
|
|
1551
|
+
isConcurrencySafe: () => false,
|
|
1552
|
+
async execute(args, exec) {
|
|
1553
|
+
const sessionId = exec.agent?.id;
|
|
1554
|
+
if (typeof sessionId !== "string") throw Error("A session-owned sketch call is required");
|
|
1555
|
+
const request = { ...args };
|
|
1556
|
+
if (args.action === "apply") try {
|
|
1557
|
+
request.commands = typeof args.commands === "string" ? JSON.parse(args.commands) : args.commands;
|
|
1558
|
+
if (!Array.isArray(request.commands)) throw Error();
|
|
1559
|
+
} catch {
|
|
1560
|
+
throw Error("commands must be a native array or JSON array string");
|
|
1561
|
+
}
|
|
1562
|
+
const value = await bridge.request(sessionId, request, exec.signal);
|
|
1563
|
+
if (value.png) {
|
|
1564
|
+
if (!/^data:image\/png;base64,/.test(value.png) || value.png.length > 8 * 1024 * 1024) throw Error("Invalid sketch preview");
|
|
1565
|
+
const image = await attachments.saveImage({
|
|
1566
|
+
data: new Uint8Array(Buffer.from(value.png.split(",")[1], "base64")),
|
|
1567
|
+
mediaType: "image/png",
|
|
1568
|
+
name: "sketch-preview.png"
|
|
1247
1569
|
});
|
|
1570
|
+
const { png, ...snapshot } = value;
|
|
1571
|
+
return {
|
|
1572
|
+
...snapshot,
|
|
1573
|
+
image
|
|
1574
|
+
};
|
|
1248
1575
|
}
|
|
1249
|
-
return
|
|
1250
|
-
},
|
|
1251
|
-
async select(id) {
|
|
1252
|
-
if (accountVault === void 0) throw new Error("Codex multi-account is unavailable");
|
|
1253
|
-
await accountVault.select(id);
|
|
1254
|
-
return this.status();
|
|
1255
|
-
},
|
|
1256
|
-
async remove(id) {
|
|
1257
|
-
if (accountVault === void 0) throw new Error("Codex multi-account is unavailable");
|
|
1258
|
-
await accountVault.remove(id);
|
|
1259
|
-
return this.status();
|
|
1576
|
+
return value;
|
|
1260
1577
|
},
|
|
1261
|
-
|
|
1262
|
-
|
|
1578
|
+
output: {
|
|
1579
|
+
schema: {
|
|
1580
|
+
type: "object",
|
|
1581
|
+
additionalProperties: true
|
|
1582
|
+
},
|
|
1583
|
+
render: (_args, value) => [{
|
|
1584
|
+
type: "text",
|
|
1585
|
+
text: JSON.stringify({
|
|
1586
|
+
...value,
|
|
1587
|
+
image: void 0
|
|
1588
|
+
})
|
|
1589
|
+
}, ...value.image ? [{
|
|
1590
|
+
type: "image",
|
|
1591
|
+
attachment: value.image
|
|
1592
|
+
}] : []]
|
|
1263
1593
|
}
|
|
1264
1594
|
});
|
|
1265
1595
|
}
|
|
1266
1596
|
//#endregion
|
|
1267
|
-
//#region src/
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1597
|
+
//#region src/sketch-codec-route.js
|
|
1598
|
+
function registerSketchCodec(connection) {
|
|
1599
|
+
let source;
|
|
1600
|
+
return connection.fetch.register({
|
|
1601
|
+
path: "/api/codex-subscription/sketch-psd-worker",
|
|
1602
|
+
methods: ["GET"],
|
|
1603
|
+
requestBody: "buffered",
|
|
1604
|
+
async fetch() {
|
|
1605
|
+
source ??= await readFile(new URL("./sketch-psd-worker.js", import.meta.url));
|
|
1606
|
+
return new Response(source, { headers: {
|
|
1607
|
+
"content-type": "text/javascript; charset=utf-8",
|
|
1608
|
+
"cache-control": "no-store"
|
|
1609
|
+
} });
|
|
1610
|
+
}
|
|
1611
|
+
});
|
|
1612
|
+
}
|
|
1613
|
+
//#endregion
|
|
1614
|
+
//#region src/account-vault.js
|
|
1615
|
+
const VERSION = 1;
|
|
1616
|
+
const DEFAULT_LABEL = "Account 1";
|
|
1617
|
+
const clone$1 = (value) => value === void 0 ? void 0 : structuredClone(value);
|
|
1618
|
+
const EMAIL_MAX_LENGTH = 254;
|
|
1619
|
+
const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/u;
|
|
1620
|
+
/** Keep only a bounded, display-safe email address from a trusted OAuth result. */
|
|
1621
|
+
function normalizeAccountEmail(value) {
|
|
1622
|
+
if (typeof value !== "string") return void 0;
|
|
1623
|
+
const email = value.trim();
|
|
1624
|
+
return email.length > 0 && email.length <= EMAIL_MAX_LENGTH && EMAIL_PATTERN.test(email) ? email : void 0;
|
|
1625
|
+
}
|
|
1626
|
+
function decodeJwtPayload(access) {
|
|
1627
|
+
if (typeof access !== "string") return void 0;
|
|
1628
|
+
const encoded = access.split(".")[1];
|
|
1629
|
+
if (typeof encoded !== "string" || encoded.length === 0) return void 0;
|
|
1272
1630
|
try {
|
|
1273
|
-
|
|
1631
|
+
return JSON.parse(Buffer.from(encoded, "base64url").toString("utf8"));
|
|
1274
1632
|
} catch {
|
|
1275
|
-
|
|
1633
|
+
return;
|
|
1276
1634
|
}
|
|
1277
|
-
if (url.protocol !== "https:") throw new Error("Codex auth URL must use HTTPS");
|
|
1278
|
-
if (url.origin !== OPENAI_AUTH_ORIGIN || url.username !== "" || url.password !== "") throw new Error("Codex auth URL must use the OpenAI auth origin");
|
|
1279
|
-
return url.href;
|
|
1280
1635
|
}
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
if (platform === "win32") return {
|
|
1285
|
-
file: "rundll32.exe",
|
|
1286
|
-
args: ["url.dll,FileProtocolHandler", url],
|
|
1287
|
-
shell: false
|
|
1288
|
-
};
|
|
1289
|
-
if (platform === "darwin") return {
|
|
1290
|
-
file: "open",
|
|
1291
|
-
args: [url],
|
|
1292
|
-
shell: false
|
|
1293
|
-
};
|
|
1294
|
-
if (platform === "linux") return {
|
|
1295
|
-
file: "xdg-open",
|
|
1296
|
-
args: [url],
|
|
1297
|
-
shell: false
|
|
1298
|
-
};
|
|
1299
|
-
throw new Error(`Codex auth URL opener is unsupported on ${platform}`);
|
|
1300
|
-
}
|
|
1301
|
-
function openCodexAuthUrl(value, options = {}) {
|
|
1302
|
-
const command = commandForCodexAuthUrl(value, options.platform);
|
|
1303
|
-
const spawnProcess = options.spawn ?? spawn;
|
|
1304
|
-
return new Promise((resolve, reject) => {
|
|
1305
|
-
const child = spawnProcess(command.file, command.args, {
|
|
1306
|
-
detached: true,
|
|
1307
|
-
stdio: "ignore",
|
|
1308
|
-
windowsHide: true,
|
|
1309
|
-
shell: command.shell
|
|
1310
|
-
});
|
|
1311
|
-
child.once("error", reject);
|
|
1312
|
-
child.once("spawn", () => {
|
|
1313
|
-
child.unref();
|
|
1314
|
-
resolve();
|
|
1315
|
-
});
|
|
1316
|
-
});
|
|
1636
|
+
function emailFromAccessToken(access) {
|
|
1637
|
+
const payload = decodeJwtPayload(access);
|
|
1638
|
+
return normalizeAccountEmail(payload?.["https://api.openai.com/profile"]?.email ?? payload?.email);
|
|
1317
1639
|
}
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
const
|
|
1321
|
-
const
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
const publicClone = (value) => structuredClone(value);
|
|
1327
|
-
const asObject = (value) => value !== null && typeof value === "object" ? value : {};
|
|
1328
|
-
const ok = (value) => ({
|
|
1329
|
-
ok: true,
|
|
1330
|
-
value
|
|
1331
|
-
});
|
|
1332
|
-
const badRequest = (message) => ({
|
|
1333
|
-
ok: false,
|
|
1334
|
-
error: {
|
|
1335
|
-
code: "bad-request",
|
|
1336
|
-
message,
|
|
1337
|
-
details: { issues: [] }
|
|
1338
|
-
}
|
|
1339
|
-
});
|
|
1340
|
-
const accountStatusError = (message) => ({
|
|
1341
|
-
ok: false,
|
|
1342
|
-
error: {
|
|
1343
|
-
code: "internal",
|
|
1344
|
-
message,
|
|
1345
|
-
details: { issues: [] }
|
|
1346
|
-
}
|
|
1347
|
-
});
|
|
1348
|
-
const classifyAccountStatusError = (error) => {
|
|
1349
|
-
const message = error instanceof Error ? error.message : "";
|
|
1350
|
-
if (/malformed (?:OAuth|grant|account vault)|received a malformed OAuth|contains malformed OAuth/iu.test(message)) return ["credential-malformed", "Codex account credentials are malformed"];
|
|
1351
|
-
if (/credential|account vault|readRecord|credential store|credentials service/iu.test(message)) return ["credential-unavailable", "Codex account credentials are unavailable"];
|
|
1352
|
-
const code = typeof error?.code === "string" ? error.code.toUpperCase() : "";
|
|
1353
|
-
if (error?.name === "TimeoutError" || [
|
|
1354
|
-
"TIMEOUT",
|
|
1355
|
-
"ETIMEDOUT",
|
|
1356
|
-
"UND_ERR_CONNECT_TIMEOUT"
|
|
1357
|
-
].includes(code)) return ["transport", "Codex account status service is unavailable"];
|
|
1358
|
-
if ([
|
|
1359
|
-
"ECONNRESET",
|
|
1360
|
-
"ECONNREFUSED",
|
|
1361
|
-
"ENOTFOUND",
|
|
1362
|
-
"EAI_AGAIN",
|
|
1363
|
-
"NETWORK",
|
|
1364
|
-
"NETWORK_ERROR",
|
|
1365
|
-
"TRANSPORT"
|
|
1366
|
-
].includes(code) || error?.name === "NetworkError") return ["transport", "Codex account status service is unavailable"];
|
|
1367
|
-
return ["unknown", "Could not read Codex account status"];
|
|
1368
|
-
};
|
|
1369
|
-
const deferred = () => {
|
|
1370
|
-
let resolve;
|
|
1371
|
-
let reject;
|
|
1640
|
+
/** Normalize the one non-secret account attribute that may cross the UI boundary. */
|
|
1641
|
+
function sanitizeOAuthCredential(value) {
|
|
1642
|
+
const credential = assertOAuthCredential$1(value);
|
|
1643
|
+
const email = emailFromAccessToken(credential.access) ?? normalizeAccountEmail(credential.email);
|
|
1644
|
+
if (email === void 0) {
|
|
1645
|
+
delete credential.email;
|
|
1646
|
+
return credential;
|
|
1647
|
+
}
|
|
1372
1648
|
return {
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
reject = onReject;
|
|
1376
|
-
}),
|
|
1377
|
-
resolve,
|
|
1378
|
-
reject
|
|
1649
|
+
...credential,
|
|
1650
|
+
email
|
|
1379
1651
|
};
|
|
1380
|
-
};
|
|
1381
|
-
const publicPrompt = (prompt) => ({
|
|
1382
|
-
type: prompt.type,
|
|
1383
|
-
message: String(prompt.message ?? ""),
|
|
1384
|
-
...typeof prompt.placeholder === "string" ? { placeholder: prompt.placeholder } : {}
|
|
1385
|
-
});
|
|
1386
|
-
function classifyLoginFailure(error) {
|
|
1387
|
-
const message = error instanceof Error ? error.message : "";
|
|
1388
|
-
if (/token exchange failed/iu.test(message)) return "token-exchange";
|
|
1389
|
-
if (/fetch failed|\b(?:ECONN|ENOTFOUND|ETIMEDOUT|CERT_|socket|network)\b/iu.test(message)) return "network";
|
|
1390
|
-
if (/extract accountId|account[_ -]?id/iu.test(message)) return "account-claim";
|
|
1391
|
-
if (/credential|credentials-local|OAuth JSON/iu.test(message)) return "credential-store";
|
|
1392
|
-
if (/Missing authorization code|State mismatch|callback/iu.test(message)) return "callback";
|
|
1393
|
-
return "provider";
|
|
1394
1652
|
}
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
}
|
|
1403
|
-
|
|
1404
|
-
|
|
1653
|
+
function assertOAuthCredential$1(value) {
|
|
1654
|
+
if (value === null || typeof value !== "object" || value.type !== "oauth" || typeof value.access !== "string" || value.access.length === 0 || typeof value.refresh !== "string" || value.refresh.length === 0 || typeof value.expires !== "number" || !Number.isFinite(value.expires)) throw new Error("Codex account vault received a malformed OAuth credential");
|
|
1655
|
+
return clone$1(value);
|
|
1656
|
+
}
|
|
1657
|
+
function parseOAuthCredential$1(value) {
|
|
1658
|
+
try {
|
|
1659
|
+
return assertOAuthCredential$1(JSON.parse(value));
|
|
1660
|
+
} catch (error) {
|
|
1661
|
+
if (error?.message === "Codex account vault received a malformed OAuth credential") throw error;
|
|
1662
|
+
throw new Error("Codex account vault contains malformed OAuth JSON", { cause: error });
|
|
1405
1663
|
}
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1664
|
+
}
|
|
1665
|
+
function normalizeLabel(value) {
|
|
1666
|
+
if (typeof value !== "string") throw new Error("Codex account label must be text");
|
|
1667
|
+
const label = value.trim().replace(/\s+/gu, " ");
|
|
1668
|
+
if (label.length === 0 || label.length > 48) throw new Error("Codex account label must contain 1 to 48 characters");
|
|
1669
|
+
return label;
|
|
1670
|
+
}
|
|
1671
|
+
function assertVaultRecord(record) {
|
|
1672
|
+
if (record?.kind !== "grant" || record.payload?.version !== VERSION || typeof record.payload.activeId !== "string" || !Array.isArray(record.payload.accounts) || record.payload.accounts.length === 0) throw new Error("Codex account vault contains a malformed grant record");
|
|
1673
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1674
|
+
const accounts = record.payload.accounts.map((account) => {
|
|
1675
|
+
if (account === null || typeof account !== "object" || typeof account.id !== "string" || account.id.length === 0 || ids.has(account.id)) throw new Error("Codex account vault contains a malformed account id");
|
|
1676
|
+
ids.add(account.id);
|
|
1409
1677
|
return {
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1678
|
+
id: account.id,
|
|
1679
|
+
label: normalizeLabel(account.label),
|
|
1680
|
+
credential: sanitizeOAuthCredential(account.credential)
|
|
1413
1681
|
};
|
|
1682
|
+
});
|
|
1683
|
+
if (!ids.has(record.payload.activeId)) throw new Error("Codex account vault active account is missing");
|
|
1684
|
+
const legacyAccountId = record.payload.legacyAccountId;
|
|
1685
|
+
if (legacyAccountId !== void 0 && !ids.has(legacyAccountId)) throw new Error("Codex account vault legacy account is missing");
|
|
1686
|
+
return {
|
|
1687
|
+
version: VERSION,
|
|
1688
|
+
activeId: record.payload.activeId,
|
|
1689
|
+
legacyAccountId,
|
|
1690
|
+
accounts
|
|
1691
|
+
};
|
|
1692
|
+
}
|
|
1693
|
+
const grant = (payload) => ({
|
|
1694
|
+
kind: "grant",
|
|
1695
|
+
payload
|
|
1696
|
+
});
|
|
1697
|
+
var PendingOAuthCredentialStore = class {
|
|
1698
|
+
#credential;
|
|
1699
|
+
async read(providerId) {
|
|
1700
|
+
if (providerId !== "openai-codex") throw new Error("Pending Codex login received an unknown provider");
|
|
1701
|
+
return clone$1(this.#credential);
|
|
1414
1702
|
}
|
|
1415
|
-
async
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1703
|
+
async list() {
|
|
1704
|
+
return this.#credential === void 0 ? [] : [{
|
|
1705
|
+
providerId: "openai-codex",
|
|
1706
|
+
type: "oauth"
|
|
1707
|
+
}];
|
|
1708
|
+
}
|
|
1709
|
+
async modify(providerId, update) {
|
|
1710
|
+
if (providerId !== "openai-codex") throw new Error("Pending Codex login received an unknown provider");
|
|
1711
|
+
const next = await update(clone$1(this.#credential));
|
|
1712
|
+
if (next !== void 0) this.#credential = sanitizeOAuthCredential(next);
|
|
1713
|
+
return clone$1(this.#credential);
|
|
1714
|
+
}
|
|
1715
|
+
async delete(providerId) {
|
|
1716
|
+
if (providerId !== "openai-codex") throw new Error("Pending Codex login received an unknown provider");
|
|
1717
|
+
this.#credential = void 0;
|
|
1718
|
+
}
|
|
1719
|
+
credential() {
|
|
1720
|
+
return clone$1(this.#credential);
|
|
1721
|
+
}
|
|
1722
|
+
};
|
|
1723
|
+
/**
|
|
1724
|
+
* Multi-account owner state stored in DSH's atomic plugin credential record.
|
|
1725
|
+
* The old single-account reference remains as a rollback source and is kept in
|
|
1726
|
+
* sync whenever that imported account rotates its refresh token.
|
|
1727
|
+
*/
|
|
1728
|
+
var DshOAuthAccountVault = class {
|
|
1729
|
+
#tail = Promise.resolve();
|
|
1730
|
+
constructor(credentials, options) {
|
|
1731
|
+
if (credentials === void 0 || credentials === null || typeof credentials.readRecord !== "function" || typeof credentials.modifyRecord !== "function") throw new Error("Codex multi-account requires DSH credential records");
|
|
1732
|
+
this.credentials = credentials;
|
|
1733
|
+
this.key = options.key;
|
|
1734
|
+
this.legacyRef = options.legacyRef;
|
|
1735
|
+
this.legacyRefs = Object.freeze([...options.legacyRefs ?? []]);
|
|
1736
|
+
this.createId = options.createId ?? randomUUID;
|
|
1737
|
+
this.onLegacySyncFailure = options.onLegacySyncFailure ?? (() => {});
|
|
1738
|
+
}
|
|
1739
|
+
#enqueue(operation) {
|
|
1740
|
+
const current = this.#tail.catch(() => void 0).then(operation);
|
|
1741
|
+
this.#tail = current.catch(() => void 0);
|
|
1742
|
+
return current;
|
|
1743
|
+
}
|
|
1744
|
+
async #legacyCredential() {
|
|
1745
|
+
for (const ref of [this.legacyRef, ...this.legacyRefs]) {
|
|
1746
|
+
const hit = await this.credentials.resolve(ref);
|
|
1747
|
+
if (hit?.value === void 0 || hit.value === "") continue;
|
|
1748
|
+
return {
|
|
1749
|
+
ref,
|
|
1750
|
+
credential: parseOAuthCredential$1(hit.value)
|
|
1426
1751
|
};
|
|
1427
|
-
active.controller.abort(/* @__PURE__ */ new Error("Codex login replaced by a new attempt"));
|
|
1428
1752
|
}
|
|
1429
|
-
|
|
1753
|
+
}
|
|
1754
|
+
async #ensurePayload() {
|
|
1755
|
+
const existing = await this.credentials.readRecord(this.key);
|
|
1756
|
+
if (existing !== void 0) return assertVaultRecord(existing);
|
|
1757
|
+
const legacy = await this.#legacyCredential();
|
|
1758
|
+
if (legacy === void 0) return void 0;
|
|
1430
1759
|
const id = this.createId();
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
this.#
|
|
1447
|
-
|
|
1448
|
-
const
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
if (controller.signal.aborted) return;
|
|
1504
|
-
const status = await this.auth.status();
|
|
1505
|
-
session.view = {
|
|
1760
|
+
return assertVaultRecord(await this.credentials.modifyRecord(this.key, (current) => {
|
|
1761
|
+
if (current !== void 0) return Promise.resolve(current);
|
|
1762
|
+
return Promise.resolve(grant({
|
|
1763
|
+
version: VERSION,
|
|
1764
|
+
activeId: id,
|
|
1765
|
+
legacyAccountId: id,
|
|
1766
|
+
accounts: [{
|
|
1767
|
+
id,
|
|
1768
|
+
label: DEFAULT_LABEL,
|
|
1769
|
+
credential: legacy.credential
|
|
1770
|
+
}]
|
|
1771
|
+
}));
|
|
1772
|
+
}));
|
|
1773
|
+
}
|
|
1774
|
+
async #modifyPayload(update) {
|
|
1775
|
+
await this.#ensurePayload();
|
|
1776
|
+
let previousLegacy;
|
|
1777
|
+
const payload = assertVaultRecord(await this.credentials.modifyRecord(this.key, async (current) => {
|
|
1778
|
+
if (current === void 0) throw new Error("Codex account vault is not signed in");
|
|
1779
|
+
const payload = assertVaultRecord(current);
|
|
1780
|
+
previousLegacy = payload.accounts.find((account) => account.id === payload.legacyAccountId)?.credential;
|
|
1781
|
+
const next = await update(clone$1(payload));
|
|
1782
|
+
return grant(next);
|
|
1783
|
+
}));
|
|
1784
|
+
const legacy = payload.accounts.find((account) => account.id === payload.legacyAccountId)?.credential;
|
|
1785
|
+
try {
|
|
1786
|
+
if (legacy === void 0) {
|
|
1787
|
+
if (previousLegacy !== void 0) await this.credentials.unset(this.legacyRef);
|
|
1788
|
+
} else if (JSON.stringify(legacy) !== JSON.stringify(previousLegacy)) await this.credentials.set(this.legacyRef, JSON.stringify(legacy));
|
|
1789
|
+
} catch {
|
|
1790
|
+
this.onLegacySyncFailure();
|
|
1791
|
+
}
|
|
1792
|
+
return payload;
|
|
1793
|
+
}
|
|
1794
|
+
list() {
|
|
1795
|
+
return this.#enqueue(async () => {
|
|
1796
|
+
const payload = await this.#ensurePayload();
|
|
1797
|
+
if (payload === void 0) return [];
|
|
1798
|
+
return payload.accounts.map((account) => ({
|
|
1799
|
+
id: account.id,
|
|
1800
|
+
label: account.label,
|
|
1801
|
+
active: account.id === payload.activeId,
|
|
1802
|
+
expiresAt: account.credential.expires,
|
|
1803
|
+
...account.credential.email === void 0 ? {} : { email: account.credential.email }
|
|
1804
|
+
}));
|
|
1805
|
+
});
|
|
1806
|
+
}
|
|
1807
|
+
readActive() {
|
|
1808
|
+
return this.#enqueue(async () => {
|
|
1809
|
+
const payload = await this.#ensurePayload();
|
|
1810
|
+
return clone$1(payload?.accounts.find((account) => account.id === payload.activeId)?.credential);
|
|
1811
|
+
});
|
|
1812
|
+
}
|
|
1813
|
+
activeId() {
|
|
1814
|
+
return this.#enqueue(async () => (await this.#ensurePayload())?.activeId);
|
|
1815
|
+
}
|
|
1816
|
+
add(label, credential) {
|
|
1817
|
+
return this.#enqueue(async () => {
|
|
1818
|
+
const normalizedLabel = normalizeLabel(label);
|
|
1819
|
+
const validated = sanitizeOAuthCredential(credential);
|
|
1820
|
+
await this.#ensurePayload();
|
|
1821
|
+
const id = this.createId();
|
|
1822
|
+
const account = (await this.#modifyPayload((current) => ({
|
|
1823
|
+
...current,
|
|
1824
|
+
activeId: id,
|
|
1825
|
+
accounts: [...current.accounts, {
|
|
1826
|
+
id,
|
|
1827
|
+
label: normalizedLabel,
|
|
1828
|
+
credential: validated
|
|
1829
|
+
}]
|
|
1830
|
+
}))).accounts.find((candidate) => candidate.id === id);
|
|
1831
|
+
return {
|
|
1506
1832
|
id,
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
...typeof status.expiresAt === "number" ? { expiresAt: status.expiresAt } : {}
|
|
1833
|
+
label: account.label,
|
|
1834
|
+
active: true,
|
|
1835
|
+
expiresAt: account.credential.expires,
|
|
1836
|
+
...account.credential.email === void 0 ? {} : { email: account.credential.email }
|
|
1512
1837
|
};
|
|
1513
|
-
})
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1838
|
+
});
|
|
1839
|
+
}
|
|
1840
|
+
select(id) {
|
|
1841
|
+
return this.#enqueue(async () => {
|
|
1842
|
+
await this.#modifyPayload((current) => {
|
|
1843
|
+
if (!current.accounts.some((account) => account.id === id)) throw new Error("Unknown Codex account");
|
|
1844
|
+
return {
|
|
1845
|
+
...current,
|
|
1846
|
+
activeId: id
|
|
1521
1847
|
};
|
|
1522
|
-
|
|
1848
|
+
});
|
|
1849
|
+
});
|
|
1850
|
+
}
|
|
1851
|
+
modifyActive(update) {
|
|
1852
|
+
return this.#enqueue(async () => {
|
|
1853
|
+
if (await this.#ensurePayload() === void 0) {
|
|
1854
|
+
const initial = await update(void 0);
|
|
1855
|
+
if (initial === void 0) return void 0;
|
|
1856
|
+
const credential = sanitizeOAuthCredential(initial);
|
|
1857
|
+
await this.credentials.set(this.legacyRef, JSON.stringify(credential));
|
|
1858
|
+
await this.#ensurePayload();
|
|
1859
|
+
return clone$1(credential);
|
|
1523
1860
|
}
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
const
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
phase: "authenticated",
|
|
1533
|
-
authenticated: true,
|
|
1534
|
-
...typeof status.expiresAt === "number" ? { expiresAt: status.expiresAt } : {}
|
|
1535
|
-
};
|
|
1536
|
-
return;
|
|
1861
|
+
let result;
|
|
1862
|
+
await this.#modifyPayload(async (current) => {
|
|
1863
|
+
const index = current.accounts.findIndex((account) => account.id === current.activeId);
|
|
1864
|
+
const previous = clone$1(current.accounts[index].credential);
|
|
1865
|
+
const next = await update(previous);
|
|
1866
|
+
if (next === void 0) {
|
|
1867
|
+
result = previous;
|
|
1868
|
+
return current;
|
|
1537
1869
|
}
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1870
|
+
const credential = sanitizeOAuthCredential(next);
|
|
1871
|
+
if (credential.email === void 0 && previous.email !== void 0) credential.email = previous.email;
|
|
1872
|
+
const accounts = [...current.accounts];
|
|
1873
|
+
accounts[index] = {
|
|
1874
|
+
...accounts[index],
|
|
1875
|
+
credential
|
|
1876
|
+
};
|
|
1877
|
+
result = clone$1(credential);
|
|
1878
|
+
return {
|
|
1879
|
+
...current,
|
|
1880
|
+
accounts
|
|
1881
|
+
};
|
|
1882
|
+
});
|
|
1883
|
+
return result;
|
|
1884
|
+
});
|
|
1550
1885
|
}
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1886
|
+
deleteAll() {
|
|
1887
|
+
return this.#enqueue(async () => {
|
|
1888
|
+
await this.credentials.deleteRecord(this.key);
|
|
1889
|
+
await this.credentials.unset(this.legacyRef);
|
|
1890
|
+
for (const ref of this.legacyRefs) await this.credentials.unset(ref);
|
|
1891
|
+
});
|
|
1555
1892
|
}
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1893
|
+
remove(id) {
|
|
1894
|
+
return this.#enqueue(async () => {
|
|
1895
|
+
await this.#modifyPayload((current) => {
|
|
1896
|
+
if (!current.accounts.some((account) => account.id === id)) throw new Error("Unknown Codex account");
|
|
1897
|
+
if (current.accounts.length === 1) throw new Error("Cannot remove the last account; sign out instead");
|
|
1898
|
+
const accounts = current.accounts.filter((account) => account.id !== id);
|
|
1899
|
+
return {
|
|
1900
|
+
...current,
|
|
1901
|
+
activeId: current.activeId === id ? accounts[0].id : current.activeId,
|
|
1902
|
+
legacyAccountId: current.legacyAccountId === id ? void 0 : current.legacyAccountId,
|
|
1903
|
+
accounts
|
|
1904
|
+
};
|
|
1905
|
+
});
|
|
1906
|
+
});
|
|
1570
1907
|
}
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1908
|
+
};
|
|
1909
|
+
//#endregion
|
|
1910
|
+
//#region src/credential-store.js
|
|
1911
|
+
const PROVIDER$1 = "openai-codex";
|
|
1912
|
+
const abortIfNeeded = (options) => options?.signal?.throwIfAborted();
|
|
1913
|
+
const clone = (value) => value === void 0 ? void 0 : structuredClone(value);
|
|
1914
|
+
function assertProvider(providerId) {
|
|
1915
|
+
if (providerId !== PROVIDER$1) throw new Error(`Codex credential store does not own provider ${JSON.stringify(providerId)}`);
|
|
1916
|
+
}
|
|
1917
|
+
function assertOAuthCredential(value) {
|
|
1918
|
+
if (value === void 0) return void 0;
|
|
1919
|
+
if (value === null || typeof value !== "object" || value.type !== "oauth" || typeof value.access !== "string" || value.access.length === 0 || typeof value.refresh !== "string" || value.refresh.length === 0 || typeof value.expires !== "number" || !Number.isFinite(value.expires)) throw new Error("Codex credential store received a malformed OAuth credential");
|
|
1920
|
+
return clone(value);
|
|
1921
|
+
}
|
|
1922
|
+
function parseOAuthCredential(value) {
|
|
1923
|
+
try {
|
|
1924
|
+
return assertOAuthCredential(JSON.parse(value));
|
|
1925
|
+
} catch (error) {
|
|
1926
|
+
if (error?.message === "Codex credential store received a malformed OAuth credential") throw error;
|
|
1927
|
+
throw new Error("Codex credential store contains malformed OAuth JSON", { cause: error });
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
/**
|
|
1931
|
+
* Adapt DSH's managed string credential service to pi-ai's typed OAuth store.
|
|
1932
|
+
* Refresh/login/logout operations are serialized so an older refresh response
|
|
1933
|
+
* cannot overwrite a newer rotated token.
|
|
1934
|
+
*/
|
|
1935
|
+
var DshOAuthCredentialStore = class {
|
|
1936
|
+
#chains = /* @__PURE__ */ new Map();
|
|
1937
|
+
constructor(credentials, ref, legacyRefs = [], options = {}) {
|
|
1938
|
+
if (credentials === void 0 || credentials === null) throw new Error("Codex OAuth requires the DSH credentials service");
|
|
1939
|
+
const expirySkewMs = options.expirySkewMs ?? 0;
|
|
1940
|
+
if (!Number.isFinite(expirySkewMs) || expirySkewMs < 0) throw new Error("Codex OAuth expiry skew must be a non-negative finite number");
|
|
1941
|
+
this.credentials = credentials;
|
|
1942
|
+
this.ref = ref;
|
|
1943
|
+
this.legacyRefs = Object.freeze([...legacyRefs]);
|
|
1944
|
+
this.expirySkewMs = expirySkewMs;
|
|
1945
|
+
this.vault = options.vault;
|
|
1946
|
+
}
|
|
1947
|
+
#enqueue(providerId, operation, options) {
|
|
1948
|
+
assertProvider(providerId);
|
|
1949
|
+
const current = (this.#chains.get(providerId) ?? Promise.resolve()).catch(() => void 0).then(async () => {
|
|
1950
|
+
abortIfNeeded(options);
|
|
1951
|
+
return operation();
|
|
1952
|
+
});
|
|
1953
|
+
const tail = current.catch(() => void 0);
|
|
1954
|
+
this.#chains.set(providerId, tail);
|
|
1955
|
+
tail.finally(() => {
|
|
1956
|
+
if (this.#chains.get(providerId) === tail) this.#chains.delete(providerId);
|
|
1957
|
+
});
|
|
1958
|
+
return current;
|
|
1959
|
+
}
|
|
1960
|
+
async #read(providerId, options) {
|
|
1961
|
+
assertProvider(providerId);
|
|
1962
|
+
abortIfNeeded(options);
|
|
1963
|
+
if (this.vault !== void 0) {
|
|
1964
|
+
const current = await this.vault.readActive();
|
|
1965
|
+
if (current === void 0) return void 0;
|
|
1966
|
+
return this.expirySkewMs === 0 ? current : {
|
|
1967
|
+
...current,
|
|
1968
|
+
expires: current.expires - this.expirySkewMs
|
|
1581
1969
|
};
|
|
1582
|
-
session.controller.abort(/* @__PURE__ */ new Error("Codex login cancelled"));
|
|
1583
1970
|
}
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
const
|
|
1589
|
-
|
|
1971
|
+
let hit = await this.credentials.resolve(this.ref);
|
|
1972
|
+
if (hit?.value === void 0 || hit.value === "") for (const legacyRef of this.legacyRefs) {
|
|
1973
|
+
const legacy = await this.credentials.resolve(legacyRef);
|
|
1974
|
+
if (legacy?.value === void 0 || legacy.value === "") continue;
|
|
1975
|
+
const migrated = parseOAuthCredential(legacy.value);
|
|
1976
|
+
await this.credentials.set(this.ref, JSON.stringify(migrated));
|
|
1977
|
+
await this.credentials.unset(legacyRef);
|
|
1978
|
+
hit = { value: JSON.stringify(migrated) };
|
|
1979
|
+
break;
|
|
1590
1980
|
}
|
|
1591
|
-
|
|
1592
|
-
|
|
1981
|
+
abortIfNeeded(options);
|
|
1982
|
+
if (hit?.value === void 0 || hit.value === "") return void 0;
|
|
1983
|
+
const credential = parseOAuthCredential(hit.value);
|
|
1984
|
+
return this.expirySkewMs === 0 ? credential : {
|
|
1985
|
+
...credential,
|
|
1986
|
+
expires: credential.expires - this.expirySkewMs
|
|
1987
|
+
};
|
|
1593
1988
|
}
|
|
1594
|
-
|
|
1595
|
-
return
|
|
1989
|
+
read(providerId, options) {
|
|
1990
|
+
return this.#enqueue(providerId, () => this.#read(providerId, options), options);
|
|
1596
1991
|
}
|
|
1597
|
-
async
|
|
1598
|
-
|
|
1992
|
+
async list(options) {
|
|
1993
|
+
abortIfNeeded(options);
|
|
1994
|
+
return await this.read(PROVIDER$1, options) === void 0 ? [] : [{
|
|
1995
|
+
providerId: PROVIDER$1,
|
|
1996
|
+
type: "oauth"
|
|
1997
|
+
}];
|
|
1599
1998
|
}
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
} catch (error) {
|
|
1611
|
-
if (signal.aborted) throw error;
|
|
1612
|
-
const [, message] = classifyAccountStatusError(error);
|
|
1613
|
-
return accountStatusError(message);
|
|
1614
|
-
}
|
|
1615
|
-
if (endpoint === "login/start") {
|
|
1616
|
-
const started = await coordinator.start({
|
|
1617
|
-
method: input.method,
|
|
1618
|
-
label: input.label
|
|
1999
|
+
modify(providerId, update, options) {
|
|
2000
|
+
return this.#enqueue(providerId, async () => {
|
|
2001
|
+
if (this.vault !== void 0) {
|
|
2002
|
+
const next = await this.vault.modifyActive(async (current) => {
|
|
2003
|
+
const visible = current === void 0 || this.expirySkewMs === 0 ? current : {
|
|
2004
|
+
...current,
|
|
2005
|
+
expires: current.expires - this.expirySkewMs
|
|
2006
|
+
};
|
|
2007
|
+
const updated = await update(clone(visible));
|
|
2008
|
+
return updated === void 0 ? void 0 : assertOAuthCredential(updated);
|
|
1619
2009
|
});
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
2010
|
+
abortIfNeeded(options);
|
|
2011
|
+
return clone(next);
|
|
2012
|
+
}
|
|
2013
|
+
const current = await this.#read(providerId, options);
|
|
2014
|
+
const next = await update(clone(current));
|
|
2015
|
+
abortIfNeeded(options);
|
|
2016
|
+
if (next === void 0) return current;
|
|
2017
|
+
const validated = assertOAuthCredential(next);
|
|
2018
|
+
await this.credentials.set(this.ref, JSON.stringify(validated));
|
|
2019
|
+
for (const legacyRef of this.legacyRefs) await this.credentials.unset(legacyRef);
|
|
2020
|
+
abortIfNeeded(options);
|
|
2021
|
+
return clone(validated);
|
|
2022
|
+
}, options);
|
|
2023
|
+
}
|
|
2024
|
+
delete(providerId, options) {
|
|
2025
|
+
return this.#enqueue(providerId, async () => {
|
|
2026
|
+
if (this.vault !== void 0) {
|
|
2027
|
+
await this.vault.deleteAll();
|
|
2028
|
+
abortIfNeeded(options);
|
|
2029
|
+
return;
|
|
2030
|
+
}
|
|
2031
|
+
await this.credentials.unset(this.ref);
|
|
2032
|
+
for (const legacyRef of this.legacyRefs) await this.credentials.unset(legacyRef);
|
|
2033
|
+
abortIfNeeded(options);
|
|
2034
|
+
}, options);
|
|
2035
|
+
}
|
|
2036
|
+
};
|
|
2037
|
+
/** Return only account state that is safe to expose to the browser client. */
|
|
2038
|
+
function createCodexAuthService(models, store, options = {}) {
|
|
2039
|
+
const runLogin = options.runLogin ?? ((run) => run());
|
|
2040
|
+
const accountVault = options.accountVault;
|
|
2041
|
+
const createLoginModels = options.createLoginModels;
|
|
2042
|
+
const createPendingStore = options.createPendingStore ?? (() => new PendingOAuthCredentialStore());
|
|
2043
|
+
return Object.freeze({
|
|
2044
|
+
async status(options) {
|
|
2045
|
+
const current = await store.read(PROVIDER$1, options);
|
|
2046
|
+
const accounts = await accountVault?.list();
|
|
2047
|
+
if (current === void 0) return {
|
|
2048
|
+
authenticated: false,
|
|
2049
|
+
provider: PROVIDER$1,
|
|
2050
|
+
...accounts === void 0 ? {} : { accounts }
|
|
2051
|
+
};
|
|
2052
|
+
return {
|
|
2053
|
+
authenticated: true,
|
|
2054
|
+
provider: PROVIDER$1,
|
|
2055
|
+
type: "oauth",
|
|
2056
|
+
expiresAt: current.expires,
|
|
2057
|
+
...accounts === void 0 ? {} : { accounts }
|
|
2058
|
+
};
|
|
2059
|
+
},
|
|
2060
|
+
login(interaction, input = {}) {
|
|
2061
|
+
if (input.label !== void 0) {
|
|
2062
|
+
if (accountVault === void 0 || createLoginModels === void 0) throw new Error("Codex multi-account is unavailable");
|
|
2063
|
+
return runLogin(async () => {
|
|
2064
|
+
const pending = createPendingStore();
|
|
2065
|
+
await createLoginModels(pending).login(PROVIDER$1, "oauth", interaction);
|
|
2066
|
+
const credential = pending.credential();
|
|
2067
|
+
if (credential === void 0) throw new Error("Codex login did not return credentials");
|
|
2068
|
+
await accountVault.add(input.label, credential);
|
|
1625
2069
|
});
|
|
1626
|
-
try {
|
|
1627
|
-
await openExternal(url);
|
|
1628
|
-
return ok({
|
|
1629
|
-
...started,
|
|
1630
|
-
externalOpened: true
|
|
1631
|
-
});
|
|
1632
|
-
} catch {
|
|
1633
|
-
return ok({
|
|
1634
|
-
...started,
|
|
1635
|
-
externalOpened: false
|
|
1636
|
-
});
|
|
1637
|
-
}
|
|
1638
2070
|
}
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
if (
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
return
|
|
2071
|
+
return runLogin(() => models.login(PROVIDER$1, "oauth", interaction));
|
|
2072
|
+
},
|
|
2073
|
+
async select(id) {
|
|
2074
|
+
if (accountVault === void 0) throw new Error("Codex multi-account is unavailable");
|
|
2075
|
+
await accountVault.select(id);
|
|
2076
|
+
return this.status();
|
|
2077
|
+
},
|
|
2078
|
+
async remove(id) {
|
|
2079
|
+
if (accountVault === void 0) throw new Error("Codex multi-account is unavailable");
|
|
2080
|
+
await accountVault.remove(id);
|
|
2081
|
+
return this.status();
|
|
2082
|
+
},
|
|
2083
|
+
logout(options) {
|
|
2084
|
+
return models.logout(PROVIDER$1, options);
|
|
1653
2085
|
}
|
|
1654
|
-
};
|
|
1655
|
-
}
|
|
1656
|
-
//#endregion
|
|
1657
|
-
//#region src/oauth-network.js
|
|
1658
|
-
const execFileAsync = promisify(execFile);
|
|
1659
|
-
const CODEX_AUTH_HOST = "auth.openai.com";
|
|
1660
|
-
const CODEX_HOSTS = /* @__PURE__ */ new Set([CODEX_AUTH_HOST, "chatgpt.com"]);
|
|
1661
|
-
const networkScope = new AsyncLocalStorage();
|
|
1662
|
-
let activeScopes = 0;
|
|
1663
|
-
let baseFetch;
|
|
1664
|
-
let scopedFetch;
|
|
1665
|
-
function normalizeProxy(raw) {
|
|
1666
|
-
if (typeof raw !== "string" || raw.trim() === "") return void 0;
|
|
1667
|
-
const value = raw.trim().includes("://") ? raw.trim() : `http://${raw.trim()}`;
|
|
1668
|
-
try {
|
|
1669
|
-
const url = new URL(value);
|
|
1670
|
-
if (!["http:", "https:"].includes(url.protocol) || url.hostname === "") return void 0;
|
|
1671
|
-
return url.toString();
|
|
1672
|
-
} catch {
|
|
1673
|
-
return;
|
|
1674
|
-
}
|
|
1675
|
-
}
|
|
1676
|
-
function bypassesProxy(hostname, port, rawNoProxy) {
|
|
1677
|
-
if (typeof rawNoProxy !== "string" || rawNoProxy.trim() === "") return false;
|
|
1678
|
-
return rawNoProxy.split(/[\s,]+/u).some((raw) => {
|
|
1679
|
-
const entry = raw.trim().toLowerCase();
|
|
1680
|
-
if (entry === "*") return true;
|
|
1681
|
-
if (entry === "") return false;
|
|
1682
|
-
const match = /^(.*?)(?::(\d+))?$/u.exec(entry);
|
|
1683
|
-
const host = match?.[1]?.replace(/^\./u, "");
|
|
1684
|
-
const entryPort = match?.[2];
|
|
1685
|
-
if (!host || entryPort && entryPort !== port) return false;
|
|
1686
|
-
return hostname === host || hostname.endsWith(`.${host}`);
|
|
1687
2086
|
});
|
|
1688
2087
|
}
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
function
|
|
1694
|
-
|
|
1695
|
-
const entries = value.split(";").map((item) => item.trim()).filter(Boolean);
|
|
1696
|
-
const https = entries.find((item) => /^https=/iu.test(item));
|
|
1697
|
-
const http = entries.find((item) => /^http=/iu.test(item));
|
|
1698
|
-
const selected = (https ?? http ?? entries.find((item) => !item.includes("=")))?.replace(/^[^=]+=/u, "");
|
|
1699
|
-
return normalizeProxy(selected);
|
|
1700
|
-
}
|
|
1701
|
-
async function windowsSystemProxy(options = {}) {
|
|
1702
|
-
const run = options.execFile ?? execFileAsync;
|
|
1703
|
-
const reg = `${process.env.SystemRoot ?? "C:\\Windows"}\\System32\\reg.exe`;
|
|
1704
|
-
const key = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings";
|
|
1705
|
-
try {
|
|
1706
|
-
const enabled = await run(reg, [
|
|
1707
|
-
"query",
|
|
1708
|
-
key,
|
|
1709
|
-
"/v",
|
|
1710
|
-
"ProxyEnable"
|
|
1711
|
-
], {
|
|
1712
|
-
windowsHide: true,
|
|
1713
|
-
encoding: "utf8"
|
|
1714
|
-
});
|
|
1715
|
-
if (!/REG_DWORD\s+0x1\b/iu.test(enabled.stdout)) return void 0;
|
|
1716
|
-
const configured = await run(reg, [
|
|
1717
|
-
"query",
|
|
1718
|
-
key,
|
|
1719
|
-
"/v",
|
|
1720
|
-
"ProxyServer"
|
|
1721
|
-
], {
|
|
1722
|
-
windowsHide: true,
|
|
1723
|
-
encoding: "utf8"
|
|
1724
|
-
});
|
|
1725
|
-
return selectWindowsProxy(/^\s*ProxyServer\s+REG_\w+\s+(.+)$/imu.exec(configured.stdout)?.[1]);
|
|
1726
|
-
} catch {
|
|
1727
|
-
return;
|
|
1728
|
-
}
|
|
1729
|
-
}
|
|
1730
|
-
async function macSystemProxy(options = {}) {
|
|
1731
|
-
const run = options.execFile ?? execFileAsync;
|
|
2088
|
+
//#endregion
|
|
2089
|
+
//#region src/external-url.js
|
|
2090
|
+
const OPENAI_AUTH_ORIGIN = "https://auth.openai.com";
|
|
2091
|
+
/** Validate the only external origin this plugin may launch. */
|
|
2092
|
+
function assertCodexAuthUrl(value) {
|
|
2093
|
+
let url;
|
|
1732
2094
|
try {
|
|
1733
|
-
|
|
1734
|
-
if (!/^\s*HTTPSEnable\s*:\s*1\s*$/imu.test(result.stdout)) return void 0;
|
|
1735
|
-
const host = /^\s*HTTPSProxy\s*:\s*(\S+)\s*$/imu.exec(result.stdout)?.[1];
|
|
1736
|
-
const port = /^\s*HTTPSPort\s*:\s*(\d+)\s*$/imu.exec(result.stdout)?.[1];
|
|
1737
|
-
return normalizeProxy(host && port ? `${host}:${port}` : void 0);
|
|
2095
|
+
url = new URL(value);
|
|
1738
2096
|
} catch {
|
|
1739
|
-
|
|
2097
|
+
throw new Error("Codex auth URL is invalid");
|
|
1740
2098
|
}
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
2099
|
+
if (url.protocol !== "https:") throw new Error("Codex auth URL must use HTTPS");
|
|
2100
|
+
if (url.origin !== OPENAI_AUTH_ORIGIN || url.username !== "" || url.password !== "") throw new Error("Codex auth URL must use the OpenAI auth origin");
|
|
2101
|
+
return url.href;
|
|
2102
|
+
}
|
|
2103
|
+
/** Return a shell-free native opener command for the current desktop. */
|
|
2104
|
+
function commandForCodexAuthUrl(value, platform = process.platform) {
|
|
2105
|
+
const url = assertCodexAuthUrl(value);
|
|
2106
|
+
if (platform === "win32") return {
|
|
2107
|
+
file: "rundll32.exe",
|
|
2108
|
+
args: ["url.dll,FileProtocolHandler", url],
|
|
2109
|
+
shell: false
|
|
1748
2110
|
};
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
2111
|
+
if (platform === "darwin") return {
|
|
2112
|
+
file: "open",
|
|
2113
|
+
args: [url],
|
|
2114
|
+
shell: false
|
|
1753
2115
|
};
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
source: "system"
|
|
1759
|
-
} : {
|
|
1760
|
-
url: void 0,
|
|
1761
|
-
source: "direct"
|
|
2116
|
+
if (platform === "linux") return {
|
|
2117
|
+
file: "xdg-open",
|
|
2118
|
+
args: [url],
|
|
2119
|
+
shell: false
|
|
1762
2120
|
};
|
|
2121
|
+
throw new Error(`Codex auth URL opener is unsupported on ${platform}`);
|
|
1763
2122
|
}
|
|
1764
|
-
function
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
if (body instanceof URLSearchParams) return Buffer.from(body.toString());
|
|
1768
|
-
if (body instanceof Uint8Array) return Buffer.from(body);
|
|
1769
|
-
throw new TypeError("Unsupported Codex OAuth request body");
|
|
1770
|
-
}
|
|
1771
|
-
function fetchThroughProxy(input, init, proxyUrl) {
|
|
1772
|
-
const target = new URL(typeof input === "string" || input instanceof URL ? input : input.url);
|
|
1773
|
-
const body = bodyBytes(init?.body);
|
|
1774
|
-
const headers = new Headers(init?.headers);
|
|
1775
|
-
if (body && !headers.has("content-length")) headers.set("content-length", String(body.byteLength));
|
|
2123
|
+
function openCodexAuthUrl(value, options = {}) {
|
|
2124
|
+
const command = commandForCodexAuthUrl(value, options.platform);
|
|
2125
|
+
const spawnProcess = options.spawn ?? spawn;
|
|
1776
2126
|
return new Promise((resolve, reject) => {
|
|
1777
|
-
const
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
}
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
const empty = init?.method === "HEAD" || [
|
|
1788
|
-
204,
|
|
1789
|
-
205,
|
|
1790
|
-
304
|
|
1791
|
-
].includes(status);
|
|
1792
|
-
resolve(new Response(empty ? null : Readable.toWeb(response), {
|
|
1793
|
-
status,
|
|
1794
|
-
statusText: response.statusMessage,
|
|
1795
|
-
headers: responseHeaders
|
|
1796
|
-
}));
|
|
2127
|
+
const child = spawnProcess(command.file, command.args, {
|
|
2128
|
+
detached: true,
|
|
2129
|
+
stdio: "ignore",
|
|
2130
|
+
windowsHide: true,
|
|
2131
|
+
shell: command.shell
|
|
2132
|
+
});
|
|
2133
|
+
child.once("error", reject);
|
|
2134
|
+
child.once("spawn", () => {
|
|
2135
|
+
child.unref();
|
|
2136
|
+
resolve();
|
|
1797
2137
|
});
|
|
1798
|
-
request$1.on("error", reject);
|
|
1799
|
-
if (body) request$1.write(body);
|
|
1800
|
-
request$1.end();
|
|
1801
2138
|
});
|
|
1802
2139
|
}
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
2140
|
+
//#endregion
|
|
2141
|
+
//#region src/login-coordinator.js
|
|
2142
|
+
const LOGIN_METHODS = /* @__PURE__ */ new Set(["browser", "device_code"]);
|
|
2143
|
+
const TERMINAL_PHASES = /* @__PURE__ */ new Set([
|
|
2144
|
+
"authenticated",
|
|
2145
|
+
"failed",
|
|
2146
|
+
"cancelled"
|
|
2147
|
+
]);
|
|
2148
|
+
const publicClone = (value) => structuredClone(value);
|
|
2149
|
+
const asObject = (value) => value !== null && typeof value === "object" ? value : {};
|
|
2150
|
+
const ok = (value) => ({
|
|
2151
|
+
ok: true,
|
|
2152
|
+
value
|
|
2153
|
+
});
|
|
2154
|
+
const badRequest = (message) => ({
|
|
2155
|
+
ok: false,
|
|
2156
|
+
error: {
|
|
2157
|
+
code: "bad-request",
|
|
2158
|
+
message,
|
|
2159
|
+
details: { issues: [] }
|
|
2160
|
+
}
|
|
2161
|
+
});
|
|
2162
|
+
const accountStatusError = (message) => ({
|
|
2163
|
+
ok: false,
|
|
2164
|
+
error: {
|
|
2165
|
+
code: "internal",
|
|
2166
|
+
message,
|
|
2167
|
+
details: { issues: [] }
|
|
2168
|
+
}
|
|
2169
|
+
});
|
|
2170
|
+
const classifyAccountStatusError = (error) => {
|
|
2171
|
+
const message = error instanceof Error ? error.message : "";
|
|
2172
|
+
if (/malformed (?:OAuth|grant|account vault)|received a malformed OAuth|contains malformed OAuth/iu.test(message)) return ["credential-malformed", "Codex account credentials are malformed"];
|
|
2173
|
+
if (/credential|account vault|readRecord|credential store|credentials service/iu.test(message)) return ["credential-unavailable", "Codex account credentials are unavailable"];
|
|
2174
|
+
const code = typeof error?.code === "string" ? error.code.toUpperCase() : "";
|
|
2175
|
+
if (error?.name === "TimeoutError" || [
|
|
2176
|
+
"TIMEOUT",
|
|
2177
|
+
"ETIMEDOUT",
|
|
2178
|
+
"UND_ERR_CONNECT_TIMEOUT"
|
|
2179
|
+
].includes(code)) return ["transport", "Codex account status service is unavailable"];
|
|
2180
|
+
if ([
|
|
2181
|
+
"ECONNRESET",
|
|
2182
|
+
"ECONNREFUSED",
|
|
2183
|
+
"ENOTFOUND",
|
|
2184
|
+
"EAI_AGAIN",
|
|
2185
|
+
"NETWORK",
|
|
2186
|
+
"NETWORK_ERROR",
|
|
2187
|
+
"TRANSPORT"
|
|
2188
|
+
].includes(code) || error?.name === "NetworkError") return ["transport", "Codex account status service is unavailable"];
|
|
2189
|
+
return ["unknown", "Could not read Codex account status"];
|
|
2190
|
+
};
|
|
2191
|
+
const deferred = () => {
|
|
2192
|
+
let resolve;
|
|
2193
|
+
let reject;
|
|
2194
|
+
return {
|
|
2195
|
+
promise: new Promise((onResolve, onReject) => {
|
|
2196
|
+
resolve = onResolve;
|
|
2197
|
+
reject = onReject;
|
|
2198
|
+
}),
|
|
2199
|
+
resolve,
|
|
2200
|
+
reject
|
|
2201
|
+
};
|
|
2202
|
+
};
|
|
2203
|
+
const publicPrompt = (prompt) => ({
|
|
2204
|
+
type: prompt.type,
|
|
2205
|
+
message: String(prompt.message ?? ""),
|
|
2206
|
+
...typeof prompt.placeholder === "string" ? { placeholder: prompt.placeholder } : {}
|
|
2207
|
+
});
|
|
2208
|
+
function classifyLoginFailure(error) {
|
|
2209
|
+
const message = error instanceof Error ? error.message : "";
|
|
2210
|
+
if (/token exchange failed/iu.test(message)) return "token-exchange";
|
|
2211
|
+
if (/fetch failed|\b(?:ECONN|ENOTFOUND|ETIMEDOUT|CERT_|socket|network)\b/iu.test(message)) return "network";
|
|
2212
|
+
if (/extract accountId|account[_ -]?id/iu.test(message)) return "account-claim";
|
|
2213
|
+
if (/credential|credentials-local|OAuth JSON/iu.test(message)) return "credential-store";
|
|
2214
|
+
if (/Missing authorization code|State mismatch|callback/iu.test(message)) return "callback";
|
|
2215
|
+
return "provider";
|
|
2216
|
+
}
|
|
2217
|
+
/** Own one host-side login without exposing tokens to the browser client. */
|
|
2218
|
+
var CodexLoginCoordinator = class {
|
|
2219
|
+
#sessions = /* @__PURE__ */ new Map();
|
|
2220
|
+
#activeId;
|
|
2221
|
+
constructor(auth, options = {}) {
|
|
2222
|
+
this.auth = auth;
|
|
2223
|
+
this.createId = options.createId ?? (() => crypto.randomUUID());
|
|
2224
|
+
}
|
|
2225
|
+
async accountStatus(options) {
|
|
2226
|
+
return publicClone(await this.auth.status(options));
|
|
2227
|
+
}
|
|
2228
|
+
supportState() {
|
|
2229
|
+
const active = this.#activeId === void 0 ? void 0 : this.#sessions.get(this.#activeId);
|
|
2230
|
+
if (active === void 0) return { phase: "idle" };
|
|
2231
|
+
return {
|
|
2232
|
+
method: active.view.method,
|
|
2233
|
+
phase: active.view.phase,
|
|
2234
|
+
...active.view.phase === "failed" ? { failure: classifyLoginFailure(active.hostError) } : {}
|
|
2235
|
+
};
|
|
2236
|
+
}
|
|
2237
|
+
async start({ method, label }) {
|
|
2238
|
+
if (!LOGIN_METHODS.has(method)) throw new Error(`unsupported Codex login method: ${String(method)}`);
|
|
2239
|
+
if (label !== void 0 && (typeof label !== "string" || label.trim().length === 0 || label.trim().length > 48)) throw new Error("unsupported Codex account label");
|
|
2240
|
+
const active = this.#activeId === void 0 ? void 0 : this.#sessions.get(this.#activeId);
|
|
2241
|
+
if (active !== void 0 && !TERMINAL_PHASES.has(active.view.phase)) {
|
|
2242
|
+
active.view = {
|
|
2243
|
+
id: active.view.id,
|
|
2244
|
+
provider: "openai-codex",
|
|
2245
|
+
method: active.view.method,
|
|
2246
|
+
phase: "cancelled",
|
|
2247
|
+
authenticated: false
|
|
2248
|
+
};
|
|
2249
|
+
active.controller.abort(/* @__PURE__ */ new Error("Codex login replaced by a new attempt"));
|
|
2250
|
+
}
|
|
2251
|
+
if (active !== void 0) this.#sessions.delete(active.view.id);
|
|
2252
|
+
const id = this.createId();
|
|
2253
|
+
const ready = deferred();
|
|
2254
|
+
const controller = new AbortController();
|
|
2255
|
+
const session = {
|
|
2256
|
+
controller,
|
|
2257
|
+
prompt: void 0,
|
|
2258
|
+
ready,
|
|
2259
|
+
view: {
|
|
2260
|
+
id,
|
|
2261
|
+
provider: "openai-codex",
|
|
2262
|
+
method,
|
|
2263
|
+
phase: "starting",
|
|
2264
|
+
authenticated: false
|
|
2265
|
+
}
|
|
2266
|
+
};
|
|
2267
|
+
this.#sessions.set(id, session);
|
|
2268
|
+
this.#activeId = id;
|
|
2269
|
+
const publishReady = () => ready.resolve(publicClone(session.view));
|
|
2270
|
+
const interaction = {
|
|
2271
|
+
signal: controller.signal,
|
|
2272
|
+
prompt: async (prompt) => {
|
|
2273
|
+
controller.signal.throwIfAborted();
|
|
2274
|
+
if (prompt.type === "select") return method;
|
|
2275
|
+
if (![
|
|
2276
|
+
"manual_code",
|
|
2277
|
+
"text",
|
|
2278
|
+
"secret"
|
|
2279
|
+
].includes(prompt.type)) throw new Error(`unsupported Codex auth prompt: ${String(prompt.type)}`);
|
|
2280
|
+
const answer = deferred();
|
|
2281
|
+
session.prompt = answer;
|
|
2282
|
+
session.view = {
|
|
2283
|
+
...session.view,
|
|
2284
|
+
phase: "waiting_input",
|
|
2285
|
+
prompt: publicPrompt(prompt)
|
|
2286
|
+
};
|
|
2287
|
+
const abortPrompt = () => answer.reject(controller.signal.reason ?? /* @__PURE__ */ new Error("login cancelled"));
|
|
2288
|
+
controller.signal.addEventListener("abort", abortPrompt, { once: true });
|
|
2289
|
+
prompt.signal?.addEventListener("abort", abortPrompt, { once: true });
|
|
2290
|
+
publishReady();
|
|
2291
|
+
try {
|
|
2292
|
+
return await answer.promise;
|
|
2293
|
+
} finally {
|
|
2294
|
+
controller.signal.removeEventListener("abort", abortPrompt);
|
|
2295
|
+
prompt.signal?.removeEventListener("abort", abortPrompt);
|
|
2296
|
+
if (session.prompt === answer) session.prompt = void 0;
|
|
2297
|
+
}
|
|
2298
|
+
},
|
|
2299
|
+
notify: (event) => {
|
|
2300
|
+
if (controller.signal.aborted) return;
|
|
2301
|
+
if (event.type === "auth_url") session.view = {
|
|
2302
|
+
...session.view,
|
|
2303
|
+
phase: "waiting_browser",
|
|
2304
|
+
authUrl: assertCodexAuthUrl(event.url),
|
|
2305
|
+
...typeof event.instructions === "string" ? { instructions: event.instructions } : {}
|
|
2306
|
+
};
|
|
2307
|
+
else if (event.type === "device_code") session.view = {
|
|
2308
|
+
...session.view,
|
|
2309
|
+
phase: "waiting_device",
|
|
2310
|
+
deviceCode: {
|
|
2311
|
+
userCode: event.userCode,
|
|
2312
|
+
verificationUri: assertCodexAuthUrl(event.verificationUri),
|
|
2313
|
+
...typeof event.intervalSeconds === "number" ? { intervalSeconds: event.intervalSeconds } : {},
|
|
2314
|
+
...typeof event.expiresInSeconds === "number" ? { expiresInSeconds: event.expiresInSeconds } : {}
|
|
2315
|
+
}
|
|
2316
|
+
};
|
|
2317
|
+
else session.view = {
|
|
2318
|
+
...session.view,
|
|
2319
|
+
message: String(event.message ?? "")
|
|
2320
|
+
};
|
|
2321
|
+
publishReady();
|
|
1820
2322
|
}
|
|
1821
|
-
const route = await proxy;
|
|
1822
|
-
scopedOptions.onRoute?.(route.source);
|
|
1823
|
-
return route.url === void 0 ? baseFetch(input, init) : proxyFetch(input, init, route.url);
|
|
1824
2323
|
};
|
|
1825
|
-
|
|
2324
|
+
session.run = Promise.resolve().then(() => this.auth.login(interaction, label === void 0 ? {} : { label: label.trim() })).then(async () => {
|
|
2325
|
+
if (controller.signal.aborted) return;
|
|
2326
|
+
const status = await this.auth.status();
|
|
2327
|
+
session.view = {
|
|
2328
|
+
id,
|
|
2329
|
+
provider: "openai-codex",
|
|
2330
|
+
method,
|
|
2331
|
+
phase: "authenticated",
|
|
2332
|
+
authenticated: status.authenticated === true,
|
|
2333
|
+
...typeof status.expiresAt === "number" ? { expiresAt: status.expiresAt } : {}
|
|
2334
|
+
};
|
|
2335
|
+
}).catch(async (error) => {
|
|
2336
|
+
if (controller.signal.aborted) {
|
|
2337
|
+
session.view = {
|
|
2338
|
+
id,
|
|
2339
|
+
provider: "openai-codex",
|
|
2340
|
+
method,
|
|
2341
|
+
phase: "cancelled",
|
|
2342
|
+
authenticated: false
|
|
2343
|
+
};
|
|
2344
|
+
return;
|
|
2345
|
+
}
|
|
2346
|
+
try {
|
|
2347
|
+
if (label !== void 0) throw error;
|
|
2348
|
+
const status = await this.auth.status();
|
|
2349
|
+
if (status.authenticated === true) {
|
|
2350
|
+
session.view = {
|
|
2351
|
+
id,
|
|
2352
|
+
provider: "openai-codex",
|
|
2353
|
+
method,
|
|
2354
|
+
phase: "authenticated",
|
|
2355
|
+
authenticated: true,
|
|
2356
|
+
...typeof status.expiresAt === "number" ? { expiresAt: status.expiresAt } : {}
|
|
2357
|
+
};
|
|
2358
|
+
return;
|
|
2359
|
+
}
|
|
2360
|
+
} catch {}
|
|
2361
|
+
session.view = {
|
|
2362
|
+
id,
|
|
2363
|
+
provider: "openai-codex",
|
|
2364
|
+
method,
|
|
2365
|
+
phase: "failed",
|
|
2366
|
+
authenticated: false,
|
|
2367
|
+
error: "Codex login failed"
|
|
2368
|
+
};
|
|
2369
|
+
session.hostError = error;
|
|
2370
|
+
}).finally(publishReady);
|
|
2371
|
+
return ready.promise;
|
|
1826
2372
|
}
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
}
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
2373
|
+
read(id) {
|
|
2374
|
+
const session = this.#sessions.get(id);
|
|
2375
|
+
if (session === void 0) throw new Error("unknown Codex login");
|
|
2376
|
+
return publicClone(session.view);
|
|
2377
|
+
}
|
|
2378
|
+
async submit({ id, value }) {
|
|
2379
|
+
const session = this.#sessions.get(id);
|
|
2380
|
+
if (session === void 0) throw new Error("unknown Codex login");
|
|
2381
|
+
if (session.prompt === void 0 || session.view.phase !== "waiting_input") throw new Error("Codex login is not waiting for input");
|
|
2382
|
+
if (typeof value !== "string" || value.trim() === "") throw new Error("Codex login input is empty");
|
|
2383
|
+
const answer = session.prompt;
|
|
2384
|
+
session.prompt = void 0;
|
|
2385
|
+
session.view = {
|
|
2386
|
+
...session.view,
|
|
2387
|
+
phase: session.view.authUrl === void 0 ? "starting" : "waiting_browser",
|
|
2388
|
+
prompt: void 0
|
|
2389
|
+
};
|
|
2390
|
+
answer.resolve(value);
|
|
2391
|
+
return this.read(id);
|
|
2392
|
+
}
|
|
2393
|
+
async cancel(id) {
|
|
2394
|
+
const session = this.#sessions.get(id);
|
|
2395
|
+
if (session === void 0) throw new Error("unknown Codex login");
|
|
2396
|
+
if (!TERMINAL_PHASES.has(session.view.phase)) {
|
|
2397
|
+
session.view = {
|
|
2398
|
+
id,
|
|
2399
|
+
provider: "openai-codex",
|
|
2400
|
+
method: session.view.method,
|
|
2401
|
+
phase: "cancelled",
|
|
2402
|
+
authenticated: false
|
|
2403
|
+
};
|
|
2404
|
+
session.controller.abort(/* @__PURE__ */ new Error("Codex login cancelled"));
|
|
1841
2405
|
}
|
|
2406
|
+
return this.read(id);
|
|
1842
2407
|
}
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
2408
|
+
async logout(options) {
|
|
2409
|
+
if (this.#activeId !== void 0) {
|
|
2410
|
+
const active = this.#sessions.get(this.#activeId);
|
|
2411
|
+
if (active !== void 0 && !TERMINAL_PHASES.has(active.view.phase)) await this.cancel(active.view.id);
|
|
2412
|
+
}
|
|
2413
|
+
await this.auth.logout(options);
|
|
2414
|
+
return this.accountStatus(options);
|
|
2415
|
+
}
|
|
2416
|
+
async selectAccount(id) {
|
|
2417
|
+
return publicClone(await this.auth.select(id));
|
|
2418
|
+
}
|
|
2419
|
+
async removeAccount(id) {
|
|
2420
|
+
return publicClone(await this.auth.remove(id));
|
|
2421
|
+
}
|
|
2422
|
+
};
|
|
2423
|
+
/** Map the loopback-only DSH Connection channel onto the coordinator. */
|
|
2424
|
+
function createCodexRpcHandler(coordinator, options = {}) {
|
|
2425
|
+
const openExternal = options.openExternal;
|
|
2426
|
+
return async (endpoint, payload, signal) => {
|
|
1861
2427
|
try {
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
2428
|
+
signal.throwIfAborted();
|
|
2429
|
+
const input = asObject(payload);
|
|
2430
|
+
if (endpoint === "status") try {
|
|
2431
|
+
return ok(await coordinator.accountStatus({ signal }));
|
|
2432
|
+
} catch (error) {
|
|
2433
|
+
if (signal.aborted) throw error;
|
|
2434
|
+
const [, message] = classifyAccountStatusError(error);
|
|
2435
|
+
return accountStatusError(message);
|
|
2436
|
+
}
|
|
2437
|
+
if (endpoint === "login/start") {
|
|
2438
|
+
const started = await coordinator.start({
|
|
2439
|
+
method: input.method,
|
|
2440
|
+
label: input.label
|
|
2441
|
+
});
|
|
2442
|
+
if (input.openExternal !== true) return ok(started);
|
|
2443
|
+
const url = started.authUrl ?? started.deviceCode?.verificationUri;
|
|
2444
|
+
if (typeof url !== "string" || openExternal === void 0) return ok({
|
|
2445
|
+
...started,
|
|
2446
|
+
externalOpened: false
|
|
2447
|
+
});
|
|
2448
|
+
try {
|
|
2449
|
+
await openExternal(url);
|
|
2450
|
+
return ok({
|
|
2451
|
+
...started,
|
|
2452
|
+
externalOpened: true
|
|
2453
|
+
});
|
|
2454
|
+
} catch {
|
|
2455
|
+
return ok({
|
|
2456
|
+
...started,
|
|
2457
|
+
externalOpened: false
|
|
2458
|
+
});
|
|
1867
2459
|
}
|
|
1868
|
-
}
|
|
1869
|
-
if (
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
route,
|
|
1880
|
-
elapsed: elapsedBucket(now() - startedAt)
|
|
1881
|
-
});
|
|
1882
|
-
return value;
|
|
2460
|
+
}
|
|
2461
|
+
if (endpoint === "login/status") return ok(coordinator.read(input.id));
|
|
2462
|
+
if (endpoint === "login/submit") return ok(await coordinator.submit({
|
|
2463
|
+
id: input.id,
|
|
2464
|
+
value: input.value
|
|
2465
|
+
}));
|
|
2466
|
+
if (endpoint === "login/cancel") return ok(await coordinator.cancel(input.id));
|
|
2467
|
+
if (endpoint === "logout") return ok(await coordinator.logout({ signal }));
|
|
2468
|
+
if (endpoint === "account/select") return ok(await coordinator.selectAccount(input.id));
|
|
2469
|
+
if (endpoint === "account/remove") return ok(await coordinator.removeAccount(input.id));
|
|
2470
|
+
return badRequest(`unknown Codex auth endpoint: ${endpoint}`);
|
|
1883
2471
|
} catch (error) {
|
|
1884
|
-
if (
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
code: classifyTransportError(error),
|
|
1888
|
-
route,
|
|
1889
|
-
elapsed: elapsedBucket(now() - startedAt)
|
|
1890
|
-
});
|
|
1891
|
-
throw error;
|
|
2472
|
+
if (signal.aborted) throw error;
|
|
2473
|
+
const message = error instanceof Error && /^(unknown|unsupported|a Codex|Codex login)/.test(error.message) ? error.message : "Codex request failed";
|
|
2474
|
+
return badRequest(message);
|
|
1892
2475
|
}
|
|
1893
2476
|
};
|
|
1894
|
-
return Object.freeze({
|
|
1895
|
-
run,
|
|
1896
|
-
fetch: (area, input, init) => run(area, () => globalThis.fetch(input, init)),
|
|
1897
|
-
snapshot: () => Object.fromEntries([...attempts].map(([area, value]) => [area, { ...value }]))
|
|
1898
|
-
});
|
|
1899
2477
|
}
|
|
1900
2478
|
//#endregion
|
|
1901
2479
|
//#region src/pi-ai-runtime.js
|
|
@@ -1912,7 +2490,7 @@ const FAST_SERVICE_TIER = "priority";
|
|
|
1912
2490
|
* persistence, headers, transport, and model behavior remain owned by the
|
|
1913
2491
|
* original provider.
|
|
1914
2492
|
*/
|
|
1915
|
-
function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0, resolveOutputVerbosity = () => OUTPUT_VERBOSITY_DEFAULT, resolveContextMode = () => void 0, resolveCustomContextWindow = () => void 0, catalog, runNetwork = (_area, operation) => operation() } = {}) {
|
|
2493
|
+
function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0, resolveOutputVerbosity = () => OUTPUT_VERBOSITY_DEFAULT, resolveContextMode = () => void 0, resolveCustomContextWindow = () => void 0, catalog, connection, compaction, runNetwork = (_area, operation) => operation() } = {}) {
|
|
1916
2494
|
const provider = createOpenAICodexProvider();
|
|
1917
2495
|
const requestToken = Object.freeze({
|
|
1918
2496
|
name: "DSH-managed Codex OAuth request token",
|
|
@@ -1946,11 +2524,12 @@ function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0, reso
|
|
|
1946
2524
|
} },
|
|
1947
2525
|
...fast ? { service_tier: FAST_SERVICE_TIER } : {}
|
|
1948
2526
|
};
|
|
1949
|
-
const
|
|
2527
|
+
const managed = compaction?.preparePayload(preferred, model?.contextWindow) ?? preferred;
|
|
2528
|
+
const next = await onPayload?.(managed, requestModel);
|
|
1950
2529
|
return {
|
|
1951
|
-
...next ??
|
|
2530
|
+
...next ?? managed,
|
|
1952
2531
|
...textVerbosity === void 0 ? {} : { text: {
|
|
1953
|
-
...(next ??
|
|
2532
|
+
...(next ?? managed).text ?? {},
|
|
1954
2533
|
verbosity: textVerbosity
|
|
1955
2534
|
} },
|
|
1956
2535
|
...fast ? { service_tier: FAST_SERVICE_TIER } : {}
|
|
@@ -1975,19 +2554,29 @@ function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0, reso
|
|
|
1975
2554
|
contextWindow: requested
|
|
1976
2555
|
};
|
|
1977
2556
|
});
|
|
1978
|
-
const networkIterable = (factory) => {
|
|
2557
|
+
const networkIterable = (factory, options) => {
|
|
1979
2558
|
let iterator;
|
|
1980
|
-
|
|
2559
|
+
let prepared;
|
|
2560
|
+
const step = async (method, value) => {
|
|
2561
|
+
const request = await (prepared ??= connection?.prepare(options) ?? Promise.resolve({ options }));
|
|
2562
|
+
return runNetwork("model", () => {
|
|
2563
|
+
iterator ??= factory(compaction?.requestOptions(request.options) ?? request.options)[Symbol.asyncIterator]();
|
|
2564
|
+
return iterator[method]?.(value) ?? (method === "throw" ? Promise.reject(value) : Promise.resolve({
|
|
2565
|
+
done: true,
|
|
2566
|
+
value
|
|
2567
|
+
}));
|
|
2568
|
+
}, compaction?.networkOptions(request.network) ?? request.network);
|
|
2569
|
+
};
|
|
1981
2570
|
return {
|
|
1982
2571
|
[Symbol.asyncIterator]() {
|
|
1983
2572
|
return this;
|
|
1984
2573
|
},
|
|
1985
|
-
next: (value) =>
|
|
1986
|
-
return: (value) =>
|
|
2574
|
+
next: (value) => step("next", value),
|
|
2575
|
+
return: (value) => iterator ? step("return", value) : Promise.resolve({
|
|
1987
2576
|
done: true,
|
|
1988
2577
|
value
|
|
1989
|
-
})
|
|
1990
|
-
throw: (error) =>
|
|
2578
|
+
}),
|
|
2579
|
+
throw: (error) => iterator ? step("throw", error) : Promise.reject(error)
|
|
1991
2580
|
};
|
|
1992
2581
|
};
|
|
1993
2582
|
return Object.freeze({
|
|
@@ -1997,14 +2586,14 @@ function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0, reso
|
|
|
1997
2586
|
apiKey: requestToken
|
|
1998
2587
|
}),
|
|
1999
2588
|
getModels,
|
|
2000
|
-
stream: (model, context, options) => networkIterable(() => provider.stream(model, context, withPreferences(model, options))
|
|
2001
|
-
streamSimple: (model, context, options) => networkIterable(() => provider.streamSimple(model, context, withPreferences(model, options))
|
|
2589
|
+
stream: (model, context, options) => networkIterable((prepared) => provider.stream(model, context, prepared), withPreferences(model, options)),
|
|
2590
|
+
streamSimple: (model, context, options) => networkIterable((prepared) => provider.streamSimple(model, context, prepared), withPreferences(model, options))
|
|
2002
2591
|
});
|
|
2003
2592
|
}
|
|
2004
2593
|
Object.freeze(["0.82.1", "0.85.1"]);
|
|
2005
2594
|
//#endregion
|
|
2006
2595
|
//#region src/version.js
|
|
2007
|
-
const PACKAGE_VERSION = "2.1.
|
|
2596
|
+
const PACKAGE_VERSION = "2.1.1-beta.2";
|
|
2008
2597
|
const USER_AGENT = `dsh-codex-subscription/${PACKAGE_VERSION}`;
|
|
2009
2598
|
//#endregion
|
|
2010
2599
|
//#region src/model-catalog.js
|
|
@@ -2029,13 +2618,35 @@ function reasoningMap(levels) {
|
|
|
2029
2618
|
for (const level of LEVELS.slice(1)) if (supported.has(level)) map[level] = level;
|
|
2030
2619
|
return map;
|
|
2031
2620
|
}
|
|
2621
|
+
const capabilityNames = (values) => [...new Set(values.filter((value) => typeof value === "string" && /^[a-z][a-z0-9_-]{0,31}$/u.test(value)))].sort().slice(0, 16);
|
|
2622
|
+
function unsupportedCapabilities(value) {
|
|
2623
|
+
const reasoning = capabilityNames((value.supported_reasoning_levels ?? []).map((item) => item?.effort)).filter((level) => !["none", ...LEVELS.slice(1)].includes(level));
|
|
2624
|
+
const inputs = capabilityNames(Array.isArray(value.input_modalities) ? value.input_modalities : []).filter((input) => !["text", "image"].includes(input));
|
|
2625
|
+
const speeds = capabilityNames([...Array.isArray(value.additional_speed_tiers) ? value.additional_speed_tiers : [], ...Array.isArray(value.service_tiers) ? value.service_tiers.map((tier) => tier?.id) : []]).filter((tier) => ![
|
|
2626
|
+
"auto",
|
|
2627
|
+
"default",
|
|
2628
|
+
"standard",
|
|
2629
|
+
"fast",
|
|
2630
|
+
"priority"
|
|
2631
|
+
].includes(tier));
|
|
2632
|
+
return {
|
|
2633
|
+
...reasoning.length ? { reasoning } : {},
|
|
2634
|
+
...inputs.length ? { inputs } : {},
|
|
2635
|
+
...speeds.length ? { speeds } : {}
|
|
2636
|
+
};
|
|
2637
|
+
}
|
|
2032
2638
|
function visibleModel(value) {
|
|
2033
2639
|
if (!record$4(value)) return void 0;
|
|
2034
2640
|
const id = nonEmpty$2(value.slug);
|
|
2035
2641
|
if (id === void 0 || value.visibility !== "list") return void 0;
|
|
2036
2642
|
const supported = Array.isArray(value.supported_reasoning_levels) ? value.supported_reasoning_levels : [];
|
|
2037
2643
|
const input = Array.isArray(value.input_modalities) ? value.input_modalities.filter((item) => ["text", "image"].includes(item)) : ["text", "image"];
|
|
2644
|
+
const unsupported = unsupportedCapabilities({
|
|
2645
|
+
...value,
|
|
2646
|
+
supported_reasoning_levels: supported
|
|
2647
|
+
});
|
|
2038
2648
|
return {
|
|
2649
|
+
...Object.keys(unsupported).length ? { unsupported } : {},
|
|
2039
2650
|
id,
|
|
2040
2651
|
name: nonEmpty$2(value.display_name) ?? id,
|
|
2041
2652
|
description: nonEmpty$2(value.description),
|
|
@@ -2179,6 +2790,10 @@ function createOfficialModelCatalog(options = {}) {
|
|
|
2179
2790
|
getModels: (fallback) => models ?? fallback,
|
|
2180
2791
|
metadata: (modelId) => metadata.get(modelId),
|
|
2181
2792
|
revision: () => revision,
|
|
2793
|
+
capabilityGaps: () => [...metadata.values()].filter((model) => model.unsupported && /^[a-z][a-z0-9._-]{0,79}$/u.test(model.id)).slice(0, 20).map((model) => ({
|
|
2794
|
+
model: model.id,
|
|
2795
|
+
...structuredClone(model.unsupported)
|
|
2796
|
+
})),
|
|
2182
2797
|
status: () => ({
|
|
2183
2798
|
source: models === void 0 ? "fallback" : "online",
|
|
2184
2799
|
refresh: refreshStatus
|
|
@@ -3081,6 +3696,22 @@ async function createSubscriptionDiagnostics({ auth, preferences, login = { phas
|
|
|
3081
3696
|
}
|
|
3082
3697
|
const preference = preferences.status();
|
|
3083
3698
|
const catalog = modelCatalog?.status?.();
|
|
3699
|
+
const gaps = (modelCatalog?.capabilityGaps?.() ?? []).slice(0, 20).flatMap((value) => {
|
|
3700
|
+
if (!value || typeof value.model !== "string" || !/^[a-z][a-z0-9._-]{0,79}$/u.test(value.model)) return [];
|
|
3701
|
+
const fields = Object.fromEntries([
|
|
3702
|
+
"reasoning",
|
|
3703
|
+
"inputs",
|
|
3704
|
+
"speeds"
|
|
3705
|
+
].flatMap((key) => {
|
|
3706
|
+
const names = [...new Set((Array.isArray(value[key]) ? value[key] : []).filter((name) => typeof name === "string" && /^[a-z][a-z0-9_-]{0,31}$/u.test(name)))].slice(0, 16);
|
|
3707
|
+
return names.length ? [[key, names]] : [];
|
|
3708
|
+
}));
|
|
3709
|
+
return Object.keys(fields).length ? [{
|
|
3710
|
+
model: value.model,
|
|
3711
|
+
...fields
|
|
3712
|
+
}] : [];
|
|
3713
|
+
});
|
|
3714
|
+
if (gaps.length) issues.push({ code: "catalog-capabilities-not-adapted" });
|
|
3084
3715
|
return {
|
|
3085
3716
|
schemaVersion: 3,
|
|
3086
3717
|
package: "dsh-codex-subscription",
|
|
@@ -3100,7 +3731,8 @@ async function createSubscriptionDiagnostics({ auth, preferences, login = { phas
|
|
|
3100
3731
|
"failed"
|
|
3101
3732
|
].includes(catalog.refresh) ? { catalog: {
|
|
3102
3733
|
source: catalog.source,
|
|
3103
|
-
refresh: catalog.refresh
|
|
3734
|
+
refresh: catalog.refresh,
|
|
3735
|
+
...gaps.length ? { unsupported: gaps } : {}
|
|
3104
3736
|
} } : {},
|
|
3105
3737
|
configuration: {
|
|
3106
3738
|
contextMode: preference.contextMode,
|
|
@@ -4205,7 +4837,7 @@ function apply(ctx) {
|
|
|
4205
4837
|
...Object.fromEntries(Object.entries(IMAGE_FEATURE_DEFAULTS).map(([key, value]) => [key, z.boolean().default(value)])),
|
|
4206
4838
|
[CUSTOM_CONTEXT_OVERRIDES_FIELD]: z.dict(z.number().step(1).min(1).max(MAX_CONTEXT_BUDGET)).default({}),
|
|
4207
4839
|
[SEARCH_MODE_FIELD]: z.union(SEARCH_MODES).default("live"),
|
|
4208
|
-
[SEARCH_DOMAINS_FIELD]: z.transform(z.array(z.string()).max(20),
|
|
4840
|
+
[SEARCH_DOMAINS_FIELD]: z.transform(z.array(z.string()).max(20), normalizeSearchDomains).default([]),
|
|
4209
4841
|
...Object.fromEntries(QUOTA_THRESHOLD_FIELDS.map((key) => [key, z.number().step(1).min(1).max(100).default(20)])),
|
|
4210
4842
|
[QUOTA_ALERTS_FIELD]: z.union(QUOTA_ALERT_MODES).default("important"),
|
|
4211
4843
|
[LEGACY_QUICK_QUOTA_FIELD]: z.boolean(),
|
|
@@ -4226,13 +4858,26 @@ function apply(ctx) {
|
|
|
4226
4858
|
});
|
|
4227
4859
|
const baseProvider = createOpenAICodexProvider();
|
|
4228
4860
|
let resolveAuth = async () => void 0;
|
|
4861
|
+
let subagentBackend;
|
|
4229
4862
|
const modelCatalog = createOfficialModelCatalog({
|
|
4230
4863
|
getAuth: (options) => resolveAuth(options),
|
|
4231
4864
|
readCredential: (options) => store.read(PROVIDER, options),
|
|
4232
4865
|
baseModels: () => baseProvider.getModels(),
|
|
4233
4866
|
fetch: (input, init) => network.fetch("catalog", input, init)
|
|
4234
4867
|
});
|
|
4868
|
+
const connection = createSubscriptionConnection({ resolveMode: () => settings.get().connectionMode });
|
|
4869
|
+
const compaction = createCompactionBridge({
|
|
4870
|
+
enabled: () => settings.get().compactionMode === "cloud",
|
|
4871
|
+
accountScope: async () => {
|
|
4872
|
+
await resolveAuth();
|
|
4873
|
+
const credential = await store.read(PROVIDER);
|
|
4874
|
+
return credential?.type === "oauth" ? credential.accountId : void 0;
|
|
4875
|
+
}
|
|
4876
|
+
});
|
|
4877
|
+
ctx.effect(() => () => connection.dispose());
|
|
4235
4878
|
const provider = openaiCodexSubscriptionProvider({
|
|
4879
|
+
connection,
|
|
4880
|
+
compaction,
|
|
4236
4881
|
resolveSpeedMode: () => settings.get()[SPEED_MODE_FIELD],
|
|
4237
4882
|
resolveOutputVerbosity: () => normalizeOutputVerbosity(settings.get()[OUTPUT_VERBOSITY_FIELD]),
|
|
4238
4883
|
resolveContextMode: () => normalizeContextMode(settings.get()[CONTEXT_MODE_FIELD]),
|
|
@@ -4248,6 +4893,10 @@ function apply(ctx) {
|
|
|
4248
4893
|
});
|
|
4249
4894
|
const preferences = {
|
|
4250
4895
|
status: () => ({
|
|
4896
|
+
compactionMode: settings.get().compactionMode ?? "dsh",
|
|
4897
|
+
connectionMode: settings.get().connectionMode ?? "sse",
|
|
4898
|
+
subagentBackend: settings.get().subagentBackend ?? "dsh",
|
|
4899
|
+
subagentBackendAvailable: subagentBackend !== void 0,
|
|
4251
4900
|
...readCapabilitySettings(settings.get()),
|
|
4252
4901
|
[QUICK_QUOTA_MODE_FIELD]: normalizeQuickQuotaMode(settings.get()[QUICK_QUOTA_MODE_FIELD], settings.get()[LEGACY_QUICK_QUOTA_FIELD]),
|
|
4253
4902
|
[SEARCH_PROVIDER_FIELD]: settings.get()[SEARCH_PROVIDER_FIELD],
|
|
@@ -4262,7 +4911,15 @@ function apply(ctx) {
|
|
|
4262
4911
|
fastModels: provider.getModels().filter((model) => modelCatalog.metadata(model.id)?.supportsFast ?? supportsCodexFastMode(model.id)).map((model) => model.id),
|
|
4263
4912
|
writable: ctx.settings.writable
|
|
4264
4913
|
}),
|
|
4265
|
-
update: (patch) =>
|
|
4914
|
+
update: async (patch) => {
|
|
4915
|
+
if (Object.hasOwn(patch, "subagentBackend")) {
|
|
4916
|
+
if (!subagentBackend) throw new Error("DSH subagent services are unavailable");
|
|
4917
|
+
await subagentBackend.select(patch.subagentBackend);
|
|
4918
|
+
}
|
|
4919
|
+
const rest = { ...patch };
|
|
4920
|
+
delete rest.subagentBackend;
|
|
4921
|
+
if (Object.keys(rest).length) await settings.update(rest);
|
|
4922
|
+
}
|
|
4266
4923
|
};
|
|
4267
4924
|
const authModels = createModels({ credentials: store });
|
|
4268
4925
|
authModels.setProvider(provider);
|
|
@@ -4295,6 +4952,53 @@ function apply(ctx) {
|
|
|
4295
4952
|
return profileSnapshot;
|
|
4296
4953
|
};
|
|
4297
4954
|
resolveAuth = () => authModels.getAuth(PROVIDER);
|
|
4955
|
+
ctx.inject([
|
|
4956
|
+
"subagents",
|
|
4957
|
+
"subprocess",
|
|
4958
|
+
"sandboxPolicy"
|
|
4959
|
+
], (scoped) => {
|
|
4960
|
+
const instance = createSubscriptionSubagent({
|
|
4961
|
+
ctx: scoped,
|
|
4962
|
+
nativeHome: dshHomePath("state", "codex-subscription", "native-subagent"),
|
|
4963
|
+
resolveAuth,
|
|
4964
|
+
store,
|
|
4965
|
+
refresh: (credential) => network.run("oauth", () => baseProvider.auth.oauth.refresh(credential)),
|
|
4966
|
+
loadRuntime: loadSubagentRuntime
|
|
4967
|
+
});
|
|
4968
|
+
scoped.subagents.registerProvider(instance.provider);
|
|
4969
|
+
const switcher = createSubagentBackendSwitcher({
|
|
4970
|
+
entries: () => scoped.loader.entries(),
|
|
4971
|
+
prepare: instance.prepare,
|
|
4972
|
+
persist: (mode) => settings.update({ subagentBackend: mode })
|
|
4973
|
+
});
|
|
4974
|
+
const unconfigure = scoped.on("internal/config", function(_config, next) {
|
|
4975
|
+
return switcher.configure(this, next());
|
|
4976
|
+
}, { global: true });
|
|
4977
|
+
let requested = "dsh";
|
|
4978
|
+
const select = async (mode) => {
|
|
4979
|
+
requested = mode;
|
|
4980
|
+
try {
|
|
4981
|
+
await switcher.select(mode);
|
|
4982
|
+
} catch (error) {
|
|
4983
|
+
requested = settings.get().subagentBackend ?? "dsh";
|
|
4984
|
+
throw error;
|
|
4985
|
+
}
|
|
4986
|
+
};
|
|
4987
|
+
subagentBackend = { select };
|
|
4988
|
+
const sync = (value) => {
|
|
4989
|
+
const mode = value.subagentBackend ?? "dsh";
|
|
4990
|
+
if (mode !== requested) select(mode).catch(() => scoped.logger.warn("Could not switch the subscription subagent backend"));
|
|
4991
|
+
};
|
|
4992
|
+
scoped.loader.await().then(() => sync(settings.get())).catch(() => scoped.logger.warn("Could not initialize the subscription subagent backend"));
|
|
4993
|
+
const unwatch = settings.watch(sync);
|
|
4994
|
+
scoped.effect(() => async () => {
|
|
4995
|
+
unwatch();
|
|
4996
|
+
unconfigure();
|
|
4997
|
+
subagentBackend = void 0;
|
|
4998
|
+
instance.dispose();
|
|
4999
|
+
await switcher.dispose();
|
|
5000
|
+
}, "codex-subscription: subagent backend");
|
|
5001
|
+
});
|
|
4298
5002
|
const adapterAuth = Object.freeze({
|
|
4299
5003
|
credentials: store,
|
|
4300
5004
|
authContext: Object.freeze({
|
|
@@ -4326,7 +5030,7 @@ function apply(ctx) {
|
|
|
4326
5030
|
auth: adapterAuth,
|
|
4327
5031
|
resolveAttachments: () => ctx.get?.("attachments")
|
|
4328
5032
|
});
|
|
4329
|
-
ctx.llm.registerAdapter([PROVIDER], adapter);
|
|
5033
|
+
ctx.llm.registerAdapter([PROVIDER], compaction.wrapAdapter(adapter));
|
|
4330
5034
|
const currentAgent = () => ctx.get?.("agents")?.currentInitiator?.();
|
|
4331
5035
|
const codexSearch = createCodexSearchProvider({
|
|
4332
5036
|
resolvePreferences: () => readCapabilitySettings(settings.get()),
|