dsh-codex-connect 0.1.0-alpha.4.20 → 0.1.0-alpha.4.21
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/INSTALL.md +28 -5
- package/README.i18n.yaml +2 -2
- package/README.md +28 -5
- package/docs/README.zh.md +28 -5
- package/docs/agent-notes/original-image-fork-access.md +7 -0
- package/lib/bin.js +368 -1
- package/lib/client.js +141 -31
- package/lib/index.js +1 -1
- package/lib/{src-B2d5HLN5.js → src-Cxq99TvF.js} +500 -107
- package/package.json +3 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { randomBytes, randomUUID } from "node:crypto";
|
|
1
|
+
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
2
2
|
import { basename, dirname, join, parse, resolve } from "node:path";
|
|
3
3
|
import z from "@deepseek-ai/schemastery";
|
|
4
4
|
import { deepEqualJson, installSettingsSection, settingsNamespace } from "@deepseek-ai/dsh-settings";
|
|
@@ -12,6 +12,7 @@ import { resolveDshHome } from "@deepseek-ai/dsh-home-paths";
|
|
|
12
12
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
13
13
|
import { Dispatcher, ProxyAgent, getGlobalDispatcher, setGlobalDispatcher } from "undici";
|
|
14
14
|
import { fileURLToPath } from "node:url";
|
|
15
|
+
import { SessionId } from "@deepseek-ai/dsh-session";
|
|
15
16
|
import { AttachmentId } from "@deepseek-ai/dsh-attachment";
|
|
16
17
|
import { TOOL_ABORTED, defineTool } from "@deepseek-ai/dsh-tools";
|
|
17
18
|
import { lookup } from "node:dns/promises";
|
|
@@ -19,8 +20,8 @@ import { request } from "node:http";
|
|
|
19
20
|
import { request as request$1 } from "node:https";
|
|
20
21
|
import { BlockList, isIP } from "node:net";
|
|
21
22
|
import { Service } from "@deepseek-ai/cordis";
|
|
22
|
-
import { WebError } from "@deepseek-ai/dsh-web";
|
|
23
23
|
import { constants } from "node:fs";
|
|
24
|
+
import { WebError } from "@deepseek-ai/dsh-web";
|
|
24
25
|
import { constants as constants$1, zstdCompressSync, zstdDecompressSync } from "node:zlib";
|
|
25
26
|
//#region src/store.ts
|
|
26
27
|
/**
|
|
@@ -53,7 +54,7 @@ async function assertOwnerOnly$1(filename) {
|
|
|
53
54
|
/* v8 ignore stop */
|
|
54
55
|
}
|
|
55
56
|
/** Validate the strict JSON document without quoting token-bearing input. */
|
|
56
|
-
function parseDocument$
|
|
57
|
+
function parseDocument$2(text, filename) {
|
|
57
58
|
let value;
|
|
58
59
|
try {
|
|
59
60
|
value = JSON.parse(text);
|
|
@@ -118,7 +119,7 @@ var OpenAICodexCredentialStore = class {
|
|
|
118
119
|
if (isENOENT$1(error)) return void 0;
|
|
119
120
|
throw error;
|
|
120
121
|
}
|
|
121
|
-
return cloneCredential(parseDocument$
|
|
122
|
+
return cloneCredential(parseDocument$2(text, this.filename).credential);
|
|
122
123
|
}
|
|
123
124
|
/** @inheritdoc */
|
|
124
125
|
async read(providerId) {
|
|
@@ -142,7 +143,7 @@ var OpenAICodexCredentialStore = class {
|
|
|
142
143
|
const current = await this.readCurrent();
|
|
143
144
|
const candidate = await fn(current);
|
|
144
145
|
if (candidate === void 0) return current;
|
|
145
|
-
const document = parseDocument$
|
|
146
|
+
const document = parseDocument$2(JSON.stringify({
|
|
146
147
|
version: AUTH_FORMAT_VERSION,
|
|
147
148
|
credential: candidate
|
|
148
149
|
}), this.filename);
|
|
@@ -521,7 +522,7 @@ async function assertOwnerOnly(filename) {
|
|
|
521
522
|
/* v8 ignore stop */
|
|
522
523
|
}
|
|
523
524
|
/** Reject malformed input without echoing its contents into an error. */
|
|
524
|
-
function parseDocument(text, filename) {
|
|
525
|
+
function parseDocument$1(text, filename) {
|
|
525
526
|
let value;
|
|
526
527
|
try {
|
|
527
528
|
value = JSON.parse(text);
|
|
@@ -601,7 +602,7 @@ var OpenAICodexTrustedOriginsStore = class {
|
|
|
601
602
|
};
|
|
602
603
|
throw error;
|
|
603
604
|
}
|
|
604
|
-
return parseDocument(text, this.filename);
|
|
605
|
+
return parseDocument$1(text, this.filename);
|
|
605
606
|
}
|
|
606
607
|
/** Read the current canonical list without acquiring the writer lock. */
|
|
607
608
|
async list() {
|
|
@@ -963,7 +964,7 @@ async function trustedRequestDecision(req, trustedOrigins = new OpenAICodexTrust
|
|
|
963
964
|
error: REMOTE_WEB_ORIGIN_NOT_TRUSTED
|
|
964
965
|
};
|
|
965
966
|
}
|
|
966
|
-
function json$
|
|
967
|
+
function json$4(res, status, value) {
|
|
967
968
|
res.writeHead(status, {
|
|
968
969
|
"content-type": "application/json; charset=utf-8",
|
|
969
970
|
"cache-control": "no-store",
|
|
@@ -1056,7 +1057,7 @@ function registerOpenAICodexAuthRoutes(ctx, store, trustedOriginsOverride, fastM
|
|
|
1056
1057
|
const authorize = async (req, res) => {
|
|
1057
1058
|
const decision = await trustedRequestDecision(req, trustedOrigins);
|
|
1058
1059
|
if (decision.trusted) return true;
|
|
1059
|
-
json$
|
|
1060
|
+
json$4(res, 403, { error: decision.error });
|
|
1060
1061
|
return false;
|
|
1061
1062
|
};
|
|
1062
1063
|
const routes = [
|
|
@@ -1064,21 +1065,21 @@ function registerOpenAICodexAuthRoutes(ctx, store, trustedOriginsOverride, fastM
|
|
|
1064
1065
|
kind: "exact",
|
|
1065
1066
|
path: OPENAI_CODEX_AUTH_STATUS_PATH,
|
|
1066
1067
|
handler: async (req, res) => {
|
|
1067
|
-
if (req.method !== "GET") return json$
|
|
1068
|
+
if (req.method !== "GET") return json$4(res, 405, { error: "method not allowed" });
|
|
1068
1069
|
if (!await authorize(req, res)) return;
|
|
1069
|
-
json$
|
|
1070
|
+
json$4(res, 200, await auth.status());
|
|
1070
1071
|
}
|
|
1071
1072
|
}),
|
|
1072
1073
|
ctx.webServer.register({
|
|
1073
1074
|
kind: "exact",
|
|
1074
1075
|
path: OPENAI_CODEX_AUTH_LOGIN_PATH,
|
|
1075
1076
|
handler: async (req, res) => {
|
|
1076
|
-
if (req.method !== "POST") return json$
|
|
1077
|
+
if (req.method !== "POST") return json$4(res, 405, { error: "method not allowed" });
|
|
1077
1078
|
if (!await authorize(req, res)) return;
|
|
1078
1079
|
try {
|
|
1079
|
-
json$
|
|
1080
|
+
json$4(res, 200, await auth.signIn());
|
|
1080
1081
|
} catch (error) {
|
|
1081
|
-
json$
|
|
1082
|
+
json$4(res, 500, { error: safeMessage(error) });
|
|
1082
1083
|
}
|
|
1083
1084
|
}
|
|
1084
1085
|
}),
|
|
@@ -1086,13 +1087,13 @@ function registerOpenAICodexAuthRoutes(ctx, store, trustedOriginsOverride, fastM
|
|
|
1086
1087
|
kind: "exact",
|
|
1087
1088
|
path: OPENAI_CODEX_AUTH_LOGOUT_PATH,
|
|
1088
1089
|
handler: async (req, res) => {
|
|
1089
|
-
if (req.method !== "POST") return json$
|
|
1090
|
+
if (req.method !== "POST") return json$4(res, 405, { error: "method not allowed" });
|
|
1090
1091
|
if (!await authorize(req, res)) return;
|
|
1091
1092
|
try {
|
|
1092
1093
|
await auth.signOut();
|
|
1093
|
-
json$
|
|
1094
|
+
json$4(res, 200, { ok: true });
|
|
1094
1095
|
} catch (error) {
|
|
1095
|
-
json$
|
|
1096
|
+
json$4(res, 500, { error: safeMessage(error) });
|
|
1096
1097
|
}
|
|
1097
1098
|
}
|
|
1098
1099
|
}),
|
|
@@ -1100,22 +1101,22 @@ function registerOpenAICodexAuthRoutes(ctx, store, trustedOriginsOverride, fastM
|
|
|
1100
1101
|
kind: "exact",
|
|
1101
1102
|
path: OPENAI_CODEX_FAST_MODE_PATH,
|
|
1102
1103
|
handler: async (req, res) => {
|
|
1103
|
-
if (req.method !== "GET" && req.method !== "POST") return json$
|
|
1104
|
+
if (req.method !== "GET" && req.method !== "POST") return json$4(res, 405, { error: "method not allowed" });
|
|
1104
1105
|
if (!await authorize(req, res)) return;
|
|
1105
1106
|
if (req.method === "GET") {
|
|
1106
1107
|
const sessionId = fastModeSessionIdFromQuery(req);
|
|
1107
|
-
if (sessionId === void 0) return json$
|
|
1108
|
-
return json$
|
|
1108
|
+
if (sessionId === void 0) return json$4(res, 400, { error: "invalid input" });
|
|
1109
|
+
return json$4(res, 200, { enabled: fastMode.isEnabled(sessionId) });
|
|
1109
1110
|
}
|
|
1110
1111
|
const type = header(req, "content-type");
|
|
1111
|
-
if (type === void 0 || !/^application\/json(?:\s*;|$)/iu.test(type.trim())) return json$
|
|
1112
|
+
if (type === void 0 || !/^application\/json(?:\s*;|$)/iu.test(type.trim())) return json$4(res, 415, { error: "unsupported content type" });
|
|
1112
1113
|
try {
|
|
1113
1114
|
const body = fastModeBody(await readFastModeBody(req));
|
|
1114
|
-
if (body === void 0) return json$
|
|
1115
|
+
if (body === void 0) return json$4(res, 400, { error: "invalid input" });
|
|
1115
1116
|
fastMode.set(body.sessionId, body.enabled);
|
|
1116
|
-
return json$
|
|
1117
|
+
return json$4(res, 200, { enabled: fastMode.isEnabled(body.sessionId) });
|
|
1117
1118
|
} catch (error) {
|
|
1118
|
-
return json$
|
|
1119
|
+
return json$4(res, error instanceof RangeError ? 413 : 400, { error: error instanceof RangeError ? "request body too large" : "invalid input" });
|
|
1119
1120
|
}
|
|
1120
1121
|
}
|
|
1121
1122
|
})
|
|
@@ -1455,7 +1456,7 @@ const OPENAI_CODEX_PROXY_DETECT_PATH = "/plugins/dsh-openai-codex/proxy/detect";
|
|
|
1455
1456
|
const OPENAI_CODEX_PROXY_TEST_PATH = "/plugins/dsh-openai-codex/proxy/test";
|
|
1456
1457
|
//#endregion
|
|
1457
1458
|
//#region src/proxy-routes.ts
|
|
1458
|
-
function json$
|
|
1459
|
+
function json$3(res, status, value) {
|
|
1459
1460
|
res.writeHead(status, {
|
|
1460
1461
|
"content-type": "application/json; charset=utf-8",
|
|
1461
1462
|
"cache-control": "no-store",
|
|
@@ -1478,17 +1479,17 @@ function registerOpenAICodexProxyRoutes(ctx, trustedOrigins, manager) {
|
|
|
1478
1479
|
const authorize = async (req, res) => {
|
|
1479
1480
|
const decision = await trustedRequestDecision(req, trustedOrigins);
|
|
1480
1481
|
if (decision.trusted) return true;
|
|
1481
|
-
json$
|
|
1482
|
+
json$3(res, 403, { error: decision.error });
|
|
1482
1483
|
return false;
|
|
1483
1484
|
};
|
|
1484
1485
|
const routes = [ctx.webServer.register({
|
|
1485
1486
|
kind: "exact",
|
|
1486
1487
|
path: OPENAI_CODEX_PROXY_DETECT_PATH,
|
|
1487
1488
|
handler: async (req, res) => {
|
|
1488
|
-
if (req.method !== "POST") return json$
|
|
1489
|
+
if (req.method !== "POST") return json$3(res, 405, { error: "method not allowed" });
|
|
1489
1490
|
if (!await authorize(req, res)) return;
|
|
1490
1491
|
const results = await detectOpenAICodexProxies(manager);
|
|
1491
|
-
return json$
|
|
1492
|
+
return json$3(res, 200, {
|
|
1492
1493
|
candidates: results.filter((result) => result.reachable),
|
|
1493
1494
|
results
|
|
1494
1495
|
});
|
|
@@ -1497,11 +1498,11 @@ function registerOpenAICodexProxyRoutes(ctx, trustedOrigins, manager) {
|
|
|
1497
1498
|
kind: "exact",
|
|
1498
1499
|
path: OPENAI_CODEX_PROXY_TEST_PATH,
|
|
1499
1500
|
handler: async (req, res) => {
|
|
1500
|
-
if (req.method !== "POST") return json$
|
|
1501
|
+
if (req.method !== "POST") return json$3(res, 405, { error: "method not allowed" });
|
|
1501
1502
|
if (!await authorize(req, res)) return;
|
|
1502
1503
|
const proxyUrl = proxyUrlFromQuery(req);
|
|
1503
|
-
if (proxyUrl === void 0) return json$
|
|
1504
|
-
return json$
|
|
1504
|
+
if (proxyUrl === void 0) return json$3(res, 400, { error: "invalid proxy URL" });
|
|
1505
|
+
return json$3(res, 200, await manager.probe(proxyUrl));
|
|
1505
1506
|
}
|
|
1506
1507
|
})];
|
|
1507
1508
|
return () => {
|
|
@@ -1616,7 +1617,12 @@ function evaluateCompatibility(input = {}) {
|
|
|
1616
1617
|
}
|
|
1617
1618
|
/** Alias for callers that prefer assessment terminology. */
|
|
1618
1619
|
const assessCompatibility = evaluateCompatibility;
|
|
1619
|
-
|
|
1620
|
+
/**
|
|
1621
|
+
* Resolve installed package metadata without returning a filesystem path.
|
|
1622
|
+
* @param name - package to resolve from this plugin installation.
|
|
1623
|
+
* @returns its version, or undefined when metadata cannot be read.
|
|
1624
|
+
*/
|
|
1625
|
+
async function readInstalledPackageVersion(name) {
|
|
1620
1626
|
let entry;
|
|
1621
1627
|
try {
|
|
1622
1628
|
const resolved = import.meta.resolve(name);
|
|
@@ -1639,7 +1645,7 @@ async function readPackageVersionFromEntry(name) {
|
|
|
1639
1645
|
}
|
|
1640
1646
|
/** Read installed package metadata and return only versions and statuses. */
|
|
1641
1647
|
async function detectCompatibility(options = {}) {
|
|
1642
|
-
const readVersion = options.readPackageVersion ??
|
|
1648
|
+
const readVersion = options.readPackageVersion ?? readInstalledPackageVersion;
|
|
1643
1649
|
const packageVersions = options.packageVersions ?? options.packages ?? options.installed?.packages;
|
|
1644
1650
|
const resolvedPackages = packageVersions === void 0 ? Object.fromEntries(await Promise.all(COMPATIBILITY_PACKAGES.map(async (name) => [name, await readVersion(name)]))) : packageVersions;
|
|
1645
1651
|
return evaluateCompatibility({
|
|
@@ -2094,7 +2100,7 @@ async function detectCurrentDshVersion() {
|
|
|
2094
2100
|
const adapter = report.packages["@deepseek-ai/dsh-llm-pi-ai"].installed;
|
|
2095
2101
|
return llm !== null && llm === adapter ? llm : void 0;
|
|
2096
2102
|
}
|
|
2097
|
-
function json$
|
|
2103
|
+
function json$2(res, status, value) {
|
|
2098
2104
|
res.writeHead(status, {
|
|
2099
2105
|
"content-type": "application/json; charset=utf-8",
|
|
2100
2106
|
"cache-control": "no-store",
|
|
@@ -2109,14 +2115,14 @@ function registerOpenAICodexUpdateRoutes(ctx, options, trustedOrigins) {
|
|
|
2109
2115
|
kind: "exact",
|
|
2110
2116
|
path: OPENAI_CODEX_RUNTIME_PATH,
|
|
2111
2117
|
handler: async (req, res) => {
|
|
2112
|
-
if (req.method !== "GET") return json$
|
|
2118
|
+
if (req.method !== "GET") return json$2(res, 405, { error: "method not allowed" });
|
|
2113
2119
|
const decision = await trustedRequestDecision(req, trustedOrigins);
|
|
2114
|
-
if (!decision.trusted) return json$
|
|
2120
|
+
if (!decision.trusted) return json$2(res, 403, { error: decision.error });
|
|
2115
2121
|
try {
|
|
2116
2122
|
const currentDshVersion = await (options.resolveCurrentDshVersion ?? detectCurrentDshVersion)();
|
|
2117
|
-
return json$
|
|
2123
|
+
return json$2(res, 200, currentDshVersion === void 0 ? {} : { currentDshVersion });
|
|
2118
2124
|
} catch {
|
|
2119
|
-
return json$
|
|
2125
|
+
return json$2(res, 200, {});
|
|
2120
2126
|
}
|
|
2121
2127
|
}
|
|
2122
2128
|
});
|
|
@@ -2124,9 +2130,9 @@ function registerOpenAICodexUpdateRoutes(ctx, options, trustedOrigins) {
|
|
|
2124
2130
|
kind: "exact",
|
|
2125
2131
|
path: OPENAI_CODEX_UPDATE_PATH,
|
|
2126
2132
|
handler: async (req, res) => {
|
|
2127
|
-
if (req.method !== "GET") return json$
|
|
2133
|
+
if (req.method !== "GET") return json$2(res, 405, { error: "method not allowed" });
|
|
2128
2134
|
const decision = await trustedRequestDecision(req, trustedOrigins);
|
|
2129
|
-
if (!decision.trusted) return json$
|
|
2135
|
+
if (!decision.trusted) return json$2(res, 403, { error: decision.error });
|
|
2130
2136
|
let result;
|
|
2131
2137
|
let currentDshVersion;
|
|
2132
2138
|
try {
|
|
@@ -2149,7 +2155,7 @@ function registerOpenAICodexUpdateRoutes(ctx, options, trustedOrigins) {
|
|
|
2149
2155
|
reason: "registry-unavailable"
|
|
2150
2156
|
};
|
|
2151
2157
|
}
|
|
2152
|
-
return json$
|
|
2158
|
+
return json$2(res, 200, result);
|
|
2153
2159
|
}
|
|
2154
2160
|
});
|
|
2155
2161
|
return () => {
|
|
@@ -2165,7 +2171,7 @@ function registerOpenAICodexUpdateRoutes(ctx, options, trustedOrigins) {
|
|
|
2165
2171
|
const OPENAI_CODEX_MODEL_CATALOG_PATH = "/plugins/dsh-codex-connect/models";
|
|
2166
2172
|
//#endregion
|
|
2167
2173
|
//#region src/model-routes.ts
|
|
2168
|
-
function json(res, status, value) {
|
|
2174
|
+
function json$1(res, status, value) {
|
|
2169
2175
|
res.writeHead(status, {
|
|
2170
2176
|
"content-type": "application/json; charset=utf-8",
|
|
2171
2177
|
"cache-control": "no-store",
|
|
@@ -2178,17 +2184,162 @@ function registerOpenAICodexModelCatalogRoute(ctx, resolveCatalog, trustedOrigin
|
|
|
2178
2184
|
ctx.effect(() => ctx.webServer.register({
|
|
2179
2185
|
kind: "exact",
|
|
2180
2186
|
path: OPENAI_CODEX_MODEL_CATALOG_PATH,
|
|
2187
|
+
handler: async (req, res) => {
|
|
2188
|
+
if (req.method !== "GET") return json$1(res, 405, { error: "method not allowed" });
|
|
2189
|
+
const decision = await trustedRequestDecision(req, trustedOrigins);
|
|
2190
|
+
if (!decision.trusted) return json$1(res, 403, { error: decision.error });
|
|
2191
|
+
return json$1(res, 200, resolveCatalog());
|
|
2192
|
+
}
|
|
2193
|
+
}), "dsh-codex-connect: model catalog route");
|
|
2194
|
+
}
|
|
2195
|
+
//#endregion
|
|
2196
|
+
//#region src/image-assets-contract.ts
|
|
2197
|
+
/** Same-origin endpoint serving one session-owned original generated image. */
|
|
2198
|
+
const OPENAI_CODEX_ORIGINAL_IMAGE_PATH = "/plugins/dsh-codex-connect/images/original";
|
|
2199
|
+
/** Opaque identifier format for one plugin-owned original image. */
|
|
2200
|
+
const OPENAI_CODEX_IMAGE_ASSET_ID_PATTERN = /^img_[0-9a-f]{32}$/u;
|
|
2201
|
+
function positiveSafeInteger$3(value) {
|
|
2202
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
|
|
2203
|
+
}
|
|
2204
|
+
/** Decode session-log metadata without trusting an asset id, filename, or media type. */
|
|
2205
|
+
function decodeOpenAICodexOriginalImageRef(value) {
|
|
2206
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
2207
|
+
const candidate = value;
|
|
2208
|
+
if (typeof candidate.assetId !== "string" || !OPENAI_CODEX_IMAGE_ASSET_ID_PATTERN.test(candidate.assetId) || candidate.mediaType !== "image/png" && candidate.mediaType !== "image/jpeg" && candidate.mediaType !== "image/webp" || !positiveSafeInteger$3(candidate.width) || !positiveSafeInteger$3(candidate.height) || !positiveSafeInteger$3(candidate.bytes) || candidate.bytes > 50331648 || typeof candidate.name !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u.test(candidate.name) || typeof candidate.sha256 !== "string" || !/^[0-9a-f]{64}$/u.test(candidate.sha256)) return void 0;
|
|
2209
|
+
return {
|
|
2210
|
+
assetId: candidate.assetId,
|
|
2211
|
+
mediaType: candidate.mediaType,
|
|
2212
|
+
width: candidate.width,
|
|
2213
|
+
height: candidate.height,
|
|
2214
|
+
bytes: candidate.bytes,
|
|
2215
|
+
name: candidate.name,
|
|
2216
|
+
sha256: candidate.sha256
|
|
2217
|
+
};
|
|
2218
|
+
}
|
|
2219
|
+
//#endregion
|
|
2220
|
+
//#region src/image-presentation.ts
|
|
2221
|
+
/** Stable metadata marker for generated image result views. */
|
|
2222
|
+
const IMAGE_PRESENTATION_KIND = "codex-connect-images";
|
|
2223
|
+
function positiveSafeInteger$2(value) {
|
|
2224
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
|
|
2225
|
+
}
|
|
2226
|
+
function mediaType$1(value) {
|
|
2227
|
+
return value === "image/png" || value === "image/jpeg" || value === "image/webp";
|
|
2228
|
+
}
|
|
2229
|
+
function imageRef(value) {
|
|
2230
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
2231
|
+
const candidate = value;
|
|
2232
|
+
if (typeof candidate.attachmentId !== "string" || candidate.attachmentId.length === 0 || !mediaType$1(candidate.mediaType) || !positiveSafeInteger$2(candidate.bytes) || !positiveSafeInteger$2(candidate.width) || !positiveSafeInteger$2(candidate.height) || candidate.name !== void 0 && (typeof candidate.name !== "string" || candidate.name.length === 0)) return void 0;
|
|
2233
|
+
return {
|
|
2234
|
+
attachmentId: candidate.attachmentId,
|
|
2235
|
+
mediaType: candidate.mediaType,
|
|
2236
|
+
bytes: candidate.bytes,
|
|
2237
|
+
width: candidate.width,
|
|
2238
|
+
height: candidate.height,
|
|
2239
|
+
...candidate.name === void 0 ? {} : { name: candidate.name }
|
|
2240
|
+
};
|
|
2241
|
+
}
|
|
2242
|
+
/** Decode durable tool-result metadata without trusting arbitrary session JSON. */
|
|
2243
|
+
function decodeImagePresentationMeta(value) {
|
|
2244
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
2245
|
+
const candidate = value;
|
|
2246
|
+
if (candidate.kind !== "codex-connect-images" || typeof candidate.prompt !== "string" || candidate.prompt.length < 1 || candidate.prompt.length > 32e3 || !Array.isArray(candidate.images) || candidate.images.length < 1 || candidate.images.length > 4) return void 0;
|
|
2247
|
+
if (candidate.schemaVersion === void 0) {
|
|
2248
|
+
const previews = candidate.images.map(imageRef);
|
|
2249
|
+
if (previews.some((image) => image === void 0)) return void 0;
|
|
2250
|
+
return {
|
|
2251
|
+
kind: IMAGE_PRESENTATION_KIND,
|
|
2252
|
+
schemaVersion: 1,
|
|
2253
|
+
prompt: candidate.prompt,
|
|
2254
|
+
images: previews.map((preview) => ({ preview }))
|
|
2255
|
+
};
|
|
2256
|
+
}
|
|
2257
|
+
if (candidate.schemaVersion !== 1) return void 0;
|
|
2258
|
+
const images = [];
|
|
2259
|
+
for (const value of candidate.images) {
|
|
2260
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
2261
|
+
const entry = value;
|
|
2262
|
+
const preview = imageRef(entry.preview);
|
|
2263
|
+
const original = decodeOpenAICodexOriginalImageRef(entry.original);
|
|
2264
|
+
if (preview === void 0 || original === void 0) return void 0;
|
|
2265
|
+
images.push({
|
|
2266
|
+
preview,
|
|
2267
|
+
original
|
|
2268
|
+
});
|
|
2269
|
+
}
|
|
2270
|
+
return {
|
|
2271
|
+
kind: IMAGE_PRESENTATION_KIND,
|
|
2272
|
+
schemaVersion: 1,
|
|
2273
|
+
prompt: candidate.prompt,
|
|
2274
|
+
images
|
|
2275
|
+
};
|
|
2276
|
+
}
|
|
2277
|
+
//#endregion
|
|
2278
|
+
//#region src/image-asset-routes.ts
|
|
2279
|
+
/** Fork access follows copied result events, not every asset owned by an ancestor. */
|
|
2280
|
+
function inheritedOriginal(session, assetId) {
|
|
2281
|
+
if (session?.header.parentSession === void 0) return void 0;
|
|
2282
|
+
const seedLength = session.header.seedLength ?? 0;
|
|
2283
|
+
for (const event of session.events) {
|
|
2284
|
+
if (event.seq >= seedLength) break;
|
|
2285
|
+
if (event.type !== "tool/result") continue;
|
|
2286
|
+
const original = decodeImagePresentationMeta(event.data.meta)?.images.find((image) => image.original?.assetId === assetId)?.original;
|
|
2287
|
+
if (original !== void 0) return original;
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
function json(res, status, value) {
|
|
2291
|
+
res.writeHead(status, {
|
|
2292
|
+
"content-type": "application/json; charset=utf-8",
|
|
2293
|
+
"cache-control": "no-store",
|
|
2294
|
+
"x-content-type-options": "nosniff"
|
|
2295
|
+
});
|
|
2296
|
+
res.end(JSON.stringify(value));
|
|
2297
|
+
}
|
|
2298
|
+
function query(req) {
|
|
2299
|
+
if (typeof req.url !== "string") return void 0;
|
|
2300
|
+
try {
|
|
2301
|
+
const parsed = new URL(req.url, "http://dsh.invalid");
|
|
2302
|
+
const sessionIds = parsed.searchParams.getAll("sessionId");
|
|
2303
|
+
const assetIds = parsed.searchParams.getAll("assetId");
|
|
2304
|
+
const sessionId = sessionIds[0];
|
|
2305
|
+
const assetId = assetIds[0];
|
|
2306
|
+
if (sessionIds.length !== 1 || assetIds.length !== 1 || sessionId === void 0 || sessionId.length < 1 || sessionId.length > 512 || assetId === void 0 || !OPENAI_CODEX_IMAGE_ASSET_ID_PATTERN.test(assetId)) return void 0;
|
|
2307
|
+
return {
|
|
2308
|
+
sessionId,
|
|
2309
|
+
assetId
|
|
2310
|
+
};
|
|
2311
|
+
} catch {
|
|
2312
|
+
return;
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
/** Register a history-safe route even while new image generation is disabled. */
|
|
2316
|
+
function registerOpenAICodexOriginalImageRoute(ctx, trustedOrigins, assets) {
|
|
2317
|
+
ctx.effect(() => ctx.webServer.register({
|
|
2318
|
+
kind: "exact",
|
|
2319
|
+
path: OPENAI_CODEX_ORIGINAL_IMAGE_PATH,
|
|
2181
2320
|
handler: async (req, res) => {
|
|
2182
2321
|
if (req.method !== "GET") return json(res, 405, { error: "method not allowed" });
|
|
2183
2322
|
const decision = await trustedRequestDecision(req, trustedOrigins);
|
|
2184
2323
|
if (!decision.trusted) return json(res, 403, { error: decision.error });
|
|
2185
|
-
|
|
2324
|
+
const requested = query(req);
|
|
2325
|
+
if (requested === void 0) return json(res, 400, { error: "invalid input" });
|
|
2326
|
+
const session = ctx.get("sessions")?.get(SessionId(requested.sessionId));
|
|
2327
|
+
const stored = await assets.read(requested.sessionId, requested.assetId, inheritedOriginal(session, requested.assetId));
|
|
2328
|
+
if (stored === void 0) return json(res, 404, { error: "original image not found" });
|
|
2329
|
+
res.writeHead(200, {
|
|
2330
|
+
"content-type": stored.ref.mediaType,
|
|
2331
|
+
"content-length": String(stored.ref.bytes),
|
|
2332
|
+
"content-disposition": `attachment; filename="${stored.ref.name}"`,
|
|
2333
|
+
"cache-control": "private, no-store",
|
|
2334
|
+
"x-content-type-options": "nosniff"
|
|
2335
|
+
});
|
|
2336
|
+
res.end(Buffer.from(stored.data));
|
|
2186
2337
|
}
|
|
2187
|
-
}), "dsh-codex-connect:
|
|
2338
|
+
}), "dsh-codex-connect: original image download route");
|
|
2188
2339
|
}
|
|
2189
2340
|
//#endregion
|
|
2190
2341
|
//#region src/version.ts
|
|
2191
|
-
const CODEX_CONNECT_VERSION = "0.1.0-alpha.4.
|
|
2342
|
+
const CODEX_CONNECT_VERSION = "0.1.0-alpha.4.21";
|
|
2192
2343
|
//#endregion
|
|
2193
2344
|
//#region src/doctor.ts
|
|
2194
2345
|
/** Secret-free diagnostics and duplicate-provider guidance. */
|
|
@@ -2370,10 +2521,6 @@ function detectEncodedImage(data) {
|
|
|
2370
2521
|
}
|
|
2371
2522
|
}
|
|
2372
2523
|
//#endregion
|
|
2373
|
-
//#region src/image-presentation.ts
|
|
2374
|
-
/** Stable metadata marker for generated image result views. */
|
|
2375
|
-
const IMAGE_PRESENTATION_KIND = "codex-connect-images";
|
|
2376
|
-
//#endregion
|
|
2377
2524
|
//#region src/image-tool.ts
|
|
2378
2525
|
/** Stable model-callable tool name. */
|
|
2379
2526
|
const IMAGE_GENERATE_TOOL_NAME = "codex_connect_image_generate";
|
|
@@ -2405,29 +2552,41 @@ function extension(mediaType) {
|
|
|
2405
2552
|
return mediaType === "image/jpeg" ? "jpg" : mediaType.slice(6);
|
|
2406
2553
|
}
|
|
2407
2554
|
function outputContent(value) {
|
|
2408
|
-
const lines = value.images.map((
|
|
2555
|
+
const lines = value.images.map(({ original, preview }, index) => `${String(index + 1)}. original ${original.mediaType}, ${String(original.width)}x${String(original.height)} px, ${String(original.bytes)} bytes; preview ${String(preview.width)}x${String(preview.height)} px, attachment ${preview.attachmentId}`);
|
|
2409
2556
|
return [{
|
|
2410
2557
|
type: "text",
|
|
2411
2558
|
text: `Generated ${String(value.images.length)} image${value.images.length === 1 ? "" : "s"}:\n${lines.join("\n")}`
|
|
2412
|
-
}, ...value.images.map((
|
|
2559
|
+
}, ...value.images.map(({ preview }) => ({
|
|
2413
2560
|
type: "image",
|
|
2414
2561
|
attachment: {
|
|
2415
|
-
attachmentId: AttachmentId(
|
|
2416
|
-
mediaType:
|
|
2417
|
-
width:
|
|
2418
|
-
height:
|
|
2419
|
-
bytes:
|
|
2420
|
-
name:
|
|
2562
|
+
attachmentId: AttachmentId(preview.attachmentId),
|
|
2563
|
+
mediaType: preview.mediaType,
|
|
2564
|
+
width: preview.width,
|
|
2565
|
+
height: preview.height,
|
|
2566
|
+
bytes: preview.bytes,
|
|
2567
|
+
name: preview.name
|
|
2421
2568
|
}
|
|
2422
2569
|
}))];
|
|
2423
2570
|
}
|
|
2424
|
-
function
|
|
2425
|
-
|
|
2571
|
+
function positiveSafeInteger$1(value) {
|
|
2572
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
|
|
2573
|
+
}
|
|
2574
|
+
function previewValue(ref, fallbackName) {
|
|
2575
|
+
if (typeof ref.attachmentId !== "string" || ref.attachmentId.length === 0 || ref.mediaType !== "image/png" && ref.mediaType !== "image/jpeg" && ref.mediaType !== "image/webp" || !positiveSafeInteger$1(ref.bytes) || !positiveSafeInteger$1(ref.width) || !positiveSafeInteger$1(ref.height)) failure("The attachment store returned invalid preview metadata.");
|
|
2576
|
+
const name = typeof ref.name === "string" && /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u.test(ref.name) ? ref.name : fallbackName;
|
|
2577
|
+
return {
|
|
2578
|
+
attachmentId: ref.attachmentId,
|
|
2579
|
+
mediaType: ref.mediaType,
|
|
2580
|
+
width: ref.width,
|
|
2581
|
+
height: ref.height,
|
|
2582
|
+
bytes: ref.bytes,
|
|
2583
|
+
name
|
|
2584
|
+
};
|
|
2426
2585
|
}
|
|
2427
2586
|
function executionKey(exec) {
|
|
2428
2587
|
return `${String(exec.agent?.id ?? "<no-agent>")}\u0000${String(exec.rootCallId)}\u0000${String(exec.callId)}`;
|
|
2429
2588
|
}
|
|
2430
|
-
async function generate(ctx, transport, prompt, exec) {
|
|
2589
|
+
async function generate(ctx, transport, assets, prompt, exec) {
|
|
2431
2590
|
let response;
|
|
2432
2591
|
try {
|
|
2433
2592
|
response = await transport.generateImages({ prompt }, { signal: exec.signal });
|
|
@@ -2463,28 +2622,49 @@ async function generate(ctx, transport, prompt, exec) {
|
|
|
2463
2622
|
name
|
|
2464
2623
|
});
|
|
2465
2624
|
}
|
|
2625
|
+
const sessionId = exec.agent?.id;
|
|
2626
|
+
if (sessionId === void 0) failure("Image generation requires a session-owned tool call.");
|
|
2627
|
+
let originals;
|
|
2628
|
+
try {
|
|
2629
|
+
originals = await assets.saveImages(String(sessionId), inputs.map((input, index) => {
|
|
2630
|
+
const parsed = parsedImages[index];
|
|
2631
|
+
if (parsed === void 0 || input.name === void 0) failure("The generated image batch is incomplete.");
|
|
2632
|
+
return {
|
|
2633
|
+
data: input.data,
|
|
2634
|
+
mediaType: parsed.mediaType,
|
|
2635
|
+
width: parsed.width,
|
|
2636
|
+
height: parsed.height,
|
|
2637
|
+
name: input.name
|
|
2638
|
+
};
|
|
2639
|
+
}));
|
|
2640
|
+
} catch {
|
|
2641
|
+
failure("The generated original images could not be saved.");
|
|
2642
|
+
}
|
|
2466
2643
|
let refs;
|
|
2467
2644
|
try {
|
|
2468
2645
|
refs = await ctx.attachments.saveImages(inputs);
|
|
2469
2646
|
} catch {
|
|
2647
|
+
await assets.removeImages(originals);
|
|
2470
2648
|
failure("The generated images could not be saved; no attachment references were returned.");
|
|
2471
2649
|
}
|
|
2472
|
-
if (refs.length !== inputs.length
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2650
|
+
if (refs.length !== inputs.length || originals.length !== inputs.length) {
|
|
2651
|
+
await assets.removeImages(originals);
|
|
2652
|
+
failure("The image stores returned an incomplete image batch.");
|
|
2653
|
+
}
|
|
2654
|
+
try {
|
|
2655
|
+
return { images: refs.map((ref, index) => {
|
|
2656
|
+
const original = originals[index];
|
|
2657
|
+
const name = inputs[index]?.name;
|
|
2658
|
+
if (original === void 0 || name === void 0) failure("The image stores returned an incomplete image batch.");
|
|
2659
|
+
return {
|
|
2660
|
+
original,
|
|
2661
|
+
preview: previewValue(ref, name)
|
|
2662
|
+
};
|
|
2663
|
+
}) };
|
|
2664
|
+
} catch (error) {
|
|
2665
|
+
await assets.removeImages(originals);
|
|
2666
|
+
throw error;
|
|
2667
|
+
}
|
|
2488
2668
|
}
|
|
2489
2669
|
function appendAbortNote(result) {
|
|
2490
2670
|
if (!result.isError || result.error.info?.code !== TOOL_ABORTED) return void 0;
|
|
@@ -2495,11 +2675,11 @@ function appendAbortNote(result) {
|
|
|
2495
2675
|
}];
|
|
2496
2676
|
}
|
|
2497
2677
|
/** Build one fiber-owned image tool, including in-flight call deduplication. */
|
|
2498
|
-
function imageGenerateTool(ctx) {
|
|
2678
|
+
function imageGenerateTool(ctx, assets) {
|
|
2499
2679
|
const inFlight = /* @__PURE__ */ new Map();
|
|
2500
2680
|
return defineTool({
|
|
2501
2681
|
name: IMAGE_GENERATE_TOOL_NAME,
|
|
2502
|
-
description: "Generate an image from a text prompt and save
|
|
2682
|
+
description: "Generate an image from a text prompt, preserve the exact original, and save a DSH conversation preview. Supports one prompt only; output size and style are service defaults.",
|
|
2503
2683
|
parameters: { prompt: {
|
|
2504
2684
|
type: "string",
|
|
2505
2685
|
required: true,
|
|
@@ -2516,34 +2696,81 @@ function imageGenerateTool(ctx) {
|
|
|
2516
2696
|
type: "object",
|
|
2517
2697
|
additionalProperties: false,
|
|
2518
2698
|
properties: {
|
|
2519
|
-
|
|
2520
|
-
type: "
|
|
2521
|
-
required: true
|
|
2522
|
-
},
|
|
2523
|
-
mediaType: {
|
|
2524
|
-
type: "string",
|
|
2699
|
+
original: {
|
|
2700
|
+
type: "object",
|
|
2525
2701
|
required: true,
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2702
|
+
additionalProperties: false,
|
|
2703
|
+
properties: {
|
|
2704
|
+
assetId: {
|
|
2705
|
+
type: "string",
|
|
2706
|
+
required: true
|
|
2707
|
+
},
|
|
2708
|
+
mediaType: {
|
|
2709
|
+
type: "string",
|
|
2710
|
+
required: true,
|
|
2711
|
+
enum: [
|
|
2712
|
+
"image/png",
|
|
2713
|
+
"image/jpeg",
|
|
2714
|
+
"image/webp"
|
|
2715
|
+
]
|
|
2716
|
+
},
|
|
2717
|
+
width: {
|
|
2718
|
+
type: "integer",
|
|
2719
|
+
required: true
|
|
2720
|
+
},
|
|
2721
|
+
height: {
|
|
2722
|
+
type: "integer",
|
|
2723
|
+
required: true
|
|
2724
|
+
},
|
|
2725
|
+
bytes: {
|
|
2726
|
+
type: "integer",
|
|
2727
|
+
required: true
|
|
2728
|
+
},
|
|
2729
|
+
name: {
|
|
2730
|
+
type: "string",
|
|
2731
|
+
required: true
|
|
2732
|
+
},
|
|
2733
|
+
sha256: {
|
|
2734
|
+
type: "string",
|
|
2735
|
+
required: true
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2531
2738
|
},
|
|
2532
|
-
|
|
2533
|
-
type: "
|
|
2534
|
-
required: true
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2739
|
+
preview: {
|
|
2740
|
+
type: "object",
|
|
2741
|
+
required: true,
|
|
2742
|
+
additionalProperties: false,
|
|
2743
|
+
properties: {
|
|
2744
|
+
attachmentId: {
|
|
2745
|
+
type: "string",
|
|
2746
|
+
required: true
|
|
2747
|
+
},
|
|
2748
|
+
mediaType: {
|
|
2749
|
+
type: "string",
|
|
2750
|
+
required: true,
|
|
2751
|
+
enum: [
|
|
2752
|
+
"image/png",
|
|
2753
|
+
"image/jpeg",
|
|
2754
|
+
"image/webp"
|
|
2755
|
+
]
|
|
2756
|
+
},
|
|
2757
|
+
width: {
|
|
2758
|
+
type: "integer",
|
|
2759
|
+
required: true
|
|
2760
|
+
},
|
|
2761
|
+
height: {
|
|
2762
|
+
type: "integer",
|
|
2763
|
+
required: true
|
|
2764
|
+
},
|
|
2765
|
+
bytes: {
|
|
2766
|
+
type: "integer",
|
|
2767
|
+
required: true
|
|
2768
|
+
},
|
|
2769
|
+
name: {
|
|
2770
|
+
type: "string",
|
|
2771
|
+
required: true
|
|
2772
|
+
}
|
|
2773
|
+
}
|
|
2547
2774
|
}
|
|
2548
2775
|
}
|
|
2549
2776
|
}
|
|
@@ -2552,6 +2779,7 @@ function imageGenerateTool(ctx) {
|
|
|
2552
2779
|
render: (_args, value) => outputContent(value),
|
|
2553
2780
|
presentationMeta: (args, value) => ({
|
|
2554
2781
|
kind: IMAGE_PRESENTATION_KIND,
|
|
2782
|
+
schemaVersion: 1,
|
|
2555
2783
|
prompt: args.prompt.trim(),
|
|
2556
2784
|
images: value.images
|
|
2557
2785
|
})
|
|
@@ -2567,7 +2795,7 @@ function imageGenerateTool(ctx) {
|
|
|
2567
2795
|
const key = executionKey(exec);
|
|
2568
2796
|
const current = inFlight.get(key);
|
|
2569
2797
|
if (current !== void 0) return current;
|
|
2570
|
-
const pending = generate(ctx, transport, prompt, exec).catch((error) => {
|
|
2798
|
+
const pending = generate(ctx, transport, assets, prompt, exec).catch((error) => {
|
|
2571
2799
|
if (error instanceof SafeToolError) throw error;
|
|
2572
2800
|
failure(fixedTransportMessage(error));
|
|
2573
2801
|
}).finally(() => {
|
|
@@ -3185,6 +3413,169 @@ var OpenAICodexTransport = class extends Service {
|
|
|
3185
3413
|
}
|
|
3186
3414
|
};
|
|
3187
3415
|
//#endregion
|
|
3416
|
+
//#region src/image-assets.ts
|
|
3417
|
+
/** Owner-only storage for exact GPT Image output bytes. */
|
|
3418
|
+
/** Versioned plugin-owned root below DSH_HOME; this is not the DSH attachment store. */
|
|
3419
|
+
const OPENAI_CODEX_IMAGE_ASSET_DIRECTORY = "dsh-codex-connect/images/v1";
|
|
3420
|
+
const METADATA_VERSION = 1;
|
|
3421
|
+
const ORIGINAL_FILENAME = "original";
|
|
3422
|
+
const METADATA_FILENAME = "metadata.json";
|
|
3423
|
+
const SAFE_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u;
|
|
3424
|
+
function digest(data) {
|
|
3425
|
+
return createHash("sha256").update(data).digest("hex");
|
|
3426
|
+
}
|
|
3427
|
+
function positiveSafeInteger(value) {
|
|
3428
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
|
|
3429
|
+
}
|
|
3430
|
+
function mediaType(value) {
|
|
3431
|
+
return value === "image/png" || value === "image/jpeg" || value === "image/webp";
|
|
3432
|
+
}
|
|
3433
|
+
function validSessionId(value) {
|
|
3434
|
+
return typeof value === "string" && value.length > 0 && value.length <= 512;
|
|
3435
|
+
}
|
|
3436
|
+
function parseDocument(text) {
|
|
3437
|
+
let value;
|
|
3438
|
+
try {
|
|
3439
|
+
value = JSON.parse(text);
|
|
3440
|
+
} catch {
|
|
3441
|
+
return;
|
|
3442
|
+
}
|
|
3443
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
3444
|
+
const candidate = value;
|
|
3445
|
+
if (candidate.version !== METADATA_VERSION || !validSessionId(candidate.sessionId)) return void 0;
|
|
3446
|
+
const image = decodeOpenAICodexOriginalImageRef(candidate.image);
|
|
3447
|
+
return image === void 0 ? void 0 : {
|
|
3448
|
+
version: METADATA_VERSION,
|
|
3449
|
+
sessionId: candidate.sessionId,
|
|
3450
|
+
image
|
|
3451
|
+
};
|
|
3452
|
+
}
|
|
3453
|
+
async function assertOwnerFile(filename) {
|
|
3454
|
+
const metadata = await lstat(filename);
|
|
3455
|
+
if (!metadata.isFile()) throw new Error("original image object is not a regular file");
|
|
3456
|
+
/* v8 ignore next -- native Windows does not expose POSIX permission semantics. */
|
|
3457
|
+
if (process.platform !== "win32" && (metadata.mode & 63) !== 0) throw new Error("original image object is readable beyond its owner");
|
|
3458
|
+
}
|
|
3459
|
+
async function writeBinaryFile(filename, data) {
|
|
3460
|
+
await mkdir(dirname(filename), {
|
|
3461
|
+
recursive: true,
|
|
3462
|
+
mode: 448
|
|
3463
|
+
});
|
|
3464
|
+
const handle = await open(filename, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 384);
|
|
3465
|
+
try {
|
|
3466
|
+
await handle.writeFile(data);
|
|
3467
|
+
await handle.sync();
|
|
3468
|
+
} finally {
|
|
3469
|
+
await handle.close();
|
|
3470
|
+
}
|
|
3471
|
+
}
|
|
3472
|
+
/** Exact-byte image store kept separate from DSH's deliberately normalized attachments. */
|
|
3473
|
+
var OpenAICodexImageAssetStore = class {
|
|
3474
|
+
root;
|
|
3475
|
+
constructor(dshHome) {
|
|
3476
|
+
this.root = resolve(join(resolveDshHome(dshHome), OPENAI_CODEX_IMAGE_ASSET_DIRECTORY));
|
|
3477
|
+
}
|
|
3478
|
+
directory(assetId) {
|
|
3479
|
+
if (!OPENAI_CODEX_IMAGE_ASSET_ID_PATTERN.test(assetId)) throw new TypeError("invalid original image asset id");
|
|
3480
|
+
return join(this.root, assetId.slice(4, 6), assetId);
|
|
3481
|
+
}
|
|
3482
|
+
async saveOne(sessionId, input) {
|
|
3483
|
+
if (!validSessionId(sessionId)) throw new TypeError("invalid original image session id");
|
|
3484
|
+
if (!(input.data instanceof Uint8Array) || input.data.byteLength < 1 || input.data.byteLength > 50331648 || !mediaType(input.mediaType) || !positiveSafeInteger(input.width) || !positiveSafeInteger(input.height) || !SAFE_NAME_PATTERN.test(input.name)) throw new TypeError("invalid original image input");
|
|
3485
|
+
const detected = detectEncodedImage(input.data);
|
|
3486
|
+
if (detected === void 0 || detected.mediaType !== input.mediaType || detected.width !== input.width || detected.height !== input.height) throw new TypeError("original image bytes do not match their declared metadata");
|
|
3487
|
+
const assetId = `img_${randomUUID().replaceAll("-", "")}`;
|
|
3488
|
+
const directory = this.directory(assetId);
|
|
3489
|
+
const ref = {
|
|
3490
|
+
assetId,
|
|
3491
|
+
mediaType: input.mediaType,
|
|
3492
|
+
width: input.width,
|
|
3493
|
+
height: input.height,
|
|
3494
|
+
bytes: input.data.byteLength,
|
|
3495
|
+
name: input.name,
|
|
3496
|
+
sha256: digest(input.data)
|
|
3497
|
+
};
|
|
3498
|
+
try {
|
|
3499
|
+
await mkdir(dirname(directory), {
|
|
3500
|
+
recursive: true,
|
|
3501
|
+
mode: 448
|
|
3502
|
+
});
|
|
3503
|
+
await mkdir(directory, {
|
|
3504
|
+
recursive: false,
|
|
3505
|
+
mode: 448
|
|
3506
|
+
});
|
|
3507
|
+
await writeBinaryFile(join(directory, ORIGINAL_FILENAME), input.data);
|
|
3508
|
+
const document = {
|
|
3509
|
+
version: METADATA_VERSION,
|
|
3510
|
+
sessionId,
|
|
3511
|
+
image: ref
|
|
3512
|
+
};
|
|
3513
|
+
await writeFileAtomic(join(directory, METADATA_FILENAME), `${JSON.stringify(document, null, 2)}\n`, {
|
|
3514
|
+
mode: 384,
|
|
3515
|
+
dirMode: 448
|
|
3516
|
+
});
|
|
3517
|
+
return ref;
|
|
3518
|
+
} catch (error) {
|
|
3519
|
+
await rm(directory, {
|
|
3520
|
+
recursive: true,
|
|
3521
|
+
force: true
|
|
3522
|
+
}).catch(() => void 0);
|
|
3523
|
+
throw error;
|
|
3524
|
+
}
|
|
3525
|
+
}
|
|
3526
|
+
/** Save one complete response batch and remove already-written members if a later write fails. */
|
|
3527
|
+
async saveImages(sessionId, inputs) {
|
|
3528
|
+
if (inputs.length < 1 || inputs.length > 4) throw new TypeError("original image batch must contain 1 to 4 images");
|
|
3529
|
+
const refs = [];
|
|
3530
|
+
try {
|
|
3531
|
+
for (const input of inputs) refs.push(await this.saveOne(sessionId, input));
|
|
3532
|
+
return refs;
|
|
3533
|
+
} catch (error) {
|
|
3534
|
+
await this.removeImages(refs);
|
|
3535
|
+
throw error;
|
|
3536
|
+
}
|
|
3537
|
+
}
|
|
3538
|
+
/** Remove exact assets created by a failed cross-store operation. */
|
|
3539
|
+
async removeImages(refs) {
|
|
3540
|
+
await Promise.all(refs.map(async (ref) => {
|
|
3541
|
+
try {
|
|
3542
|
+
await rm(this.directory(ref.assetId), {
|
|
3543
|
+
recursive: true,
|
|
3544
|
+
force: true
|
|
3545
|
+
});
|
|
3546
|
+
} catch {}
|
|
3547
|
+
}));
|
|
3548
|
+
}
|
|
3549
|
+
/**
|
|
3550
|
+
* Read verified bytes for the owner or a server-authorized inherited reference.
|
|
3551
|
+
* @param sessionId - requesting session.
|
|
3552
|
+
* @param assetId - opaque original identifier.
|
|
3553
|
+
* @param inherited - reference resolved from the session's immutable fork prefix, never request data.
|
|
3554
|
+
* @returns the exact original, or undefined for denied access or invalid files.
|
|
3555
|
+
*/
|
|
3556
|
+
async read(sessionId, assetId, inherited) {
|
|
3557
|
+
if (!validSessionId(sessionId) || !OPENAI_CODEX_IMAGE_ASSET_ID_PATTERN.test(assetId)) return void 0;
|
|
3558
|
+
const directory = this.directory(assetId);
|
|
3559
|
+
const metadataPath = join(directory, METADATA_FILENAME);
|
|
3560
|
+
const originalPath = join(directory, ORIGINAL_FILENAME);
|
|
3561
|
+
try {
|
|
3562
|
+
await Promise.all([assertOwnerFile(metadataPath), assertOwnerFile(originalPath)]);
|
|
3563
|
+
const document = parseDocument(await readFile(metadataPath, "utf8"));
|
|
3564
|
+
if (document === void 0 || document.image.assetId !== assetId) return void 0;
|
|
3565
|
+
if (document.sessionId !== sessionId && (inherited === void 0 || inherited.assetId !== document.image.assetId || inherited.sha256 !== document.image.sha256 || inherited.mediaType !== document.image.mediaType || inherited.width !== document.image.width || inherited.height !== document.image.height || inherited.bytes !== document.image.bytes || inherited.name !== document.image.name)) return void 0;
|
|
3566
|
+
const data = new Uint8Array(await readFile(originalPath));
|
|
3567
|
+
const detected = detectEncodedImage(data);
|
|
3568
|
+
if (data.byteLength !== document.image.bytes || digest(data) !== document.image.sha256 || detected === void 0 || detected.mediaType !== document.image.mediaType || detected.width !== document.image.width || detected.height !== document.image.height) return void 0;
|
|
3569
|
+
return {
|
|
3570
|
+
ref: document.image,
|
|
3571
|
+
data
|
|
3572
|
+
};
|
|
3573
|
+
} catch {
|
|
3574
|
+
return;
|
|
3575
|
+
}
|
|
3576
|
+
}
|
|
3577
|
+
};
|
|
3578
|
+
//#endregion
|
|
3188
3579
|
//#region src/search.ts
|
|
3189
3580
|
/**
|
|
3190
3581
|
* OpenAI Codex standalone web search over the dsh web provider seam.
|
|
@@ -3706,6 +4097,7 @@ function apply(ctx, config) {
|
|
|
3706
4097
|
const resolveProviderProxyUrl = () => resolveOpenAICodexProxyUrl(resolveOpenAICodexSettings(current()));
|
|
3707
4098
|
let proxyWasActive = resolveProviderProxyUrl() !== void 0;
|
|
3708
4099
|
const credentials = new OpenAICodexCredentialStore();
|
|
4100
|
+
const imageAssets = new OpenAICodexImageAssetStore();
|
|
3709
4101
|
const trustedOrigins = new OpenAICodexTrustedOriginsStore(join(dirname(credentials.filename), OPENAI_CODEX_TRUSTED_ORIGINS_FILENAME));
|
|
3710
4102
|
const fastMode = new FastModeRegistry();
|
|
3711
4103
|
assertNoOpenAICodexProviderConflict(ctx.llm.listProviders().map((provider) => provider.id));
|
|
@@ -3716,6 +4108,7 @@ function apply(ctx, config) {
|
|
|
3716
4108
|
registerOpenAICodexProxyRoutes(webCtx, trustedOrigins, proxyManager);
|
|
3717
4109
|
registerOpenAICodexUpdateRoutes(webCtx, { currentVersion: CODEX_CONNECT_VERSION }, trustedOrigins);
|
|
3718
4110
|
registerOpenAICodexModelCatalogRoute(webCtx, openAICodexModelCatalog, trustedOrigins);
|
|
4111
|
+
registerOpenAICodexOriginalImageRoute(webCtx, trustedOrigins, imageAssets);
|
|
3719
4112
|
});
|
|
3720
4113
|
let stopped = false;
|
|
3721
4114
|
let searchFiber;
|
|
@@ -3789,7 +4182,7 @@ function apply(ctx, config) {
|
|
|
3789
4182
|
imageGenerationFiber = void 0;
|
|
3790
4183
|
if (previous !== void 0) await previous.dispose();
|
|
3791
4184
|
if (stopped || !enabled) return;
|
|
3792
|
-
const fiber = ctx.inject(["tools", "attachments"], (toolCtx) => toolCtx.tools.register(imageGenerateTool(toolCtx)));
|
|
4185
|
+
const fiber = ctx.inject(["tools", "attachments"], (toolCtx) => toolCtx.tools.register(imageGenerateTool(toolCtx, imageAssets)));
|
|
3793
4186
|
imageGenerationFiber = fiber;
|
|
3794
4187
|
Promise.resolve(fiber).catch((error) => {
|
|
3795
4188
|
if (imageGenerationFiber === fiber) imageGenerationFiber = void 0;
|
|
@@ -3852,4 +4245,4 @@ function apply(ctx, config) {
|
|
|
3852
4245
|
scheduleCapabilities();
|
|
3853
4246
|
}
|
|
3854
4247
|
//#endregion
|
|
3855
|
-
export {
|
|
4248
|
+
export { OPENAI_CODEX_PROXY_PROBE_TIMEOUT_MS as $, openAICodexConflictMessage as A, OPENAI_CODEX_AUTH_FILENAME as At, DSH_PLUGIN_API_PACKAGES as B, OpenAICodexTransport as C, OPENAI_CODEX_USAGE_URL as Ct, IMAGE_GENERATE_TOOL_NAME as D, logoutOpenAICodex as Dt, VIEW_IMAGE_TOOL_NAME as E, loginOpenAICodex as Et, parseOpenAICodexUpdateResult as F, assessCompatibility as G, SUPPORTED_DSH_PLUGIN_API_VERSION as H, parseOpenAICodexVersion as I, readInstalledPackageVersion as J, detectCompatibility as K, COMPATIBILITY_CONTRACT as L, OPENAI_CODEX_UPDATE_PATH as M, OpenAICodexCredentialStore as Mt, checkForOpenAICodexUpdate as N, openAICodexAuthPath as Nt, assertNoOpenAICodexProviderConflict as O, openAICodexAuthStatus as Ot, compareOpenAICodexVersions as P, OPENAI_CODEX_PROXY_CANDIDATE_LIMIT as Q, COMPATIBILITY_PACKAGES as R, OPENAI_CODEX_TRANSPORT_SERVICE as S, normalizeTrustedOrigin as St, isOpenAICodexTransportError as T, readOpenAICodexRateLimits as Tt, SUPPORTED_NODE_RANGE as U, PI_AI_PACKAGE as V, SUPPORTED_PI_AI_VERSION as W, OPENAI_CODEX_PROXY_TEST_PATH as X, OPENAI_CODEX_PROXY_DETECT_PATH as Y, OPENAI_CODEX_LOCAL_PROXY_CANDIDATES as Z, OPENAI_CODEX_IMAGE_MAX_RESPONSE_BYTES as _, FastModeRegistry as _t, name as a, DEFAULT_OPENAI_CODEX_SEARCH_CONTEXT_SIZE as at, OPENAI_CODEX_TRANSPORT_API_VERSION as b, isFastModeSessionId as bt, migrateOpenAICodexSearchHistory as c, DEFAULT_OPENAI_CODEX_SEARCH_MODEL as ct, OPENAI_CODEX_SEARCH_URL as d, decodeOpenAICodexSettings as dt, OPENAI_CODEX_PROXY_PROBE_URL as et, OpenAICodexSearchProvider as f, isValidOpenAICodexProxyUrl as ft, OPENAI_CODEX_IMAGE_MAX_ERROR_BYTES as g, OPENAI_CODEX_FAST_MODE_PATH as gt, OPENAI_CODEX_IMAGE_MAX_COUNT as h, resolveOpenAICodexSettings as ht, inject as i, DEFAULT_OPENAI_CODEX_PROXY_URL as it, CODEX_CONNECT_VERSION as j, OPENAI_CODEX_PROVIDER as jt, diagnoseOpenAICodex as k, openAICodexModelCatalog as kt, OPENAI_CODEX_BASE_URL as l, DEFAULT_OPENAI_CODEX_SETTINGS as lt, OPENAI_CODEX_IMAGE_GENERATION_URL as m, resolveOpenAICodexProxyUrl as mt, OPENAI_CODEX_SETTINGS_NS as n, detectOpenAICodexProxies as nt, OPENAI_CODEX_HISTORY_BACKUP_SUFFIX as o, DEFAULT_OPENAI_CODEX_SEARCH_MAX_OUTPUT_TOKENS as ot, mapOpenAICodexSearchResponse as p, normalizeOpenAICodexProxyUrl as pt, evaluateCompatibility as q, apply as r, listOpenAICodexProxyCandidates as rt, OPENAI_CODEX_SEARCH_MODEL_REQUEST_EVENT as s, DEFAULT_OPENAI_CODEX_SEARCH_MODE as st, Config as t, OpenAICodexProxyManager as tt, OPENAI_CODEX_SEARCH_PROVIDER as u, OPENAI_CODEX_SETTINGS_NAMESPACE as ut, OPENAI_CODEX_IMAGE_PROMPT_MAX_LENGTH as v, OPENAI_CODEX_FAST_MODE_MAX_SESSIONS as vt, OpenAICodexTransportError as w, parseOpenAICodexUsage as wt, OPENAI_CODEX_TRANSPORT_ERROR_CODES as x, OpenAICodexTrustedOriginsStore as xt, OPENAI_CODEX_IMAGE_REQUEST_TIMEOUT_MS as y, OPENAI_CODEX_FAST_MODE_MAX_SESSION_ID_LENGTH as yt, COMPATIBILITY_SCHEMA_VERSION as z };
|