dsh-codex-subscription 2.4.2 → 2.5.0
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 +105 -37
- package/lib/index.js +1531 -1499
- 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,322 +1979,913 @@ 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.0";
|
|
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
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
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")}` });
|
|
2237
|
+
}
|
|
2238
|
+
return images;
|
|
2239
|
+
}
|
|
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
|
+
}];
|
|
2249
|
+
}
|
|
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
|
+
};
|
|
2347
|
+
}
|
|
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
|
|
2471
|
+
};
|
|
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
|
|
2506
|
+
});
|
|
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 });
|
|
2510
|
+
}
|
|
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})`);
|
|
2515
|
+
}
|
|
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}`
|
|
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
|
+
}
|
|
2619
|
+
},
|
|
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"
|
|
2646
|
+
});
|
|
2647
|
+
const { png, ...snapshot } = value;
|
|
2648
|
+
return {
|
|
2649
|
+
...snapshot,
|
|
2650
|
+
image
|
|
2651
|
+
};
|
|
2652
|
+
}
|
|
2653
|
+
return value;
|
|
2654
|
+
},
|
|
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
|
+
}] : []]
|
|
2670
|
+
}
|
|
2671
|
+
});
|
|
2672
|
+
}
|
|
2673
|
+
//#endregion
|
|
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;
|
|
2707
|
+
try {
|
|
2708
|
+
return JSON.parse(Buffer.from(encoded, "base64url").toString("utf8"));
|
|
2709
|
+
} catch {
|
|
2710
|
+
return;
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
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
|
|
2728
|
+
};
|
|
2729
|
+
}
|
|
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);
|
|
2733
|
+
}
|
|
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 });
|
|
2740
|
+
}
|
|
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");
|
|
2763
|
+
return {
|
|
2764
|
+
version: VERSION,
|
|
2765
|
+
activeId: record.payload.activeId,
|
|
2766
|
+
legacyAccountId,
|
|
2767
|
+
accounts
|
|
2768
|
+
};
|
|
2769
|
+
}
|
|
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);
|
|
2779
|
+
}
|
|
2780
|
+
async list() {
|
|
2781
|
+
return this.#credential === void 0 ? [] : [{
|
|
2782
|
+
providerId: "openai-codex",
|
|
2783
|
+
type: "oauth"
|
|
2784
|
+
}];
|
|
2785
|
+
}
|
|
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);
|
|
2791
|
+
}
|
|
2792
|
+
async delete(providerId) {
|
|
2793
|
+
if (providerId !== "openai-codex") throw new Error("Pending Codex login received an unknown provider");
|
|
2794
|
+
this.#credential = void 0;
|
|
2795
|
+
}
|
|
2796
|
+
credential() {
|
|
2797
|
+
return clone$1(this.#credential);
|
|
2798
|
+
}
|
|
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 ?? (() => {});
|
|
2815
|
+
}
|
|
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)
|
|
2828
|
+
};
|
|
2829
|
+
}
|
|
2830
|
+
}
|
|
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();
|
|
2868
|
+
}
|
|
2869
|
+
return payload;
|
|
2870
|
+
}
|
|
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
|
+
});
|
|
2883
|
+
}
|
|
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
|
+
});
|
|
2299
2889
|
}
|
|
2300
2890
|
activeId() {
|
|
2301
2891
|
return this.#enqueue(async () => (await this.#ensurePayload())?.activeId);
|
|
@@ -2843,1208 +3433,652 @@ var CodexLoginCoordinator = class {
|
|
|
2843
3433
|
...typeof status.expiresAt === "number" ? { expiresAt: status.expiresAt } : {}
|
|
2844
3434
|
};
|
|
2845
3435
|
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;
|
|
2859
|
-
}
|
|
2860
|
-
read(id) {
|
|
2861
|
-
const session = this.#sessions.get(id);
|
|
2862
|
-
if (session === void 0) throw new Error("unknown Codex login");
|
|
2863
|
-
return publicClone(session.view);
|
|
2864
|
-
}
|
|
2865
|
-
async submit({ id, value }) {
|
|
2866
|
-
const session = this.#sessions.get(id);
|
|
2867
|
-
if (session === void 0) throw new Error("unknown Codex login");
|
|
2868
|
-
if (session.prompt === void 0 || session.view.phase !== "waiting_input") throw new Error("Codex login is not waiting for input");
|
|
2869
|
-
if (typeof value !== "string" || value.trim() === "") throw new Error("Codex login input is empty");
|
|
2870
|
-
const answer = session.prompt;
|
|
2871
|
-
session.prompt = void 0;
|
|
2872
|
-
session.view = {
|
|
2873
|
-
...session.view,
|
|
2874
|
-
phase: session.view.authUrl === void 0 ? "starting" : "waiting_browser",
|
|
2875
|
-
prompt: void 0
|
|
2876
|
-
};
|
|
2877
|
-
answer.resolve(value);
|
|
2878
|
-
return this.read(id);
|
|
2879
|
-
}
|
|
2880
|
-
async cancel(id) {
|
|
2881
|
-
const session = this.#sessions.get(id);
|
|
2882
|
-
if (session === void 0) throw new Error("unknown Codex login");
|
|
2883
|
-
if (!TERMINAL_PHASES.has(session.view.phase)) {
|
|
2884
|
-
session.view = {
|
|
2885
|
-
id,
|
|
2886
|
-
provider: "openai-codex",
|
|
2887
|
-
method: session.view.method,
|
|
2888
|
-
phase: "cancelled",
|
|
2889
|
-
authenticated: false
|
|
2890
|
-
};
|
|
2891
|
-
session.controller.abort(/* @__PURE__ */ new Error("Codex login cancelled"));
|
|
2892
|
-
}
|
|
2893
|
-
return this.read(id);
|
|
2894
|
-
}
|
|
2895
|
-
async logout(options) {
|
|
2896
|
-
if (this.#activeId !== void 0) {
|
|
2897
|
-
const active = this.#sessions.get(this.#activeId);
|
|
2898
|
-
if (active !== void 0 && !TERMINAL_PHASES.has(active.view.phase)) await this.cancel(active.view.id);
|
|
2899
|
-
}
|
|
2900
|
-
await this.auth.logout(options);
|
|
2901
|
-
return this.accountStatus(options);
|
|
2902
|
-
}
|
|
2903
|
-
async selectAccount(id) {
|
|
2904
|
-
return publicClone(await this.auth.select(id));
|
|
2905
|
-
}
|
|
2906
|
-
async removeAccount(id) {
|
|
2907
|
-
return publicClone(await this.auth.remove(id));
|
|
2908
|
-
}
|
|
2909
|
-
};
|
|
2910
|
-
/** Map the loopback-only DSH Connection channel onto the coordinator. */
|
|
2911
|
-
function createCodexRpcHandler(coordinator, options = {}) {
|
|
2912
|
-
const openExternal = options.openExternal;
|
|
2913
|
-
return async (endpoint, payload, signal) => {
|
|
2914
|
-
try {
|
|
2915
|
-
signal.throwIfAborted();
|
|
2916
|
-
const input = asObject(payload);
|
|
2917
|
-
if (endpoint === "status") try {
|
|
2918
|
-
return ok(await coordinator.accountStatus({ signal }));
|
|
2919
|
-
} catch (error) {
|
|
2920
|
-
if (signal.aborted) throw error;
|
|
2921
|
-
const [, message] = classifyAccountStatusError(error);
|
|
2922
|
-
return accountStatusError(message);
|
|
2923
|
-
}
|
|
2924
|
-
if (endpoint === "login/start") {
|
|
2925
|
-
const started = await coordinator.start({
|
|
2926
|
-
method: input.method,
|
|
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;
|
|
2994
|
-
return {
|
|
2995
|
-
...part,
|
|
2996
|
-
detail
|
|
2997
|
-
};
|
|
2998
|
-
});
|
|
2999
|
-
if (!itemChanged) return item;
|
|
3000
|
-
changed = true;
|
|
3001
|
-
return {
|
|
3002
|
-
...item,
|
|
3003
|
-
[field]: parts
|
|
3004
|
-
};
|
|
3005
|
-
});
|
|
3006
|
-
return changed ? {
|
|
3007
|
-
...payload,
|
|
3008
|
-
input
|
|
3009
|
-
} : payload;
|
|
3010
|
-
}
|
|
3011
|
-
/**
|
|
3012
|
-
* Preserve pi-ai's native Codex OAuth provider while allowing DSH's generic
|
|
3013
|
-
* PiAiAdapter to pass the access token resolved by the host credential store.
|
|
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.
|
|
3022
|
-
*/
|
|
3023
|
-
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() } = {}) {
|
|
3024
|
-
const provider = createOpenAICodexProvider();
|
|
3025
|
-
const requestToken = Object.freeze({
|
|
3026
|
-
name: "DSH-managed Codex OAuth request token",
|
|
3027
|
-
async resolve({ credential }) {
|
|
3028
|
-
const token = credential?.type === "api_key" ? credential.key : void 0;
|
|
3029
|
-
if (typeof token !== "string" || token.length === 0) return void 0;
|
|
3030
|
-
return {
|
|
3031
|
-
auth: { apiKey: token },
|
|
3032
|
-
source: "DSH-managed OAuth request"
|
|
3033
|
-
};
|
|
3034
|
-
}
|
|
3035
|
-
});
|
|
3036
|
-
const modelMetadata = (model) => catalog?.metadata(model?.id);
|
|
3037
|
-
const supportsVerbosity = (model) => modelMetadata(model)?.supportVerbosity ?? model?.id !== "gpt-5.3-codex-spark";
|
|
3038
|
-
const withPreferences = (model, options = {}) => {
|
|
3039
|
-
const metadata = modelMetadata(model);
|
|
3040
|
-
const requestedVerbosity = resolveOutputVerbosity();
|
|
3041
|
-
const textVerbosity = supportsVerbosity(model) ? requestedVerbosity === "default" ? metadata?.defaultVerbosity ?? "medium" : requestedVerbosity : void 0;
|
|
3042
|
-
const speedMode = resolveSpeedMode();
|
|
3043
|
-
const serviceTier = speedMode === "ultrafast" && metadata?.supportsUltrafast === true ? ULTRAFAST_SERVICE_TIER : speedMode === "fast" && (metadata?.supportsFast ?? supportsCodexFastMode(model?.id)) ? FAST_SERVICE_TIER : void 0;
|
|
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
|
|
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"
|
|
3082
3445
|
};
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
return
|
|
3086
|
-
|
|
3087
|
-
|
|
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
|
|
3088
3466
|
};
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
const
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
return result.done ? result : {
|
|
3103
|
-
...result,
|
|
3104
|
-
value: normalizeTransportEvent(result.value, request.options?.signal)
|
|
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
|
|
3105
3480
|
};
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
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
|
|
3148
|
-
};
|
|
3481
|
+
session.controller.abort(/* @__PURE__ */ new Error("Codex login cancelled"));
|
|
3482
|
+
}
|
|
3483
|
+
return this.read(id);
|
|
3149
3484
|
}
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
}
|
|
3155
|
-
//#endregion
|
|
3156
|
-
//#region src/version.js
|
|
3157
|
-
const PACKAGE_VERSION = "2.4.2";
|
|
3158
|
-
const USER_AGENT = `dsh-codex-subscription/${PACKAGE_VERSION}`;
|
|
3159
|
-
//#endregion
|
|
3160
|
-
//#region src/model-catalog.js
|
|
3161
|
-
const CODEX_MODELS_URL = `https://chatgpt.com/backend-api/codex/models?client_version=${encodeURIComponent(PACKAGE_VERSION)}`;
|
|
3162
|
-
const LEVELS = [
|
|
3163
|
-
"off",
|
|
3164
|
-
"minimal",
|
|
3165
|
-
"low",
|
|
3166
|
-
"medium",
|
|
3167
|
-
"high",
|
|
3168
|
-
"xhigh",
|
|
3169
|
-
"max"
|
|
3170
|
-
];
|
|
3171
|
-
const DEFAULT_REFRESH_TIMEOUT_MS = 1e4;
|
|
3172
|
-
const record$4 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3173
|
-
const nonEmpty$2 = (value) => typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
|
|
3174
|
-
const positiveInteger$1 = (value) => Number.isSafeInteger(value) && value > 0 ? value : void 0;
|
|
3175
|
-
function upgradeModelId(value) {
|
|
3176
|
-
return (typeof value.upgrade === "string" ? nonEmpty$2(value.upgrade) : record$4(value.upgrade) ? nonEmpty$2(value.upgrade.model) : void 0) ?? (record$4(value.upgrade_info) ? nonEmpty$2(value.upgrade_info.model) : void 0) ?? (record$4(value.upgradeInfo) ? nonEmpty$2(value.upgradeInfo.model) : void 0);
|
|
3177
|
-
}
|
|
3178
|
-
function retirementMillis(value) {
|
|
3179
|
-
for (const source of [
|
|
3180
|
-
value,
|
|
3181
|
-
value.upgrade,
|
|
3182
|
-
value.upgrade_info,
|
|
3183
|
-
value.upgradeInfo
|
|
3184
|
-
]) {
|
|
3185
|
-
if (!record$4(source)) continue;
|
|
3186
|
-
for (const raw of [source.retirement_at, source.retirementAt]) {
|
|
3187
|
-
const seconds = positiveInteger$1(raw);
|
|
3188
|
-
if (seconds !== void 0 && Number.isSafeInteger(seconds * 1e3)) return seconds * 1e3;
|
|
3189
|
-
if (typeof raw === "string" && /^\d{4}-\d{2}-\d{2}T[\d:.]+(?:Z|[+-]\d{2}:\d{2})$/u.test(raw)) {
|
|
3190
|
-
const parsed = Date.parse(raw);
|
|
3191
|
-
if (Number.isFinite(parsed) && parsed > 0) return parsed;
|
|
3192
|
-
}
|
|
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);
|
|
3193
3489
|
}
|
|
3490
|
+
await this.auth.logout(options);
|
|
3491
|
+
return this.accountStatus(options);
|
|
3194
3492
|
}
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
const
|
|
3205
|
-
return {
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
}
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
}
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
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")
|
|
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 }));
|
|
3509
|
+
} catch (error) {
|
|
3510
|
+
if (signal.aborted) throw error;
|
|
3511
|
+
const [, message] = classifyAccountStatusError(error);
|
|
3512
|
+
return accountStatusError(message);
|
|
3513
|
+
}
|
|
3514
|
+
if (endpoint === "login/start") {
|
|
3515
|
+
const started = await coordinator.start({
|
|
3516
|
+
method: input.method,
|
|
3517
|
+
label: input.label
|
|
3518
|
+
});
|
|
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
|
+
}
|
|
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);
|
|
3552
|
+
}
|
|
3276
3553
|
};
|
|
3277
3554
|
}
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
const name = nonEmpty$2(model.display_name);
|
|
3285
|
-
if (id !== void 0 && name !== void 0 && !displayNames.has(id)) displayNames.set(id, name);
|
|
3286
|
-
}
|
|
3287
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3288
|
-
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);
|
|
3289
|
-
}
|
|
3290
|
-
function mergeModel(baseModels, remote) {
|
|
3291
|
-
const base = baseModels.find((model) => model.id === remote.id) ?? baseModels.find((model) => model.id !== "gpt-5.3-codex-spark") ?? baseModels[0];
|
|
3292
|
-
if (base === void 0) return void 0;
|
|
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;
|
|
3293
3561
|
return {
|
|
3294
|
-
...
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
reasoning: remote.reasoning,
|
|
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
|
-
} }
|
|
3562
|
+
...event,
|
|
3563
|
+
error: {
|
|
3564
|
+
...event.error,
|
|
3565
|
+
errorMessage: `Network transport failure: ${message}`
|
|
3566
|
+
}
|
|
3309
3567
|
};
|
|
3310
3568
|
}
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
let
|
|
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"));
|
|
3334
|
-
};
|
|
3335
|
-
signal?.addEventListener("abort", abort, { once: true });
|
|
3336
|
-
const requestSignal = controller.signal;
|
|
3337
|
-
let timer;
|
|
3338
|
-
const timeoutError = /* @__PURE__ */ new Error("Codex model catalog refresh timed out");
|
|
3339
|
-
const work = (async () => {
|
|
3340
|
-
const { access, accountId } = await readSubscriptionCredentials(options.getAuth, options.readCredential, requestSignal);
|
|
3341
|
-
if (currentGeneration !== generation || requestSignal.aborted) return false;
|
|
3342
|
-
if (typeof access !== "string" || access.length === 0 || typeof accountId !== "string" || accountId.length === 0) return false;
|
|
3343
|
-
const headers = {
|
|
3344
|
-
authorization: `Bearer ${access}`,
|
|
3345
|
-
"chatgpt-account-id": accountId,
|
|
3346
|
-
accept: "application/json",
|
|
3347
|
-
originator: "pi",
|
|
3348
|
-
"user-agent": USER_AGENT,
|
|
3349
|
-
...etag === void 0 ? {} : { "if-none-match": etag }
|
|
3569
|
+
//#endregion
|
|
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
|
|
3350
3587
|
};
|
|
3351
|
-
const response = await fetchCatalog(CODEX_MODELS_URL, {
|
|
3352
|
-
method: "GET",
|
|
3353
|
-
redirect: "error",
|
|
3354
|
-
headers,
|
|
3355
|
-
signal: requestSignal
|
|
3356
|
-
});
|
|
3357
|
-
if (currentGeneration !== generation || requestSignal.aborted) return false;
|
|
3358
|
-
if (response.status === 304) {
|
|
3359
|
-
outcome = "ok";
|
|
3360
|
-
if (rawCatalog === void 0) return false;
|
|
3361
|
-
const remote = parseOfficialModelCatalog(rawCatalog, getNow());
|
|
3362
|
-
const next = remote.map((model) => mergeModel(options.baseModels(), model)).filter(Boolean);
|
|
3363
|
-
if (next.length === 0 || JSON.stringify(next) === JSON.stringify(models)) return false;
|
|
3364
|
-
models = next;
|
|
3365
|
-
metadata = new Map(remote.map((model) => [model.id, model]));
|
|
3366
|
-
revision += 1;
|
|
3367
|
-
options.onUpdated?.();
|
|
3368
|
-
return true;
|
|
3369
|
-
}
|
|
3370
|
-
if (!response.ok) throw new Error(`Codex model catalog failed (HTTP ${response.status})`);
|
|
3371
|
-
const raw = await response.json();
|
|
3372
|
-
const remote = parseOfficialModelCatalog(raw, getNow());
|
|
3373
|
-
if (currentGeneration !== generation || requestSignal.aborted) return false;
|
|
3374
|
-
if (remote.length === 0) throw new Error("Codex returned an empty model catalog");
|
|
3375
|
-
const baseModels = options.baseModels();
|
|
3376
|
-
const next = remote.map((model) => mergeModel(baseModels, model)).filter(Boolean);
|
|
3377
|
-
if (next.length === 0) throw new Error("Codex model catalog has no compatible models");
|
|
3378
|
-
if (currentGeneration !== generation || requestSignal.aborted) return false;
|
|
3379
|
-
models = next;
|
|
3380
|
-
rawCatalog = raw;
|
|
3381
|
-
metadata = new Map(remote.map((model) => [model.id, model]));
|
|
3382
|
-
etag = nonEmpty$2(response.headers.get("etag")) ?? etag;
|
|
3383
|
-
revision += 1;
|
|
3384
|
-
outcome = "ok";
|
|
3385
|
-
options.onUpdated?.();
|
|
3386
|
-
return true;
|
|
3387
|
-
})();
|
|
3388
|
-
let rejectAborted;
|
|
3389
|
-
const abortPromise = new Promise((_, reject) => {
|
|
3390
|
-
rejectAborted = () => reject(requestSignal.reason ?? /* @__PURE__ */ new Error("Codex model catalog refresh aborted"));
|
|
3391
|
-
if (requestSignal.aborted) rejectAborted();
|
|
3392
|
-
else requestSignal.addEventListener("abort", rejectAborted, { once: true });
|
|
3393
3588
|
});
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
}
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
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;
|
|
3600
|
+
}
|
|
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
|
+
};
|
|
3406
3660
|
}
|
|
3407
|
-
});
|
|
3408
|
-
refreshing = {
|
|
3409
|
-
generation: currentGeneration,
|
|
3410
|
-
promise,
|
|
3411
|
-
cancel: () => controller.abort()
|
|
3412
3661
|
};
|
|
3413
|
-
|
|
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
|
+
};
|
|
3414
3708
|
};
|
|
3415
3709
|
return Object.freeze({
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
capabilityGaps: () => [...metadata.values()].filter((model) => model.unsupported && /^[a-z][a-z0-9._-]{0,79}$/u.test(model.id)).slice(0, 20).map((model) => ({
|
|
3421
|
-
model: model.id,
|
|
3422
|
-
...structuredClone(model.unsupported)
|
|
3423
|
-
})),
|
|
3424
|
-
status: () => ({
|
|
3425
|
-
source: models === void 0 ? "unavailable" : "online",
|
|
3426
|
-
refresh: refreshStatus
|
|
3710
|
+
...provider,
|
|
3711
|
+
auth: Object.freeze({
|
|
3712
|
+
...provider.auth,
|
|
3713
|
+
apiKey: requestToken
|
|
3427
3714
|
}),
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
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?.();
|
|
3440
|
-
}
|
|
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))
|
|
3441
3718
|
});
|
|
3442
3719
|
}
|
|
3720
|
+
Object.freeze([
|
|
3721
|
+
"0.82.1",
|
|
3722
|
+
"0.85.1",
|
|
3723
|
+
"0.87.1"
|
|
3724
|
+
]);
|
|
3443
3725
|
//#endregion
|
|
3444
|
-
//#region src/
|
|
3445
|
-
const
|
|
3446
|
-
const
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
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;
|
|
3451
3738
|
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
|
|
3454
|
-
|
|
3455
|
-
|
|
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;
|
|
3472
|
-
}
|
|
3473
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return void 0;
|
|
3474
|
-
const title = displayText(value.title) ?? parsed.hostname;
|
|
3475
|
-
const snippet = displayText(value.snippet);
|
|
3476
|
-
const publishedAt = boundedDisplayText(value.published_at, MAX_SOURCE_DATE) ?? boundedDisplayText(value.publishedAt, MAX_SOURCE_DATE);
|
|
3477
|
-
return {
|
|
3478
|
-
url,
|
|
3479
|
-
title,
|
|
3480
|
-
...snippet === void 0 ? {} : { snippet },
|
|
3481
|
-
...publishedAt === void 0 ? {} : { publishedAt }
|
|
3482
|
-
};
|
|
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);
|
|
3483
3743
|
}
|
|
3484
|
-
function
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
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
|
+
}
|
|
3759
|
+
}
|
|
3493
3760
|
}
|
|
3761
|
+
}
|
|
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;
|
|
3494
3771
|
return {
|
|
3495
|
-
|
|
3496
|
-
|
|
3772
|
+
text: `Retires on ${dateText} (UTC).${upgradeName === void 0 ? "" : ` Switch to ${upgradeName} to keep working.`}`,
|
|
3773
|
+
at,
|
|
3774
|
+
upgradeTo,
|
|
3775
|
+
dateText
|
|
3497
3776
|
};
|
|
3498
3777
|
}
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
const
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
async search(request, signal) {
|
|
3506
|
-
signal?.throwIfAborted();
|
|
3507
|
-
const preferences = readCapabilitySettings(options.resolvePreferences?.());
|
|
3508
|
-
if (preferences.searchMode === "disabled") throw new WebError("Codex search is disabled in subscription settings", "WEB_PROVIDER_UNAVAILABLE");
|
|
3509
|
-
const { access, accountId } = await readSubscriptionCredentials(options.getAuth, options.readCredential, signal);
|
|
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();
|
|
3550
|
-
} catch (error) {
|
|
3551
|
-
throw new WebError("Codex returned an unreadable search response", "WEB_PROVIDER_ERROR", { cause: error });
|
|
3552
|
-
}
|
|
3553
|
-
try {
|
|
3554
|
-
const result = parseSearchResponse(value);
|
|
3555
|
-
if (preferences.searchDomains.length > 0) result.sources = result.sources.filter((source) => {
|
|
3556
|
-
const hostname = new URL(source.url).hostname.toLowerCase();
|
|
3557
|
-
return preferences.searchDomains.some((domain) => hostname === domain || hostname.endsWith(`.${domain}`));
|
|
3558
|
-
});
|
|
3559
|
-
return result;
|
|
3560
|
-
} catch (error) {
|
|
3561
|
-
throw new WebError("Codex returned a malformed search response", "WEB_PROVIDER_ERROR", { cause: error });
|
|
3562
|
-
}
|
|
3563
|
-
}
|
|
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
|
-
});
|
|
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;
|
|
3578
3784
|
}
|
|
3579
|
-
const
|
|
3580
|
-
const
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
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));
|
|
3584
3798
|
return {
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
width: value.width,
|
|
3589
|
-
height: value.height,
|
|
3590
|
-
name: value.name,
|
|
3591
|
-
sha256: value.sha256
|
|
3799
|
+
...reasoning.length ? { reasoning } : {},
|
|
3800
|
+
...inputs.length ? { inputs } : {},
|
|
3801
|
+
...speeds.length ? { speeds } : {}
|
|
3592
3802
|
};
|
|
3593
3803
|
}
|
|
3594
|
-
function
|
|
3595
|
-
if (
|
|
3596
|
-
const
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
const
|
|
3601
|
-
const
|
|
3602
|
-
|
|
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({
|
|
3812
|
+
...value,
|
|
3813
|
+
supported_reasoning_levels: supported
|
|
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
|
+
};
|
|
3603
3843
|
}
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
const
|
|
3607
|
-
const
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
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;
|
|
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);
|
|
3620
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);
|
|
3621
3855
|
}
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
const CODEX_IMAGE_GENERATION_URL = "https://chatgpt.com/backend-api/codex/images/generations";
|
|
3626
|
-
const CODEX_IMAGE_EDIT_URL = "https://chatgpt.com/backend-api/codex/images/edits";
|
|
3627
|
-
const MAX_REFERENCE_IMAGES = 5;
|
|
3628
|
-
const RESPONSE_ENVELOPE_BYTES = 1024 * 1024;
|
|
3629
|
-
const IMAGE_BACKGROUNDS = /* @__PURE__ */ new Set([
|
|
3630
|
-
"auto",
|
|
3631
|
-
"transparent",
|
|
3632
|
-
"opaque"
|
|
3633
|
-
]);
|
|
3634
|
-
const PNG_SIGNATURE = Buffer.from([
|
|
3635
|
-
137,
|
|
3636
|
-
80,
|
|
3637
|
-
78,
|
|
3638
|
-
71,
|
|
3639
|
-
13,
|
|
3640
|
-
10,
|
|
3641
|
-
26,
|
|
3642
|
-
10
|
|
3643
|
-
]);
|
|
3644
|
-
const FULL_ATTACHMENT_ID = /^sha256:[0-9a-f]{64}$/u;
|
|
3645
|
-
const BARE_ATTACHMENT_DIGEST = /^[0-9a-f]{64}$/iu;
|
|
3646
|
-
const PATH_LIKE_ATTACHMENT_ID = /[\\/]/u;
|
|
3647
|
-
const FILE_NAME_ATTACHMENT_ID = /\.[A-Za-z0-9]{1,16}$/u;
|
|
3648
|
-
const record$2 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3649
|
-
const nonEmpty = (value) => typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
|
|
3650
|
-
const NETWORK_REASONS = {
|
|
3651
|
-
UND_ERR_HEADERS_TIMEOUT: "the image service sent no response within 5 minutes",
|
|
3652
|
-
UND_ERR_BODY_TIMEOUT: "the image response stalled",
|
|
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 "";
|
|
3666
|
-
}
|
|
3667
|
-
function normalizeImageOptions(args) {
|
|
3668
|
-
const quality = nonEmpty(args?.quality) ?? "auto";
|
|
3669
|
-
const background = nonEmpty(args?.background) ?? "auto";
|
|
3670
|
-
const size = nonEmpty(args?.size) ?? "auto";
|
|
3671
|
-
validateImageQuality(resolveImageModel(args?.model), quality);
|
|
3672
|
-
if (!IMAGE_BACKGROUNDS.has(background)) throw new Error("background must be auto, transparent, or opaque");
|
|
3673
|
-
if (size !== "auto") {
|
|
3674
|
-
const match = /^(\d+)x(\d+)$/u.exec(size);
|
|
3675
|
-
const width = Number(match?.[1]);
|
|
3676
|
-
const height = Number(match?.[2]);
|
|
3677
|
-
const short = Math.min(width, height);
|
|
3678
|
-
const long = Math.max(width, height);
|
|
3679
|
-
const pixels = width * height;
|
|
3680
|
-
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");
|
|
3681
|
-
}
|
|
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;
|
|
3682
3859
|
return {
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
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;
|
|
3686
3980
|
};
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
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?.();
|
|
3712
4006
|
}
|
|
3713
|
-
chunks.push(value);
|
|
3714
|
-
}
|
|
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
|
-
}
|
|
3733
|
-
function imageReference(value) {
|
|
3734
|
-
const originalDimensions = record$2(value.originalDimensions) ? {
|
|
3735
|
-
width: value.originalDimensions.width,
|
|
3736
|
-
height: value.originalDimensions.height
|
|
3737
|
-
} : void 0;
|
|
3738
|
-
return {
|
|
3739
|
-
attachmentId: value.attachmentId,
|
|
3740
|
-
mediaType: value.mediaType,
|
|
3741
|
-
bytes: value.bytes,
|
|
3742
|
-
width: value.width,
|
|
3743
|
-
height: value.height,
|
|
3744
|
-
...value.name === void 0 ? {} : { name: value.name },
|
|
3745
|
-
...originalDimensions === void 0 ? {} : { originalDimensions }
|
|
3746
|
-
};
|
|
3747
|
-
}
|
|
3748
|
-
function normalizeAttachmentId(value) {
|
|
3749
|
-
if (typeof value !== "string") return void 0;
|
|
3750
|
-
if (FULL_ATTACHMENT_ID.test(value)) return value;
|
|
3751
|
-
const bare = BARE_ATTACHMENT_DIGEST.exec(value);
|
|
3752
|
-
return bare === null ? void 0 : `sha256:${bare[0].toLowerCase()}`;
|
|
3753
|
-
}
|
|
3754
|
-
function invalidAttachmentId(value) {
|
|
3755
|
-
const attachmentId = value?.attachmentId;
|
|
3756
|
-
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.");
|
|
3757
|
-
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.");
|
|
3758
|
-
}
|
|
3759
|
-
function referenceOf(value, attachments) {
|
|
3760
|
-
const attachmentId = normalizeAttachmentId(value?.attachmentId);
|
|
3761
|
-
if (attachmentId === void 0) invalidAttachmentId(value);
|
|
3762
|
-
if (!record$2(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$2(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");
|
|
3763
|
-
return imageReference({
|
|
3764
|
-
...value,
|
|
3765
|
-
attachmentId
|
|
3766
|
-
});
|
|
3767
|
-
}
|
|
3768
|
-
function sessionImageReferences(messages) {
|
|
3769
|
-
const references = /* @__PURE__ */ new Map();
|
|
3770
|
-
const visit = (content) => {
|
|
3771
|
-
if (!Array.isArray(content)) return;
|
|
3772
|
-
for (const block of content) if (block?.type === "image" && record$2(block.attachment)) {
|
|
3773
|
-
const id = normalizeAttachmentId(block.attachment.attachmentId);
|
|
3774
|
-
if (id !== void 0) references.set(id, block.attachment);
|
|
3775
|
-
} else if (block?.type === "tool-result") visit(block.content);
|
|
3776
|
-
};
|
|
3777
|
-
for (const message of messages ?? []) visit(message?.content);
|
|
3778
|
-
return references;
|
|
3779
|
-
}
|
|
3780
|
-
async function editImages(values, attachments, signal, messages) {
|
|
3781
|
-
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`);
|
|
3782
|
-
const available = messages === void 0 ? void 0 : sessionImageReferences(messages);
|
|
3783
|
-
const references = values.map((value) => {
|
|
3784
|
-
const id = normalizeAttachmentId(value?.attachmentId);
|
|
3785
|
-
if (id === void 0) invalidAttachmentId(value);
|
|
3786
|
-
if (available === void 0) return referenceOf(value, attachments);
|
|
3787
|
-
const selected = available.get(id);
|
|
3788
|
-
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.");
|
|
3789
|
-
return referenceOf(selected, attachments);
|
|
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
|
}
|
|
@@ -6478,7 +6510,7 @@ function apply(ctx, config = {}) {
|
|
|
6478
6510
|
let dispose;
|
|
6479
6511
|
const sync = () => {
|
|
6480
6512
|
const value = settings.get();
|
|
6481
|
-
if (value.imageSketchAgent && value.imageSketch && value.imageEditing) dispose ??= ctx.tools.register(createSketchAgentTool(sketchBridge, ctx.attachments));
|
|
6513
|
+
if (value.imageSketchAgent && value.imageSketch && value.imageEditing) dispose ??= ctx.tools.register(createSketchAgentTool(sketchBridge, ctx.attachments, (sessionId) => ctx.get?.("sessions")?.get?.(sessionId)?.deriveMessages?.() ?? []));
|
|
6482
6514
|
else {
|
|
6483
6515
|
dispose?.();
|
|
6484
6516
|
dispose = void 0;
|