dsh-codex-subscription 2.4.2 → 2.5.1
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 +8 -4
- package/README.md +11 -1
- package/lib/client.js +194 -38
- package/lib/index.js +2068 -1987
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -14,9 +14,9 @@ import { constants, readFileSync } from "node:fs";
|
|
|
14
14
|
import { dirname, join, resolve } from "node:path";
|
|
15
15
|
import { pathToFileURL } from "node:url";
|
|
16
16
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
17
|
+
import { LlmError, createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
17
18
|
import * as dshCredentials from "@deepseek-ai/dsh-credentials";
|
|
18
19
|
import { dshHomePath, resolveDshHome } from "@deepseek-ai/dsh-home-paths";
|
|
19
|
-
import { LlmError, createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
20
20
|
import { PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
|
|
21
21
|
import z from "@deepseek-ai/schemastery";
|
|
22
22
|
import { openaiCodexProvider as createOpenAICodexProvider } from "@earendil-works/pi-ai/providers/openai-codex";
|
|
@@ -166,8 +166,7 @@ const IMAGE_FEATURE_DEFAULTS = Object.freeze({
|
|
|
166
166
|
imageViewer: true,
|
|
167
167
|
imageAnnotations: true,
|
|
168
168
|
imageSketch: false,
|
|
169
|
-
imageSketchAgent: false
|
|
170
|
-
imageSketchAgentPreview: false
|
|
169
|
+
imageSketchAgent: false
|
|
171
170
|
});
|
|
172
171
|
function readImageFeatures(value = {}) {
|
|
173
172
|
return Object.fromEntries(Object.entries(IMAGE_FEATURE_DEFAULTS).map(([key, fallback]) => [key, typeof value?.[key] === "boolean" ? value[key] : fallback]));
|
|
@@ -1980,2071 +1979,2106 @@ const sketchCommandArray = {
|
|
|
1980
1979
|
})
|
|
1981
1980
|
};
|
|
1982
1981
|
//#endregion
|
|
1983
|
-
//#region src/
|
|
1984
|
-
function
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
type: "string",
|
|
2002
|
-
description: "From inspect; required for all other actions. Never reuse a stopped run."
|
|
2003
|
-
},
|
|
2004
|
-
documentId: {
|
|
2005
|
-
type: "string",
|
|
2006
|
-
description: "From inspect; required except for inspect."
|
|
2007
|
-
},
|
|
2008
|
-
revision: {
|
|
2009
|
-
type: "integer",
|
|
2010
|
-
description: "From latest response; required for apply/save/finish."
|
|
2011
|
-
},
|
|
2012
|
-
requestId: {
|
|
2013
|
-
type: "string",
|
|
2014
|
-
description: "Unique id for apply/save/finish; exact retries are deduplicated. After timeout inspect recentRequests before repeating a write."
|
|
2015
|
-
},
|
|
2016
|
-
commands: {
|
|
2017
|
-
oneOf: [sketchCommandArray, { type: "string" }],
|
|
2018
|
-
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."
|
|
2019
|
-
},
|
|
2020
|
-
name: {
|
|
2021
|
-
type: "string",
|
|
2022
|
-
description: "Draft name for save/finish."
|
|
2023
|
-
},
|
|
2024
|
-
offset: {
|
|
2025
|
-
type: "integer",
|
|
2026
|
-
description: "inspect only: object list offset, default 0. Follow nextOffset for further pages."
|
|
2027
|
-
},
|
|
2028
|
-
objectId: {
|
|
2029
|
-
type: "string",
|
|
2030
|
-
description: "inspect only: return full editable geometry for this object, in layer (defaults to active layer)."
|
|
2031
|
-
},
|
|
2032
|
-
layer: {
|
|
2033
|
-
type: "integer",
|
|
2034
|
-
description: "inspect only: layer containing objectId."
|
|
2035
|
-
}
|
|
2036
|
-
},
|
|
2037
|
-
timeoutMs: 25e3,
|
|
2038
|
-
isConcurrencySafe: () => false,
|
|
2039
|
-
async execute(args, exec) {
|
|
2040
|
-
const sessionId = exec.agent?.id;
|
|
2041
|
-
if (typeof sessionId !== "string") throw Error("A session-owned sketch call is required");
|
|
2042
|
-
const request = { ...args };
|
|
2043
|
-
if (args.action === "apply") try {
|
|
2044
|
-
request.commands = typeof args.commands === "string" ? JSON.parse(args.commands) : args.commands;
|
|
2045
|
-
if (!Array.isArray(request.commands)) throw Error();
|
|
2046
|
-
} catch {
|
|
2047
|
-
throw Error("commands must be a native array or JSON array string");
|
|
2048
|
-
}
|
|
2049
|
-
const value = await bridge.request(sessionId, request, exec.signal);
|
|
2050
|
-
if (value.png) {
|
|
2051
|
-
if (!/^data:image\/png;base64,/.test(value.png) || value.png.length > 8 * 1024 * 1024) throw Error("Invalid sketch preview");
|
|
2052
|
-
const image = await attachments.saveImage({
|
|
2053
|
-
data: new Uint8Array(Buffer.from(value.png.split(",")[1], "base64")),
|
|
2054
|
-
mediaType: "image/png",
|
|
2055
|
-
name: "sketch-preview.png"
|
|
2056
|
-
});
|
|
2057
|
-
const { png, ...snapshot } = value;
|
|
2058
|
-
return {
|
|
2059
|
-
...snapshot,
|
|
2060
|
-
image
|
|
2061
|
-
};
|
|
2062
|
-
}
|
|
2063
|
-
return value;
|
|
2064
|
-
},
|
|
2065
|
-
output: {
|
|
2066
|
-
schema: {
|
|
2067
|
-
type: "object",
|
|
2068
|
-
additionalProperties: true
|
|
2069
|
-
},
|
|
2070
|
-
render: (_args, value) => [{
|
|
2071
|
-
type: "text",
|
|
2072
|
-
text: JSON.stringify({
|
|
2073
|
-
...value,
|
|
2074
|
-
image: void 0
|
|
2075
|
-
})
|
|
2076
|
-
}, ...value.image ? [{
|
|
2077
|
-
type: "image",
|
|
2078
|
-
attachment: value.image
|
|
2079
|
-
}] : []]
|
|
2080
|
-
}
|
|
2081
|
-
});
|
|
1982
|
+
//#region src/subscription-credentials.js
|
|
1983
|
+
async function readSubscriptionCredentials(getAuth, readCredential, signal) {
|
|
1984
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
1985
|
+
const auth = await getAuth({ signal });
|
|
1986
|
+
signal?.throwIfAborted();
|
|
1987
|
+
if (typeof auth?.auth?.apiKey !== "string" || !auth.auth.apiKey) break;
|
|
1988
|
+
const credential = await readCredential({ signal });
|
|
1989
|
+
signal?.throwIfAborted();
|
|
1990
|
+
if (credential?.type !== "oauth") break;
|
|
1991
|
+
if (credential.access === auth.auth.apiKey) return {
|
|
1992
|
+
access: credential.access,
|
|
1993
|
+
accountId: credential.accountId
|
|
1994
|
+
};
|
|
1995
|
+
}
|
|
1996
|
+
return {
|
|
1997
|
+
access: void 0,
|
|
1998
|
+
accountId: void 0
|
|
1999
|
+
};
|
|
2082
2000
|
}
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
}
|
|
2098
|
-
});
|
|
2001
|
+
const ORIGINAL_IMAGE_CHUNK_BYTES = 4 * 1024 * 1024;
|
|
2002
|
+
const ORIGINAL_IMAGE_ID_PATTERN = /^img_[0-9a-f]{32}$/u;
|
|
2003
|
+
const positiveInteger$1 = (value) => Number.isSafeInteger(value) && value > 0;
|
|
2004
|
+
function decodeOriginalImageRef(value) {
|
|
2005
|
+
if (value === null || typeof value !== "object" || Array.isArray(value) || typeof value.assetId !== "string" || !ORIGINAL_IMAGE_ID_PATTERN.test(value.assetId) || value.mediaType !== "image/png" || !positiveInteger$1(value.bytes) || value.bytes > 48 * 1024 * 1024 || !positiveInteger$1(value.width) || !positiveInteger$1(value.height) || typeof value.name !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u.test(value.name) || typeof value.sha256 !== "string" || !/^[0-9a-f]{64}$/u.test(value.sha256)) return void 0;
|
|
2006
|
+
return {
|
|
2007
|
+
assetId: value.assetId,
|
|
2008
|
+
mediaType: value.mediaType,
|
|
2009
|
+
bytes: value.bytes,
|
|
2010
|
+
width: value.width,
|
|
2011
|
+
height: value.height,
|
|
2012
|
+
name: value.name,
|
|
2013
|
+
sha256: value.sha256
|
|
2014
|
+
};
|
|
2099
2015
|
}
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
const
|
|
2103
|
-
|
|
2104
|
-
const clone$1 = (value) => value === void 0 ? void 0 : structuredClone(value);
|
|
2105
|
-
const EMAIL_MAX_LENGTH = 254;
|
|
2106
|
-
const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/u;
|
|
2107
|
-
/** Keep only a bounded, display-safe email address from a trusted OAuth result. */
|
|
2108
|
-
function normalizeAccountEmail(value) {
|
|
2109
|
-
if (typeof value !== "string") return void 0;
|
|
2110
|
-
const email = value.trim();
|
|
2111
|
-
return email.length > 0 && email.length <= EMAIL_MAX_LENGTH && EMAIL_PATTERN.test(email) ? email : void 0;
|
|
2016
|
+
function decodeImagePresentation(value) {
|
|
2017
|
+
if (value === null || typeof value !== "object" || Array.isArray(value) || value.kind !== "codex-subscription-image" || value.schemaVersion !== 1) return void 0;
|
|
2018
|
+
const original = decodeOriginalImageRef(value.original);
|
|
2019
|
+
return original === void 0 ? void 0 : { original };
|
|
2112
2020
|
}
|
|
2113
|
-
function
|
|
2114
|
-
|
|
2115
|
-
const
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2021
|
+
function originalImageRefsEqual(left, right) {
|
|
2022
|
+
const a = decodeOriginalImageRef(left);
|
|
2023
|
+
const b = decodeOriginalImageRef(right);
|
|
2024
|
+
return a !== void 0 && b !== void 0 && a.assetId === b.assetId && a.mediaType === b.mediaType && a.bytes === b.bytes && a.width === b.width && a.height === b.height && a.name === b.name && a.sha256 === b.sha256;
|
|
2025
|
+
}
|
|
2026
|
+
/** Resolve only an exact original reference copied into a DSH fork prefix. */
|
|
2027
|
+
function inheritedOriginalImageRef(session, assetId) {
|
|
2028
|
+
const parentSession = session?.header?.parentSession;
|
|
2029
|
+
const seedLength = Number.isSafeInteger(session?.inheritedEventCount) ? session.inheritedEventCount : session?.header?.seedLength;
|
|
2030
|
+
if (typeof parentSession !== "string" || parentSession.length === 0 || !Number.isSafeInteger(seedLength) || seedLength < 0 || !ORIGINAL_IMAGE_ID_PATTERN.test(assetId)) return void 0;
|
|
2031
|
+
let events = session?.events;
|
|
2032
|
+
if (!Array.isArray(events) && typeof session?.snapshotEvents === "function") try {
|
|
2033
|
+
events = session.snapshotEvents();
|
|
2119
2034
|
} catch {
|
|
2120
2035
|
return;
|
|
2121
2036
|
}
|
|
2037
|
+
if (!Array.isArray(events)) return void 0;
|
|
2038
|
+
for (const event of events) {
|
|
2039
|
+
if (!Number.isSafeInteger(event?.seq) || event.seq < 0 || event.seq >= seedLength || event.type !== "tool/result") continue;
|
|
2040
|
+
const original = decodeImagePresentation(event.data?.meta)?.original;
|
|
2041
|
+
if (original?.assetId === assetId) return original;
|
|
2042
|
+
}
|
|
2122
2043
|
}
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2044
|
+
//#endregion
|
|
2045
|
+
//#region src/version.js
|
|
2046
|
+
const PACKAGE_VERSION = "2.5.1";
|
|
2047
|
+
const USER_AGENT = `dsh-codex-subscription/${PACKAGE_VERSION}`;
|
|
2048
|
+
//#endregion
|
|
2049
|
+
//#region src/codex-images.js
|
|
2050
|
+
const CODEX_IMAGE_TOOL_NAME = "codex_image_generate";
|
|
2051
|
+
const CODEX_IMAGE_GENERATION_URL = "https://chatgpt.com/backend-api/codex/images/generations";
|
|
2052
|
+
const CODEX_IMAGE_EDIT_URL = "https://chatgpt.com/backend-api/codex/images/edits";
|
|
2053
|
+
const MAX_REFERENCE_IMAGES = 5;
|
|
2054
|
+
const RESPONSE_ENVELOPE_BYTES = 1024 * 1024;
|
|
2055
|
+
const IMAGE_BACKGROUNDS = /* @__PURE__ */ new Set([
|
|
2056
|
+
"auto",
|
|
2057
|
+
"transparent",
|
|
2058
|
+
"opaque"
|
|
2059
|
+
]);
|
|
2060
|
+
const PNG_SIGNATURE = Buffer.from([
|
|
2061
|
+
137,
|
|
2062
|
+
80,
|
|
2063
|
+
78,
|
|
2064
|
+
71,
|
|
2065
|
+
13,
|
|
2066
|
+
10,
|
|
2067
|
+
26,
|
|
2068
|
+
10
|
|
2069
|
+
]);
|
|
2070
|
+
const FULL_ATTACHMENT_ID = /^sha256:[0-9a-f]{64}$/u;
|
|
2071
|
+
const BARE_ATTACHMENT_DIGEST = /^[0-9a-f]{64}$/iu;
|
|
2072
|
+
const PATH_LIKE_ATTACHMENT_ID = /[\\/]/u;
|
|
2073
|
+
const FILE_NAME_ATTACHMENT_ID = /\.[A-Za-z0-9]{1,16}$/u;
|
|
2074
|
+
const record$4 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2075
|
+
const nonEmpty$2 = (value) => typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
|
|
2076
|
+
const NETWORK_REASONS = {
|
|
2077
|
+
UND_ERR_HEADERS_TIMEOUT: "the image service sent no response within 5 minutes",
|
|
2078
|
+
UND_ERR_BODY_TIMEOUT: "the image response stalled",
|
|
2079
|
+
UND_ERR_CONNECT_TIMEOUT: "the connection to the image service timed out",
|
|
2080
|
+
UND_ERR_SOCKET: "the connection closed unexpectedly",
|
|
2081
|
+
ECONNRESET: "the connection was reset",
|
|
2082
|
+
ETIMEDOUT: "the connection timed out",
|
|
2083
|
+
ENOTFOUND: "DNS lookup failed",
|
|
2084
|
+
ECONNREFUSED: "the connection was refused",
|
|
2085
|
+
EAI_AGAIN: "DNS lookup failed"
|
|
2086
|
+
};
|
|
2087
|
+
function requestFailureReason(error) {
|
|
2088
|
+
const code = [(error?.cause ?? error)?.code, error?.code].find((value) => typeof value === "string" && /^[A-Z][A-Z0-9_]{2,39}$/u.test(value));
|
|
2089
|
+
if (code !== void 0) return `: ${NETWORK_REASONS[code] ?? "network error"} (${code})`;
|
|
2090
|
+
if (error?.name === "TimeoutError") return ": the image request timed out";
|
|
2091
|
+
return "";
|
|
2126
2092
|
}
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
const
|
|
2130
|
-
const
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2093
|
+
function normalizeImageOptions(args) {
|
|
2094
|
+
const quality = nonEmpty$2(args?.quality) ?? "auto";
|
|
2095
|
+
const background = nonEmpty$2(args?.background) ?? "auto";
|
|
2096
|
+
const size = nonEmpty$2(args?.size) ?? "auto";
|
|
2097
|
+
validateImageQuality(resolveImageModel(args?.model), quality);
|
|
2098
|
+
if (!IMAGE_BACKGROUNDS.has(background)) throw new Error("background must be auto, transparent, or opaque");
|
|
2099
|
+
if (size !== "auto") {
|
|
2100
|
+
const match = /^(\d+)x(\d+)$/u.exec(size);
|
|
2101
|
+
const width = Number(match?.[1]);
|
|
2102
|
+
const height = Number(match?.[2]);
|
|
2103
|
+
const short = Math.min(width, height);
|
|
2104
|
+
const long = Math.max(width, height);
|
|
2105
|
+
const pixels = width * height;
|
|
2106
|
+
if (match === null || width % 16 !== 0 || height % 16 !== 0 || long > 3840 || long > short * 3 || pixels < 655360 || pixels > 8294400) throw new Error("size must be auto or a valid GPT Image 2 widthxheight resolution");
|
|
2134
2107
|
}
|
|
2135
2108
|
return {
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
}
|
|
2140
|
-
function assertOAuthCredential$1(value) {
|
|
2141
|
-
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");
|
|
2142
|
-
return clone$1(value);
|
|
2109
|
+
quality,
|
|
2110
|
+
background,
|
|
2111
|
+
size
|
|
2112
|
+
};
|
|
2143
2113
|
}
|
|
2144
|
-
function
|
|
2114
|
+
function encodedLimit(decodedBytes) {
|
|
2115
|
+
return Math.ceil(decodedBytes / 3) * 4;
|
|
2116
|
+
}
|
|
2117
|
+
function validBase64Body(value, end) {
|
|
2118
|
+
for (let index = 0; index < end; index += 1) {
|
|
2119
|
+
const code = value.charCodeAt(index);
|
|
2120
|
+
if (!(code >= 65 && code <= 90 || code >= 97 && code <= 122 || code >= 48 && code <= 57 || code === 43 || code === 47)) return false;
|
|
2121
|
+
}
|
|
2122
|
+
return true;
|
|
2123
|
+
}
|
|
2124
|
+
async function readJsonWithin(response, maximumBytes) {
|
|
2125
|
+
const contentLength = Number(response.headers.get("content-length"));
|
|
2126
|
+
if (Number.isFinite(contentLength) && contentLength > maximumBytes) throw new Error("Codex image response exceeds the image size limit");
|
|
2127
|
+
if (response.body === null) throw new Error("Codex returned an unreadable image response");
|
|
2128
|
+
const reader = response.body.getReader();
|
|
2129
|
+
const chunks = [];
|
|
2130
|
+
let bytes = 0;
|
|
2131
|
+
while (true) {
|
|
2132
|
+
const { done, value } = await reader.read();
|
|
2133
|
+
if (done) break;
|
|
2134
|
+
bytes += value.byteLength;
|
|
2135
|
+
if (bytes > maximumBytes) {
|
|
2136
|
+
await reader.cancel();
|
|
2137
|
+
throw new Error("Codex image response exceeds the image size limit");
|
|
2138
|
+
}
|
|
2139
|
+
chunks.push(value);
|
|
2140
|
+
}
|
|
2141
|
+
const body = Buffer.concat(chunks.map((chunk) => Buffer.from(chunk)), bytes).toString("utf8");
|
|
2145
2142
|
try {
|
|
2146
|
-
return
|
|
2147
|
-
} catch
|
|
2148
|
-
|
|
2149
|
-
throw new Error("Codex account vault contains malformed OAuth JSON", { cause: error });
|
|
2143
|
+
return JSON.parse(body);
|
|
2144
|
+
} catch {
|
|
2145
|
+
throw new Error("Codex returned an unreadable image response");
|
|
2150
2146
|
}
|
|
2151
2147
|
}
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
const
|
|
2155
|
-
|
|
2156
|
-
|
|
2148
|
+
/** Strictly decode one PNG returned by the subscription backend. */
|
|
2149
|
+
function decodeCodexPng(value, maximumBytes) {
|
|
2150
|
+
const encoded = nonEmpty$2(value);
|
|
2151
|
+
const padding = encoded?.endsWith("==") ? 2 : encoded?.endsWith("=") ? 1 : 0;
|
|
2152
|
+
if (encoded === void 0 || encoded.length % 4 !== 0 || !validBase64Body(encoded, encoded.length - padding)) throw new Error("Codex returned an invalid base64 PNG");
|
|
2153
|
+
const decodedBytes = encoded.length / 4 * 3 - padding;
|
|
2154
|
+
if (decodedBytes > maximumBytes) throw new Error("Codex image exceeds the image size limit");
|
|
2155
|
+
const data = Buffer.from(encoded, "base64");
|
|
2156
|
+
if (data.length !== decodedBytes || data.length < PNG_SIGNATURE.length || !data.subarray(0, PNG_SIGNATURE.length).equals(PNG_SIGNATURE)) throw new Error("Codex returned an invalid PNG");
|
|
2157
|
+
return new Uint8Array(data);
|
|
2157
2158
|
}
|
|
2158
|
-
function
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2159
|
+
function imageReference(value) {
|
|
2160
|
+
const originalDimensions = record$4(value.originalDimensions) ? {
|
|
2161
|
+
width: value.originalDimensions.width,
|
|
2162
|
+
height: value.originalDimensions.height
|
|
2163
|
+
} : void 0;
|
|
2164
|
+
return {
|
|
2165
|
+
attachmentId: value.attachmentId,
|
|
2166
|
+
mediaType: value.mediaType,
|
|
2167
|
+
bytes: value.bytes,
|
|
2168
|
+
width: value.width,
|
|
2169
|
+
height: value.height,
|
|
2170
|
+
...value.name === void 0 ? {} : { name: value.name },
|
|
2171
|
+
...originalDimensions === void 0 ? {} : { originalDimensions }
|
|
2172
|
+
};
|
|
2173
|
+
}
|
|
2174
|
+
function normalizeAttachmentId(value) {
|
|
2175
|
+
if (typeof value !== "string") return void 0;
|
|
2176
|
+
if (FULL_ATTACHMENT_ID.test(value)) return value;
|
|
2177
|
+
const bare = BARE_ATTACHMENT_DIGEST.exec(value);
|
|
2178
|
+
return bare === null ? void 0 : `sha256:${bare[0].toLowerCase()}`;
|
|
2179
|
+
}
|
|
2180
|
+
function invalidAttachmentId(value) {
|
|
2181
|
+
const attachmentId = value?.attachmentId;
|
|
2182
|
+
if (typeof attachmentId === "string" && (PATH_LIKE_ATTACHMENT_ID.test(attachmentId) || FILE_NAME_ATTACHMENT_ID.test(attachmentId))) throw new Error("referenceImages attachmentId is a file path or filename; call read_image on that file and retry with its complete sha256:<64 lowercase hex> attachment reference. Do not omit referenceImages or fall back to new image generation.");
|
|
2183
|
+
throw new Error("referenceImages attachmentId must be sha256:<64 lowercase hex> copied from an image block or read_image result. Do not omit referenceImages or fall back to new image generation.");
|
|
2184
|
+
}
|
|
2185
|
+
function referenceOf(value, attachments) {
|
|
2186
|
+
const attachmentId = normalizeAttachmentId(value?.attachmentId);
|
|
2187
|
+
if (attachmentId === void 0) invalidAttachmentId(value);
|
|
2188
|
+
if (!record$4(value) || !attachments.imageLimits.mediaTypes.includes(value.mediaType) || !Number.isSafeInteger(value.bytes) || value.bytes <= 0 || !Number.isSafeInteger(value.width) || value.width <= 0 || !Number.isSafeInteger(value.height) || value.height <= 0 || value.name !== void 0 && (typeof value.name !== "string" || value.name.length > 256) || value.originalDimensions !== void 0 && (!record$4(value.originalDimensions) || !Number.isSafeInteger(value.originalDimensions.width) || value.originalDimensions.width <= 0 || !Number.isSafeInteger(value.originalDimensions.height) || value.originalDimensions.height <= 0)) throw new Error("referenceImages contains an invalid image reference");
|
|
2189
|
+
return imageReference({
|
|
2190
|
+
...value,
|
|
2191
|
+
attachmentId
|
|
2169
2192
|
});
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2193
|
+
}
|
|
2194
|
+
function sessionImageReferences(messages) {
|
|
2195
|
+
const references = /* @__PURE__ */ new Map();
|
|
2196
|
+
const visit = (content) => {
|
|
2197
|
+
if (!Array.isArray(content)) return;
|
|
2198
|
+
for (const block of content) if (block?.type === "image" && record$4(block.attachment)) {
|
|
2199
|
+
const id = normalizeAttachmentId(block.attachment.attachmentId);
|
|
2200
|
+
if (id !== void 0) references.set(id, block.attachment);
|
|
2201
|
+
} else if (block?.type === "tool-result") visit(block.content);
|
|
2202
|
+
};
|
|
2203
|
+
for (const message of messages ?? []) visit(message?.content);
|
|
2204
|
+
return references;
|
|
2205
|
+
}
|
|
2206
|
+
async function readSessionImage(attachmentId, attachments, signal, messages) {
|
|
2207
|
+
const id = normalizeAttachmentId(attachmentId);
|
|
2208
|
+
if (id === void 0) invalidAttachmentId({ attachmentId });
|
|
2209
|
+
const selected = sessionImageReferences(messages).get(id);
|
|
2210
|
+
if (selected === void 0) throw new Error("The selected image attachment cannot be found in the current session. Copy attachmentId from the image block of the user message; never pass a path or filename.");
|
|
2211
|
+
const stored = await attachments.readImage(referenceOf(selected, attachments), signal);
|
|
2173
2212
|
return {
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
accounts
|
|
2213
|
+
data: stored.data,
|
|
2214
|
+
mediaType: stored.ref.mediaType,
|
|
2215
|
+
name: stored.ref.name
|
|
2178
2216
|
};
|
|
2179
2217
|
}
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
if (
|
|
2198
|
-
|
|
2199
|
-
if (next !== void 0) this.#credential = sanitizeOAuthCredential(next);
|
|
2200
|
-
return clone$1(this.#credential);
|
|
2201
|
-
}
|
|
2202
|
-
async delete(providerId) {
|
|
2203
|
-
if (providerId !== "openai-codex") throw new Error("Pending Codex login received an unknown provider");
|
|
2204
|
-
this.#credential = void 0;
|
|
2205
|
-
}
|
|
2206
|
-
credential() {
|
|
2207
|
-
return clone$1(this.#credential);
|
|
2208
|
-
}
|
|
2209
|
-
};
|
|
2210
|
-
/**
|
|
2211
|
-
* Multi-account owner state stored in DSH's atomic plugin credential record.
|
|
2212
|
-
* The old single-account reference remains as a rollback source and is kept in
|
|
2213
|
-
* sync whenever that imported account rotates its refresh token.
|
|
2214
|
-
*/
|
|
2215
|
-
var DshOAuthAccountVault = class {
|
|
2216
|
-
#tail = Promise.resolve();
|
|
2217
|
-
constructor(credentials, options) {
|
|
2218
|
-
if (credentials === void 0 || credentials === null || typeof credentials.readRecord !== "function" || typeof credentials.modifyRecord !== "function") throw new Error("Codex multi-account requires DSH credential records");
|
|
2219
|
-
this.credentials = credentials;
|
|
2220
|
-
this.key = options.key;
|
|
2221
|
-
this.legacyRef = options.legacyRef;
|
|
2222
|
-
this.legacyRefs = Object.freeze([...options.legacyRefs ?? []]);
|
|
2223
|
-
this.createId = options.createId ?? randomUUID;
|
|
2224
|
-
this.onLegacySyncFailure = options.onLegacySyncFailure ?? (() => {});
|
|
2225
|
-
}
|
|
2226
|
-
#enqueue(operation) {
|
|
2227
|
-
const current = this.#tail.catch(() => void 0).then(operation);
|
|
2228
|
-
this.#tail = current.catch(() => void 0);
|
|
2229
|
-
return current;
|
|
2230
|
-
}
|
|
2231
|
-
async #legacyCredential() {
|
|
2232
|
-
for (const ref of [this.legacyRef, ...this.legacyRefs]) {
|
|
2233
|
-
const hit = await this.credentials.resolve(ref);
|
|
2234
|
-
if (hit?.value === void 0 || hit.value === "") continue;
|
|
2235
|
-
return {
|
|
2236
|
-
ref,
|
|
2237
|
-
credential: parseOAuthCredential$1(hit.value)
|
|
2238
|
-
};
|
|
2239
|
-
}
|
|
2240
|
-
}
|
|
2241
|
-
async #ensurePayload() {
|
|
2242
|
-
const existing = await this.credentials.readRecord(this.key);
|
|
2243
|
-
if (existing !== void 0) return assertVaultRecord(existing);
|
|
2244
|
-
const legacy = await this.#legacyCredential();
|
|
2245
|
-
if (legacy === void 0) return void 0;
|
|
2246
|
-
const id = this.createId();
|
|
2247
|
-
return assertVaultRecord(await this.credentials.modifyRecord(this.key, (current) => {
|
|
2248
|
-
if (current !== void 0) return Promise.resolve(current);
|
|
2249
|
-
return Promise.resolve(grant({
|
|
2250
|
-
version: VERSION,
|
|
2251
|
-
activeId: id,
|
|
2252
|
-
legacyAccountId: id,
|
|
2253
|
-
accounts: [{
|
|
2254
|
-
id,
|
|
2255
|
-
label: DEFAULT_LABEL,
|
|
2256
|
-
credential: legacy.credential
|
|
2257
|
-
}]
|
|
2258
|
-
}));
|
|
2259
|
-
}));
|
|
2260
|
-
}
|
|
2261
|
-
async #modifyPayload(update) {
|
|
2262
|
-
await this.#ensurePayload();
|
|
2263
|
-
let previousLegacy;
|
|
2264
|
-
const payload = assertVaultRecord(await this.credentials.modifyRecord(this.key, async (current) => {
|
|
2265
|
-
if (current === void 0) throw new Error("Codex account vault is not signed in");
|
|
2266
|
-
const payload = assertVaultRecord(current);
|
|
2267
|
-
previousLegacy = payload.accounts.find((account) => account.id === payload.legacyAccountId)?.credential;
|
|
2268
|
-
const next = await update(clone$1(payload));
|
|
2269
|
-
return grant(next);
|
|
2270
|
-
}));
|
|
2271
|
-
const legacy = payload.accounts.find((account) => account.id === payload.legacyAccountId)?.credential;
|
|
2272
|
-
try {
|
|
2273
|
-
if (legacy === void 0) {
|
|
2274
|
-
if (previousLegacy !== void 0) await this.credentials.unset(this.legacyRef);
|
|
2275
|
-
} else if (JSON.stringify(legacy) !== JSON.stringify(previousLegacy)) await this.credentials.set(this.legacyRef, JSON.stringify(legacy));
|
|
2276
|
-
} catch {
|
|
2277
|
-
this.onLegacySyncFailure();
|
|
2278
|
-
}
|
|
2279
|
-
return payload;
|
|
2280
|
-
}
|
|
2281
|
-
list() {
|
|
2282
|
-
return this.#enqueue(async () => {
|
|
2283
|
-
const payload = await this.#ensurePayload();
|
|
2284
|
-
if (payload === void 0) return [];
|
|
2285
|
-
return payload.accounts.map((account) => ({
|
|
2286
|
-
id: account.id,
|
|
2287
|
-
label: account.label,
|
|
2288
|
-
active: account.id === payload.activeId,
|
|
2289
|
-
expiresAt: account.credential.expires,
|
|
2290
|
-
...account.credential.email === void 0 ? {} : { email: account.credential.email }
|
|
2291
|
-
}));
|
|
2292
|
-
});
|
|
2293
|
-
}
|
|
2294
|
-
readActive() {
|
|
2295
|
-
return this.#enqueue(async () => {
|
|
2296
|
-
const payload = await this.#ensurePayload();
|
|
2297
|
-
return clone$1(payload?.accounts.find((account) => account.id === payload.activeId)?.credential);
|
|
2298
|
-
});
|
|
2218
|
+
async function editImages(values, attachments, signal, messages) {
|
|
2219
|
+
if (!Array.isArray(values) || values.length === 0 || values.length > MAX_REFERENCE_IMAGES) throw new Error(`referenceImages must contain between 1 and ${MAX_REFERENCE_IMAGES} images`);
|
|
2220
|
+
const available = messages === void 0 ? void 0 : sessionImageReferences(messages);
|
|
2221
|
+
const references = values.map((value) => {
|
|
2222
|
+
const id = normalizeAttachmentId(value?.attachmentId);
|
|
2223
|
+
if (id === void 0) invalidAttachmentId(value);
|
|
2224
|
+
if (available === void 0) return referenceOf(value, attachments);
|
|
2225
|
+
const selected = available.get(id);
|
|
2226
|
+
if (selected === void 0) throw new Error("The selected image attachment cannot be found in the current session. Call read_image on the intended image and retry with its returned reference. Do not omit referenceImages or substitute another image.");
|
|
2227
|
+
return referenceOf(selected, attachments);
|
|
2228
|
+
});
|
|
2229
|
+
if (new Set(references.map((value) => value.attachmentId)).size !== references.length) throw new Error("referenceImages must not contain duplicates");
|
|
2230
|
+
const images = [];
|
|
2231
|
+
let totalBytes = 0;
|
|
2232
|
+
for (const reference of references) {
|
|
2233
|
+
const stored = await attachments.readImage(reference, signal);
|
|
2234
|
+
totalBytes += stored.data.byteLength;
|
|
2235
|
+
if (totalBytes > attachments.imageLimits.maxMessageImageBytes) throw new Error("referenceImages exceed the DSH message image limit");
|
|
2236
|
+
images.push({ image_url: `data:${stored.ref.mediaType};base64,${Buffer.from(stored.data).toString("base64")}` });
|
|
2299
2237
|
}
|
|
2300
|
-
|
|
2301
|
-
return this.#enqueue(async () => (await this.#ensurePayload())?.activeId);
|
|
2302
|
-
}
|
|
2303
|
-
add(label, credential) {
|
|
2304
|
-
return this.#enqueue(async () => {
|
|
2305
|
-
const normalizedLabel = normalizeLabel(label);
|
|
2306
|
-
const validated = sanitizeOAuthCredential(credential);
|
|
2307
|
-
await this.#ensurePayload();
|
|
2308
|
-
const id = this.createId();
|
|
2309
|
-
const account = (await this.#modifyPayload((current) => ({
|
|
2310
|
-
...current,
|
|
2311
|
-
activeId: id,
|
|
2312
|
-
accounts: [...current.accounts, {
|
|
2313
|
-
id,
|
|
2314
|
-
label: normalizedLabel,
|
|
2315
|
-
credential: validated
|
|
2316
|
-
}]
|
|
2317
|
-
}))).accounts.find((candidate) => candidate.id === id);
|
|
2318
|
-
return {
|
|
2319
|
-
id,
|
|
2320
|
-
label: account.label,
|
|
2321
|
-
active: true,
|
|
2322
|
-
expiresAt: account.credential.expires,
|
|
2323
|
-
...account.credential.email === void 0 ? {} : { email: account.credential.email }
|
|
2324
|
-
};
|
|
2325
|
-
});
|
|
2326
|
-
}
|
|
2327
|
-
select(id) {
|
|
2328
|
-
return this.#enqueue(async () => {
|
|
2329
|
-
await this.#modifyPayload((current) => {
|
|
2330
|
-
if (!current.accounts.some((account) => account.id === id)) throw new Error("Unknown Codex account");
|
|
2331
|
-
return {
|
|
2332
|
-
...current,
|
|
2333
|
-
activeId: id
|
|
2334
|
-
};
|
|
2335
|
-
});
|
|
2336
|
-
});
|
|
2337
|
-
}
|
|
2338
|
-
modifyActive(update) {
|
|
2339
|
-
return this.#enqueue(async () => {
|
|
2340
|
-
if (await this.#ensurePayload() === void 0) {
|
|
2341
|
-
const initial = await update(void 0);
|
|
2342
|
-
if (initial === void 0) return void 0;
|
|
2343
|
-
const credential = sanitizeOAuthCredential(initial);
|
|
2344
|
-
await this.credentials.set(this.legacyRef, JSON.stringify(credential));
|
|
2345
|
-
await this.#ensurePayload();
|
|
2346
|
-
return clone$1(credential);
|
|
2347
|
-
}
|
|
2348
|
-
let result;
|
|
2349
|
-
await this.#modifyPayload(async (current) => {
|
|
2350
|
-
const index = current.accounts.findIndex((account) => account.id === current.activeId);
|
|
2351
|
-
const previous = clone$1(current.accounts[index].credential);
|
|
2352
|
-
const next = await update(previous);
|
|
2353
|
-
if (next === void 0) {
|
|
2354
|
-
result = previous;
|
|
2355
|
-
return current;
|
|
2356
|
-
}
|
|
2357
|
-
const credential = sanitizeOAuthCredential(next);
|
|
2358
|
-
if (credential.email === void 0 && previous.email !== void 0) credential.email = previous.email;
|
|
2359
|
-
const accounts = [...current.accounts];
|
|
2360
|
-
accounts[index] = {
|
|
2361
|
-
...accounts[index],
|
|
2362
|
-
credential
|
|
2363
|
-
};
|
|
2364
|
-
result = clone$1(credential);
|
|
2365
|
-
return {
|
|
2366
|
-
...current,
|
|
2367
|
-
accounts
|
|
2368
|
-
};
|
|
2369
|
-
});
|
|
2370
|
-
return result;
|
|
2371
|
-
});
|
|
2372
|
-
}
|
|
2373
|
-
deleteAll() {
|
|
2374
|
-
return this.#enqueue(async () => {
|
|
2375
|
-
await this.credentials.deleteRecord(this.key);
|
|
2376
|
-
await this.credentials.unset(this.legacyRef);
|
|
2377
|
-
for (const ref of this.legacyRefs) await this.credentials.unset(ref);
|
|
2378
|
-
});
|
|
2379
|
-
}
|
|
2380
|
-
remove(id) {
|
|
2381
|
-
return this.#enqueue(async () => {
|
|
2382
|
-
await this.#modifyPayload((current) => {
|
|
2383
|
-
if (!current.accounts.some((account) => account.id === id)) throw new Error("Unknown Codex account");
|
|
2384
|
-
if (current.accounts.length === 1) throw new Error("Cannot remove the last account; sign out instead");
|
|
2385
|
-
const accounts = current.accounts.filter((account) => account.id !== id);
|
|
2386
|
-
return {
|
|
2387
|
-
...current,
|
|
2388
|
-
activeId: current.activeId === id ? accounts[0].id : current.activeId,
|
|
2389
|
-
legacyAccountId: current.legacyAccountId === id ? void 0 : current.legacyAccountId,
|
|
2390
|
-
accounts
|
|
2391
|
-
};
|
|
2392
|
-
});
|
|
2393
|
-
});
|
|
2394
|
-
}
|
|
2395
|
-
};
|
|
2396
|
-
//#endregion
|
|
2397
|
-
//#region src/credential-store.js
|
|
2398
|
-
const PROVIDER$1 = "openai-codex";
|
|
2399
|
-
const abortIfNeeded = (options) => options?.signal?.throwIfAborted();
|
|
2400
|
-
const clone = (value) => value === void 0 ? void 0 : structuredClone(value);
|
|
2401
|
-
function assertProvider(providerId) {
|
|
2402
|
-
if (providerId !== PROVIDER$1) throw new Error(`Codex credential store does not own provider ${JSON.stringify(providerId)}`);
|
|
2238
|
+
return images;
|
|
2403
2239
|
}
|
|
2404
|
-
function
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2240
|
+
function imageContent(value) {
|
|
2241
|
+
const label = typeof value.size === "string" && value.size.length > 0 ? `Generated a ${value.size} image.` : "Generated an image.";
|
|
2242
|
+
return [{
|
|
2243
|
+
type: "text",
|
|
2244
|
+
text: value.localPath === void 0 ? label : `${label}\nOriginal PNG saved on the DSH host at: ${JSON.stringify(value.localPath)}. Read this file or copy it to the workspace with a .png extension; this host path is not a browser URL.`
|
|
2245
|
+
}, {
|
|
2246
|
+
type: "image",
|
|
2247
|
+
attachment: imageReference(value.image)
|
|
2248
|
+
}];
|
|
2408
2249
|
}
|
|
2409
|
-
function
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2250
|
+
function imageOutputSchema() {
|
|
2251
|
+
return {
|
|
2252
|
+
type: "object",
|
|
2253
|
+
additionalProperties: false,
|
|
2254
|
+
properties: {
|
|
2255
|
+
image: {
|
|
2256
|
+
type: "object",
|
|
2257
|
+
required: true,
|
|
2258
|
+
additionalProperties: false,
|
|
2259
|
+
properties: {
|
|
2260
|
+
attachmentId: {
|
|
2261
|
+
type: "string",
|
|
2262
|
+
required: true
|
|
2263
|
+
},
|
|
2264
|
+
mediaType: {
|
|
2265
|
+
type: "string",
|
|
2266
|
+
enum: ["image/png"],
|
|
2267
|
+
required: true
|
|
2268
|
+
},
|
|
2269
|
+
bytes: {
|
|
2270
|
+
type: "integer",
|
|
2271
|
+
required: true
|
|
2272
|
+
},
|
|
2273
|
+
width: {
|
|
2274
|
+
type: "integer",
|
|
2275
|
+
required: true
|
|
2276
|
+
},
|
|
2277
|
+
height: {
|
|
2278
|
+
type: "integer",
|
|
2279
|
+
required: true
|
|
2280
|
+
},
|
|
2281
|
+
name: { type: "string" },
|
|
2282
|
+
originalDimensions: {
|
|
2283
|
+
type: "object",
|
|
2284
|
+
additionalProperties: false,
|
|
2285
|
+
properties: {
|
|
2286
|
+
width: {
|
|
2287
|
+
type: "integer",
|
|
2288
|
+
required: true
|
|
2289
|
+
},
|
|
2290
|
+
height: {
|
|
2291
|
+
type: "integer",
|
|
2292
|
+
required: true
|
|
2293
|
+
}
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2297
|
+
},
|
|
2298
|
+
original: {
|
|
2299
|
+
type: "object",
|
|
2300
|
+
required: true,
|
|
2301
|
+
additionalProperties: false,
|
|
2302
|
+
properties: {
|
|
2303
|
+
assetId: {
|
|
2304
|
+
type: "string",
|
|
2305
|
+
required: true
|
|
2306
|
+
},
|
|
2307
|
+
mediaType: {
|
|
2308
|
+
type: "string",
|
|
2309
|
+
enum: ["image/png"],
|
|
2310
|
+
required: true
|
|
2311
|
+
},
|
|
2312
|
+
bytes: {
|
|
2313
|
+
type: "integer",
|
|
2314
|
+
required: true
|
|
2315
|
+
},
|
|
2316
|
+
width: {
|
|
2317
|
+
type: "integer",
|
|
2318
|
+
required: true
|
|
2319
|
+
},
|
|
2320
|
+
height: {
|
|
2321
|
+
type: "integer",
|
|
2322
|
+
required: true
|
|
2323
|
+
},
|
|
2324
|
+
name: {
|
|
2325
|
+
type: "string",
|
|
2326
|
+
required: true
|
|
2327
|
+
},
|
|
2328
|
+
sha256: {
|
|
2329
|
+
type: "string",
|
|
2330
|
+
required: true
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
},
|
|
2334
|
+
background: { type: "string" },
|
|
2335
|
+
requestedModel: { type: "string" },
|
|
2336
|
+
reportedModel: { type: "string" },
|
|
2337
|
+
requestedSize: { type: "string" },
|
|
2338
|
+
localPath: {
|
|
2339
|
+
type: "string",
|
|
2340
|
+
required: true,
|
|
2341
|
+
description: "Absolute path to the original PNG on the DSH host."
|
|
2342
|
+
},
|
|
2343
|
+
quality: { type: "string" },
|
|
2344
|
+
size: { type: "string" }
|
|
2345
|
+
}
|
|
2346
|
+
};
|
|
2416
2347
|
}
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2348
|
+
function responseMetadata(value) {
|
|
2349
|
+
const data = Array.isArray(value?.data) ? value.data[0] : void 0;
|
|
2350
|
+
const encoded = record$4(data) ? data.b64_json : void 0;
|
|
2351
|
+
if (typeof encoded !== "string") throw new Error("Codex returned no image data");
|
|
2352
|
+
return {
|
|
2353
|
+
encoded,
|
|
2354
|
+
background: nonEmpty$2(value.background),
|
|
2355
|
+
quality: nonEmpty$2(value.quality),
|
|
2356
|
+
size: nonEmpty$2(value.size),
|
|
2357
|
+
reportedModel: nonEmpty$2(value.model)
|
|
2358
|
+
};
|
|
2359
|
+
}
|
|
2360
|
+
/** Create the DSH-native image-generation tool backed only by the ChatGPT subscription. */
|
|
2361
|
+
function createCodexImageTool(options) {
|
|
2362
|
+
const fetchImage = options.fetch ?? fetch;
|
|
2363
|
+
const attachments = options.attachments;
|
|
2364
|
+
return defineTool({
|
|
2365
|
+
name: CODEX_IMAGE_TOOL_NAME,
|
|
2366
|
+
description: "Generate or edit images only when the user asks for image output, not when merely discussing images. Uses the signed-in Codex subscription. For a new image, omit referenceImages. For edits, copy attachmentId only from the selected session image block; the host supplies its metadata. Never call read_image for an attachmentId or attachment filename. Use read_image only for an actual local file whose reference is not already in the conversation. Never substitute paths, unrelated images, or text-only generation for an edit. For numbered annotations, include the clean source and location-reference image, preserve the requested changes and coordinates in the prompt, and remove guidance markers from the result. If the intended references cannot be identified, ask rather than guessing.",
|
|
2367
|
+
parameters: {
|
|
2368
|
+
model: {
|
|
2369
|
+
type: "string",
|
|
2370
|
+
enum: Object.keys(IMAGE_MODELS),
|
|
2371
|
+
description: "Optional image engine, independent of the conversation model. When omitted, uses the user image setting (initially gpt-image-2). The 2.5 identifiers are experimental subscription candidates; override only when explicitly requested. Never silently retry with another model."
|
|
2372
|
+
},
|
|
2373
|
+
prompt: {
|
|
2374
|
+
type: "string",
|
|
2375
|
+
required: true,
|
|
2376
|
+
description: "A complete, production-ready description of the image to generate."
|
|
2377
|
+
},
|
|
2378
|
+
size: {
|
|
2379
|
+
type: "string",
|
|
2380
|
+
description: "Optional GPT Image 2 output size. Use auto unless the user requests an exact valid widthxheight resolution."
|
|
2381
|
+
},
|
|
2382
|
+
quality: {
|
|
2383
|
+
type: "string",
|
|
2384
|
+
enum: [
|
|
2385
|
+
"auto",
|
|
2386
|
+
"low",
|
|
2387
|
+
"medium",
|
|
2388
|
+
"high",
|
|
2389
|
+
"xhigh",
|
|
2390
|
+
"max"
|
|
2391
|
+
],
|
|
2392
|
+
description: "Optional rendering quality. Use auto unless the user requests draft speed or final quality."
|
|
2393
|
+
},
|
|
2394
|
+
background: {
|
|
2395
|
+
type: "string",
|
|
2396
|
+
enum: [
|
|
2397
|
+
"auto",
|
|
2398
|
+
"transparent",
|
|
2399
|
+
"opaque"
|
|
2400
|
+
],
|
|
2401
|
+
description: "Optional background mode. Request transparent only when the user needs transparency."
|
|
2402
|
+
},
|
|
2403
|
+
referenceImages: {
|
|
2404
|
+
type: "array",
|
|
2405
|
+
description: "Optional references to 1-5 selected images. For session images provide only attachmentId; the host resolves trusted metadata. Omit only for a new image; never drop an invalid reference to bypass editing.",
|
|
2406
|
+
items: {
|
|
2407
|
+
type: "object",
|
|
2408
|
+
additionalProperties: false,
|
|
2409
|
+
properties: {
|
|
2410
|
+
attachmentId: {
|
|
2411
|
+
type: "string",
|
|
2412
|
+
required: true,
|
|
2413
|
+
description: "Copy the complete attachmentId from the image block or read_image result: sha256:<64 lowercase hex>. Never pass a workspace path, absolute path, or filename. A bare 64-character hex digest is accepted and normalized to sha256:<64 lowercase hex>."
|
|
2414
|
+
},
|
|
2415
|
+
mediaType: {
|
|
2416
|
+
type: "string",
|
|
2417
|
+
...typeof options.getSessionMessages !== "function" ? { required: true } : {}
|
|
2418
|
+
},
|
|
2419
|
+
bytes: {
|
|
2420
|
+
type: "integer",
|
|
2421
|
+
...typeof options.getSessionMessages !== "function" ? { required: true } : {}
|
|
2422
|
+
},
|
|
2423
|
+
width: {
|
|
2424
|
+
type: "integer",
|
|
2425
|
+
...typeof options.getSessionMessages !== "function" ? { required: true } : {}
|
|
2426
|
+
},
|
|
2427
|
+
height: {
|
|
2428
|
+
type: "integer",
|
|
2429
|
+
...typeof options.getSessionMessages !== "function" ? { required: true } : {}
|
|
2430
|
+
},
|
|
2431
|
+
name: { type: "string" },
|
|
2432
|
+
originalDimensions: {
|
|
2433
|
+
type: "object",
|
|
2434
|
+
additionalProperties: false,
|
|
2435
|
+
properties: {
|
|
2436
|
+
width: {
|
|
2437
|
+
type: "integer",
|
|
2438
|
+
...typeof options.getSessionMessages !== "function" ? { required: true } : {}
|
|
2439
|
+
},
|
|
2440
|
+
height: {
|
|
2441
|
+
type: "integer",
|
|
2442
|
+
...typeof options.getSessionMessages !== "function" ? { required: true } : {}
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2449
|
+
},
|
|
2450
|
+
output: {
|
|
2451
|
+
schema: imageOutputSchema(),
|
|
2452
|
+
render: (_args, value) => imageContent(value),
|
|
2453
|
+
presentationMeta: (_args, value) => ({
|
|
2454
|
+
kind: "codex-subscription-image",
|
|
2455
|
+
schemaVersion: 1,
|
|
2456
|
+
original: value.original,
|
|
2457
|
+
...value.requestedModel === void 0 ? {} : { requestedModel: value.requestedModel },
|
|
2458
|
+
...value.reportedModel === void 0 ? {} : { reportedModel: value.reportedModel },
|
|
2459
|
+
...value.requestedSize === void 0 ? {} : { requestedSize: value.requestedSize }
|
|
2460
|
+
})
|
|
2461
|
+
},
|
|
2462
|
+
timeoutMs: 300 * 1e3,
|
|
2463
|
+
isConcurrencySafe: () => false,
|
|
2464
|
+
async execute(args, exec) {
|
|
2465
|
+
assertImageOperation(options.getFeatures?.(), args.referenceImages !== void 0);
|
|
2466
|
+
const defaults = readImageDefaults(options.getFeatures?.());
|
|
2467
|
+
args = {
|
|
2468
|
+
...args,
|
|
2469
|
+
model: args.model ?? defaults.imageModel,
|
|
2470
|
+
quality: args.quality ?? defaults.imageQuality
|
|
2456
2471
|
};
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
const
|
|
2461
|
-
if (
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
...current,
|
|
2492
|
-
expires: current.expires - this.expirySkewMs
|
|
2493
|
-
};
|
|
2494
|
-
const updated = await update(clone(visible));
|
|
2495
|
-
return updated === void 0 ? void 0 : assertOAuthCredential(updated);
|
|
2472
|
+
const prompt = nonEmpty$2(args.prompt);
|
|
2473
|
+
if (prompt === void 0) throw new Error("prompt must be a non-empty string");
|
|
2474
|
+
const imageOptions = normalizeImageOptions(args);
|
|
2475
|
+
const { access, accountId } = await readSubscriptionCredentials(options.getAuth, options.readCredential, exec.signal);
|
|
2476
|
+
if (typeof access !== "string" || access.length === 0 || typeof accountId !== "string" || accountId.length === 0) throw new Error("ChatGPT subscription is not signed in");
|
|
2477
|
+
if (!attachments.imageLimits.mediaTypes.includes("image/png")) throw new Error("This DSH installation does not accept PNG image attachments");
|
|
2478
|
+
const maximumBytes = Math.min(attachments.imageLimits.maxImageBytes, attachments.imageLimits.maxMessageImageBytes);
|
|
2479
|
+
const editing = args.referenceImages !== void 0;
|
|
2480
|
+
const sessionMessages = editing && typeof options.getSessionMessages === "function" ? await options.getSessionMessages(exec.agent?.id) : void 0;
|
|
2481
|
+
const images = editing ? await editImages(args.referenceImages, attachments, exec.signal, sessionMessages ?? (options.getSessionMessages ? [] : void 0)) : void 0;
|
|
2482
|
+
let response;
|
|
2483
|
+
assertImageOperation(options.getFeatures?.(), editing);
|
|
2484
|
+
try {
|
|
2485
|
+
response = await fetchImage(editing ? CODEX_IMAGE_EDIT_URL : CODEX_IMAGE_GENERATION_URL, {
|
|
2486
|
+
method: "POST",
|
|
2487
|
+
redirect: "error",
|
|
2488
|
+
headers: {
|
|
2489
|
+
authorization: `Bearer ${access}`,
|
|
2490
|
+
"chatgpt-account-id": accountId,
|
|
2491
|
+
accept: "application/json",
|
|
2492
|
+
"content-type": "application/json",
|
|
2493
|
+
originator: "pi",
|
|
2494
|
+
"x-codex-image-turn-id": String(exec.callId),
|
|
2495
|
+
"user-agent": USER_AGENT
|
|
2496
|
+
},
|
|
2497
|
+
body: JSON.stringify({
|
|
2498
|
+
...images === void 0 ? {} : { images },
|
|
2499
|
+
prompt,
|
|
2500
|
+
background: imageOptions.background,
|
|
2501
|
+
model: resolveImageModel(args.model),
|
|
2502
|
+
quality: imageOptions.quality,
|
|
2503
|
+
size: imageOptions.size
|
|
2504
|
+
}),
|
|
2505
|
+
signal: exec.signal
|
|
2496
2506
|
});
|
|
2497
|
-
|
|
2498
|
-
|
|
2507
|
+
} catch (error) {
|
|
2508
|
+
if (exec.signal.aborted) throw exec.signal.reason;
|
|
2509
|
+
throw new Error(`Codex image ${editing ? "edit" : "generation"} request failed${requestFailureReason(error)}`, { cause: error });
|
|
2499
2510
|
}
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
const validated = assertOAuthCredential(next);
|
|
2505
|
-
await this.credentials.set(this.ref, JSON.stringify(validated));
|
|
2506
|
-
for (const legacyRef of this.legacyRefs) await this.credentials.unset(legacyRef);
|
|
2507
|
-
abortIfNeeded(options);
|
|
2508
|
-
return clone(validated);
|
|
2509
|
-
}, options);
|
|
2510
|
-
}
|
|
2511
|
-
delete(providerId, options) {
|
|
2512
|
-
return this.#enqueue(providerId, async () => {
|
|
2513
|
-
if (this.vault !== void 0) {
|
|
2514
|
-
await this.vault.deleteAll();
|
|
2515
|
-
abortIfNeeded(options);
|
|
2516
|
-
return;
|
|
2511
|
+
if (!response.ok) {
|
|
2512
|
+
if (response.status === 401 || response.status === 403) throw new Error("ChatGPT sign-in needs to be renewed");
|
|
2513
|
+
if (response.status === 429) throw new Error("Codex image generation quota is unavailable");
|
|
2514
|
+
throw new Error(`Codex image ${editing ? "edit" : "generation"} failed (HTTP ${response.status})`);
|
|
2517
2515
|
}
|
|
2518
|
-
await
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
...
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
...accounts === void 0 ? {} : { accounts }
|
|
2516
|
+
const metadata = responseMetadata(await readJsonWithin(response, encodedLimit(maximumBytes) + RESPONSE_ENVELOPE_BYTES));
|
|
2517
|
+
const data = decodeCodexPng(metadata.encoded, maximumBytes);
|
|
2518
|
+
const sessionId = exec.agent?.id;
|
|
2519
|
+
if (sessionId === void 0) throw new Error("Codex image generation requires a session-owned tool call");
|
|
2520
|
+
const original = await options.originalImages.save(String(sessionId), data);
|
|
2521
|
+
let ref;
|
|
2522
|
+
try {
|
|
2523
|
+
ref = await attachments.saveImage({
|
|
2524
|
+
data,
|
|
2525
|
+
mediaType: "image/png",
|
|
2526
|
+
name: "codex-generated.png"
|
|
2527
|
+
});
|
|
2528
|
+
} catch (error) {
|
|
2529
|
+
await options.originalImages.remove(original);
|
|
2530
|
+
throw error;
|
|
2531
|
+
}
|
|
2532
|
+
const result = {
|
|
2533
|
+
requestedModel: resolveImageModel(args.model),
|
|
2534
|
+
requestedSize: imageOptions.size,
|
|
2535
|
+
...metadata.reportedModel === void 0 ? {} : { reportedModel: metadata.reportedModel },
|
|
2536
|
+
image: imageReference(ref),
|
|
2537
|
+
original,
|
|
2538
|
+
localPath: options.originalImages.originalPath(original.assetId),
|
|
2539
|
+
...metadata.background === void 0 ? {} : { background: metadata.background },
|
|
2540
|
+
...metadata.quality === void 0 ? {} : { quality: metadata.quality },
|
|
2541
|
+
size: `${original.width}x${original.height}`
|
|
2545
2542
|
};
|
|
2543
|
+
if (exec.parent !== void 0) exec.deferContext(createUserMessage({
|
|
2544
|
+
content: imageContent(result),
|
|
2545
|
+
source: { kind: "codex-subscription-image" }
|
|
2546
|
+
}));
|
|
2547
|
+
return result;
|
|
2548
|
+
}
|
|
2549
|
+
});
|
|
2550
|
+
}
|
|
2551
|
+
//#endregion
|
|
2552
|
+
//#region src/sketch-agent-tool.js
|
|
2553
|
+
const MAX_REFERENCE_BYTES = 14e5;
|
|
2554
|
+
function createSketchAgentTool(bridge, attachments, getSessionMessages = () => []) {
|
|
2555
|
+
return defineTool({
|
|
2556
|
+
name: "codex_sketch",
|
|
2557
|
+
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. Draw with the svg op whenever possible: one SVG document per stage (paths, shapes, groups, transforms; see the reference from inspect) is far faster and richer than hand-placed strokes, and still becomes native editable strokes. Draw the shapes, hair, clothing and lettering outlines of every layer with svg; use stroke commands only for small corrections and native text. Apply atomic batches and preview between stages. When the user attached a reference image, call reference with its attachmentId once after inspect to put it on the board as a hidden picture layer. When a reference picture is on the board, keep its layer hidden while drawing (compare shows it anyway), measure its key positions with preview grid, draw big shapes first, and after each stage check preview compare and fix the largest offset before adding detail. For repeated details such as hair strands or lace, generate the SVG with a script instead of placing each one by hand. and call finish to save the finished draft and release the editing lock. save is only a checkpoint. 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.",
|
|
2558
|
+
parameters: {
|
|
2559
|
+
action: {
|
|
2560
|
+
type: "string",
|
|
2561
|
+
required: true,
|
|
2562
|
+
enum: [
|
|
2563
|
+
"inspect",
|
|
2564
|
+
"apply",
|
|
2565
|
+
"reference",
|
|
2566
|
+
"preview",
|
|
2567
|
+
"save",
|
|
2568
|
+
"finish"
|
|
2569
|
+
]
|
|
2570
|
+
},
|
|
2571
|
+
runId: {
|
|
2572
|
+
type: "string",
|
|
2573
|
+
description: "From inspect; required for all other actions. Never reuse a stopped run."
|
|
2574
|
+
},
|
|
2575
|
+
documentId: {
|
|
2576
|
+
type: "string",
|
|
2577
|
+
description: "From inspect; required except for inspect."
|
|
2578
|
+
},
|
|
2579
|
+
revision: {
|
|
2580
|
+
type: "integer",
|
|
2581
|
+
description: "From latest response; required for apply/reference/save/finish."
|
|
2582
|
+
},
|
|
2583
|
+
requestId: {
|
|
2584
|
+
type: "string",
|
|
2585
|
+
description: "Unique id for apply/reference/save/finish; exact retries are deduplicated. After timeout inspect recentRequests before repeating a write."
|
|
2586
|
+
},
|
|
2587
|
+
commands: {
|
|
2588
|
+
oneOf: [sketchCommandArray, { type: "string" }],
|
|
2589
|
+
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."
|
|
2590
|
+
},
|
|
2591
|
+
name: {
|
|
2592
|
+
type: "string",
|
|
2593
|
+
description: "Draft name for save/finish."
|
|
2594
|
+
},
|
|
2595
|
+
attachmentId: {
|
|
2596
|
+
type: "string",
|
|
2597
|
+
description: "reference only: the attachmentId of an image the user attached to this session (copy it from the image block, never a path or filename). It is added to the board as a hidden picture layer, ready for preview compare."
|
|
2598
|
+
},
|
|
2599
|
+
compare: {
|
|
2600
|
+
type: "boolean",
|
|
2601
|
+
description: "preview only: blend the drawing 50/50 with the reference picture layer on the board, so offsets show as ghosting. Needs a picture layer; if there is none, ask the user to add the reference with the pictures button."
|
|
2602
|
+
},
|
|
2603
|
+
grid: {
|
|
2604
|
+
type: "boolean",
|
|
2605
|
+
description: "preview only: overlay a labelled grid every 100 canvas pixels, to read positions and sizes instead of guessing them."
|
|
2606
|
+
},
|
|
2607
|
+
offset: {
|
|
2608
|
+
type: "integer",
|
|
2609
|
+
description: "inspect only: object list offset, default 0. Follow nextOffset for further pages."
|
|
2610
|
+
},
|
|
2611
|
+
objectId: {
|
|
2612
|
+
type: "string",
|
|
2613
|
+
description: "inspect only: return full editable geometry for this object, in layer (defaults to active layer)."
|
|
2614
|
+
},
|
|
2615
|
+
layer: {
|
|
2616
|
+
type: "integer",
|
|
2617
|
+
description: "inspect only: layer containing objectId."
|
|
2618
|
+
}
|
|
2546
2619
|
},
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2620
|
+
timeoutMs: 25e3,
|
|
2621
|
+
isConcurrencySafe: () => false,
|
|
2622
|
+
async execute(args, exec) {
|
|
2623
|
+
const sessionId = exec.agent?.id;
|
|
2624
|
+
if (typeof sessionId !== "string") throw Error("A session-owned sketch call is required");
|
|
2625
|
+
const request = { ...args };
|
|
2626
|
+
delete request.attachmentId;
|
|
2627
|
+
if (args.action === "apply") try {
|
|
2628
|
+
request.commands = typeof args.commands === "string" ? JSON.parse(args.commands) : args.commands;
|
|
2629
|
+
if (!Array.isArray(request.commands)) throw Error();
|
|
2630
|
+
} catch {
|
|
2631
|
+
throw Error("commands must be a native array or JSON array string");
|
|
2632
|
+
}
|
|
2633
|
+
if (args.action === "reference") {
|
|
2634
|
+
const image = await readSessionImage(args.attachmentId, attachments, exec.signal, await getSessionMessages(sessionId));
|
|
2635
|
+
if (image.data.byteLength > MAX_REFERENCE_BYTES) throw Error("The reference image is too large to send to the board; ask the user to add it with the pictures button on the board");
|
|
2636
|
+
request.image = `data:${image.mediaType};base64,${Buffer.from(image.data).toString("base64")}`;
|
|
2637
|
+
request.name = image.name ?? "reference";
|
|
2638
|
+
}
|
|
2639
|
+
const value = await bridge.request(sessionId, request, exec.signal);
|
|
2640
|
+
if (value.png) {
|
|
2641
|
+
if (!/^data:image\/png;base64,/.test(value.png) || value.png.length > 8 * 1024 * 1024) throw Error("Invalid sketch preview");
|
|
2642
|
+
const image = await attachments.saveImage({
|
|
2643
|
+
data: new Uint8Array(Buffer.from(value.png.split(",")[1], "base64")),
|
|
2644
|
+
mediaType: "image/png",
|
|
2645
|
+
name: "sketch-preview.png"
|
|
2556
2646
|
});
|
|
2647
|
+
const { png, ...snapshot } = value;
|
|
2648
|
+
return {
|
|
2649
|
+
...snapshot,
|
|
2650
|
+
image
|
|
2651
|
+
};
|
|
2557
2652
|
}
|
|
2558
|
-
return
|
|
2559
|
-
},
|
|
2560
|
-
async select(id) {
|
|
2561
|
-
if (accountVault === void 0) throw new Error("Codex multi-account is unavailable");
|
|
2562
|
-
await accountVault.select(id);
|
|
2563
|
-
return this.status();
|
|
2564
|
-
},
|
|
2565
|
-
async remove(id) {
|
|
2566
|
-
if (accountVault === void 0) throw new Error("Codex multi-account is unavailable");
|
|
2567
|
-
await accountVault.remove(id);
|
|
2568
|
-
return this.status();
|
|
2653
|
+
return value;
|
|
2569
2654
|
},
|
|
2570
|
-
|
|
2571
|
-
|
|
2655
|
+
output: {
|
|
2656
|
+
schema: {
|
|
2657
|
+
type: "object",
|
|
2658
|
+
additionalProperties: true
|
|
2659
|
+
},
|
|
2660
|
+
render: (_args, value) => [{
|
|
2661
|
+
type: "text",
|
|
2662
|
+
text: JSON.stringify({
|
|
2663
|
+
...value,
|
|
2664
|
+
image: void 0
|
|
2665
|
+
})
|
|
2666
|
+
}, ...value.image ? [{
|
|
2667
|
+
type: "image",
|
|
2668
|
+
attachment: value.image
|
|
2669
|
+
}] : []]
|
|
2572
2670
|
}
|
|
2573
2671
|
});
|
|
2574
2672
|
}
|
|
2575
2673
|
//#endregion
|
|
2576
|
-
//#region src/
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2674
|
+
//#region src/sketch-codec-route.js
|
|
2675
|
+
function registerSketchCodec(connection) {
|
|
2676
|
+
let source;
|
|
2677
|
+
return connection.fetch.register({
|
|
2678
|
+
path: "/api/codex-subscription/sketch-psd-worker",
|
|
2679
|
+
methods: ["GET"],
|
|
2680
|
+
requestBody: "buffered",
|
|
2681
|
+
async fetch() {
|
|
2682
|
+
source ??= await readFile(new URL("./sketch-psd-worker.js", import.meta.url));
|
|
2683
|
+
return new Response(source, { headers: {
|
|
2684
|
+
"content-type": "text/javascript; charset=utf-8",
|
|
2685
|
+
"cache-control": "no-store"
|
|
2686
|
+
} });
|
|
2687
|
+
}
|
|
2688
|
+
});
|
|
2689
|
+
}
|
|
2690
|
+
//#endregion
|
|
2691
|
+
//#region src/account-vault.js
|
|
2692
|
+
const VERSION = 1;
|
|
2693
|
+
const DEFAULT_LABEL = "Account 1";
|
|
2694
|
+
const clone$1 = (value) => value === void 0 ? void 0 : structuredClone(value);
|
|
2695
|
+
const EMAIL_MAX_LENGTH = 254;
|
|
2696
|
+
const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/u;
|
|
2697
|
+
/** Keep only a bounded, display-safe email address from a trusted OAuth result. */
|
|
2698
|
+
function normalizeAccountEmail(value) {
|
|
2699
|
+
if (typeof value !== "string") return void 0;
|
|
2700
|
+
const email = value.trim();
|
|
2701
|
+
return email.length > 0 && email.length <= EMAIL_MAX_LENGTH && EMAIL_PATTERN.test(email) ? email : void 0;
|
|
2702
|
+
}
|
|
2703
|
+
function decodeJwtPayload(access) {
|
|
2704
|
+
if (typeof access !== "string") return void 0;
|
|
2705
|
+
const encoded = access.split(".")[1];
|
|
2706
|
+
if (typeof encoded !== "string" || encoded.length === 0) return void 0;
|
|
2581
2707
|
try {
|
|
2582
|
-
|
|
2708
|
+
return JSON.parse(Buffer.from(encoded, "base64url").toString("utf8"));
|
|
2583
2709
|
} catch {
|
|
2584
|
-
|
|
2710
|
+
return;
|
|
2585
2711
|
}
|
|
2586
|
-
if (url.protocol !== "https:") throw new Error("Codex auth URL must use HTTPS");
|
|
2587
|
-
if (url.origin !== OPENAI_AUTH_ORIGIN || url.username !== "" || url.password !== "") throw new Error("Codex auth URL must use the OpenAI auth origin");
|
|
2588
|
-
return url.href;
|
|
2589
2712
|
}
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
if (
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
args: [url],
|
|
2606
|
-
shell: false
|
|
2713
|
+
function emailFromAccessToken(access) {
|
|
2714
|
+
const payload = decodeJwtPayload(access);
|
|
2715
|
+
return normalizeAccountEmail(payload?.["https://api.openai.com/profile"]?.email ?? payload?.email);
|
|
2716
|
+
}
|
|
2717
|
+
/** Normalize the one non-secret account attribute that may cross the UI boundary. */
|
|
2718
|
+
function sanitizeOAuthCredential(value) {
|
|
2719
|
+
const credential = assertOAuthCredential$1(value);
|
|
2720
|
+
const email = emailFromAccessToken(credential.access) ?? normalizeAccountEmail(credential.email);
|
|
2721
|
+
if (email === void 0) {
|
|
2722
|
+
delete credential.email;
|
|
2723
|
+
return credential;
|
|
2724
|
+
}
|
|
2725
|
+
return {
|
|
2726
|
+
...credential,
|
|
2727
|
+
email
|
|
2607
2728
|
};
|
|
2608
|
-
throw new Error(`Codex auth URL opener is unsupported on ${platform}`);
|
|
2609
2729
|
}
|
|
2610
|
-
function
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
return new Promise((resolve, reject) => {
|
|
2614
|
-
const child = spawnProcess(command.file, command.args, {
|
|
2615
|
-
detached: true,
|
|
2616
|
-
stdio: "ignore",
|
|
2617
|
-
windowsHide: true,
|
|
2618
|
-
shell: command.shell
|
|
2619
|
-
});
|
|
2620
|
-
child.once("error", reject);
|
|
2621
|
-
child.once("spawn", () => {
|
|
2622
|
-
child.unref();
|
|
2623
|
-
resolve();
|
|
2624
|
-
});
|
|
2625
|
-
});
|
|
2730
|
+
function assertOAuthCredential$1(value) {
|
|
2731
|
+
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");
|
|
2732
|
+
return clone$1(value);
|
|
2626
2733
|
}
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
"cancelled"
|
|
2634
|
-
]);
|
|
2635
|
-
const publicClone = (value) => structuredClone(value);
|
|
2636
|
-
const asObject = (value) => value !== null && typeof value === "object" ? value : {};
|
|
2637
|
-
const ok = (value) => ({
|
|
2638
|
-
ok: true,
|
|
2639
|
-
value
|
|
2640
|
-
});
|
|
2641
|
-
const badRequest = (message) => ({
|
|
2642
|
-
ok: false,
|
|
2643
|
-
error: {
|
|
2644
|
-
code: "bad-request",
|
|
2645
|
-
message,
|
|
2646
|
-
details: { issues: [] }
|
|
2647
|
-
}
|
|
2648
|
-
});
|
|
2649
|
-
const accountStatusError = (message) => ({
|
|
2650
|
-
ok: false,
|
|
2651
|
-
error: {
|
|
2652
|
-
code: "internal",
|
|
2653
|
-
message,
|
|
2654
|
-
details: { issues: [] }
|
|
2734
|
+
function parseOAuthCredential$1(value) {
|
|
2735
|
+
try {
|
|
2736
|
+
return assertOAuthCredential$1(JSON.parse(value));
|
|
2737
|
+
} catch (error) {
|
|
2738
|
+
if (error?.message === "Codex account vault received a malformed OAuth credential") throw error;
|
|
2739
|
+
throw new Error("Codex account vault contains malformed OAuth JSON", { cause: error });
|
|
2655
2740
|
}
|
|
2656
|
-
}
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
if (
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
const deferred = () => {
|
|
2679
|
-
let resolve;
|
|
2680
|
-
let reject;
|
|
2741
|
+
}
|
|
2742
|
+
function normalizeLabel(value) {
|
|
2743
|
+
if (typeof value !== "string") throw new Error("Codex account label must be text");
|
|
2744
|
+
const label = value.trim().replace(/\s+/gu, " ");
|
|
2745
|
+
if (label.length === 0 || label.length > 48) throw new Error("Codex account label must contain 1 to 48 characters");
|
|
2746
|
+
return label;
|
|
2747
|
+
}
|
|
2748
|
+
function assertVaultRecord(record) {
|
|
2749
|
+
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");
|
|
2750
|
+
const ids = /* @__PURE__ */ new Set();
|
|
2751
|
+
const accounts = record.payload.accounts.map((account) => {
|
|
2752
|
+
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");
|
|
2753
|
+
ids.add(account.id);
|
|
2754
|
+
return {
|
|
2755
|
+
id: account.id,
|
|
2756
|
+
label: normalizeLabel(account.label),
|
|
2757
|
+
credential: sanitizeOAuthCredential(account.credential)
|
|
2758
|
+
};
|
|
2759
|
+
});
|
|
2760
|
+
if (!ids.has(record.payload.activeId)) throw new Error("Codex account vault active account is missing");
|
|
2761
|
+
const legacyAccountId = record.payload.legacyAccountId;
|
|
2762
|
+
if (legacyAccountId !== void 0 && !ids.has(legacyAccountId)) throw new Error("Codex account vault legacy account is missing");
|
|
2681
2763
|
return {
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
resolve,
|
|
2687
|
-
reject
|
|
2764
|
+
version: VERSION,
|
|
2765
|
+
activeId: record.payload.activeId,
|
|
2766
|
+
legacyAccountId,
|
|
2767
|
+
accounts
|
|
2688
2768
|
};
|
|
2689
|
-
};
|
|
2690
|
-
const publicPrompt = (prompt) => ({
|
|
2691
|
-
type: prompt.type,
|
|
2692
|
-
message: String(prompt.message ?? ""),
|
|
2693
|
-
...typeof prompt.placeholder === "string" ? { placeholder: prompt.placeholder } : {}
|
|
2694
|
-
});
|
|
2695
|
-
function classifyLoginFailure(error) {
|
|
2696
|
-
const message = error instanceof Error ? error.message : "";
|
|
2697
|
-
if (/token exchange failed/iu.test(message)) return "token-exchange";
|
|
2698
|
-
if (/fetch failed|\b(?:ECONN|ENOTFOUND|ETIMEDOUT|CERT_|socket|network)\b/iu.test(message)) return "network";
|
|
2699
|
-
if (/extract accountId|account[_ -]?id/iu.test(message)) return "account-claim";
|
|
2700
|
-
if (/credential|credentials-local|OAuth JSON/iu.test(message)) return "credential-store";
|
|
2701
|
-
if (/Missing authorization code|State mismatch|callback/iu.test(message)) return "callback";
|
|
2702
|
-
return "provider";
|
|
2703
2769
|
}
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2770
|
+
const grant = (payload) => ({
|
|
2771
|
+
kind: "grant",
|
|
2772
|
+
payload
|
|
2773
|
+
});
|
|
2774
|
+
var PendingOAuthCredentialStore = class {
|
|
2775
|
+
#credential;
|
|
2776
|
+
async read(providerId) {
|
|
2777
|
+
if (providerId !== "openai-codex") throw new Error("Pending Codex login received an unknown provider");
|
|
2778
|
+
return clone$1(this.#credential);
|
|
2711
2779
|
}
|
|
2712
|
-
async
|
|
2713
|
-
return
|
|
2780
|
+
async list() {
|
|
2781
|
+
return this.#credential === void 0 ? [] : [{
|
|
2782
|
+
providerId: "openai-codex",
|
|
2783
|
+
type: "oauth"
|
|
2784
|
+
}];
|
|
2714
2785
|
}
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
phase: active.view.phase,
|
|
2721
|
-
...active.view.phase === "failed" ? { failure: classifyLoginFailure(active.hostError) } : {}
|
|
2722
|
-
};
|
|
2786
|
+
async modify(providerId, update) {
|
|
2787
|
+
if (providerId !== "openai-codex") throw new Error("Pending Codex login received an unknown provider");
|
|
2788
|
+
const next = await update(clone$1(this.#credential));
|
|
2789
|
+
if (next !== void 0) this.#credential = sanitizeOAuthCredential(next);
|
|
2790
|
+
return clone$1(this.#credential);
|
|
2723
2791
|
}
|
|
2724
|
-
async
|
|
2725
|
-
if (
|
|
2726
|
-
|
|
2727
|
-
const active = this.#activeId === void 0 ? void 0 : this.#sessions.get(this.#activeId);
|
|
2728
|
-
if (active !== void 0 && !TERMINAL_PHASES.has(active.view.phase)) {
|
|
2729
|
-
active.view = {
|
|
2730
|
-
id: active.view.id,
|
|
2731
|
-
provider: "openai-codex",
|
|
2732
|
-
method: active.view.method,
|
|
2733
|
-
phase: "cancelled",
|
|
2734
|
-
authenticated: false
|
|
2735
|
-
};
|
|
2736
|
-
active.controller.abort(/* @__PURE__ */ new Error("Codex login replaced by a new attempt"));
|
|
2737
|
-
}
|
|
2738
|
-
if (active !== void 0) this.#sessions.delete(active.view.id);
|
|
2739
|
-
const id = this.createId();
|
|
2740
|
-
const ready = deferred();
|
|
2741
|
-
const controller = new AbortController();
|
|
2742
|
-
const session = {
|
|
2743
|
-
controller,
|
|
2744
|
-
prompt: void 0,
|
|
2745
|
-
ready,
|
|
2746
|
-
view: {
|
|
2747
|
-
id,
|
|
2748
|
-
provider: "openai-codex",
|
|
2749
|
-
method,
|
|
2750
|
-
phase: "starting",
|
|
2751
|
-
authenticated: false
|
|
2752
|
-
}
|
|
2753
|
-
};
|
|
2754
|
-
this.#sessions.set(id, session);
|
|
2755
|
-
this.#activeId = id;
|
|
2756
|
-
const publishReady = () => ready.resolve(publicClone(session.view));
|
|
2757
|
-
const interaction = {
|
|
2758
|
-
signal: controller.signal,
|
|
2759
|
-
prompt: async (prompt) => {
|
|
2760
|
-
controller.signal.throwIfAborted();
|
|
2761
|
-
if (prompt.type === "select") return method;
|
|
2762
|
-
if (![
|
|
2763
|
-
"manual_code",
|
|
2764
|
-
"text",
|
|
2765
|
-
"secret"
|
|
2766
|
-
].includes(prompt.type)) throw new Error(`unsupported Codex auth prompt: ${String(prompt.type)}`);
|
|
2767
|
-
const answer = deferred();
|
|
2768
|
-
session.prompt = answer;
|
|
2769
|
-
session.view = {
|
|
2770
|
-
...session.view,
|
|
2771
|
-
phase: "waiting_input",
|
|
2772
|
-
prompt: publicPrompt(prompt)
|
|
2773
|
-
};
|
|
2774
|
-
const abortPrompt = () => answer.reject(controller.signal.reason ?? /* @__PURE__ */ new Error("login cancelled"));
|
|
2775
|
-
controller.signal.addEventListener("abort", abortPrompt, { once: true });
|
|
2776
|
-
prompt.signal?.addEventListener("abort", abortPrompt, { once: true });
|
|
2777
|
-
publishReady();
|
|
2778
|
-
try {
|
|
2779
|
-
return await answer.promise;
|
|
2780
|
-
} finally {
|
|
2781
|
-
controller.signal.removeEventListener("abort", abortPrompt);
|
|
2782
|
-
prompt.signal?.removeEventListener("abort", abortPrompt);
|
|
2783
|
-
if (session.prompt === answer) session.prompt = void 0;
|
|
2784
|
-
}
|
|
2785
|
-
},
|
|
2786
|
-
notify: (event) => {
|
|
2787
|
-
if (controller.signal.aborted) return;
|
|
2788
|
-
if (event.type === "auth_url") session.view = {
|
|
2789
|
-
...session.view,
|
|
2790
|
-
phase: "waiting_browser",
|
|
2791
|
-
authUrl: assertCodexAuthUrl(event.url),
|
|
2792
|
-
...typeof event.instructions === "string" ? { instructions: event.instructions } : {}
|
|
2793
|
-
};
|
|
2794
|
-
else if (event.type === "device_code") session.view = {
|
|
2795
|
-
...session.view,
|
|
2796
|
-
phase: "waiting_device",
|
|
2797
|
-
deviceCode: {
|
|
2798
|
-
userCode: event.userCode,
|
|
2799
|
-
verificationUri: assertCodexAuthUrl(event.verificationUri),
|
|
2800
|
-
...typeof event.intervalSeconds === "number" ? { intervalSeconds: event.intervalSeconds } : {},
|
|
2801
|
-
...typeof event.expiresInSeconds === "number" ? { expiresInSeconds: event.expiresInSeconds } : {}
|
|
2802
|
-
}
|
|
2803
|
-
};
|
|
2804
|
-
else session.view = {
|
|
2805
|
-
...session.view,
|
|
2806
|
-
message: String(event.message ?? "")
|
|
2807
|
-
};
|
|
2808
|
-
publishReady();
|
|
2809
|
-
}
|
|
2810
|
-
};
|
|
2811
|
-
session.run = Promise.resolve().then(() => this.auth.login(interaction, label === void 0 ? {} : { label: label.trim() })).then(async () => {
|
|
2812
|
-
if (controller.signal.aborted) return;
|
|
2813
|
-
const status = await this.auth.status();
|
|
2814
|
-
session.view = {
|
|
2815
|
-
id,
|
|
2816
|
-
provider: "openai-codex",
|
|
2817
|
-
method,
|
|
2818
|
-
phase: "authenticated",
|
|
2819
|
-
authenticated: status.authenticated === true,
|
|
2820
|
-
...typeof status.expiresAt === "number" ? { expiresAt: status.expiresAt } : {}
|
|
2821
|
-
};
|
|
2822
|
-
}).catch(async (error) => {
|
|
2823
|
-
if (controller.signal.aborted) {
|
|
2824
|
-
session.view = {
|
|
2825
|
-
id,
|
|
2826
|
-
provider: "openai-codex",
|
|
2827
|
-
method,
|
|
2828
|
-
phase: "cancelled",
|
|
2829
|
-
authenticated: false
|
|
2830
|
-
};
|
|
2831
|
-
return;
|
|
2832
|
-
}
|
|
2833
|
-
try {
|
|
2834
|
-
if (label !== void 0) throw error;
|
|
2835
|
-
const status = await this.auth.status();
|
|
2836
|
-
if (status.authenticated === true) {
|
|
2837
|
-
session.view = {
|
|
2838
|
-
id,
|
|
2839
|
-
provider: "openai-codex",
|
|
2840
|
-
method,
|
|
2841
|
-
phase: "authenticated",
|
|
2842
|
-
authenticated: true,
|
|
2843
|
-
...typeof status.expiresAt === "number" ? { expiresAt: status.expiresAt } : {}
|
|
2844
|
-
};
|
|
2845
|
-
return;
|
|
2846
|
-
}
|
|
2847
|
-
} catch {}
|
|
2848
|
-
session.view = {
|
|
2849
|
-
id,
|
|
2850
|
-
provider: "openai-codex",
|
|
2851
|
-
method,
|
|
2852
|
-
phase: "failed",
|
|
2853
|
-
authenticated: false,
|
|
2854
|
-
error: "Codex login failed"
|
|
2855
|
-
};
|
|
2856
|
-
session.hostError = error;
|
|
2857
|
-
}).finally(publishReady);
|
|
2858
|
-
return ready.promise;
|
|
2792
|
+
async delete(providerId) {
|
|
2793
|
+
if (providerId !== "openai-codex") throw new Error("Pending Codex login received an unknown provider");
|
|
2794
|
+
this.#credential = void 0;
|
|
2859
2795
|
}
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
if (session === void 0) throw new Error("unknown Codex login");
|
|
2863
|
-
return publicClone(session.view);
|
|
2796
|
+
credential() {
|
|
2797
|
+
return clone$1(this.#credential);
|
|
2864
2798
|
}
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2799
|
+
};
|
|
2800
|
+
/**
|
|
2801
|
+
* Multi-account owner state stored in DSH's atomic plugin credential record.
|
|
2802
|
+
* The old single-account reference remains as a rollback source and is kept in
|
|
2803
|
+
* sync whenever that imported account rotates its refresh token.
|
|
2804
|
+
*/
|
|
2805
|
+
var DshOAuthAccountVault = class {
|
|
2806
|
+
#tail = Promise.resolve();
|
|
2807
|
+
constructor(credentials, options) {
|
|
2808
|
+
if (credentials === void 0 || credentials === null || typeof credentials.readRecord !== "function" || typeof credentials.modifyRecord !== "function") throw new Error("Codex multi-account requires DSH credential records");
|
|
2809
|
+
this.credentials = credentials;
|
|
2810
|
+
this.key = options.key;
|
|
2811
|
+
this.legacyRef = options.legacyRef;
|
|
2812
|
+
this.legacyRefs = Object.freeze([...options.legacyRefs ?? []]);
|
|
2813
|
+
this.createId = options.createId ?? randomUUID;
|
|
2814
|
+
this.onLegacySyncFailure = options.onLegacySyncFailure ?? (() => {});
|
|
2879
2815
|
}
|
|
2880
|
-
|
|
2881
|
-
const
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2816
|
+
#enqueue(operation) {
|
|
2817
|
+
const current = this.#tail.catch(() => void 0).then(operation);
|
|
2818
|
+
this.#tail = current.catch(() => void 0);
|
|
2819
|
+
return current;
|
|
2820
|
+
}
|
|
2821
|
+
async #legacyCredential() {
|
|
2822
|
+
for (const ref of [this.legacyRef, ...this.legacyRefs]) {
|
|
2823
|
+
const hit = await this.credentials.resolve(ref);
|
|
2824
|
+
if (hit?.value === void 0 || hit.value === "") continue;
|
|
2825
|
+
return {
|
|
2826
|
+
ref,
|
|
2827
|
+
credential: parseOAuthCredential$1(hit.value)
|
|
2890
2828
|
};
|
|
2891
|
-
session.controller.abort(/* @__PURE__ */ new Error("Codex login cancelled"));
|
|
2892
2829
|
}
|
|
2893
|
-
return this.read(id);
|
|
2894
2830
|
}
|
|
2895
|
-
async
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2831
|
+
async #ensurePayload() {
|
|
2832
|
+
const existing = await this.credentials.readRecord(this.key);
|
|
2833
|
+
if (existing !== void 0) return assertVaultRecord(existing);
|
|
2834
|
+
const legacy = await this.#legacyCredential();
|
|
2835
|
+
if (legacy === void 0) return void 0;
|
|
2836
|
+
const id = this.createId();
|
|
2837
|
+
return assertVaultRecord(await this.credentials.modifyRecord(this.key, (current) => {
|
|
2838
|
+
if (current !== void 0) return Promise.resolve(current);
|
|
2839
|
+
return Promise.resolve(grant({
|
|
2840
|
+
version: VERSION,
|
|
2841
|
+
activeId: id,
|
|
2842
|
+
legacyAccountId: id,
|
|
2843
|
+
accounts: [{
|
|
2844
|
+
id,
|
|
2845
|
+
label: DEFAULT_LABEL,
|
|
2846
|
+
credential: legacy.credential
|
|
2847
|
+
}]
|
|
2848
|
+
}));
|
|
2849
|
+
}));
|
|
2850
|
+
}
|
|
2851
|
+
async #modifyPayload(update) {
|
|
2852
|
+
await this.#ensurePayload();
|
|
2853
|
+
let previousLegacy;
|
|
2854
|
+
const payload = assertVaultRecord(await this.credentials.modifyRecord(this.key, async (current) => {
|
|
2855
|
+
if (current === void 0) throw new Error("Codex account vault is not signed in");
|
|
2856
|
+
const payload = assertVaultRecord(current);
|
|
2857
|
+
previousLegacy = payload.accounts.find((account) => account.id === payload.legacyAccountId)?.credential;
|
|
2858
|
+
const next = await update(clone$1(payload));
|
|
2859
|
+
return grant(next);
|
|
2860
|
+
}));
|
|
2861
|
+
const legacy = payload.accounts.find((account) => account.id === payload.legacyAccountId)?.credential;
|
|
2862
|
+
try {
|
|
2863
|
+
if (legacy === void 0) {
|
|
2864
|
+
if (previousLegacy !== void 0) await this.credentials.unset(this.legacyRef);
|
|
2865
|
+
} else if (JSON.stringify(legacy) !== JSON.stringify(previousLegacy)) await this.credentials.set(this.legacyRef, JSON.stringify(legacy));
|
|
2866
|
+
} catch {
|
|
2867
|
+
this.onLegacySyncFailure();
|
|
2899
2868
|
}
|
|
2900
|
-
|
|
2901
|
-
return this.accountStatus(options);
|
|
2869
|
+
return payload;
|
|
2902
2870
|
}
|
|
2903
|
-
|
|
2904
|
-
return
|
|
2871
|
+
list() {
|
|
2872
|
+
return this.#enqueue(async () => {
|
|
2873
|
+
const payload = await this.#ensurePayload();
|
|
2874
|
+
if (payload === void 0) return [];
|
|
2875
|
+
return payload.accounts.map((account) => ({
|
|
2876
|
+
id: account.id,
|
|
2877
|
+
label: account.label,
|
|
2878
|
+
active: account.id === payload.activeId,
|
|
2879
|
+
expiresAt: account.credential.expires,
|
|
2880
|
+
...account.credential.email === void 0 ? {} : { email: account.credential.email }
|
|
2881
|
+
}));
|
|
2882
|
+
});
|
|
2905
2883
|
}
|
|
2906
|
-
|
|
2907
|
-
return
|
|
2884
|
+
readActive() {
|
|
2885
|
+
return this.#enqueue(async () => {
|
|
2886
|
+
const payload = await this.#ensurePayload();
|
|
2887
|
+
return clone$1(payload?.accounts.find((account) => account.id === payload.activeId)?.credential);
|
|
2888
|
+
});
|
|
2908
2889
|
}
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
label: input.label
|
|
2928
|
-
});
|
|
2929
|
-
if (input.openExternal !== true) return ok(started);
|
|
2930
|
-
const url = started.authUrl ?? started.deviceCode?.verificationUri;
|
|
2931
|
-
if (typeof url !== "string" || openExternal === void 0) return ok({
|
|
2932
|
-
...started,
|
|
2933
|
-
externalOpened: false
|
|
2934
|
-
});
|
|
2935
|
-
try {
|
|
2936
|
-
await openExternal(url);
|
|
2937
|
-
return ok({
|
|
2938
|
-
...started,
|
|
2939
|
-
externalOpened: true
|
|
2940
|
-
});
|
|
2941
|
-
} catch {
|
|
2942
|
-
return ok({
|
|
2943
|
-
...started,
|
|
2944
|
-
externalOpened: false
|
|
2945
|
-
});
|
|
2946
|
-
}
|
|
2947
|
-
}
|
|
2948
|
-
if (endpoint === "login/status") return ok(coordinator.read(input.id));
|
|
2949
|
-
if (endpoint === "login/submit") return ok(await coordinator.submit({
|
|
2950
|
-
id: input.id,
|
|
2951
|
-
value: input.value
|
|
2952
|
-
}));
|
|
2953
|
-
if (endpoint === "login/cancel") return ok(await coordinator.cancel(input.id));
|
|
2954
|
-
if (endpoint === "logout") return ok(await coordinator.logout({ signal }));
|
|
2955
|
-
if (endpoint === "account/select") return ok(await coordinator.selectAccount(input.id));
|
|
2956
|
-
if (endpoint === "account/remove") return ok(await coordinator.removeAccount(input.id));
|
|
2957
|
-
return badRequest(`unknown Codex auth endpoint: ${endpoint}`);
|
|
2958
|
-
} catch (error) {
|
|
2959
|
-
if (signal.aborted) throw error;
|
|
2960
|
-
const message = error instanceof Error && /^(unknown|unsupported|a Codex|Codex login)/.test(error.message) ? error.message : "Codex request failed";
|
|
2961
|
-
return badRequest(message);
|
|
2962
|
-
}
|
|
2963
|
-
};
|
|
2964
|
-
}
|
|
2965
|
-
//#endregion
|
|
2966
|
-
//#region src/transport-failure.js
|
|
2967
|
-
function normalizeTransportEvent(event, signal) {
|
|
2968
|
-
if (signal?.aborted || event?.type !== "error" || event.reason !== "error") return event;
|
|
2969
|
-
const message = event.error?.errorMessage;
|
|
2970
|
-
if (typeof message !== "string" || !/^WebSocket closed (?:1006|1011|1012|1013)(?:\s|$)/.test(message)) return event;
|
|
2971
|
-
return {
|
|
2972
|
-
...event,
|
|
2973
|
-
error: {
|
|
2974
|
-
...event.error,
|
|
2975
|
-
errorMessage: `Network transport failure: ${message}`
|
|
2976
|
-
}
|
|
2977
|
-
};
|
|
2978
|
-
}
|
|
2979
|
-
//#endregion
|
|
2980
|
-
//#region src/pi-ai-runtime.js
|
|
2981
|
-
const FAST_SERVICE_TIER = "priority";
|
|
2982
|
-
const ULTRAFAST_SERVICE_TIER = "ultrafast";
|
|
2983
|
-
/** Apply the chosen detail to user and tool-result images after the provider assembles the request. */
|
|
2984
|
-
function withInputImageDetail(payload, detail) {
|
|
2985
|
-
if (!Array.isArray(payload.input)) return payload;
|
|
2986
|
-
let changed = false;
|
|
2987
|
-
const input = payload.input.map((item) => {
|
|
2988
|
-
const field = Array.isArray(item?.content) ? "content" : ["function_call_output", "custom_tool_call_output"].includes(item?.type) && Array.isArray(item.output) ? "output" : void 0;
|
|
2989
|
-
if (field === void 0) return item;
|
|
2990
|
-
let itemChanged = false;
|
|
2991
|
-
const parts = item[field].map((part) => {
|
|
2992
|
-
if (part?.type !== "input_image" || part.detail === detail) return part;
|
|
2993
|
-
itemChanged = true;
|
|
2890
|
+
activeId() {
|
|
2891
|
+
return this.#enqueue(async () => (await this.#ensurePayload())?.activeId);
|
|
2892
|
+
}
|
|
2893
|
+
add(label, credential) {
|
|
2894
|
+
return this.#enqueue(async () => {
|
|
2895
|
+
const normalizedLabel = normalizeLabel(label);
|
|
2896
|
+
const validated = sanitizeOAuthCredential(credential);
|
|
2897
|
+
await this.#ensurePayload();
|
|
2898
|
+
const id = this.createId();
|
|
2899
|
+
const account = (await this.#modifyPayload((current) => ({
|
|
2900
|
+
...current,
|
|
2901
|
+
activeId: id,
|
|
2902
|
+
accounts: [...current.accounts, {
|
|
2903
|
+
id,
|
|
2904
|
+
label: normalizedLabel,
|
|
2905
|
+
credential: validated
|
|
2906
|
+
}]
|
|
2907
|
+
}))).accounts.find((candidate) => candidate.id === id);
|
|
2994
2908
|
return {
|
|
2995
|
-
|
|
2996
|
-
|
|
2909
|
+
id,
|
|
2910
|
+
label: account.label,
|
|
2911
|
+
active: true,
|
|
2912
|
+
expiresAt: account.credential.expires,
|
|
2913
|
+
...account.credential.email === void 0 ? {} : { email: account.credential.email }
|
|
2997
2914
|
};
|
|
2998
2915
|
});
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
return {
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
2916
|
+
}
|
|
2917
|
+
select(id) {
|
|
2918
|
+
return this.#enqueue(async () => {
|
|
2919
|
+
await this.#modifyPayload((current) => {
|
|
2920
|
+
if (!current.accounts.some((account) => account.id === id)) throw new Error("Unknown Codex account");
|
|
2921
|
+
return {
|
|
2922
|
+
...current,
|
|
2923
|
+
activeId: id
|
|
2924
|
+
};
|
|
2925
|
+
});
|
|
2926
|
+
});
|
|
2927
|
+
}
|
|
2928
|
+
modifyActive(update) {
|
|
2929
|
+
return this.#enqueue(async () => {
|
|
2930
|
+
if (await this.#ensurePayload() === void 0) {
|
|
2931
|
+
const initial = await update(void 0);
|
|
2932
|
+
if (initial === void 0) return void 0;
|
|
2933
|
+
const credential = sanitizeOAuthCredential(initial);
|
|
2934
|
+
await this.credentials.set(this.legacyRef, JSON.stringify(credential));
|
|
2935
|
+
await this.#ensurePayload();
|
|
2936
|
+
return clone$1(credential);
|
|
2937
|
+
}
|
|
2938
|
+
let result;
|
|
2939
|
+
await this.#modifyPayload(async (current) => {
|
|
2940
|
+
const index = current.accounts.findIndex((account) => account.id === current.activeId);
|
|
2941
|
+
const previous = clone$1(current.accounts[index].credential);
|
|
2942
|
+
const next = await update(previous);
|
|
2943
|
+
if (next === void 0) {
|
|
2944
|
+
result = previous;
|
|
2945
|
+
return current;
|
|
2946
|
+
}
|
|
2947
|
+
const credential = sanitizeOAuthCredential(next);
|
|
2948
|
+
if (credential.email === void 0 && previous.email !== void 0) credential.email = previous.email;
|
|
2949
|
+
const accounts = [...current.accounts];
|
|
2950
|
+
accounts[index] = {
|
|
2951
|
+
...accounts[index],
|
|
2952
|
+
credential
|
|
2953
|
+
};
|
|
2954
|
+
result = clone$1(credential);
|
|
2955
|
+
return {
|
|
2956
|
+
...current,
|
|
2957
|
+
accounts
|
|
2958
|
+
};
|
|
2959
|
+
});
|
|
2960
|
+
return result;
|
|
2961
|
+
});
|
|
2962
|
+
}
|
|
2963
|
+
deleteAll() {
|
|
2964
|
+
return this.#enqueue(async () => {
|
|
2965
|
+
await this.credentials.deleteRecord(this.key);
|
|
2966
|
+
await this.credentials.unset(this.legacyRef);
|
|
2967
|
+
for (const ref of this.legacyRefs) await this.credentials.unset(ref);
|
|
2968
|
+
});
|
|
2969
|
+
}
|
|
2970
|
+
remove(id) {
|
|
2971
|
+
return this.#enqueue(async () => {
|
|
2972
|
+
await this.#modifyPayload((current) => {
|
|
2973
|
+
if (!current.accounts.some((account) => account.id === id)) throw new Error("Unknown Codex account");
|
|
2974
|
+
if (current.accounts.length === 1) throw new Error("Cannot remove the last account; sign out instead");
|
|
2975
|
+
const accounts = current.accounts.filter((account) => account.id !== id);
|
|
2976
|
+
return {
|
|
2977
|
+
...current,
|
|
2978
|
+
activeId: current.activeId === id ? accounts[0].id : current.activeId,
|
|
2979
|
+
legacyAccountId: current.legacyAccountId === id ? void 0 : current.legacyAccountId,
|
|
2980
|
+
accounts
|
|
2981
|
+
};
|
|
2982
|
+
});
|
|
2983
|
+
});
|
|
2984
|
+
}
|
|
2985
|
+
};
|
|
2986
|
+
//#endregion
|
|
2987
|
+
//#region src/credential-store.js
|
|
2988
|
+
const PROVIDER$1 = "openai-codex";
|
|
2989
|
+
const abortIfNeeded = (options) => options?.signal?.throwIfAborted();
|
|
2990
|
+
const clone = (value) => value === void 0 ? void 0 : structuredClone(value);
|
|
2991
|
+
function assertProvider(providerId) {
|
|
2992
|
+
if (providerId !== PROVIDER$1) throw new Error(`Codex credential store does not own provider ${JSON.stringify(providerId)}`);
|
|
2993
|
+
}
|
|
2994
|
+
function assertOAuthCredential(value) {
|
|
2995
|
+
if (value === void 0) return void 0;
|
|
2996
|
+
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");
|
|
2997
|
+
return clone(value);
|
|
2998
|
+
}
|
|
2999
|
+
function parseOAuthCredential(value) {
|
|
3000
|
+
try {
|
|
3001
|
+
return assertOAuthCredential(JSON.parse(value));
|
|
3002
|
+
} catch (error) {
|
|
3003
|
+
if (error?.message === "Codex credential store received a malformed OAuth credential") throw error;
|
|
3004
|
+
throw new Error("Codex credential store contains malformed OAuth JSON", { cause: error });
|
|
3005
|
+
}
|
|
3010
3006
|
}
|
|
3011
3007
|
/**
|
|
3012
|
-
*
|
|
3013
|
-
*
|
|
3014
|
-
*
|
|
3015
|
-
* PiAiAdapter owns a request-local Models collection backed by the same DSH
|
|
3016
|
-
* credential store as this provider. A pure OAuth provider ignores its
|
|
3017
|
-
* `apiKey` request override and otherwise fails before dispatch with "Provider
|
|
3018
|
-
* is not configured". This non-interactive bridge teaches that collection how
|
|
3019
|
-
* to consume only the already-refreshed token for this request; login, refresh,
|
|
3020
|
-
* persistence, headers, transport, and model behavior remain owned by the
|
|
3021
|
-
* original provider.
|
|
3008
|
+
* Adapt DSH's managed string credential service to pi-ai's typed OAuth store.
|
|
3009
|
+
* Refresh/login/logout operations are serialized so an older refresh response
|
|
3010
|
+
* cannot overwrite a newer rotated token.
|
|
3022
3011
|
*/
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3012
|
+
var DshOAuthCredentialStore = class {
|
|
3013
|
+
#chains = /* @__PURE__ */ new Map();
|
|
3014
|
+
constructor(credentials, ref, legacyRefs = [], options = {}) {
|
|
3015
|
+
if (credentials === void 0 || credentials === null) throw new Error("Codex OAuth requires the DSH credentials service");
|
|
3016
|
+
const expirySkewMs = options.expirySkewMs ?? 0;
|
|
3017
|
+
if (!Number.isFinite(expirySkewMs) || expirySkewMs < 0) throw new Error("Codex OAuth expiry skew must be a non-negative finite number");
|
|
3018
|
+
this.credentials = credentials;
|
|
3019
|
+
this.ref = ref;
|
|
3020
|
+
this.legacyRefs = Object.freeze([...legacyRefs]);
|
|
3021
|
+
this.expirySkewMs = expirySkewMs;
|
|
3022
|
+
this.vault = options.vault;
|
|
3023
|
+
}
|
|
3024
|
+
#enqueue(providerId, operation, options) {
|
|
3025
|
+
assertProvider(providerId);
|
|
3026
|
+
const current = (this.#chains.get(providerId) ?? Promise.resolve()).catch(() => void 0).then(async () => {
|
|
3027
|
+
abortIfNeeded(options);
|
|
3028
|
+
return operation();
|
|
3029
|
+
});
|
|
3030
|
+
const tail = current.catch(() => void 0);
|
|
3031
|
+
this.#chains.set(providerId, tail);
|
|
3032
|
+
tail.finally(() => {
|
|
3033
|
+
if (this.#chains.get(providerId) === tail) this.#chains.delete(providerId);
|
|
3034
|
+
});
|
|
3035
|
+
return current;
|
|
3036
|
+
}
|
|
3037
|
+
async #read(providerId, options) {
|
|
3038
|
+
assertProvider(providerId);
|
|
3039
|
+
abortIfNeeded(options);
|
|
3040
|
+
if (this.vault !== void 0) {
|
|
3041
|
+
const current = await this.vault.readActive();
|
|
3042
|
+
if (current === void 0) return void 0;
|
|
3043
|
+
return this.expirySkewMs === 0 ? current : {
|
|
3044
|
+
...current,
|
|
3045
|
+
expires: current.expires - this.expirySkewMs
|
|
3033
3046
|
};
|
|
3034
3047
|
}
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
const inputImageDetail = normalizeInputImageDetail(resolveInputImageDetail());
|
|
3045
|
-
const onPayload = options.onPayload;
|
|
3046
|
-
return {
|
|
3047
|
-
...options,
|
|
3048
|
-
...textVerbosity === void 0 ? {} : { textVerbosity },
|
|
3049
|
-
...serviceTier === void 0 ? {} : { serviceTier },
|
|
3050
|
-
async onPayload(payload, requestModel) {
|
|
3051
|
-
const preferred = {
|
|
3052
|
-
...payload,
|
|
3053
|
-
...textVerbosity === void 0 ? {} : { text: {
|
|
3054
|
-
...payload.text ?? {},
|
|
3055
|
-
verbosity: textVerbosity
|
|
3056
|
-
} },
|
|
3057
|
-
...serviceTier === void 0 ? {} : { service_tier: serviceTier }
|
|
3058
|
-
};
|
|
3059
|
-
const managed = compaction?.preparePayload(preferred, model?.contextWindow) ?? preferred;
|
|
3060
|
-
const next = await onPayload?.(managed, requestModel);
|
|
3061
|
-
const detailed = inputImageDetail === "auto" ? next ?? managed : withInputImageDetail(next ?? managed, inputImageDetail);
|
|
3062
|
-
return {
|
|
3063
|
-
...detailed,
|
|
3064
|
-
...textVerbosity === void 0 ? {} : { text: {
|
|
3065
|
-
...detailed.text ?? {},
|
|
3066
|
-
verbosity: textVerbosity
|
|
3067
|
-
} },
|
|
3068
|
-
...serviceTier === void 0 ? {} : { service_tier: serviceTier }
|
|
3069
|
-
};
|
|
3070
|
-
}
|
|
3071
|
-
};
|
|
3072
|
-
};
|
|
3073
|
-
const getModels = () => (catalog?.getModels() ?? provider.getModels()).map((model) => {
|
|
3074
|
-
const maximum = modelContextMaximum(model);
|
|
3075
|
-
const mode = resolveContextMode();
|
|
3076
|
-
if (model.id === "gpt-5.3-codex-spark" || !["extended", "custom"].includes(mode)) return model;
|
|
3077
|
-
if (mode === "extended") {
|
|
3078
|
-
const contextWindow = maximum;
|
|
3079
|
-
return {
|
|
3080
|
-
...model,
|
|
3081
|
-
contextWindow
|
|
3082
|
-
};
|
|
3048
|
+
let hit = await this.credentials.resolve(this.ref);
|
|
3049
|
+
if (hit?.value === void 0 || hit.value === "") for (const legacyRef of this.legacyRefs) {
|
|
3050
|
+
const legacy = await this.credentials.resolve(legacyRef);
|
|
3051
|
+
if (legacy?.value === void 0 || legacy.value === "") continue;
|
|
3052
|
+
const migrated = parseOAuthCredential(legacy.value);
|
|
3053
|
+
await this.credentials.set(this.ref, JSON.stringify(migrated));
|
|
3054
|
+
await this.credentials.unset(legacyRef);
|
|
3055
|
+
hit = { value: JSON.stringify(migrated) };
|
|
3056
|
+
break;
|
|
3083
3057
|
}
|
|
3084
|
-
|
|
3085
|
-
return
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
const networkIterable = (factory, options) => {
|
|
3091
|
-
let iterator;
|
|
3092
|
-
let prepared;
|
|
3093
|
-
const step = async (method, value) => {
|
|
3094
|
-
const request = await (prepared ??= connection?.prepare(options) ?? Promise.resolve({ options }));
|
|
3095
|
-
const result = await runNetwork("model", () => {
|
|
3096
|
-
iterator ??= factory(compaction?.requestOptions(request.options) ?? request.options)[Symbol.asyncIterator]();
|
|
3097
|
-
return iterator[method]?.(value) ?? (method === "throw" ? Promise.reject(value) : Promise.resolve({
|
|
3098
|
-
done: true,
|
|
3099
|
-
value
|
|
3100
|
-
}));
|
|
3101
|
-
}, compaction?.networkOptions(request.network) ?? request.network);
|
|
3102
|
-
return result.done ? result : {
|
|
3103
|
-
...result,
|
|
3104
|
-
value: normalizeTransportEvent(result.value, request.options?.signal)
|
|
3105
|
-
};
|
|
3106
|
-
};
|
|
3107
|
-
return {
|
|
3108
|
-
[Symbol.asyncIterator]() {
|
|
3109
|
-
return this;
|
|
3110
|
-
},
|
|
3111
|
-
next: (value) => step("next", value),
|
|
3112
|
-
return: (value) => iterator ? step("return", value) : Promise.resolve({
|
|
3113
|
-
done: true,
|
|
3114
|
-
value
|
|
3115
|
-
}),
|
|
3116
|
-
throw: (error) => iterator ? step("throw", error) : Promise.reject(error)
|
|
3117
|
-
};
|
|
3118
|
-
};
|
|
3119
|
-
return Object.freeze({
|
|
3120
|
-
...provider,
|
|
3121
|
-
auth: Object.freeze({
|
|
3122
|
-
...provider.auth,
|
|
3123
|
-
apiKey: requestToken
|
|
3124
|
-
}),
|
|
3125
|
-
getModels,
|
|
3126
|
-
stream: (model, context, options) => networkIterable((prepared) => provider.stream(model, context, prepared), withPreferences(model, options)),
|
|
3127
|
-
streamSimple: (model, context, options) => networkIterable((prepared) => provider.streamSimple(model, context, prepared), withPreferences(model, options))
|
|
3128
|
-
});
|
|
3129
|
-
}
|
|
3130
|
-
Object.freeze([
|
|
3131
|
-
"0.82.1",
|
|
3132
|
-
"0.85.1",
|
|
3133
|
-
"0.87.1"
|
|
3134
|
-
]);
|
|
3135
|
-
//#endregion
|
|
3136
|
-
//#region src/subscription-credentials.js
|
|
3137
|
-
async function readSubscriptionCredentials(getAuth, readCredential, signal) {
|
|
3138
|
-
for (let attempt = 0; attempt < 2; attempt++) {
|
|
3139
|
-
const auth = await getAuth({ signal });
|
|
3140
|
-
signal?.throwIfAborted();
|
|
3141
|
-
if (typeof auth?.auth?.apiKey !== "string" || !auth.auth.apiKey) break;
|
|
3142
|
-
const credential = await readCredential({ signal });
|
|
3143
|
-
signal?.throwIfAborted();
|
|
3144
|
-
if (credential?.type !== "oauth") break;
|
|
3145
|
-
if (credential.access === auth.auth.apiKey) return {
|
|
3146
|
-
access: credential.access,
|
|
3147
|
-
accountId: credential.accountId
|
|
3058
|
+
abortIfNeeded(options);
|
|
3059
|
+
if (hit?.value === void 0 || hit.value === "") return void 0;
|
|
3060
|
+
const credential = parseOAuthCredential(hit.value);
|
|
3061
|
+
return this.expirySkewMs === 0 ? credential : {
|
|
3062
|
+
...credential,
|
|
3063
|
+
expires: credential.expires - this.expirySkewMs
|
|
3148
3064
|
};
|
|
3149
3065
|
}
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
const
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3066
|
+
read(providerId, options) {
|
|
3067
|
+
return this.#enqueue(providerId, () => this.#read(providerId, options), options);
|
|
3068
|
+
}
|
|
3069
|
+
async list(options) {
|
|
3070
|
+
abortIfNeeded(options);
|
|
3071
|
+
return await this.read(PROVIDER$1, options) === void 0 ? [] : [{
|
|
3072
|
+
providerId: PROVIDER$1,
|
|
3073
|
+
type: "oauth"
|
|
3074
|
+
}];
|
|
3075
|
+
}
|
|
3076
|
+
modify(providerId, update, options) {
|
|
3077
|
+
return this.#enqueue(providerId, async () => {
|
|
3078
|
+
if (this.vault !== void 0) {
|
|
3079
|
+
const next = await this.vault.modifyActive(async (current) => {
|
|
3080
|
+
const visible = current === void 0 || this.expirySkewMs === 0 ? current : {
|
|
3081
|
+
...current,
|
|
3082
|
+
expires: current.expires - this.expirySkewMs
|
|
3083
|
+
};
|
|
3084
|
+
const updated = await update(clone(visible));
|
|
3085
|
+
return updated === void 0 ? void 0 : assertOAuthCredential(updated);
|
|
3086
|
+
});
|
|
3087
|
+
abortIfNeeded(options);
|
|
3088
|
+
return clone(next);
|
|
3089
|
+
}
|
|
3090
|
+
const current = await this.#read(providerId, options);
|
|
3091
|
+
const next = await update(clone(current));
|
|
3092
|
+
abortIfNeeded(options);
|
|
3093
|
+
if (next === void 0) return current;
|
|
3094
|
+
const validated = assertOAuthCredential(next);
|
|
3095
|
+
await this.credentials.set(this.ref, JSON.stringify(validated));
|
|
3096
|
+
for (const legacyRef of this.legacyRefs) await this.credentials.unset(legacyRef);
|
|
3097
|
+
abortIfNeeded(options);
|
|
3098
|
+
return clone(validated);
|
|
3099
|
+
}, options);
|
|
3100
|
+
}
|
|
3101
|
+
delete(providerId, options) {
|
|
3102
|
+
return this.#enqueue(providerId, async () => {
|
|
3103
|
+
if (this.vault !== void 0) {
|
|
3104
|
+
await this.vault.deleteAll();
|
|
3105
|
+
abortIfNeeded(options);
|
|
3106
|
+
return;
|
|
3107
|
+
}
|
|
3108
|
+
await this.credentials.unset(this.ref);
|
|
3109
|
+
for (const legacyRef of this.legacyRefs) await this.credentials.unset(legacyRef);
|
|
3110
|
+
abortIfNeeded(options);
|
|
3111
|
+
}, options);
|
|
3112
|
+
}
|
|
3113
|
+
};
|
|
3114
|
+
/** Return only account state that is safe to expose to the browser client. */
|
|
3115
|
+
function createCodexAuthService(models, store, options = {}) {
|
|
3116
|
+
const runLogin = options.runLogin ?? ((run) => run());
|
|
3117
|
+
const accountVault = options.accountVault;
|
|
3118
|
+
const createLoginModels = options.createLoginModels;
|
|
3119
|
+
const createPendingStore = options.createPendingStore ?? (() => new PendingOAuthCredentialStore());
|
|
3120
|
+
return Object.freeze({
|
|
3121
|
+
async status(options) {
|
|
3122
|
+
const current = await store.read(PROVIDER$1, options);
|
|
3123
|
+
const accounts = await accountVault?.list();
|
|
3124
|
+
if (current === void 0) return {
|
|
3125
|
+
authenticated: false,
|
|
3126
|
+
provider: PROVIDER$1,
|
|
3127
|
+
...accounts === void 0 ? {} : { accounts }
|
|
3128
|
+
};
|
|
3129
|
+
return {
|
|
3130
|
+
authenticated: true,
|
|
3131
|
+
provider: PROVIDER$1,
|
|
3132
|
+
type: "oauth",
|
|
3133
|
+
expiresAt: current.expires,
|
|
3134
|
+
...accounts === void 0 ? {} : { accounts }
|
|
3135
|
+
};
|
|
3136
|
+
},
|
|
3137
|
+
login(interaction, input = {}) {
|
|
3138
|
+
if (input.label !== void 0) {
|
|
3139
|
+
if (accountVault === void 0 || createLoginModels === void 0) throw new Error("Codex multi-account is unavailable");
|
|
3140
|
+
return runLogin(async () => {
|
|
3141
|
+
const pending = createPendingStore();
|
|
3142
|
+
await createLoginModels(pending).login(PROVIDER$1, "oauth", interaction);
|
|
3143
|
+
const credential = pending.credential();
|
|
3144
|
+
if (credential === void 0) throw new Error("Codex login did not return credentials");
|
|
3145
|
+
await accountVault.add(input.label, credential);
|
|
3146
|
+
});
|
|
3192
3147
|
}
|
|
3148
|
+
return runLogin(() => models.login(PROVIDER$1, "oauth", interaction));
|
|
3149
|
+
},
|
|
3150
|
+
async select(id) {
|
|
3151
|
+
if (accountVault === void 0) throw new Error("Codex multi-account is unavailable");
|
|
3152
|
+
await accountVault.select(id);
|
|
3153
|
+
return this.status();
|
|
3154
|
+
},
|
|
3155
|
+
async remove(id) {
|
|
3156
|
+
if (accountVault === void 0) throw new Error("Codex multi-account is unavailable");
|
|
3157
|
+
await accountVault.remove(id);
|
|
3158
|
+
return this.status();
|
|
3159
|
+
},
|
|
3160
|
+
logout(options) {
|
|
3161
|
+
return models.logout(PROVIDER$1, options);
|
|
3193
3162
|
}
|
|
3163
|
+
});
|
|
3164
|
+
}
|
|
3165
|
+
//#endregion
|
|
3166
|
+
//#region src/external-url.js
|
|
3167
|
+
const OPENAI_AUTH_ORIGIN = "https://auth.openai.com";
|
|
3168
|
+
/** Validate the only external origin this plugin may launch. */
|
|
3169
|
+
function assertCodexAuthUrl(value) {
|
|
3170
|
+
let url;
|
|
3171
|
+
try {
|
|
3172
|
+
url = new URL(value);
|
|
3173
|
+
} catch {
|
|
3174
|
+
throw new Error("Codex auth URL is invalid");
|
|
3194
3175
|
}
|
|
3176
|
+
if (url.protocol !== "https:") throw new Error("Codex auth URL must use HTTPS");
|
|
3177
|
+
if (url.origin !== OPENAI_AUTH_ORIGIN || url.username !== "" || url.password !== "") throw new Error("Codex auth URL must use the OpenAI auth origin");
|
|
3178
|
+
return url.href;
|
|
3195
3179
|
}
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
const
|
|
3199
|
-
if (
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
const upgradeTo = upgradeModelId(value);
|
|
3204
|
-
const upgradeName = upgradeTo === void 0 ? void 0 : nonEmpty$2(targetDisplayName) ?? upgradeTo;
|
|
3205
|
-
return {
|
|
3206
|
-
text: `Retires on ${dateText} (UTC).${upgradeName === void 0 ? "" : ` Switch to ${upgradeName} to keep working.`}`,
|
|
3207
|
-
at,
|
|
3208
|
-
upgradeTo,
|
|
3209
|
-
dateText
|
|
3180
|
+
/** Return a shell-free native opener command for the current desktop. */
|
|
3181
|
+
function commandForCodexAuthUrl(value, platform = process.platform) {
|
|
3182
|
+
const url = assertCodexAuthUrl(value);
|
|
3183
|
+
if (platform === "win32") return {
|
|
3184
|
+
file: "rundll32.exe",
|
|
3185
|
+
args: ["url.dll,FileProtocolHandler", url],
|
|
3186
|
+
shell: false
|
|
3210
3187
|
};
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
const speedTierIds = (value) => [...Array.isArray(value.additional_speed_tiers) ? value.additional_speed_tiers : [], ...Array.isArray(value.service_tiers) ? value.service_tiers.map((tier) => tier?.id) : []];
|
|
3221
|
-
function unsupportedCapabilities(value) {
|
|
3222
|
-
const reasoning = capabilityNames((value.supported_reasoning_levels ?? []).map((item) => item?.effort)).filter((level) => !["none", ...LEVELS.slice(1)].includes(level));
|
|
3223
|
-
const inputs = capabilityNames(Array.isArray(value.input_modalities) ? value.input_modalities : []).filter((input) => !["text", "image"].includes(input));
|
|
3224
|
-
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) => ![
|
|
3225
|
-
"auto",
|
|
3226
|
-
"default",
|
|
3227
|
-
"standard",
|
|
3228
|
-
"fast",
|
|
3229
|
-
"priority",
|
|
3230
|
-
"ultrafast"
|
|
3231
|
-
].includes(tier));
|
|
3232
|
-
return {
|
|
3233
|
-
...reasoning.length ? { reasoning } : {},
|
|
3234
|
-
...inputs.length ? { inputs } : {},
|
|
3235
|
-
...speeds.length ? { speeds } : {}
|
|
3188
|
+
if (platform === "darwin") return {
|
|
3189
|
+
file: "open",
|
|
3190
|
+
args: [url],
|
|
3191
|
+
shell: false
|
|
3192
|
+
};
|
|
3193
|
+
if (platform === "linux") return {
|
|
3194
|
+
file: "xdg-open",
|
|
3195
|
+
args: [url],
|
|
3196
|
+
shell: false
|
|
3236
3197
|
};
|
|
3198
|
+
throw new Error(`Codex auth URL opener is unsupported on ${platform}`);
|
|
3237
3199
|
}
|
|
3238
|
-
function
|
|
3239
|
-
|
|
3240
|
-
const
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3200
|
+
function openCodexAuthUrl(value, options = {}) {
|
|
3201
|
+
const command = commandForCodexAuthUrl(value, options.platform);
|
|
3202
|
+
const spawnProcess = options.spawn ?? spawn;
|
|
3203
|
+
return new Promise((resolve, reject) => {
|
|
3204
|
+
const child = spawnProcess(command.file, command.args, {
|
|
3205
|
+
detached: true,
|
|
3206
|
+
stdio: "ignore",
|
|
3207
|
+
windowsHide: true,
|
|
3208
|
+
shell: command.shell
|
|
3209
|
+
});
|
|
3210
|
+
child.once("error", reject);
|
|
3211
|
+
child.once("spawn", () => {
|
|
3212
|
+
child.unref();
|
|
3213
|
+
resolve();
|
|
3214
|
+
});
|
|
3248
3215
|
});
|
|
3249
|
-
const upgradeTo = upgradeModelId(value);
|
|
3250
|
-
const retirement = retirementNotice(value, now, displayNames.get(upgradeTo));
|
|
3251
|
-
const notice = retirement?.text;
|
|
3252
|
-
const description = nonEmpty$2(value.description);
|
|
3253
|
-
return {
|
|
3254
|
-
...Object.keys(unsupported).length ? { unsupported } : {},
|
|
3255
|
-
id,
|
|
3256
|
-
name: `${reserve ? "GPT-Reserve (Experimental)" : nonEmpty$2(value.display_name) ?? id}${retirement === void 0 ? "" : ` (retires ${retirement.dateText})`}`,
|
|
3257
|
-
description: notice === void 0 ? description : description === void 0 ? notice : `${description}\n${notice}`,
|
|
3258
|
-
...retirement === void 0 ? {} : { retirement: {
|
|
3259
|
-
at: retirement.at,
|
|
3260
|
-
upgradeTo: retirement.upgradeTo
|
|
3261
|
-
} },
|
|
3262
|
-
priority: Number.isFinite(value.priority) ? value.priority : 0,
|
|
3263
|
-
input: input.length > 0 ? input : ["text"],
|
|
3264
|
-
contextWindow: positiveInteger$1(value.context_window) ?? positiveInteger$1(value.max_context_window),
|
|
3265
|
-
...positiveInteger$1(value.max_context_window) === void 0 ? {} : { maxContextWindow: value.max_context_window },
|
|
3266
|
-
reasoning: supported.length > 0,
|
|
3267
|
-
thinkingLevelMap: reasoningMap(supported),
|
|
3268
|
-
supportVerbosity: value.support_verbosity === true,
|
|
3269
|
-
defaultVerbosity: [
|
|
3270
|
-
"low",
|
|
3271
|
-
"medium",
|
|
3272
|
-
"high"
|
|
3273
|
-
].includes(value.default_verbosity) ? value.default_verbosity : void 0,
|
|
3274
|
-
supportsFast: speedTierIds(value).some((tier) => tier === "fast" || tier === "priority"),
|
|
3275
|
-
supportsUltrafast: speedTierIds(value).includes("ultrafast")
|
|
3276
|
-
};
|
|
3277
3216
|
}
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3217
|
+
//#endregion
|
|
3218
|
+
//#region src/login-coordinator.js
|
|
3219
|
+
const LOGIN_METHODS = /* @__PURE__ */ new Set(["browser", "device_code"]);
|
|
3220
|
+
const TERMINAL_PHASES = /* @__PURE__ */ new Set([
|
|
3221
|
+
"authenticated",
|
|
3222
|
+
"failed",
|
|
3223
|
+
"cancelled"
|
|
3224
|
+
]);
|
|
3225
|
+
const publicClone = (value) => structuredClone(value);
|
|
3226
|
+
const asObject = (value) => value !== null && typeof value === "object" ? value : {};
|
|
3227
|
+
const ok = (value) => ({
|
|
3228
|
+
ok: true,
|
|
3229
|
+
value
|
|
3230
|
+
});
|
|
3231
|
+
const badRequest = (message) => ({
|
|
3232
|
+
ok: false,
|
|
3233
|
+
error: {
|
|
3234
|
+
code: "bad-request",
|
|
3235
|
+
message,
|
|
3236
|
+
details: { issues: [] }
|
|
3286
3237
|
}
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3238
|
+
});
|
|
3239
|
+
const accountStatusError = (message) => ({
|
|
3240
|
+
ok: false,
|
|
3241
|
+
error: {
|
|
3242
|
+
code: "internal",
|
|
3243
|
+
message,
|
|
3244
|
+
details: { issues: [] }
|
|
3245
|
+
}
|
|
3246
|
+
});
|
|
3247
|
+
const classifyAccountStatusError = (error) => {
|
|
3248
|
+
const message = error instanceof Error ? error.message : "";
|
|
3249
|
+
if (/malformed (?:OAuth|grant|account vault)|received a malformed OAuth|contains malformed OAuth/iu.test(message)) return ["credential-malformed", "Codex account credentials are malformed"];
|
|
3250
|
+
if (/credential|account vault|readRecord|credential store|credentials service/iu.test(message)) return ["credential-unavailable", "Codex account credentials are unavailable"];
|
|
3251
|
+
const code = typeof error?.code === "string" ? error.code.toUpperCase() : "";
|
|
3252
|
+
if (error?.name === "TimeoutError" || [
|
|
3253
|
+
"TIMEOUT",
|
|
3254
|
+
"ETIMEDOUT",
|
|
3255
|
+
"UND_ERR_CONNECT_TIMEOUT"
|
|
3256
|
+
].includes(code)) return ["transport", "Codex account status service is unavailable"];
|
|
3257
|
+
if ([
|
|
3258
|
+
"ECONNRESET",
|
|
3259
|
+
"ECONNREFUSED",
|
|
3260
|
+
"ENOTFOUND",
|
|
3261
|
+
"EAI_AGAIN",
|
|
3262
|
+
"NETWORK",
|
|
3263
|
+
"NETWORK_ERROR",
|
|
3264
|
+
"TRANSPORT"
|
|
3265
|
+
].includes(code) || error?.name === "NetworkError") return ["transport", "Codex account status service is unavailable"];
|
|
3266
|
+
return ["unknown", "Could not read Codex account status"];
|
|
3267
|
+
};
|
|
3268
|
+
const deferred = () => {
|
|
3269
|
+
let resolve;
|
|
3270
|
+
let reject;
|
|
3293
3271
|
return {
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
thinkingLevelMap: remote.thinkingLevelMap,
|
|
3301
|
-
...remote.contextWindow === void 0 ? {} : { contextWindow: remote.contextWindow },
|
|
3302
|
-
...remote.maxContextWindow === void 0 ? {} : { maxContextWindow: remote.maxContextWindow },
|
|
3303
|
-
...base.id === remote.id ? {} : { cost: {
|
|
3304
|
-
input: 0,
|
|
3305
|
-
output: 0,
|
|
3306
|
-
cacheRead: 0,
|
|
3307
|
-
cacheWrite: 0
|
|
3308
|
-
} }
|
|
3272
|
+
promise: new Promise((onResolve, onReject) => {
|
|
3273
|
+
resolve = onResolve;
|
|
3274
|
+
reject = onReject;
|
|
3275
|
+
}),
|
|
3276
|
+
resolve,
|
|
3277
|
+
reject
|
|
3309
3278
|
};
|
|
3279
|
+
};
|
|
3280
|
+
const publicPrompt = (prompt) => ({
|
|
3281
|
+
type: prompt.type,
|
|
3282
|
+
message: String(prompt.message ?? ""),
|
|
3283
|
+
...typeof prompt.placeholder === "string" ? { placeholder: prompt.placeholder } : {}
|
|
3284
|
+
});
|
|
3285
|
+
function classifyLoginFailure(error) {
|
|
3286
|
+
const message = error instanceof Error ? error.message : "";
|
|
3287
|
+
if (/token exchange failed/iu.test(message)) return "token-exchange";
|
|
3288
|
+
if (/fetch failed|\b(?:ECONN|ENOTFOUND|ETIMEDOUT|CERT_|socket|network)\b/iu.test(message)) return "network";
|
|
3289
|
+
if (/extract accountId|account[_ -]?id/iu.test(message)) return "account-claim";
|
|
3290
|
+
if (/credential|credentials-local|OAuth JSON/iu.test(message)) return "credential-store";
|
|
3291
|
+
if (/Missing authorization code|State mismatch|callback/iu.test(message)) return "callback";
|
|
3292
|
+
return "provider";
|
|
3310
3293
|
}
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
refreshStatus = "refreshing";
|
|
3330
|
-
let outcome = "idle";
|
|
3331
|
-
const controller = new AbortController();
|
|
3332
|
-
const abort = () => {
|
|
3333
|
-
if (!controller.signal.aborted) controller.abort(signal?.reason ?? /* @__PURE__ */ new Error("Codex model catalog refresh aborted"));
|
|
3294
|
+
/** Own one host-side login without exposing tokens to the browser client. */
|
|
3295
|
+
var CodexLoginCoordinator = class {
|
|
3296
|
+
#sessions = /* @__PURE__ */ new Map();
|
|
3297
|
+
#activeId;
|
|
3298
|
+
constructor(auth, options = {}) {
|
|
3299
|
+
this.auth = auth;
|
|
3300
|
+
this.createId = options.createId ?? (() => crypto.randomUUID());
|
|
3301
|
+
}
|
|
3302
|
+
async accountStatus(options) {
|
|
3303
|
+
return publicClone(await this.auth.status(options));
|
|
3304
|
+
}
|
|
3305
|
+
supportState() {
|
|
3306
|
+
const active = this.#activeId === void 0 ? void 0 : this.#sessions.get(this.#activeId);
|
|
3307
|
+
if (active === void 0) return { phase: "idle" };
|
|
3308
|
+
return {
|
|
3309
|
+
method: active.view.method,
|
|
3310
|
+
phase: active.view.phase,
|
|
3311
|
+
...active.view.phase === "failed" ? { failure: classifyLoginFailure(active.hostError) } : {}
|
|
3334
3312
|
};
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
const
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
"
|
|
3346
|
-
|
|
3347
|
-
originator: "pi",
|
|
3348
|
-
"user-agent": USER_AGENT,
|
|
3349
|
-
...etag === void 0 ? {} : { "if-none-match": etag }
|
|
3313
|
+
}
|
|
3314
|
+
async start({ method, label }) {
|
|
3315
|
+
if (!LOGIN_METHODS.has(method)) throw new Error(`unsupported Codex login method: ${String(method)}`);
|
|
3316
|
+
if (label !== void 0 && (typeof label !== "string" || label.trim().length === 0 || label.trim().length > 48)) throw new Error("unsupported Codex account label");
|
|
3317
|
+
const active = this.#activeId === void 0 ? void 0 : this.#sessions.get(this.#activeId);
|
|
3318
|
+
if (active !== void 0 && !TERMINAL_PHASES.has(active.view.phase)) {
|
|
3319
|
+
active.view = {
|
|
3320
|
+
id: active.view.id,
|
|
3321
|
+
provider: "openai-codex",
|
|
3322
|
+
method: active.view.method,
|
|
3323
|
+
phase: "cancelled",
|
|
3324
|
+
authenticated: false
|
|
3350
3325
|
};
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
options.onUpdated?.();
|
|
3368
|
-
return true;
|
|
3326
|
+
active.controller.abort(/* @__PURE__ */ new Error("Codex login replaced by a new attempt"));
|
|
3327
|
+
}
|
|
3328
|
+
if (active !== void 0) this.#sessions.delete(active.view.id);
|
|
3329
|
+
const id = this.createId();
|
|
3330
|
+
const ready = deferred();
|
|
3331
|
+
const controller = new AbortController();
|
|
3332
|
+
const session = {
|
|
3333
|
+
controller,
|
|
3334
|
+
prompt: void 0,
|
|
3335
|
+
ready,
|
|
3336
|
+
view: {
|
|
3337
|
+
id,
|
|
3338
|
+
provider: "openai-codex",
|
|
3339
|
+
method,
|
|
3340
|
+
phase: "starting",
|
|
3341
|
+
authenticated: false
|
|
3369
3342
|
}
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3343
|
+
};
|
|
3344
|
+
this.#sessions.set(id, session);
|
|
3345
|
+
this.#activeId = id;
|
|
3346
|
+
const publishReady = () => ready.resolve(publicClone(session.view));
|
|
3347
|
+
const interaction = {
|
|
3348
|
+
signal: controller.signal,
|
|
3349
|
+
prompt: async (prompt) => {
|
|
3350
|
+
controller.signal.throwIfAborted();
|
|
3351
|
+
if (prompt.type === "select") return method;
|
|
3352
|
+
if (![
|
|
3353
|
+
"manual_code",
|
|
3354
|
+
"text",
|
|
3355
|
+
"secret"
|
|
3356
|
+
].includes(prompt.type)) throw new Error(`unsupported Codex auth prompt: ${String(prompt.type)}`);
|
|
3357
|
+
const answer = deferred();
|
|
3358
|
+
session.prompt = answer;
|
|
3359
|
+
session.view = {
|
|
3360
|
+
...session.view,
|
|
3361
|
+
phase: "waiting_input",
|
|
3362
|
+
prompt: publicPrompt(prompt)
|
|
3363
|
+
};
|
|
3364
|
+
const abortPrompt = () => answer.reject(controller.signal.reason ?? /* @__PURE__ */ new Error("login cancelled"));
|
|
3365
|
+
controller.signal.addEventListener("abort", abortPrompt, { once: true });
|
|
3366
|
+
prompt.signal?.addEventListener("abort", abortPrompt, { once: true });
|
|
3367
|
+
publishReady();
|
|
3368
|
+
try {
|
|
3369
|
+
return await answer.promise;
|
|
3370
|
+
} finally {
|
|
3371
|
+
controller.signal.removeEventListener("abort", abortPrompt);
|
|
3372
|
+
prompt.signal?.removeEventListener("abort", abortPrompt);
|
|
3373
|
+
if (session.prompt === answer) session.prompt = void 0;
|
|
3374
|
+
}
|
|
3375
|
+
},
|
|
3376
|
+
notify: (event) => {
|
|
3377
|
+
if (controller.signal.aborted) return;
|
|
3378
|
+
if (event.type === "auth_url") session.view = {
|
|
3379
|
+
...session.view,
|
|
3380
|
+
phase: "waiting_browser",
|
|
3381
|
+
authUrl: assertCodexAuthUrl(event.url),
|
|
3382
|
+
...typeof event.instructions === "string" ? { instructions: event.instructions } : {}
|
|
3383
|
+
};
|
|
3384
|
+
else if (event.type === "device_code") session.view = {
|
|
3385
|
+
...session.view,
|
|
3386
|
+
phase: "waiting_device",
|
|
3387
|
+
deviceCode: {
|
|
3388
|
+
userCode: event.userCode,
|
|
3389
|
+
verificationUri: assertCodexAuthUrl(event.verificationUri),
|
|
3390
|
+
...typeof event.intervalSeconds === "number" ? { intervalSeconds: event.intervalSeconds } : {},
|
|
3391
|
+
...typeof event.expiresInSeconds === "number" ? { expiresInSeconds: event.expiresInSeconds } : {}
|
|
3392
|
+
}
|
|
3393
|
+
};
|
|
3394
|
+
else session.view = {
|
|
3395
|
+
...session.view,
|
|
3396
|
+
message: String(event.message ?? "")
|
|
3397
|
+
};
|
|
3398
|
+
publishReady();
|
|
3399
|
+
}
|
|
3400
|
+
};
|
|
3401
|
+
session.run = Promise.resolve().then(() => this.auth.login(interaction, label === void 0 ? {} : { label: label.trim() })).then(async () => {
|
|
3402
|
+
if (controller.signal.aborted) return;
|
|
3403
|
+
const status = await this.auth.status();
|
|
3404
|
+
session.view = {
|
|
3405
|
+
id,
|
|
3406
|
+
provider: "openai-codex",
|
|
3407
|
+
method,
|
|
3408
|
+
phase: "authenticated",
|
|
3409
|
+
authenticated: status.authenticated === true,
|
|
3410
|
+
...typeof status.expiresAt === "number" ? { expiresAt: status.expiresAt } : {}
|
|
3411
|
+
};
|
|
3412
|
+
}).catch(async (error) => {
|
|
3413
|
+
if (controller.signal.aborted) {
|
|
3414
|
+
session.view = {
|
|
3415
|
+
id,
|
|
3416
|
+
provider: "openai-codex",
|
|
3417
|
+
method,
|
|
3418
|
+
phase: "cancelled",
|
|
3419
|
+
authenticated: false
|
|
3420
|
+
};
|
|
3421
|
+
return;
|
|
3406
3422
|
}
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3423
|
+
try {
|
|
3424
|
+
if (label !== void 0) throw error;
|
|
3425
|
+
const status = await this.auth.status();
|
|
3426
|
+
if (status.authenticated === true) {
|
|
3427
|
+
session.view = {
|
|
3428
|
+
id,
|
|
3429
|
+
provider: "openai-codex",
|
|
3430
|
+
method,
|
|
3431
|
+
phase: "authenticated",
|
|
3432
|
+
authenticated: true,
|
|
3433
|
+
...typeof status.expiresAt === "number" ? { expiresAt: status.expiresAt } : {}
|
|
3434
|
+
};
|
|
3435
|
+
return;
|
|
3436
|
+
}
|
|
3437
|
+
} catch {}
|
|
3438
|
+
session.view = {
|
|
3439
|
+
id,
|
|
3440
|
+
provider: "openai-codex",
|
|
3441
|
+
method,
|
|
3442
|
+
phase: "failed",
|
|
3443
|
+
authenticated: false,
|
|
3444
|
+
error: "Codex login failed"
|
|
3445
|
+
};
|
|
3446
|
+
session.hostError = error;
|
|
3447
|
+
}).finally(publishReady);
|
|
3448
|
+
return ready.promise;
|
|
3449
|
+
}
|
|
3450
|
+
read(id) {
|
|
3451
|
+
const session = this.#sessions.get(id);
|
|
3452
|
+
if (session === void 0) throw new Error("unknown Codex login");
|
|
3453
|
+
return publicClone(session.view);
|
|
3454
|
+
}
|
|
3455
|
+
async submit({ id, value }) {
|
|
3456
|
+
const session = this.#sessions.get(id);
|
|
3457
|
+
if (session === void 0) throw new Error("unknown Codex login");
|
|
3458
|
+
if (session.prompt === void 0 || session.view.phase !== "waiting_input") throw new Error("Codex login is not waiting for input");
|
|
3459
|
+
if (typeof value !== "string" || value.trim() === "") throw new Error("Codex login input is empty");
|
|
3460
|
+
const answer = session.prompt;
|
|
3461
|
+
session.prompt = void 0;
|
|
3462
|
+
session.view = {
|
|
3463
|
+
...session.view,
|
|
3464
|
+
phase: session.view.authUrl === void 0 ? "starting" : "waiting_browser",
|
|
3465
|
+
prompt: void 0
|
|
3412
3466
|
};
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
clear() {
|
|
3429
|
-
generation += 1;
|
|
3430
|
-
const flight = refreshing;
|
|
3431
|
-
refreshing = void 0;
|
|
3432
|
-
flight?.cancel();
|
|
3433
|
-
models = void 0;
|
|
3434
|
-
rawCatalog = void 0;
|
|
3435
|
-
metadata = /* @__PURE__ */ new Map();
|
|
3436
|
-
etag = void 0;
|
|
3437
|
-
refreshStatus = "idle";
|
|
3438
|
-
revision += 1;
|
|
3439
|
-
options.onUpdated?.();
|
|
3467
|
+
answer.resolve(value);
|
|
3468
|
+
return this.read(id);
|
|
3469
|
+
}
|
|
3470
|
+
async cancel(id) {
|
|
3471
|
+
const session = this.#sessions.get(id);
|
|
3472
|
+
if (session === void 0) throw new Error("unknown Codex login");
|
|
3473
|
+
if (!TERMINAL_PHASES.has(session.view.phase)) {
|
|
3474
|
+
session.view = {
|
|
3475
|
+
id,
|
|
3476
|
+
provider: "openai-codex",
|
|
3477
|
+
method: session.view.method,
|
|
3478
|
+
phase: "cancelled",
|
|
3479
|
+
authenticated: false
|
|
3480
|
+
};
|
|
3481
|
+
session.controller.abort(/* @__PURE__ */ new Error("Codex login cancelled"));
|
|
3440
3482
|
}
|
|
3441
|
-
|
|
3442
|
-
}
|
|
3443
|
-
//#endregion
|
|
3444
|
-
//#region src/codex-search.js
|
|
3445
|
-
const CODEX_SEARCH_PROVIDER_ID = "codex-subscription";
|
|
3446
|
-
const CODEX_AUTO_SEARCH_PROVIDER_ID = "codex-subscription-auto";
|
|
3447
|
-
const CODEX_SEARCH_URL = "https://chatgpt.com/backend-api/codex/alpha/search";
|
|
3448
|
-
const DEFAULT_MODEL = "gpt-5.6-luna";
|
|
3449
|
-
const MAX_OUTPUT_TOKENS = 4096;
|
|
3450
|
-
const MAX_SOURCE_DATE = 64;
|
|
3451
|
-
const record$3 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3452
|
-
const nonEmpty$1 = (value) => typeof value === "string" && value.length > 0 ? value : void 0;
|
|
3453
|
-
const displayText = (value) => {
|
|
3454
|
-
const text = nonEmpty$1(value)?.replace(/\s+/gu, " ").trim();
|
|
3455
|
-
if (text === void 0 || text.length === 0) return void 0;
|
|
3456
|
-
return text;
|
|
3457
|
-
};
|
|
3458
|
-
const boundedDisplayText = (value, maximum) => {
|
|
3459
|
-
const text = displayText(value);
|
|
3460
|
-
if (text === void 0 || text.length <= maximum) return text;
|
|
3461
|
-
return `${text.slice(0, maximum - 1)}…`;
|
|
3462
|
-
};
|
|
3463
|
-
function sourceOf(value) {
|
|
3464
|
-
if (!record$3(value)) return void 0;
|
|
3465
|
-
const url = nonEmpty$1(value.url);
|
|
3466
|
-
if (url === void 0) return void 0;
|
|
3467
|
-
let parsed;
|
|
3468
|
-
try {
|
|
3469
|
-
parsed = new URL(url);
|
|
3470
|
-
} catch {
|
|
3471
|
-
return;
|
|
3483
|
+
return this.read(id);
|
|
3472
3484
|
}
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
...snippet === void 0 ? {} : { snippet },
|
|
3481
|
-
...publishedAt === void 0 ? {} : { publishedAt }
|
|
3482
|
-
};
|
|
3483
|
-
}
|
|
3484
|
-
function parseSearchResponse(value) {
|
|
3485
|
-
if (!record$3(value) || !Array.isArray(value.results)) throw new Error("Codex returned a malformed search response");
|
|
3486
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3487
|
-
const sources = [];
|
|
3488
|
-
for (const result of value.results ?? []) {
|
|
3489
|
-
const source = sourceOf(result);
|
|
3490
|
-
if (source === void 0 || seen.has(source.url)) continue;
|
|
3491
|
-
seen.add(source.url);
|
|
3492
|
-
sources.push(source);
|
|
3485
|
+
async logout(options) {
|
|
3486
|
+
if (this.#activeId !== void 0) {
|
|
3487
|
+
const active = this.#sessions.get(this.#activeId);
|
|
3488
|
+
if (active !== void 0 && !TERMINAL_PHASES.has(active.view.phase)) await this.cancel(active.view.id);
|
|
3489
|
+
}
|
|
3490
|
+
await this.auth.logout(options);
|
|
3491
|
+
return this.accountStatus(options);
|
|
3493
3492
|
}
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
signal
|
|
3507
|
-
const
|
|
3508
|
-
if (
|
|
3509
|
-
|
|
3510
|
-
if (typeof access !== "string" || access.length === 0 || typeof accountId !== "string" || accountId.length === 0) throw new WebError("ChatGPT subscription is not signed in", "WEB_PROVIDER_CREDENTIAL_MISSING");
|
|
3511
|
-
const model = nonEmpty$1(options.resolveModel?.()) ?? DEFAULT_MODEL;
|
|
3512
|
-
const id = nonEmpty$1(options.resolveSessionId?.()) ?? randomUUID();
|
|
3513
|
-
let response;
|
|
3514
|
-
try {
|
|
3515
|
-
response = await fetchSearch(CODEX_SEARCH_URL, {
|
|
3516
|
-
method: "POST",
|
|
3517
|
-
redirect: "error",
|
|
3518
|
-
headers: {
|
|
3519
|
-
authorization: `Bearer ${access}`,
|
|
3520
|
-
"chatgpt-account-id": accountId,
|
|
3521
|
-
accept: "application/json",
|
|
3522
|
-
"content-type": "application/json",
|
|
3523
|
-
originator: "pi",
|
|
3524
|
-
"user-agent": USER_AGENT
|
|
3525
|
-
},
|
|
3526
|
-
body: JSON.stringify({
|
|
3527
|
-
id,
|
|
3528
|
-
model,
|
|
3529
|
-
input: request.query,
|
|
3530
|
-
commands: {
|
|
3531
|
-
search_query: [{ q: request.query }],
|
|
3532
|
-
response_length: "short"
|
|
3533
|
-
},
|
|
3534
|
-
settings: {
|
|
3535
|
-
allowed_callers: ["direct"],
|
|
3536
|
-
external_web_access: preferences.searchMode === "live"
|
|
3537
|
-
},
|
|
3538
|
-
max_output_tokens: MAX_OUTPUT_TOKENS
|
|
3539
|
-
}),
|
|
3540
|
-
signal
|
|
3541
|
-
});
|
|
3542
|
-
} catch (error) {
|
|
3543
|
-
if (signal?.aborted || error?.name === "AbortError") throw new WebError("Codex search aborted", "WEB_ABORTED", { cause: error });
|
|
3544
|
-
throw new WebError("Codex search request failed", "WEB_PROVIDER_ERROR", { cause: error });
|
|
3545
|
-
}
|
|
3546
|
-
if (!response.ok) throw response.status === 401 || response.status === 403 ? new WebError("ChatGPT sign-in needs to be renewed", "WEB_PROVIDER_CREDENTIAL_MISSING") : new WebError(`Codex search request failed (HTTP ${response.status})`, "WEB_PROVIDER_ERROR");
|
|
3547
|
-
let value;
|
|
3548
|
-
try {
|
|
3549
|
-
value = await response.json();
|
|
3493
|
+
async selectAccount(id) {
|
|
3494
|
+
return publicClone(await this.auth.select(id));
|
|
3495
|
+
}
|
|
3496
|
+
async removeAccount(id) {
|
|
3497
|
+
return publicClone(await this.auth.remove(id));
|
|
3498
|
+
}
|
|
3499
|
+
};
|
|
3500
|
+
/** Map the loopback-only DSH Connection channel onto the coordinator. */
|
|
3501
|
+
function createCodexRpcHandler(coordinator, options = {}) {
|
|
3502
|
+
const openExternal = options.openExternal;
|
|
3503
|
+
return async (endpoint, payload, signal) => {
|
|
3504
|
+
try {
|
|
3505
|
+
signal.throwIfAborted();
|
|
3506
|
+
const input = asObject(payload);
|
|
3507
|
+
if (endpoint === "status") try {
|
|
3508
|
+
return ok(await coordinator.accountStatus({ signal }));
|
|
3550
3509
|
} catch (error) {
|
|
3551
|
-
|
|
3510
|
+
if (signal.aborted) throw error;
|
|
3511
|
+
const [, message] = classifyAccountStatusError(error);
|
|
3512
|
+
return accountStatusError(message);
|
|
3552
3513
|
}
|
|
3553
|
-
|
|
3554
|
-
const
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
return preferences.searchDomains.some((domain) => hostname === domain || hostname.endsWith(`.${domain}`));
|
|
3514
|
+
if (endpoint === "login/start") {
|
|
3515
|
+
const started = await coordinator.start({
|
|
3516
|
+
method: input.method,
|
|
3517
|
+
label: input.label
|
|
3558
3518
|
});
|
|
3559
|
-
return
|
|
3560
|
-
|
|
3561
|
-
|
|
3519
|
+
if (input.openExternal !== true) return ok(started);
|
|
3520
|
+
const url = started.authUrl ?? started.deviceCode?.verificationUri;
|
|
3521
|
+
if (typeof url !== "string" || openExternal === void 0) return ok({
|
|
3522
|
+
...started,
|
|
3523
|
+
externalOpened: false
|
|
3524
|
+
});
|
|
3525
|
+
try {
|
|
3526
|
+
await openExternal(url);
|
|
3527
|
+
return ok({
|
|
3528
|
+
...started,
|
|
3529
|
+
externalOpened: true
|
|
3530
|
+
});
|
|
3531
|
+
} catch {
|
|
3532
|
+
return ok({
|
|
3533
|
+
...started,
|
|
3534
|
+
externalOpened: false
|
|
3535
|
+
});
|
|
3536
|
+
}
|
|
3562
3537
|
}
|
|
3538
|
+
if (endpoint === "login/status") return ok(coordinator.read(input.id));
|
|
3539
|
+
if (endpoint === "login/submit") return ok(await coordinator.submit({
|
|
3540
|
+
id: input.id,
|
|
3541
|
+
value: input.value
|
|
3542
|
+
}));
|
|
3543
|
+
if (endpoint === "login/cancel") return ok(await coordinator.cancel(input.id));
|
|
3544
|
+
if (endpoint === "logout") return ok(await coordinator.logout({ signal }));
|
|
3545
|
+
if (endpoint === "account/select") return ok(await coordinator.selectAccount(input.id));
|
|
3546
|
+
if (endpoint === "account/remove") return ok(await coordinator.removeAccount(input.id));
|
|
3547
|
+
return badRequest(`unknown Codex auth endpoint: ${endpoint}`);
|
|
3548
|
+
} catch (error) {
|
|
3549
|
+
if (signal.aborted) throw error;
|
|
3550
|
+
const message = error instanceof Error && /^(unknown|unsupported|a Codex|Codex login)/.test(error.message) ? error.message : "Codex request failed";
|
|
3551
|
+
return badRequest(message);
|
|
3563
3552
|
}
|
|
3564
|
-
}
|
|
3565
|
-
}
|
|
3566
|
-
/** Route each request by its initiating model without changing the user's explicit overrides. */
|
|
3567
|
-
function createCodexAutoSearchProvider(options) {
|
|
3568
|
-
return Object.freeze({
|
|
3569
|
-
id: CODEX_AUTO_SEARCH_PROVIDER_ID,
|
|
3570
|
-
available: () => true,
|
|
3571
|
-
async search(request, signal) {
|
|
3572
|
-
if (options.resolveModelProvider?.() === "openai-codex") return options.codex.search(request, signal);
|
|
3573
|
-
const provider = options.resolveDshProvider?.();
|
|
3574
|
-
if (provider === void 0 || provider.id === "codex-subscription-auto" || provider.id === "codex-subscription" || provider.available() !== true) throw new WebError("DSH default search is unavailable", "WEB_PROVIDER_UNAVAILABLE");
|
|
3575
|
-
return provider.search(request, signal);
|
|
3576
|
-
}
|
|
3577
|
-
});
|
|
3553
|
+
};
|
|
3578
3554
|
}
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3555
|
+
//#endregion
|
|
3556
|
+
//#region src/transport-failure.js
|
|
3557
|
+
function normalizeTransportEvent(event, signal) {
|
|
3558
|
+
if (signal?.aborted || event?.type !== "error" || event.reason !== "error") return event;
|
|
3559
|
+
const message = event.error?.errorMessage;
|
|
3560
|
+
if (typeof message !== "string" || !/^WebSocket closed (?:1006|1011|1012|1013)(?:\s|$)/.test(message)) return event;
|
|
3584
3561
|
return {
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
name: value.name,
|
|
3591
|
-
sha256: value.sha256
|
|
3562
|
+
...event,
|
|
3563
|
+
error: {
|
|
3564
|
+
...event.error,
|
|
3565
|
+
errorMessage: `Network transport failure: ${message}`
|
|
3566
|
+
}
|
|
3592
3567
|
};
|
|
3593
3568
|
}
|
|
3594
|
-
function decodeImagePresentation(value) {
|
|
3595
|
-
if (value === null || typeof value !== "object" || Array.isArray(value) || value.kind !== "codex-subscription-image" || value.schemaVersion !== 1) return void 0;
|
|
3596
|
-
const original = decodeOriginalImageRef(value.original);
|
|
3597
|
-
return original === void 0 ? void 0 : { original };
|
|
3598
|
-
}
|
|
3599
|
-
function originalImageRefsEqual(left, right) {
|
|
3600
|
-
const a = decodeOriginalImageRef(left);
|
|
3601
|
-
const b = decodeOriginalImageRef(right);
|
|
3602
|
-
return a !== void 0 && b !== void 0 && a.assetId === b.assetId && a.mediaType === b.mediaType && a.bytes === b.bytes && a.width === b.width && a.height === b.height && a.name === b.name && a.sha256 === b.sha256;
|
|
3603
|
-
}
|
|
3604
|
-
/** Resolve only an exact original reference copied into a DSH fork prefix. */
|
|
3605
|
-
function inheritedOriginalImageRef(session, assetId) {
|
|
3606
|
-
const parentSession = session?.header?.parentSession;
|
|
3607
|
-
const seedLength = Number.isSafeInteger(session?.inheritedEventCount) ? session.inheritedEventCount : session?.header?.seedLength;
|
|
3608
|
-
if (typeof parentSession !== "string" || parentSession.length === 0 || !Number.isSafeInteger(seedLength) || seedLength < 0 || !ORIGINAL_IMAGE_ID_PATTERN.test(assetId)) return void 0;
|
|
3609
|
-
let events = session?.events;
|
|
3610
|
-
if (!Array.isArray(events) && typeof session?.snapshotEvents === "function") try {
|
|
3611
|
-
events = session.snapshotEvents();
|
|
3612
|
-
} catch {
|
|
3613
|
-
return;
|
|
3614
|
-
}
|
|
3615
|
-
if (!Array.isArray(events)) return void 0;
|
|
3616
|
-
for (const event of events) {
|
|
3617
|
-
if (!Number.isSafeInteger(event?.seq) || event.seq < 0 || event.seq >= seedLength || event.type !== "tool/result") continue;
|
|
3618
|
-
const original = decodeImagePresentation(event.data?.meta)?.original;
|
|
3619
|
-
if (original?.assetId === assetId) return original;
|
|
3620
|
-
}
|
|
3621
|
-
}
|
|
3622
3569
|
//#endregion
|
|
3623
|
-
//#region src/
|
|
3624
|
-
const
|
|
3625
|
-
const
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
const
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
UND_ERR_CONNECT_TIMEOUT: "the connection to the image service timed out",
|
|
3654
|
-
UND_ERR_SOCKET: "the connection closed unexpectedly",
|
|
3655
|
-
ECONNRESET: "the connection was reset",
|
|
3656
|
-
ETIMEDOUT: "the connection timed out",
|
|
3657
|
-
ENOTFOUND: "DNS lookup failed",
|
|
3658
|
-
ECONNREFUSED: "the connection was refused",
|
|
3659
|
-
EAI_AGAIN: "DNS lookup failed"
|
|
3660
|
-
};
|
|
3661
|
-
function requestFailureReason(error) {
|
|
3662
|
-
const code = [(error?.cause ?? error)?.code, error?.code].find((value) => typeof value === "string" && /^[A-Z][A-Z0-9_]{2,39}$/u.test(value));
|
|
3663
|
-
if (code !== void 0) return `: ${NETWORK_REASONS[code] ?? "network error"} (${code})`;
|
|
3664
|
-
if (error?.name === "TimeoutError") return ": the image request timed out";
|
|
3665
|
-
return "";
|
|
3570
|
+
//#region src/pi-ai-runtime.js
|
|
3571
|
+
const FAST_SERVICE_TIER = "priority";
|
|
3572
|
+
const ULTRAFAST_SERVICE_TIER = "ultrafast";
|
|
3573
|
+
/** Apply the chosen detail to user and tool-result images after the provider assembles the request. */
|
|
3574
|
+
function withInputImageDetail(payload, detail) {
|
|
3575
|
+
if (!Array.isArray(payload.input)) return payload;
|
|
3576
|
+
let changed = false;
|
|
3577
|
+
const input = payload.input.map((item) => {
|
|
3578
|
+
const field = Array.isArray(item?.content) ? "content" : ["function_call_output", "custom_tool_call_output"].includes(item?.type) && Array.isArray(item.output) ? "output" : void 0;
|
|
3579
|
+
if (field === void 0) return item;
|
|
3580
|
+
let itemChanged = false;
|
|
3581
|
+
const parts = item[field].map((part) => {
|
|
3582
|
+
if (part?.type !== "input_image" || part.detail === detail) return part;
|
|
3583
|
+
itemChanged = true;
|
|
3584
|
+
return {
|
|
3585
|
+
...part,
|
|
3586
|
+
detail
|
|
3587
|
+
};
|
|
3588
|
+
});
|
|
3589
|
+
if (!itemChanged) return item;
|
|
3590
|
+
changed = true;
|
|
3591
|
+
return {
|
|
3592
|
+
...item,
|
|
3593
|
+
[field]: parts
|
|
3594
|
+
};
|
|
3595
|
+
});
|
|
3596
|
+
return changed ? {
|
|
3597
|
+
...payload,
|
|
3598
|
+
input
|
|
3599
|
+
} : payload;
|
|
3666
3600
|
}
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3601
|
+
/**
|
|
3602
|
+
* Preserve pi-ai's native Codex OAuth provider while allowing DSH's generic
|
|
3603
|
+
* PiAiAdapter to pass the access token resolved by the host credential store.
|
|
3604
|
+
*
|
|
3605
|
+
* PiAiAdapter owns a request-local Models collection backed by the same DSH
|
|
3606
|
+
* credential store as this provider. A pure OAuth provider ignores its
|
|
3607
|
+
* `apiKey` request override and otherwise fails before dispatch with "Provider
|
|
3608
|
+
* is not configured". This non-interactive bridge teaches that collection how
|
|
3609
|
+
* to consume only the already-refreshed token for this request; login, refresh,
|
|
3610
|
+
* persistence, headers, transport, and model behavior remain owned by the
|
|
3611
|
+
* original provider.
|
|
3612
|
+
*/
|
|
3613
|
+
function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0, resolveOutputVerbosity = () => OUTPUT_VERBOSITY_DEFAULT, resolveInputImageDetail = () => void 0, resolveContextMode = () => void 0, resolveCustomContextWindow = () => void 0, catalog, connection, compaction, runNetwork = (_area, operation) => operation() } = {}) {
|
|
3614
|
+
const provider = createOpenAICodexProvider();
|
|
3615
|
+
const requestToken = Object.freeze({
|
|
3616
|
+
name: "DSH-managed Codex OAuth request token",
|
|
3617
|
+
async resolve({ credential }) {
|
|
3618
|
+
const token = credential?.type === "api_key" ? credential.key : void 0;
|
|
3619
|
+
if (typeof token !== "string" || token.length === 0) return void 0;
|
|
3620
|
+
return {
|
|
3621
|
+
auth: { apiKey: token },
|
|
3622
|
+
source: "DSH-managed OAuth request"
|
|
3623
|
+
};
|
|
3624
|
+
}
|
|
3625
|
+
});
|
|
3626
|
+
const modelMetadata = (model) => catalog?.metadata(model?.id);
|
|
3627
|
+
const supportsVerbosity = (model) => modelMetadata(model)?.supportVerbosity ?? model?.id !== "gpt-5.3-codex-spark";
|
|
3628
|
+
const withPreferences = (model, options = {}) => {
|
|
3629
|
+
const metadata = modelMetadata(model);
|
|
3630
|
+
const requestedVerbosity = resolveOutputVerbosity();
|
|
3631
|
+
const textVerbosity = supportsVerbosity(model) ? requestedVerbosity === "default" ? metadata?.defaultVerbosity ?? "medium" : requestedVerbosity : void 0;
|
|
3632
|
+
const speedMode = resolveSpeedMode();
|
|
3633
|
+
const serviceTier = speedMode === "ultrafast" && metadata?.supportsUltrafast === true ? ULTRAFAST_SERVICE_TIER : speedMode === "fast" && (metadata?.supportsFast ?? supportsCodexFastMode(model?.id)) ? FAST_SERVICE_TIER : void 0;
|
|
3634
|
+
const inputImageDetail = normalizeInputImageDetail(resolveInputImageDetail());
|
|
3635
|
+
const onPayload = options.onPayload;
|
|
3636
|
+
return {
|
|
3637
|
+
...options,
|
|
3638
|
+
...textVerbosity === void 0 ? {} : { textVerbosity },
|
|
3639
|
+
...serviceTier === void 0 ? {} : { serviceTier },
|
|
3640
|
+
async onPayload(payload, requestModel) {
|
|
3641
|
+
const preferred = {
|
|
3642
|
+
...payload,
|
|
3643
|
+
...textVerbosity === void 0 ? {} : { text: {
|
|
3644
|
+
...payload.text ?? {},
|
|
3645
|
+
verbosity: textVerbosity
|
|
3646
|
+
} },
|
|
3647
|
+
...serviceTier === void 0 ? {} : { service_tier: serviceTier }
|
|
3648
|
+
};
|
|
3649
|
+
const managed = compaction?.preparePayload(preferred, model?.contextWindow) ?? preferred;
|
|
3650
|
+
const next = await onPayload?.(managed, requestModel);
|
|
3651
|
+
const detailed = inputImageDetail === "auto" ? next ?? managed : withInputImageDetail(next ?? managed, inputImageDetail);
|
|
3652
|
+
return {
|
|
3653
|
+
...detailed,
|
|
3654
|
+
...textVerbosity === void 0 ? {} : { text: {
|
|
3655
|
+
...detailed.text ?? {},
|
|
3656
|
+
verbosity: textVerbosity
|
|
3657
|
+
} },
|
|
3658
|
+
...serviceTier === void 0 ? {} : { service_tier: serviceTier }
|
|
3659
|
+
};
|
|
3660
|
+
}
|
|
3661
|
+
};
|
|
3662
|
+
};
|
|
3663
|
+
const getModels = () => (catalog?.getModels() ?? provider.getModels()).map((model) => {
|
|
3664
|
+
const maximum = modelContextMaximum(model);
|
|
3665
|
+
const mode = resolveContextMode();
|
|
3666
|
+
if (model.id === "gpt-5.3-codex-spark" || !["extended", "custom"].includes(mode)) return model;
|
|
3667
|
+
if (mode === "extended") {
|
|
3668
|
+
const contextWindow = maximum;
|
|
3669
|
+
return {
|
|
3670
|
+
...model,
|
|
3671
|
+
contextWindow
|
|
3672
|
+
};
|
|
3673
|
+
}
|
|
3674
|
+
const requested = clampModelContext(resolveCustomContextWindow(customContextModelKey(model.id)), maximum, model.contextWindow);
|
|
3675
|
+
return {
|
|
3676
|
+
...model,
|
|
3677
|
+
contextWindow: requested
|
|
3678
|
+
};
|
|
3679
|
+
});
|
|
3680
|
+
const networkIterable = (factory, options) => {
|
|
3681
|
+
let iterator;
|
|
3682
|
+
let prepared;
|
|
3683
|
+
const step = async (method, value) => {
|
|
3684
|
+
const request = await (prepared ??= connection?.prepare(options) ?? Promise.resolve({ options }));
|
|
3685
|
+
const result = await runNetwork("model", () => {
|
|
3686
|
+
iterator ??= factory(compaction?.requestOptions(request.options) ?? request.options)[Symbol.asyncIterator]();
|
|
3687
|
+
return iterator[method]?.(value) ?? (method === "throw" ? Promise.reject(value) : Promise.resolve({
|
|
3688
|
+
done: true,
|
|
3689
|
+
value
|
|
3690
|
+
}));
|
|
3691
|
+
}, compaction?.networkOptions(request.network) ?? request.network);
|
|
3692
|
+
return result.done ? result : {
|
|
3693
|
+
...result,
|
|
3694
|
+
value: normalizeTransportEvent(result.value, request.options?.signal)
|
|
3695
|
+
};
|
|
3696
|
+
};
|
|
3697
|
+
return {
|
|
3698
|
+
[Symbol.asyncIterator]() {
|
|
3699
|
+
return this;
|
|
3700
|
+
},
|
|
3701
|
+
next: (value) => step("next", value),
|
|
3702
|
+
return: (value) => iterator ? step("return", value) : Promise.resolve({
|
|
3703
|
+
done: true,
|
|
3704
|
+
value
|
|
3705
|
+
}),
|
|
3706
|
+
throw: (error) => iterator ? step("throw", error) : Promise.reject(error)
|
|
3707
|
+
};
|
|
3686
3708
|
};
|
|
3709
|
+
return Object.freeze({
|
|
3710
|
+
...provider,
|
|
3711
|
+
auth: Object.freeze({
|
|
3712
|
+
...provider.auth,
|
|
3713
|
+
apiKey: requestToken
|
|
3714
|
+
}),
|
|
3715
|
+
getModels,
|
|
3716
|
+
stream: (model, context, options) => networkIterable((prepared) => provider.stream(model, context, prepared), withPreferences(model, options)),
|
|
3717
|
+
streamSimple: (model, context, options) => networkIterable((prepared) => provider.streamSimple(model, context, prepared), withPreferences(model, options))
|
|
3718
|
+
});
|
|
3687
3719
|
}
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3720
|
+
Object.freeze([
|
|
3721
|
+
"0.82.1",
|
|
3722
|
+
"0.85.1",
|
|
3723
|
+
"0.87.1"
|
|
3724
|
+
]);
|
|
3725
|
+
//#endregion
|
|
3726
|
+
//#region src/model-catalog.js
|
|
3727
|
+
const CODEX_MODELS_URL = `https://chatgpt.com/backend-api/codex/models?client_version=${encodeURIComponent(PACKAGE_VERSION)}`;
|
|
3728
|
+
const LEVELS = [
|
|
3729
|
+
"off",
|
|
3730
|
+
"minimal",
|
|
3731
|
+
"low",
|
|
3732
|
+
"medium",
|
|
3733
|
+
"high",
|
|
3734
|
+
"xhigh",
|
|
3735
|
+
"max"
|
|
3736
|
+
];
|
|
3737
|
+
const DEFAULT_REFRESH_TIMEOUT_MS = 1e4;
|
|
3738
|
+
const record$3 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3739
|
+
const nonEmpty$1 = (value) => typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
|
|
3740
|
+
const positiveInteger = (value) => Number.isSafeInteger(value) && value > 0 ? value : void 0;
|
|
3741
|
+
function upgradeModelId(value) {
|
|
3742
|
+
return (typeof value.upgrade === "string" ? nonEmpty$1(value.upgrade) : record$3(value.upgrade) ? nonEmpty$1(value.upgrade.model) : void 0) ?? (record$3(value.upgrade_info) ? nonEmpty$1(value.upgrade_info.model) : void 0) ?? (record$3(value.upgradeInfo) ? nonEmpty$1(value.upgradeInfo.model) : void 0);
|
|
3697
3743
|
}
|
|
3698
|
-
|
|
3699
|
-
const
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
const
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3744
|
+
function retirementMillis(value) {
|
|
3745
|
+
for (const source of [
|
|
3746
|
+
value,
|
|
3747
|
+
value.upgrade,
|
|
3748
|
+
value.upgrade_info,
|
|
3749
|
+
value.upgradeInfo
|
|
3750
|
+
]) {
|
|
3751
|
+
if (!record$3(source)) continue;
|
|
3752
|
+
for (const raw of [source.retirement_at, source.retirementAt]) {
|
|
3753
|
+
const seconds = positiveInteger(raw);
|
|
3754
|
+
if (seconds !== void 0 && Number.isSafeInteger(seconds * 1e3)) return seconds * 1e3;
|
|
3755
|
+
if (typeof raw === "string" && /^\d{4}-\d{2}-\d{2}T[\d:.]+(?:Z|[+-]\d{2}:\d{2})$/u.test(raw)) {
|
|
3756
|
+
const parsed = Date.parse(raw);
|
|
3757
|
+
if (Number.isFinite(parsed) && parsed > 0) return parsed;
|
|
3758
|
+
}
|
|
3712
3759
|
}
|
|
3713
|
-
chunks.push(value);
|
|
3714
3760
|
}
|
|
3715
|
-
const body = Buffer.concat(chunks.map((chunk) => Buffer.from(chunk)), bytes).toString("utf8");
|
|
3716
|
-
try {
|
|
3717
|
-
return JSON.parse(body);
|
|
3718
|
-
} catch {
|
|
3719
|
-
throw new Error("Codex returned an unreadable image response");
|
|
3720
|
-
}
|
|
3721
|
-
}
|
|
3722
|
-
/** Strictly decode one PNG returned by the subscription backend. */
|
|
3723
|
-
function decodeCodexPng(value, maximumBytes) {
|
|
3724
|
-
const encoded = nonEmpty(value);
|
|
3725
|
-
const padding = encoded?.endsWith("==") ? 2 : encoded?.endsWith("=") ? 1 : 0;
|
|
3726
|
-
if (encoded === void 0 || encoded.length % 4 !== 0 || !validBase64Body(encoded, encoded.length - padding)) throw new Error("Codex returned an invalid base64 PNG");
|
|
3727
|
-
const decodedBytes = encoded.length / 4 * 3 - padding;
|
|
3728
|
-
if (decodedBytes > maximumBytes) throw new Error("Codex image exceeds the image size limit");
|
|
3729
|
-
const data = Buffer.from(encoded, "base64");
|
|
3730
|
-
if (data.length !== decodedBytes || data.length < PNG_SIGNATURE.length || !data.subarray(0, PNG_SIGNATURE.length).equals(PNG_SIGNATURE)) throw new Error("Codex returned an invalid PNG");
|
|
3731
|
-
return new Uint8Array(data);
|
|
3732
3761
|
}
|
|
3733
|
-
function
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3762
|
+
function retirementNotice(value, now = Date.now(), targetDisplayName) {
|
|
3763
|
+
if (!record$3(value) || !Number.isFinite(now)) return void 0;
|
|
3764
|
+
const at = retirementMillis(value);
|
|
3765
|
+
if (at === void 0 || at <= now) return void 0;
|
|
3766
|
+
const date = new Date(at);
|
|
3767
|
+
if (!Number.isFinite(date.getTime())) return void 0;
|
|
3768
|
+
const dateText = `${String(date.getUTCFullYear()).padStart(4, "0")}-${String(date.getUTCMonth() + 1).padStart(2, "0")}-${String(date.getUTCDate()).padStart(2, "0")}`;
|
|
3769
|
+
const upgradeTo = upgradeModelId(value);
|
|
3770
|
+
const upgradeName = upgradeTo === void 0 ? void 0 : nonEmpty$1(targetDisplayName) ?? upgradeTo;
|
|
3738
3771
|
return {
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
height: value.height,
|
|
3744
|
-
...value.name === void 0 ? {} : { name: value.name },
|
|
3745
|
-
...originalDimensions === void 0 ? {} : { originalDimensions }
|
|
3772
|
+
text: `Retires on ${dateText} (UTC).${upgradeName === void 0 ? "" : ` Switch to ${upgradeName} to keep working.`}`,
|
|
3773
|
+
at,
|
|
3774
|
+
upgradeTo,
|
|
3775
|
+
dateText
|
|
3746
3776
|
};
|
|
3747
3777
|
}
|
|
3748
|
-
function
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3778
|
+
function reasoningMap(levels) {
|
|
3779
|
+
const supported = new Set((Array.isArray(levels) ? levels : []).map((level) => nonEmpty$1(record$3(level) ? level.effort : void 0)).filter(Boolean));
|
|
3780
|
+
const map = Object.fromEntries(LEVELS.map((level) => [level, null]));
|
|
3781
|
+
if (supported.has("none")) map.off = "none";
|
|
3782
|
+
for (const level of LEVELS.slice(1)) if (supported.has(level)) map[level] = level;
|
|
3783
|
+
return map;
|
|
3753
3784
|
}
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3785
|
+
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);
|
|
3786
|
+
const speedTierIds = (value) => [...Array.isArray(value.additional_speed_tiers) ? value.additional_speed_tiers : [], ...Array.isArray(value.service_tiers) ? value.service_tiers.map((tier) => tier?.id) : []];
|
|
3787
|
+
function unsupportedCapabilities(value) {
|
|
3788
|
+
const reasoning = capabilityNames((value.supported_reasoning_levels ?? []).map((item) => item?.effort)).filter((level) => !["none", ...LEVELS.slice(1)].includes(level));
|
|
3789
|
+
const inputs = capabilityNames(Array.isArray(value.input_modalities) ? value.input_modalities : []).filter((input) => !["text", "image"].includes(input));
|
|
3790
|
+
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) => ![
|
|
3791
|
+
"auto",
|
|
3792
|
+
"default",
|
|
3793
|
+
"standard",
|
|
3794
|
+
"fast",
|
|
3795
|
+
"priority",
|
|
3796
|
+
"ultrafast"
|
|
3797
|
+
].includes(tier));
|
|
3798
|
+
return {
|
|
3799
|
+
...reasoning.length ? { reasoning } : {},
|
|
3800
|
+
...inputs.length ? { inputs } : {},
|
|
3801
|
+
...speeds.length ? { speeds } : {}
|
|
3802
|
+
};
|
|
3758
3803
|
}
|
|
3759
|
-
function
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
return
|
|
3804
|
+
function visibleModel(value, displayNames, now) {
|
|
3805
|
+
if (!record$3(value)) return void 0;
|
|
3806
|
+
const id = nonEmpty$1(value.slug);
|
|
3807
|
+
const reserve = id === "gpt-reserve" && ["list", "hide"].includes(value.visibility);
|
|
3808
|
+
if (id === void 0 || value.visibility !== "list" && !reserve) return void 0;
|
|
3809
|
+
const supported = Array.isArray(value.supported_reasoning_levels) ? value.supported_reasoning_levels : [];
|
|
3810
|
+
const input = Array.isArray(value.input_modalities) ? value.input_modalities.filter((item) => ["text", "image"].includes(item)) : ["text", "image"];
|
|
3811
|
+
const unsupported = unsupportedCapabilities({
|
|
3764
3812
|
...value,
|
|
3765
|
-
|
|
3813
|
+
supported_reasoning_levels: supported
|
|
3766
3814
|
});
|
|
3815
|
+
const upgradeTo = upgradeModelId(value);
|
|
3816
|
+
const retirement = retirementNotice(value, now, displayNames.get(upgradeTo));
|
|
3817
|
+
const notice = retirement?.text;
|
|
3818
|
+
const description = nonEmpty$1(value.description);
|
|
3819
|
+
return {
|
|
3820
|
+
...Object.keys(unsupported).length ? { unsupported } : {},
|
|
3821
|
+
id,
|
|
3822
|
+
name: `${reserve ? "GPT-Reserve (Experimental)" : nonEmpty$1(value.display_name) ?? id}${retirement === void 0 ? "" : ` (retires ${retirement.dateText})`}`,
|
|
3823
|
+
description: notice === void 0 ? description : description === void 0 ? notice : `${description}\n${notice}`,
|
|
3824
|
+
...retirement === void 0 ? {} : { retirement: {
|
|
3825
|
+
at: retirement.at,
|
|
3826
|
+
upgradeTo: retirement.upgradeTo
|
|
3827
|
+
} },
|
|
3828
|
+
priority: Number.isFinite(value.priority) ? value.priority : 0,
|
|
3829
|
+
input: input.length > 0 ? input : ["text"],
|
|
3830
|
+
contextWindow: positiveInteger(value.context_window) ?? positiveInteger(value.max_context_window),
|
|
3831
|
+
...positiveInteger(value.max_context_window) === void 0 ? {} : { maxContextWindow: value.max_context_window },
|
|
3832
|
+
reasoning: supported.length > 0,
|
|
3833
|
+
thinkingLevelMap: reasoningMap(supported),
|
|
3834
|
+
supportVerbosity: value.support_verbosity === true,
|
|
3835
|
+
defaultVerbosity: [
|
|
3836
|
+
"low",
|
|
3837
|
+
"medium",
|
|
3838
|
+
"high"
|
|
3839
|
+
].includes(value.default_verbosity) ? value.default_verbosity : void 0,
|
|
3840
|
+
supportsFast: speedTierIds(value).some((tier) => tier === "fast" || tier === "priority"),
|
|
3841
|
+
supportsUltrafast: speedTierIds(value).includes("ultrafast")
|
|
3842
|
+
};
|
|
3767
3843
|
}
|
|
3768
|
-
function
|
|
3769
|
-
|
|
3770
|
-
const
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3844
|
+
function parseOfficialModelCatalog(value, now = Date.now()) {
|
|
3845
|
+
if (!record$3(value) || !Array.isArray(value.models)) throw new Error("Codex returned a malformed model catalog");
|
|
3846
|
+
const displayNames = /* @__PURE__ */ new Map();
|
|
3847
|
+
for (const model of value.models) {
|
|
3848
|
+
if (!record$3(model)) continue;
|
|
3849
|
+
const id = nonEmpty$1(model.slug);
|
|
3850
|
+
const name = nonEmpty$1(model.display_name);
|
|
3851
|
+
if (id !== void 0 && name !== void 0 && !displayNames.has(id)) displayNames.set(id, name);
|
|
3852
|
+
}
|
|
3853
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3854
|
+
return value.models.map((model) => visibleModel(model, displayNames, now)).filter((model) => model !== void 0 && !seen.has(model.id) && seen.add(model.id)).sort((left, right) => Number(left.id === "gpt-reserve") - Number(right.id === "gpt-reserve") || right.priority - left.priority);
|
|
3855
|
+
}
|
|
3856
|
+
function mergeModel(baseModels, remote) {
|
|
3857
|
+
const base = baseModels.find((model) => model.id === remote.id) ?? baseModels.find((model) => model.id !== "gpt-5.3-codex-spark") ?? baseModels[0];
|
|
3858
|
+
if (base === void 0) return void 0;
|
|
3859
|
+
return {
|
|
3860
|
+
...base,
|
|
3861
|
+
id: remote.id,
|
|
3862
|
+
name: remote.name,
|
|
3863
|
+
...remote.retirement === void 0 || remote.description === void 0 ? {} : { description: remote.description },
|
|
3864
|
+
input: remote.input,
|
|
3865
|
+
reasoning: remote.reasoning,
|
|
3866
|
+
thinkingLevelMap: remote.thinkingLevelMap,
|
|
3867
|
+
...remote.contextWindow === void 0 ? {} : { contextWindow: remote.contextWindow },
|
|
3868
|
+
...remote.maxContextWindow === void 0 ? {} : { maxContextWindow: remote.maxContextWindow },
|
|
3869
|
+
...base.id === remote.id ? {} : { cost: {
|
|
3870
|
+
input: 0,
|
|
3871
|
+
output: 0,
|
|
3872
|
+
cacheRead: 0,
|
|
3873
|
+
cacheWrite: 0
|
|
3874
|
+
} }
|
|
3875
|
+
};
|
|
3876
|
+
}
|
|
3877
|
+
function createOfficialModelCatalog(options = {}) {
|
|
3878
|
+
const fetchCatalog = options.fetch ?? fetch;
|
|
3879
|
+
const getNow = options.now ?? Date.now;
|
|
3880
|
+
const scheduleTimeout = options.setTimeout ?? setTimeout;
|
|
3881
|
+
const cancelTimeout = options.clearTimeout ?? clearTimeout;
|
|
3882
|
+
const timeoutMs = Number.isFinite(options.timeoutMs) && options.timeoutMs > 0 ? options.timeoutMs : DEFAULT_REFRESH_TIMEOUT_MS;
|
|
3883
|
+
let models;
|
|
3884
|
+
let metadata = /* @__PURE__ */ new Map();
|
|
3885
|
+
let etag;
|
|
3886
|
+
let rawCatalog;
|
|
3887
|
+
let revision = 0;
|
|
3888
|
+
let refreshing;
|
|
3889
|
+
let generation = 0;
|
|
3890
|
+
let refreshStatus = "idle";
|
|
3891
|
+
const refresh = ({ signal } = {}) => {
|
|
3892
|
+
if (signal?.aborted) return Promise.reject(signal.reason ?? /* @__PURE__ */ new Error("Codex model catalog refresh aborted"));
|
|
3893
|
+
if (refreshing?.generation === generation) return refreshing.promise;
|
|
3894
|
+
const currentGeneration = generation;
|
|
3895
|
+
refreshStatus = "refreshing";
|
|
3896
|
+
let outcome = "idle";
|
|
3897
|
+
const controller = new AbortController();
|
|
3898
|
+
const abort = () => {
|
|
3899
|
+
if (!controller.signal.aborted) controller.abort(signal?.reason ?? /* @__PURE__ */ new Error("Codex model catalog refresh aborted"));
|
|
3900
|
+
};
|
|
3901
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
3902
|
+
const requestSignal = controller.signal;
|
|
3903
|
+
let timer;
|
|
3904
|
+
const timeoutError = /* @__PURE__ */ new Error("Codex model catalog refresh timed out");
|
|
3905
|
+
const work = (async () => {
|
|
3906
|
+
const { access, accountId } = await readSubscriptionCredentials(options.getAuth, options.readCredential, requestSignal);
|
|
3907
|
+
if (currentGeneration !== generation || requestSignal.aborted) return false;
|
|
3908
|
+
if (typeof access !== "string" || access.length === 0 || typeof accountId !== "string" || accountId.length === 0) return false;
|
|
3909
|
+
const headers = {
|
|
3910
|
+
authorization: `Bearer ${access}`,
|
|
3911
|
+
"chatgpt-account-id": accountId,
|
|
3912
|
+
accept: "application/json",
|
|
3913
|
+
originator: "pi",
|
|
3914
|
+
"user-agent": USER_AGENT,
|
|
3915
|
+
...etag === void 0 ? {} : { "if-none-match": etag }
|
|
3916
|
+
};
|
|
3917
|
+
const response = await fetchCatalog(CODEX_MODELS_URL, {
|
|
3918
|
+
method: "GET",
|
|
3919
|
+
redirect: "error",
|
|
3920
|
+
headers,
|
|
3921
|
+
signal: requestSignal
|
|
3922
|
+
});
|
|
3923
|
+
if (currentGeneration !== generation || requestSignal.aborted) return false;
|
|
3924
|
+
if (response.status === 304) {
|
|
3925
|
+
outcome = "ok";
|
|
3926
|
+
if (rawCatalog === void 0) return false;
|
|
3927
|
+
const remote = parseOfficialModelCatalog(rawCatalog, getNow());
|
|
3928
|
+
const next = remote.map((model) => mergeModel(options.baseModels(), model)).filter(Boolean);
|
|
3929
|
+
if (next.length === 0 || JSON.stringify(next) === JSON.stringify(models)) return false;
|
|
3930
|
+
models = next;
|
|
3931
|
+
metadata = new Map(remote.map((model) => [model.id, model]));
|
|
3932
|
+
revision += 1;
|
|
3933
|
+
options.onUpdated?.();
|
|
3934
|
+
return true;
|
|
3935
|
+
}
|
|
3936
|
+
if (!response.ok) throw new Error(`Codex model catalog failed (HTTP ${response.status})`);
|
|
3937
|
+
const raw = await response.json();
|
|
3938
|
+
const remote = parseOfficialModelCatalog(raw, getNow());
|
|
3939
|
+
if (currentGeneration !== generation || requestSignal.aborted) return false;
|
|
3940
|
+
if (remote.length === 0) throw new Error("Codex returned an empty model catalog");
|
|
3941
|
+
const baseModels = options.baseModels();
|
|
3942
|
+
const next = remote.map((model) => mergeModel(baseModels, model)).filter(Boolean);
|
|
3943
|
+
if (next.length === 0) throw new Error("Codex model catalog has no compatible models");
|
|
3944
|
+
if (currentGeneration !== generation || requestSignal.aborted) return false;
|
|
3945
|
+
models = next;
|
|
3946
|
+
rawCatalog = raw;
|
|
3947
|
+
metadata = new Map(remote.map((model) => [model.id, model]));
|
|
3948
|
+
etag = nonEmpty$1(response.headers.get("etag")) ?? etag;
|
|
3949
|
+
revision += 1;
|
|
3950
|
+
outcome = "ok";
|
|
3951
|
+
options.onUpdated?.();
|
|
3952
|
+
return true;
|
|
3953
|
+
})();
|
|
3954
|
+
let rejectAborted;
|
|
3955
|
+
const abortPromise = new Promise((_, reject) => {
|
|
3956
|
+
rejectAborted = () => reject(requestSignal.reason ?? /* @__PURE__ */ new Error("Codex model catalog refresh aborted"));
|
|
3957
|
+
if (requestSignal.aborted) rejectAborted();
|
|
3958
|
+
else requestSignal.addEventListener("abort", rejectAborted, { once: true });
|
|
3959
|
+
});
|
|
3960
|
+
timer = scheduleTimeout(() => controller.abort(timeoutError), timeoutMs);
|
|
3961
|
+
timer.unref?.();
|
|
3962
|
+
const promise = Promise.race([work, abortPromise]).catch((error) => {
|
|
3963
|
+
outcome = "failed";
|
|
3964
|
+
throw error;
|
|
3965
|
+
}).finally(() => {
|
|
3966
|
+
cancelTimeout(timer);
|
|
3967
|
+
signal?.removeEventListener("abort", abort);
|
|
3968
|
+
requestSignal.removeEventListener("abort", rejectAborted);
|
|
3969
|
+
if (refreshing?.promise === promise) {
|
|
3970
|
+
refreshing = void 0;
|
|
3971
|
+
refreshStatus = outcome;
|
|
3972
|
+
}
|
|
3973
|
+
});
|
|
3974
|
+
refreshing = {
|
|
3975
|
+
generation: currentGeneration,
|
|
3976
|
+
promise,
|
|
3977
|
+
cancel: () => controller.abort()
|
|
3978
|
+
};
|
|
3979
|
+
return promise;
|
|
3776
3980
|
};
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3981
|
+
return Object.freeze({
|
|
3982
|
+
refresh,
|
|
3983
|
+
getModels: () => models ?? [],
|
|
3984
|
+
metadata: (modelId) => metadata.get(modelId),
|
|
3985
|
+
revision: () => revision,
|
|
3986
|
+
capabilityGaps: () => [...metadata.values()].filter((model) => model.unsupported && /^[a-z][a-z0-9._-]{0,79}$/u.test(model.id)).slice(0, 20).map((model) => ({
|
|
3987
|
+
model: model.id,
|
|
3988
|
+
...structuredClone(model.unsupported)
|
|
3989
|
+
})),
|
|
3990
|
+
status: () => ({
|
|
3991
|
+
source: models === void 0 ? "unavailable" : "online",
|
|
3992
|
+
refresh: refreshStatus
|
|
3993
|
+
}),
|
|
3994
|
+
clear() {
|
|
3995
|
+
generation += 1;
|
|
3996
|
+
const flight = refreshing;
|
|
3997
|
+
refreshing = void 0;
|
|
3998
|
+
flight?.cancel();
|
|
3999
|
+
models = void 0;
|
|
4000
|
+
rawCatalog = void 0;
|
|
4001
|
+
metadata = /* @__PURE__ */ new Map();
|
|
4002
|
+
etag = void 0;
|
|
4003
|
+
refreshStatus = "idle";
|
|
4004
|
+
revision += 1;
|
|
4005
|
+
options.onUpdated?.();
|
|
4006
|
+
}
|
|
3790
4007
|
});
|
|
3791
|
-
if (new Set(references.map((value) => value.attachmentId)).size !== references.length) throw new Error("referenceImages must not contain duplicates");
|
|
3792
|
-
const images = [];
|
|
3793
|
-
let totalBytes = 0;
|
|
3794
|
-
for (const reference of references) {
|
|
3795
|
-
const stored = await attachments.readImage(reference, signal);
|
|
3796
|
-
totalBytes += stored.data.byteLength;
|
|
3797
|
-
if (totalBytes > attachments.imageLimits.maxMessageImageBytes) throw new Error("referenceImages exceed the DSH message image limit");
|
|
3798
|
-
images.push({ image_url: `data:${stored.ref.mediaType};base64,${Buffer.from(stored.data).toString("base64")}` });
|
|
3799
|
-
}
|
|
3800
|
-
return images;
|
|
3801
|
-
}
|
|
3802
|
-
function imageContent(value) {
|
|
3803
|
-
const label = typeof value.size === "string" && value.size.length > 0 ? `Generated a ${value.size} image.` : "Generated an image.";
|
|
3804
|
-
return [{
|
|
3805
|
-
type: "text",
|
|
3806
|
-
text: value.localPath === void 0 ? label : `${label}\nOriginal PNG saved on the DSH host at: ${JSON.stringify(value.localPath)}. Read this file or copy it to the workspace with a .png extension; this host path is not a browser URL.`
|
|
3807
|
-
}, {
|
|
3808
|
-
type: "image",
|
|
3809
|
-
attachment: imageReference(value.image)
|
|
3810
|
-
}];
|
|
3811
4008
|
}
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
},
|
|
3852
|
-
height: {
|
|
3853
|
-
type: "integer",
|
|
3854
|
-
required: true
|
|
3855
|
-
}
|
|
3856
|
-
}
|
|
3857
|
-
}
|
|
3858
|
-
}
|
|
3859
|
-
},
|
|
3860
|
-
original: {
|
|
3861
|
-
type: "object",
|
|
3862
|
-
required: true,
|
|
3863
|
-
additionalProperties: false,
|
|
3864
|
-
properties: {
|
|
3865
|
-
assetId: {
|
|
3866
|
-
type: "string",
|
|
3867
|
-
required: true
|
|
3868
|
-
},
|
|
3869
|
-
mediaType: {
|
|
3870
|
-
type: "string",
|
|
3871
|
-
enum: ["image/png"],
|
|
3872
|
-
required: true
|
|
3873
|
-
},
|
|
3874
|
-
bytes: {
|
|
3875
|
-
type: "integer",
|
|
3876
|
-
required: true
|
|
3877
|
-
},
|
|
3878
|
-
width: {
|
|
3879
|
-
type: "integer",
|
|
3880
|
-
required: true
|
|
3881
|
-
},
|
|
3882
|
-
height: {
|
|
3883
|
-
type: "integer",
|
|
3884
|
-
required: true
|
|
3885
|
-
},
|
|
3886
|
-
name: {
|
|
3887
|
-
type: "string",
|
|
3888
|
-
required: true
|
|
3889
|
-
},
|
|
3890
|
-
sha256: {
|
|
3891
|
-
type: "string",
|
|
3892
|
-
required: true
|
|
3893
|
-
}
|
|
3894
|
-
}
|
|
3895
|
-
},
|
|
3896
|
-
background: { type: "string" },
|
|
3897
|
-
requestedModel: { type: "string" },
|
|
3898
|
-
reportedModel: { type: "string" },
|
|
3899
|
-
requestedSize: { type: "string" },
|
|
3900
|
-
localPath: {
|
|
3901
|
-
type: "string",
|
|
3902
|
-
required: true,
|
|
3903
|
-
description: "Absolute path to the original PNG on the DSH host."
|
|
3904
|
-
},
|
|
3905
|
-
quality: { type: "string" },
|
|
3906
|
-
size: { type: "string" }
|
|
3907
|
-
}
|
|
4009
|
+
//#endregion
|
|
4010
|
+
//#region src/codex-search.js
|
|
4011
|
+
const CODEX_SEARCH_PROVIDER_ID = "codex-subscription";
|
|
4012
|
+
const CODEX_AUTO_SEARCH_PROVIDER_ID = "codex-subscription-auto";
|
|
4013
|
+
const CODEX_SEARCH_URL = "https://chatgpt.com/backend-api/codex/alpha/search";
|
|
4014
|
+
const DEFAULT_MODEL = "gpt-5.6-luna";
|
|
4015
|
+
const MAX_OUTPUT_TOKENS = 4096;
|
|
4016
|
+
const MAX_SOURCE_DATE = 64;
|
|
4017
|
+
const record$2 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
4018
|
+
const nonEmpty = (value) => typeof value === "string" && value.length > 0 ? value : void 0;
|
|
4019
|
+
const displayText = (value) => {
|
|
4020
|
+
const text = nonEmpty(value)?.replace(/\s+/gu, " ").trim();
|
|
4021
|
+
if (text === void 0 || text.length === 0) return void 0;
|
|
4022
|
+
return text;
|
|
4023
|
+
};
|
|
4024
|
+
const boundedDisplayText = (value, maximum) => {
|
|
4025
|
+
const text = displayText(value);
|
|
4026
|
+
if (text === void 0 || text.length <= maximum) return text;
|
|
4027
|
+
return `${text.slice(0, maximum - 1)}…`;
|
|
4028
|
+
};
|
|
4029
|
+
function sourceOf(value) {
|
|
4030
|
+
if (!record$2(value)) return void 0;
|
|
4031
|
+
const url = nonEmpty(value.url);
|
|
4032
|
+
if (url === void 0) return void 0;
|
|
4033
|
+
let parsed;
|
|
4034
|
+
try {
|
|
4035
|
+
parsed = new URL(url);
|
|
4036
|
+
} catch {
|
|
4037
|
+
return;
|
|
4038
|
+
}
|
|
4039
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return void 0;
|
|
4040
|
+
const title = displayText(value.title) ?? parsed.hostname;
|
|
4041
|
+
const snippet = displayText(value.snippet);
|
|
4042
|
+
const publishedAt = boundedDisplayText(value.published_at, MAX_SOURCE_DATE) ?? boundedDisplayText(value.publishedAt, MAX_SOURCE_DATE);
|
|
4043
|
+
return {
|
|
4044
|
+
url,
|
|
4045
|
+
title,
|
|
4046
|
+
...snippet === void 0 ? {} : { snippet },
|
|
4047
|
+
...publishedAt === void 0 ? {} : { publishedAt }
|
|
3908
4048
|
};
|
|
3909
4049
|
}
|
|
3910
|
-
function
|
|
3911
|
-
|
|
3912
|
-
const
|
|
3913
|
-
|
|
4050
|
+
function parseSearchResponse(value) {
|
|
4051
|
+
if (!record$2(value) || !Array.isArray(value.results)) throw new Error("Codex returned a malformed search response");
|
|
4052
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4053
|
+
const sources = [];
|
|
4054
|
+
for (const result of value.results ?? []) {
|
|
4055
|
+
const source = sourceOf(result);
|
|
4056
|
+
if (source === void 0 || seen.has(source.url)) continue;
|
|
4057
|
+
seen.add(source.url);
|
|
4058
|
+
sources.push(source);
|
|
4059
|
+
}
|
|
3914
4060
|
return {
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
quality: nonEmpty(value.quality),
|
|
3918
|
-
size: nonEmpty(value.size),
|
|
3919
|
-
reportedModel: nonEmpty(value.model)
|
|
4061
|
+
sources,
|
|
4062
|
+
truncated: false
|
|
3920
4063
|
};
|
|
3921
4064
|
}
|
|
3922
|
-
/** Create the DSH
|
|
3923
|
-
function
|
|
3924
|
-
const
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
type: "string",
|
|
3937
|
-
required: true,
|
|
3938
|
-
description: "A complete, production-ready description of the image to generate."
|
|
3939
|
-
},
|
|
3940
|
-
size: {
|
|
3941
|
-
type: "string",
|
|
3942
|
-
description: "Optional GPT Image 2 output size. Use auto unless the user requests an exact valid widthxheight resolution."
|
|
3943
|
-
},
|
|
3944
|
-
quality: {
|
|
3945
|
-
type: "string",
|
|
3946
|
-
enum: [
|
|
3947
|
-
"auto",
|
|
3948
|
-
"low",
|
|
3949
|
-
"medium",
|
|
3950
|
-
"high",
|
|
3951
|
-
"xhigh",
|
|
3952
|
-
"max"
|
|
3953
|
-
],
|
|
3954
|
-
description: "Optional rendering quality. Use auto unless the user requests draft speed or final quality."
|
|
3955
|
-
},
|
|
3956
|
-
background: {
|
|
3957
|
-
type: "string",
|
|
3958
|
-
enum: [
|
|
3959
|
-
"auto",
|
|
3960
|
-
"transparent",
|
|
3961
|
-
"opaque"
|
|
3962
|
-
],
|
|
3963
|
-
description: "Optional background mode. Request transparent only when the user needs transparency."
|
|
3964
|
-
},
|
|
3965
|
-
referenceImages: {
|
|
3966
|
-
type: "array",
|
|
3967
|
-
description: "Optional references to 1-5 selected images. For session images provide only attachmentId; the host resolves trusted metadata. Omit only for a new image; never drop an invalid reference to bypass editing.",
|
|
3968
|
-
items: {
|
|
3969
|
-
type: "object",
|
|
3970
|
-
additionalProperties: false,
|
|
3971
|
-
properties: {
|
|
3972
|
-
attachmentId: {
|
|
3973
|
-
type: "string",
|
|
3974
|
-
required: true,
|
|
3975
|
-
description: "Copy the complete attachmentId from the image block or read_image result: sha256:<64 lowercase hex>. Never pass a workspace path, absolute path, or filename. A bare 64-character hex digest is accepted and normalized to sha256:<64 lowercase hex>."
|
|
3976
|
-
},
|
|
3977
|
-
mediaType: {
|
|
3978
|
-
type: "string",
|
|
3979
|
-
...typeof options.getSessionMessages !== "function" ? { required: true } : {}
|
|
3980
|
-
},
|
|
3981
|
-
bytes: {
|
|
3982
|
-
type: "integer",
|
|
3983
|
-
...typeof options.getSessionMessages !== "function" ? { required: true } : {}
|
|
3984
|
-
},
|
|
3985
|
-
width: {
|
|
3986
|
-
type: "integer",
|
|
3987
|
-
...typeof options.getSessionMessages !== "function" ? { required: true } : {}
|
|
3988
|
-
},
|
|
3989
|
-
height: {
|
|
3990
|
-
type: "integer",
|
|
3991
|
-
...typeof options.getSessionMessages !== "function" ? { required: true } : {}
|
|
3992
|
-
},
|
|
3993
|
-
name: { type: "string" },
|
|
3994
|
-
originalDimensions: {
|
|
3995
|
-
type: "object",
|
|
3996
|
-
additionalProperties: false,
|
|
3997
|
-
properties: {
|
|
3998
|
-
width: {
|
|
3999
|
-
type: "integer",
|
|
4000
|
-
...typeof options.getSessionMessages !== "function" ? { required: true } : {}
|
|
4001
|
-
},
|
|
4002
|
-
height: {
|
|
4003
|
-
type: "integer",
|
|
4004
|
-
...typeof options.getSessionMessages !== "function" ? { required: true } : {}
|
|
4005
|
-
}
|
|
4006
|
-
}
|
|
4007
|
-
}
|
|
4008
|
-
}
|
|
4009
|
-
}
|
|
4010
|
-
}
|
|
4011
|
-
},
|
|
4012
|
-
output: {
|
|
4013
|
-
schema: imageOutputSchema(),
|
|
4014
|
-
render: (_args, value) => imageContent(value),
|
|
4015
|
-
presentationMeta: (_args, value) => ({
|
|
4016
|
-
kind: "codex-subscription-image",
|
|
4017
|
-
schemaVersion: 1,
|
|
4018
|
-
original: value.original,
|
|
4019
|
-
...value.requestedModel === void 0 ? {} : { requestedModel: value.requestedModel },
|
|
4020
|
-
...value.reportedModel === void 0 ? {} : { reportedModel: value.reportedModel },
|
|
4021
|
-
...value.requestedSize === void 0 ? {} : { requestedSize: value.requestedSize }
|
|
4022
|
-
})
|
|
4023
|
-
},
|
|
4024
|
-
timeoutMs: 300 * 1e3,
|
|
4025
|
-
isConcurrencySafe: () => false,
|
|
4026
|
-
async execute(args, exec) {
|
|
4027
|
-
assertImageOperation(options.getFeatures?.(), args.referenceImages !== void 0);
|
|
4028
|
-
const defaults = readImageDefaults(options.getFeatures?.());
|
|
4029
|
-
args = {
|
|
4030
|
-
...args,
|
|
4031
|
-
model: args.model ?? defaults.imageModel,
|
|
4032
|
-
quality: args.quality ?? defaults.imageQuality
|
|
4033
|
-
};
|
|
4034
|
-
const prompt = nonEmpty(args.prompt);
|
|
4035
|
-
if (prompt === void 0) throw new Error("prompt must be a non-empty string");
|
|
4036
|
-
const imageOptions = normalizeImageOptions(args);
|
|
4037
|
-
const { access, accountId } = await readSubscriptionCredentials(options.getAuth, options.readCredential, exec.signal);
|
|
4038
|
-
if (typeof access !== "string" || access.length === 0 || typeof accountId !== "string" || accountId.length === 0) throw new Error("ChatGPT subscription is not signed in");
|
|
4039
|
-
if (!attachments.imageLimits.mediaTypes.includes("image/png")) throw new Error("This DSH installation does not accept PNG image attachments");
|
|
4040
|
-
const maximumBytes = Math.min(attachments.imageLimits.maxImageBytes, attachments.imageLimits.maxMessageImageBytes);
|
|
4041
|
-
const editing = args.referenceImages !== void 0;
|
|
4042
|
-
const sessionMessages = editing && typeof options.getSessionMessages === "function" ? await options.getSessionMessages(exec.agent?.id) : void 0;
|
|
4043
|
-
const images = editing ? await editImages(args.referenceImages, attachments, exec.signal, sessionMessages ?? (options.getSessionMessages ? [] : void 0)) : void 0;
|
|
4065
|
+
/** Create the DSH web provider backed only by the ChatGPT subscription search endpoint. */
|
|
4066
|
+
function createCodexSearchProvider(options) {
|
|
4067
|
+
const fetchSearch = options.fetch ?? fetch;
|
|
4068
|
+
return Object.freeze({
|
|
4069
|
+
id: CODEX_SEARCH_PROVIDER_ID,
|
|
4070
|
+
available: () => true,
|
|
4071
|
+
async search(request, signal) {
|
|
4072
|
+
signal?.throwIfAborted();
|
|
4073
|
+
const preferences = readCapabilitySettings(options.resolvePreferences?.());
|
|
4074
|
+
if (preferences.searchMode === "disabled") throw new WebError("Codex search is disabled in subscription settings", "WEB_PROVIDER_UNAVAILABLE");
|
|
4075
|
+
const { access, accountId } = await readSubscriptionCredentials(options.getAuth, options.readCredential, signal);
|
|
4076
|
+
if (typeof access !== "string" || access.length === 0 || typeof accountId !== "string" || accountId.length === 0) throw new WebError("ChatGPT subscription is not signed in", "WEB_PROVIDER_CREDENTIAL_MISSING");
|
|
4077
|
+
const model = nonEmpty(options.resolveModel?.()) ?? DEFAULT_MODEL;
|
|
4078
|
+
const id = nonEmpty(options.resolveSessionId?.()) ?? randomUUID();
|
|
4044
4079
|
let response;
|
|
4045
|
-
assertImageOperation(options.getFeatures?.(), editing);
|
|
4046
4080
|
try {
|
|
4047
|
-
response = await
|
|
4081
|
+
response = await fetchSearch(CODEX_SEARCH_URL, {
|
|
4048
4082
|
method: "POST",
|
|
4049
4083
|
redirect: "error",
|
|
4050
4084
|
headers: {
|
|
@@ -4053,60 +4087,58 @@ function createCodexImageTool(options) {
|
|
|
4053
4087
|
accept: "application/json",
|
|
4054
4088
|
"content-type": "application/json",
|
|
4055
4089
|
originator: "pi",
|
|
4056
|
-
"x-codex-image-turn-id": String(exec.callId),
|
|
4057
4090
|
"user-agent": USER_AGENT
|
|
4058
4091
|
},
|
|
4059
4092
|
body: JSON.stringify({
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4093
|
+
id,
|
|
4094
|
+
model,
|
|
4095
|
+
input: request.query,
|
|
4096
|
+
commands: {
|
|
4097
|
+
search_query: [{ q: request.query }],
|
|
4098
|
+
response_length: "short"
|
|
4099
|
+
},
|
|
4100
|
+
settings: {
|
|
4101
|
+
allowed_callers: ["direct"],
|
|
4102
|
+
external_web_access: preferences.searchMode === "live"
|
|
4103
|
+
},
|
|
4104
|
+
max_output_tokens: MAX_OUTPUT_TOKENS
|
|
4066
4105
|
}),
|
|
4067
|
-
signal
|
|
4106
|
+
signal
|
|
4068
4107
|
});
|
|
4069
4108
|
} catch (error) {
|
|
4070
|
-
if (
|
|
4071
|
-
throw new
|
|
4109
|
+
if (signal?.aborted || error?.name === "AbortError") throw new WebError("Codex search aborted", "WEB_ABORTED", { cause: error });
|
|
4110
|
+
throw new WebError("Codex search request failed", "WEB_PROVIDER_ERROR", { cause: error });
|
|
4072
4111
|
}
|
|
4073
|
-
if (!response.ok) {
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4112
|
+
if (!response.ok) throw response.status === 401 || response.status === 403 ? new WebError("ChatGPT sign-in needs to be renewed", "WEB_PROVIDER_CREDENTIAL_MISSING") : new WebError(`Codex search request failed (HTTP ${response.status})`, "WEB_PROVIDER_ERROR");
|
|
4113
|
+
let value;
|
|
4114
|
+
try {
|
|
4115
|
+
value = await response.json();
|
|
4116
|
+
} catch (error) {
|
|
4117
|
+
throw new WebError("Codex returned an unreadable search response", "WEB_PROVIDER_ERROR", { cause: error });
|
|
4077
4118
|
}
|
|
4078
|
-
const metadata = responseMetadata(await readJsonWithin(response, encodedLimit(maximumBytes) + RESPONSE_ENVELOPE_BYTES));
|
|
4079
|
-
const data = decodeCodexPng(metadata.encoded, maximumBytes);
|
|
4080
|
-
const sessionId = exec.agent?.id;
|
|
4081
|
-
if (sessionId === void 0) throw new Error("Codex image generation requires a session-owned tool call");
|
|
4082
|
-
const original = await options.originalImages.save(String(sessionId), data);
|
|
4083
|
-
let ref;
|
|
4084
4119
|
try {
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4120
|
+
const result = parseSearchResponse(value);
|
|
4121
|
+
if (preferences.searchDomains.length > 0) result.sources = result.sources.filter((source) => {
|
|
4122
|
+
const hostname = new URL(source.url).hostname.toLowerCase();
|
|
4123
|
+
return preferences.searchDomains.some((domain) => hostname === domain || hostname.endsWith(`.${domain}`));
|
|
4089
4124
|
});
|
|
4125
|
+
return result;
|
|
4090
4126
|
} catch (error) {
|
|
4091
|
-
|
|
4092
|
-
throw error;
|
|
4127
|
+
throw new WebError("Codex returned a malformed search response", "WEB_PROVIDER_ERROR", { cause: error });
|
|
4093
4128
|
}
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
if (
|
|
4106
|
-
|
|
4107
|
-
source: { kind: "codex-subscription-image" }
|
|
4108
|
-
}));
|
|
4109
|
-
return result;
|
|
4129
|
+
}
|
|
4130
|
+
});
|
|
4131
|
+
}
|
|
4132
|
+
/** Route each request by its initiating model without changing the user's explicit overrides. */
|
|
4133
|
+
function createCodexAutoSearchProvider(options) {
|
|
4134
|
+
return Object.freeze({
|
|
4135
|
+
id: CODEX_AUTO_SEARCH_PROVIDER_ID,
|
|
4136
|
+
available: () => true,
|
|
4137
|
+
async search(request, signal) {
|
|
4138
|
+
if (options.resolveModelProvider?.() === "openai-codex") return options.codex.search(request, signal);
|
|
4139
|
+
const provider = options.resolveDshProvider?.();
|
|
4140
|
+
if (provider === void 0 || provider.id === "codex-subscription-auto" || provider.id === "codex-subscription" || provider.available() !== true) throw new WebError("DSH default search is unavailable", "WEB_PROVIDER_UNAVAILABLE");
|
|
4141
|
+
return provider.search(request, signal);
|
|
4110
4142
|
}
|
|
4111
4143
|
});
|
|
4112
4144
|
}
|
|
@@ -4792,6 +4824,24 @@ function safeCounters(source, keys) {
|
|
|
4792
4824
|
return Object.fromEntries(keys.filter((key) => Number.isSafeInteger(values?.[key]) && values[key] >= 0).map((key) => [key, values[key]]));
|
|
4793
4825
|
}
|
|
4794
4826
|
//#endregion
|
|
4827
|
+
//#region src/credit-expiry.js
|
|
4828
|
+
const PRO_200_COMPENSATION = Object.freeze({
|
|
4829
|
+
credits: 62500,
|
|
4830
|
+
expiresAt: Date.UTC(2026, 11, 31, 12)
|
|
4831
|
+
});
|
|
4832
|
+
/** Expiry of the balance: the API's own value when it has one, otherwise the known grant. */
|
|
4833
|
+
function creditExpiry({ planType, balance, apiExpiresAt }, now = Date.now()) {
|
|
4834
|
+
if (Number.isFinite(apiExpiresAt) && apiExpiresAt > now) return {
|
|
4835
|
+
expiresAt: apiExpiresAt,
|
|
4836
|
+
source: "api"
|
|
4837
|
+
};
|
|
4838
|
+
const amount = Number(balance);
|
|
4839
|
+
if (planType === "pro" && Number.isFinite(amount) && amount >= PRO_200_COMPENSATION.credits && now < PRO_200_COMPENSATION.expiresAt) return {
|
|
4840
|
+
expiresAt: PRO_200_COMPENSATION.expiresAt,
|
|
4841
|
+
source: "estimate"
|
|
4842
|
+
};
|
|
4843
|
+
}
|
|
4844
|
+
//#endregion
|
|
4795
4845
|
//#region src/usage.js
|
|
4796
4846
|
const CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
|
|
4797
4847
|
const DEFAULT_TTL_MS = 6e4;
|
|
@@ -4833,15 +4883,44 @@ function epochSeconds(value, label) {
|
|
|
4833
4883
|
if (!Number.isSafeInteger(value) || value < 0) throw new Error(`Codex returned an invalid ${label}`);
|
|
4834
4884
|
return value;
|
|
4835
4885
|
}
|
|
4836
|
-
function
|
|
4886
|
+
function messageRange(value) {
|
|
4887
|
+
return Array.isArray(value) && value.length === 2 && value.every((item) => Number.isSafeInteger(item) && item >= 0) ? [value[0], value[1]] : void 0;
|
|
4888
|
+
}
|
|
4889
|
+
function expiryOf(value) {
|
|
4890
|
+
const raw = value?.expires_at;
|
|
4891
|
+
if (Number.isSafeInteger(raw) && raw > 0) return raw * 1e3;
|
|
4892
|
+
if (typeof raw === "string" && raw.length <= 64) {
|
|
4893
|
+
const parsed = Date.parse(raw);
|
|
4894
|
+
if (Number.isFinite(parsed) && parsed > 0) return parsed;
|
|
4895
|
+
}
|
|
4896
|
+
}
|
|
4897
|
+
function creditsOf(value, planType) {
|
|
4837
4898
|
if (value === void 0 || value === null) return void 0;
|
|
4838
4899
|
if (!record$1(value) || typeof value.has_credits !== "boolean" || typeof value.unlimited !== "boolean") throw new Error("Codex returned malformed credit details");
|
|
4839
4900
|
if (!value.has_credits) return void 0;
|
|
4901
|
+
const balance = value.balance === void 0 || value.balance === null ? void 0 : decimal(value.balance, "credit balance");
|
|
4902
|
+
const expiry = value.unlimited ? void 0 : creditExpiry({
|
|
4903
|
+
planType,
|
|
4904
|
+
balance,
|
|
4905
|
+
apiExpiresAt: expiryOf(value)
|
|
4906
|
+
});
|
|
4907
|
+
const local = messageRange(value.approx_local_messages), cloud = messageRange(value.approx_cloud_messages);
|
|
4840
4908
|
return {
|
|
4841
4909
|
unlimited: value.unlimited,
|
|
4842
|
-
...
|
|
4910
|
+
...balance === void 0 ? {} : { balance },
|
|
4911
|
+
...expiry === void 0 ? {} : {
|
|
4912
|
+
expiresAt: expiry.expiresAt,
|
|
4913
|
+
expirySource: expiry.source
|
|
4914
|
+
},
|
|
4915
|
+
...local === void 0 ? {} : { approxLocalMessages: local },
|
|
4916
|
+
...cloud === void 0 ? {} : { approxCloudMessages: cloud }
|
|
4843
4917
|
};
|
|
4844
4918
|
}
|
|
4919
|
+
function chatPassOf(value) {
|
|
4920
|
+
if (!record$1(value) || !Array.isArray(value.windows)) return void 0;
|
|
4921
|
+
const windows = value.windows.map(windowOf).filter(Boolean);
|
|
4922
|
+
return windows.length === 0 ? void 0 : { windows };
|
|
4923
|
+
}
|
|
4845
4924
|
function individualOf(value) {
|
|
4846
4925
|
if (value === void 0 || value === null) return void 0;
|
|
4847
4926
|
if (!record$1(value)) throw new Error("Codex returned malformed spend control");
|
|
@@ -4905,12 +4984,14 @@ function parseCodexUsage(value) {
|
|
|
4905
4984
|
addLimit(limitOf(entry.metered_feature, entry.limit_name || void 0, entry.rate_limit));
|
|
4906
4985
|
}
|
|
4907
4986
|
addLimit(limitOf("code_review", "Code review", value.code_review_rate_limit));
|
|
4908
|
-
const
|
|
4987
|
+
const chatPass = chatPassOf(value.chatpass);
|
|
4988
|
+
const credits = creditsOf(value.credits, typeof value.plan_type === "string" ? value.plan_type : void 0);
|
|
4909
4989
|
const individualLimit = individualOf(value.spend_control);
|
|
4910
4990
|
const spendControlReached = spendControlReachedOf(value.spend_control);
|
|
4911
4991
|
const resetCredits = resetCreditsOf(value.rate_limit_reset_credits);
|
|
4912
4992
|
return {
|
|
4913
4993
|
rateLimits,
|
|
4994
|
+
...chatPass === void 0 ? {} : { chatPass },
|
|
4914
4995
|
...credits === void 0 ? {} : { credits },
|
|
4915
4996
|
...individualLimit === void 0 ? {} : { individualLimit },
|
|
4916
4997
|
...spendControlReached === void 0 ? {} : { spendControlReached },
|
|
@@ -6478,7 +6559,7 @@ function apply(ctx, config = {}) {
|
|
|
6478
6559
|
let dispose;
|
|
6479
6560
|
const sync = () => {
|
|
6480
6561
|
const value = settings.get();
|
|
6481
|
-
if (value.imageSketchAgent && value.imageSketch && value.imageEditing) dispose ??= ctx.tools.register(createSketchAgentTool(sketchBridge, ctx.attachments));
|
|
6562
|
+
if (value.imageSketchAgent && value.imageSketch && value.imageEditing) dispose ??= ctx.tools.register(createSketchAgentTool(sketchBridge, ctx.attachments, (sessionId) => ctx.get?.("sessions")?.get?.(sessionId)?.deriveMessages?.() ?? []));
|
|
6482
6563
|
else {
|
|
6483
6564
|
dispose?.();
|
|
6484
6565
|
dispose = void 0;
|