dsh-codex-connect 0.1.0-alpha.4.19 → 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 +39 -5
- package/cordis.patch.yml +1 -0
- package/docs/README.zh.md +39 -5
- package/docs/agent-notes/original-image-fork-access.md +7 -0
- package/lib/bin.js +368 -1
- package/lib/client.js +427 -34
- package/lib/index.d.ts +84 -3
- package/lib/index.js +2 -2
- package/lib/{src-CiOi4nFr.js → src-Cxq99TvF.js} +946 -176
- package/package.json +6 -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";
|
|
@@ -9,7 +9,10 @@ import { PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
|
|
|
9
9
|
import { link, lstat, mkdir, open, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
10
10
|
import { withFileLock, writeFileAtomic } from "@deepseek-ai/dsh-atomic-write";
|
|
11
11
|
import { resolveDshHome } from "@deepseek-ai/dsh-home-paths";
|
|
12
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
13
|
+
import { Dispatcher, ProxyAgent, getGlobalDispatcher, setGlobalDispatcher } from "undici";
|
|
12
14
|
import { fileURLToPath } from "node:url";
|
|
15
|
+
import { SessionId } from "@deepseek-ai/dsh-session";
|
|
13
16
|
import { AttachmentId } from "@deepseek-ai/dsh-attachment";
|
|
14
17
|
import { TOOL_ABORTED, defineTool } from "@deepseek-ai/dsh-tools";
|
|
15
18
|
import { lookup } from "node:dns/promises";
|
|
@@ -17,8 +20,8 @@ import { request } from "node:http";
|
|
|
17
20
|
import { request as request$1 } from "node:https";
|
|
18
21
|
import { BlockList, isIP } from "node:net";
|
|
19
22
|
import { Service } from "@deepseek-ai/cordis";
|
|
20
|
-
import { WebError } from "@deepseek-ai/dsh-web";
|
|
21
23
|
import { constants } from "node:fs";
|
|
24
|
+
import { WebError } from "@deepseek-ai/dsh-web";
|
|
22
25
|
import { constants as constants$1, zstdCompressSync, zstdDecompressSync } from "node:zlib";
|
|
23
26
|
//#region src/store.ts
|
|
24
27
|
/**
|
|
@@ -51,7 +54,7 @@ async function assertOwnerOnly$1(filename) {
|
|
|
51
54
|
/* v8 ignore stop */
|
|
52
55
|
}
|
|
53
56
|
/** Validate the strict JSON document without quoting token-bearing input. */
|
|
54
|
-
function parseDocument$
|
|
57
|
+
function parseDocument$2(text, filename) {
|
|
55
58
|
let value;
|
|
56
59
|
try {
|
|
57
60
|
value = JSON.parse(text);
|
|
@@ -116,7 +119,7 @@ var OpenAICodexCredentialStore = class {
|
|
|
116
119
|
if (isENOENT$1(error)) return void 0;
|
|
117
120
|
throw error;
|
|
118
121
|
}
|
|
119
|
-
return cloneCredential(parseDocument$
|
|
122
|
+
return cloneCredential(parseDocument$2(text, this.filename).credential);
|
|
120
123
|
}
|
|
121
124
|
/** @inheritdoc */
|
|
122
125
|
async read(providerId) {
|
|
@@ -140,7 +143,7 @@ var OpenAICodexCredentialStore = class {
|
|
|
140
143
|
const current = await this.readCurrent();
|
|
141
144
|
const candidate = await fn(current);
|
|
142
145
|
if (candidate === void 0) return current;
|
|
143
|
-
const document = parseDocument$
|
|
146
|
+
const document = parseDocument$2(JSON.stringify({
|
|
144
147
|
version: AUTH_FORMAT_VERSION,
|
|
145
148
|
credential: candidate
|
|
146
149
|
}), this.filename);
|
|
@@ -212,9 +215,16 @@ function withOpenAICodexFastMode(provider, fastMode) {
|
|
|
212
215
|
}
|
|
213
216
|
};
|
|
214
217
|
}
|
|
215
|
-
function requestProvider(provider, fastMode) {
|
|
218
|
+
function requestProvider(provider, fastMode, proxyManager, resolveProxyUrl) {
|
|
219
|
+
const configured = withOpenAICodexFastMode(provider, fastMode);
|
|
220
|
+
const streamSimple = configured.streamSimple;
|
|
216
221
|
return {
|
|
217
|
-
...
|
|
222
|
+
...configured,
|
|
223
|
+
streamSimple(model, context, options) {
|
|
224
|
+
const proxyUrl = resolveProxyUrl?.();
|
|
225
|
+
const operation = () => streamSimple.call(configured, model, context, options);
|
|
226
|
+
return proxyManager?.runStream(proxyUrl, operation) ?? operation();
|
|
227
|
+
},
|
|
218
228
|
auth: {
|
|
219
229
|
...provider.auth,
|
|
220
230
|
apiKey: {
|
|
@@ -231,17 +241,18 @@ function requestProvider(provider, fastMode) {
|
|
|
231
241
|
};
|
|
232
242
|
}
|
|
233
243
|
/** Build the immutable profile consumed by the rc.2 pi-ai adapter. */
|
|
234
|
-
function createOpenAICodexProfile(provider, fastMode) {
|
|
244
|
+
function createOpenAICodexProfile(provider, fastMode, proxyManager, resolveProxyUrl) {
|
|
235
245
|
return {
|
|
236
246
|
provider: OPENAI_CODEX_PROVIDER,
|
|
237
247
|
displayName: "OpenAI Codex",
|
|
248
|
+
transport: "sse",
|
|
238
249
|
streamIdleTimeoutMs: OPENAI_CODEX_STREAM_IDLE_TIMEOUT_MS,
|
|
239
250
|
maxRequestImageBytes: OPENAI_CODEX_MAX_REQUEST_IMAGE_BYTES,
|
|
240
251
|
requestImagePixelBudget: OPENAI_CODEX_REQUEST_IMAGE_PIXEL_BUDGET,
|
|
241
252
|
requestImageMaxBytes: OPENAI_CODEX_REQUEST_IMAGE_MAX_BYTES,
|
|
242
253
|
retryPolicy: resolveRetryPolicy(void 0, "dsh-codex-connect retryPolicy"),
|
|
243
254
|
configuredMaxTokens: /* @__PURE__ */ new Map(),
|
|
244
|
-
piProvider: requestProvider(provider, fastMode)
|
|
255
|
+
piProvider: requestProvider(provider, fastMode, proxyManager, resolveProxyUrl)
|
|
245
256
|
};
|
|
246
257
|
}
|
|
247
258
|
/**
|
|
@@ -250,9 +261,9 @@ function createOpenAICodexProfile(provider, fastMode) {
|
|
|
250
261
|
* resolution, streaming, reasoning metadata, and compaction behavior; this
|
|
251
262
|
* plugin supplies its provider-native OAuth token for each request.
|
|
252
263
|
*/
|
|
253
|
-
function createOpenAICodexAdapter(credentials, resolveAttachments, fastMode, visibleModelIds) {
|
|
264
|
+
function createOpenAICodexAdapter(credentials, resolveAttachments, fastMode, visibleModelIds, proxyManager, resolveProxyUrl) {
|
|
254
265
|
const provider = openaiCodexProvider();
|
|
255
|
-
const profiles = /* @__PURE__ */ new Map([[OPENAI_CODEX_PROVIDER, createOpenAICodexProfile(provider, fastMode)]]);
|
|
266
|
+
const profiles = /* @__PURE__ */ new Map([[OPENAI_CODEX_PROVIDER, createOpenAICodexProfile(provider, fastMode, proxyManager, resolveProxyUrl)]]);
|
|
256
267
|
const models = createModels({ credentials });
|
|
257
268
|
models.setProvider(provider);
|
|
258
269
|
class OpenAICodexAdapter extends PiAiAdapter {
|
|
@@ -266,7 +277,10 @@ function createOpenAICodexAdapter(credentials, resolveAttachments, fastMode, vis
|
|
|
266
277
|
}
|
|
267
278
|
return new OpenAICodexAdapter({
|
|
268
279
|
profiles: () => profiles,
|
|
269
|
-
resolveApiKey: async () =>
|
|
280
|
+
resolveApiKey: async () => {
|
|
281
|
+
const operation = async () => (await models.getAuth(OPENAI_CODEX_PROVIDER))?.auth.apiKey;
|
|
282
|
+
return proxyManager?.run(resolveProxyUrl?.(), operation) ?? operation();
|
|
283
|
+
},
|
|
270
284
|
auth: {
|
|
271
285
|
credentials,
|
|
272
286
|
authContext: defaultProviderAuthContext()
|
|
@@ -508,7 +522,7 @@ async function assertOwnerOnly(filename) {
|
|
|
508
522
|
/* v8 ignore stop */
|
|
509
523
|
}
|
|
510
524
|
/** Reject malformed input without echoing its contents into an error. */
|
|
511
|
-
function parseDocument(text, filename) {
|
|
525
|
+
function parseDocument$1(text, filename) {
|
|
512
526
|
let value;
|
|
513
527
|
try {
|
|
514
528
|
value = JSON.parse(text);
|
|
@@ -588,7 +602,7 @@ var OpenAICodexTrustedOriginsStore = class {
|
|
|
588
602
|
};
|
|
589
603
|
throw error;
|
|
590
604
|
}
|
|
591
|
-
return parseDocument(text, this.filename);
|
|
605
|
+
return parseDocument$1(text, this.filename);
|
|
592
606
|
}
|
|
593
607
|
/** Read the current canonical list without acquiring the writer lock. */
|
|
594
608
|
async list() {
|
|
@@ -750,9 +764,13 @@ var OpenAICodexWebAuth = class {
|
|
|
750
764
|
challengeWaiters = [];
|
|
751
765
|
challengeTimer;
|
|
752
766
|
challengeTimeoutMs;
|
|
767
|
+
proxyManager;
|
|
768
|
+
resolveProxyUrl;
|
|
753
769
|
constructor(store, options = {}) {
|
|
754
770
|
this.store = store;
|
|
755
771
|
this.challengeTimeoutMs = options.challengeTimeoutMs ?? 3e4;
|
|
772
|
+
this.proxyManager = options.proxyManager;
|
|
773
|
+
this.resolveProxyUrl = options.resolveProxyUrl ?? (() => void 0);
|
|
756
774
|
if (!Number.isFinite(this.challengeTimeoutMs) || this.challengeTimeoutMs <= 0) throw new TypeError("OpenAI Codex auth URL timeout must be a positive finite number");
|
|
757
775
|
}
|
|
758
776
|
/** Read current public state, consulting durable storage while idle. */
|
|
@@ -794,13 +812,14 @@ var OpenAICodexWebAuth = class {
|
|
|
794
812
|
this.cancelSignIn(/* @__PURE__ */ new Error(`OpenAI Codex did not provide an authorization URL within ${String(this.challengeTimeoutMs)}ms`));
|
|
795
813
|
}, this.challengeTimeoutMs);
|
|
796
814
|
this.challengeTimer.unref();
|
|
797
|
-
|
|
815
|
+
const login = () => loginOpenAICodex({
|
|
798
816
|
signal: cancellation.signal,
|
|
799
817
|
prompt: (prompt) => prompt.type === "select" ? Promise.resolve("browser") : waitForPromptAbort(prompt),
|
|
800
818
|
notify: (event) => {
|
|
801
819
|
this.onEvent(event);
|
|
802
820
|
}
|
|
803
|
-
}, this.store)
|
|
821
|
+
}, this.store);
|
|
822
|
+
this.operation = (this.proxyManager?.run(this.resolveProxyUrl(), login) ?? login()).then(async () => {
|
|
804
823
|
if (this.challenge === void 0) {
|
|
805
824
|
const error = /* @__PURE__ */ new Error("OpenAI Codex sign-in finished without an authorization URL");
|
|
806
825
|
this.rejectChallenge(error);
|
|
@@ -846,9 +865,10 @@ var OpenAICodexWebAuth = class {
|
|
|
846
865
|
async readStoredStatus() {
|
|
847
866
|
if (!(await openAICodexAuthStatus(this.store)).authenticated) return { status: "signed-out" };
|
|
848
867
|
try {
|
|
868
|
+
const readUsage = () => readOpenAICodexRateLimits(this.store);
|
|
849
869
|
return {
|
|
850
870
|
status: "signed-in",
|
|
851
|
-
usage: await
|
|
871
|
+
usage: await (this.proxyManager?.run(this.resolveProxyUrl(), readUsage) ?? readUsage())
|
|
852
872
|
};
|
|
853
873
|
} catch (error) {
|
|
854
874
|
if (isOpenAICodexReauthRequiredError(error)) return {
|
|
@@ -944,7 +964,7 @@ async function trustedRequestDecision(req, trustedOrigins = new OpenAICodexTrust
|
|
|
944
964
|
error: REMOTE_WEB_ORIGIN_NOT_TRUSTED
|
|
945
965
|
};
|
|
946
966
|
}
|
|
947
|
-
function json$
|
|
967
|
+
function json$4(res, status, value) {
|
|
948
968
|
res.writeHead(status, {
|
|
949
969
|
"content-type": "application/json; charset=utf-8",
|
|
950
970
|
"cache-control": "no-store",
|
|
@@ -1025,8 +1045,11 @@ function fastModeBody(value) {
|
|
|
1025
1045
|
} : void 0;
|
|
1026
1046
|
}
|
|
1027
1047
|
/** Register the plugin-owned OAuth routes when the Web server is composed. */
|
|
1028
|
-
function registerOpenAICodexAuthRoutes(ctx, store, trustedOriginsOverride, fastModeOverride) {
|
|
1029
|
-
const auth = new OpenAICodexWebAuth(store
|
|
1048
|
+
function registerOpenAICodexAuthRoutes(ctx, store, trustedOriginsOverride, fastModeOverride, proxyManager, resolveProxyUrl) {
|
|
1049
|
+
const auth = new OpenAICodexWebAuth(store, {
|
|
1050
|
+
proxyManager,
|
|
1051
|
+
resolveProxyUrl
|
|
1052
|
+
});
|
|
1030
1053
|
const storedFilename = store.filename;
|
|
1031
1054
|
const fastMode = fastModeOverride ?? new FastModeRegistry();
|
|
1032
1055
|
const trustedOrigins = trustedOriginsOverride ?? (typeof storedFilename === "string" ? new OpenAICodexTrustedOriginsStore(join(dirname(storedFilename), ".openai-codex-trusted-origins.json")) : new OpenAICodexTrustedOriginsStore());
|
|
@@ -1034,7 +1057,7 @@ function registerOpenAICodexAuthRoutes(ctx, store, trustedOriginsOverride, fastM
|
|
|
1034
1057
|
const authorize = async (req, res) => {
|
|
1035
1058
|
const decision = await trustedRequestDecision(req, trustedOrigins);
|
|
1036
1059
|
if (decision.trusted) return true;
|
|
1037
|
-
json$
|
|
1060
|
+
json$4(res, 403, { error: decision.error });
|
|
1038
1061
|
return false;
|
|
1039
1062
|
};
|
|
1040
1063
|
const routes = [
|
|
@@ -1042,21 +1065,21 @@ function registerOpenAICodexAuthRoutes(ctx, store, trustedOriginsOverride, fastM
|
|
|
1042
1065
|
kind: "exact",
|
|
1043
1066
|
path: OPENAI_CODEX_AUTH_STATUS_PATH,
|
|
1044
1067
|
handler: async (req, res) => {
|
|
1045
|
-
if (req.method !== "GET") return json$
|
|
1068
|
+
if (req.method !== "GET") return json$4(res, 405, { error: "method not allowed" });
|
|
1046
1069
|
if (!await authorize(req, res)) return;
|
|
1047
|
-
json$
|
|
1070
|
+
json$4(res, 200, await auth.status());
|
|
1048
1071
|
}
|
|
1049
1072
|
}),
|
|
1050
1073
|
ctx.webServer.register({
|
|
1051
1074
|
kind: "exact",
|
|
1052
1075
|
path: OPENAI_CODEX_AUTH_LOGIN_PATH,
|
|
1053
1076
|
handler: async (req, res) => {
|
|
1054
|
-
if (req.method !== "POST") return json$
|
|
1077
|
+
if (req.method !== "POST") return json$4(res, 405, { error: "method not allowed" });
|
|
1055
1078
|
if (!await authorize(req, res)) return;
|
|
1056
1079
|
try {
|
|
1057
|
-
json$
|
|
1080
|
+
json$4(res, 200, await auth.signIn());
|
|
1058
1081
|
} catch (error) {
|
|
1059
|
-
json$
|
|
1082
|
+
json$4(res, 500, { error: safeMessage(error) });
|
|
1060
1083
|
}
|
|
1061
1084
|
}
|
|
1062
1085
|
}),
|
|
@@ -1064,13 +1087,13 @@ function registerOpenAICodexAuthRoutes(ctx, store, trustedOriginsOverride, fastM
|
|
|
1064
1087
|
kind: "exact",
|
|
1065
1088
|
path: OPENAI_CODEX_AUTH_LOGOUT_PATH,
|
|
1066
1089
|
handler: async (req, res) => {
|
|
1067
|
-
if (req.method !== "POST") return json$
|
|
1090
|
+
if (req.method !== "POST") return json$4(res, 405, { error: "method not allowed" });
|
|
1068
1091
|
if (!await authorize(req, res)) return;
|
|
1069
1092
|
try {
|
|
1070
1093
|
await auth.signOut();
|
|
1071
|
-
json$
|
|
1094
|
+
json$4(res, 200, { ok: true });
|
|
1072
1095
|
} catch (error) {
|
|
1073
|
-
json$
|
|
1096
|
+
json$4(res, 500, { error: safeMessage(error) });
|
|
1074
1097
|
}
|
|
1075
1098
|
}
|
|
1076
1099
|
}),
|
|
@@ -1078,22 +1101,22 @@ function registerOpenAICodexAuthRoutes(ctx, store, trustedOriginsOverride, fastM
|
|
|
1078
1101
|
kind: "exact",
|
|
1079
1102
|
path: OPENAI_CODEX_FAST_MODE_PATH,
|
|
1080
1103
|
handler: async (req, res) => {
|
|
1081
|
-
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" });
|
|
1082
1105
|
if (!await authorize(req, res)) return;
|
|
1083
1106
|
if (req.method === "GET") {
|
|
1084
1107
|
const sessionId = fastModeSessionIdFromQuery(req);
|
|
1085
|
-
if (sessionId === void 0) return json$
|
|
1086
|
-
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) });
|
|
1087
1110
|
}
|
|
1088
1111
|
const type = header(req, "content-type");
|
|
1089
|
-
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" });
|
|
1090
1113
|
try {
|
|
1091
1114
|
const body = fastModeBody(await readFastModeBody(req));
|
|
1092
|
-
if (body === void 0) return json$
|
|
1115
|
+
if (body === void 0) return json$4(res, 400, { error: "invalid input" });
|
|
1093
1116
|
fastMode.set(body.sessionId, body.enabled);
|
|
1094
|
-
return json$
|
|
1117
|
+
return json$4(res, 200, { enabled: fastMode.isEnabled(body.sessionId) });
|
|
1095
1118
|
} catch (error) {
|
|
1096
|
-
return json$
|
|
1119
|
+
return json$4(res, error instanceof RangeError ? 413 : 400, { error: error instanceof RangeError ? "request body too large" : "invalid input" });
|
|
1097
1120
|
}
|
|
1098
1121
|
}
|
|
1099
1122
|
})
|
|
@@ -1105,6 +1128,389 @@ function registerOpenAICodexAuthRoutes(ctx, store, trustedOriginsOverride, fastM
|
|
|
1105
1128
|
}, "dsh-codex-connect: Web OAuth routes");
|
|
1106
1129
|
}
|
|
1107
1130
|
//#endregion
|
|
1131
|
+
//#region src/settings-contract.ts
|
|
1132
|
+
/** Node-free settings contract shared by the Host plugin and browser card. */
|
|
1133
|
+
/** Stable Harness settings namespace owned by this plugin. */
|
|
1134
|
+
const OPENAI_CODEX_SETTINGS_NAMESPACE = "llm-openai-codex";
|
|
1135
|
+
/** Suggested local HTTP proxy shown by the settings UI; it is never enabled by default. */
|
|
1136
|
+
const DEFAULT_OPENAI_CODEX_PROXY_URL = "http://127.0.0.1:7890";
|
|
1137
|
+
/**
|
|
1138
|
+
* Normalize the credential-free HTTP proxy URL accepted by Codex Connect.
|
|
1139
|
+
* Paths, query strings, fragments, and embedded credentials are rejected so
|
|
1140
|
+
* the value remains an origin rather than an opaque request target.
|
|
1141
|
+
*/
|
|
1142
|
+
function normalizeOpenAICodexProxyUrl(value) {
|
|
1143
|
+
if (typeof value !== "string" || value.trim().length === 0) return void 0;
|
|
1144
|
+
try {
|
|
1145
|
+
const parsed = new URL(value.trim());
|
|
1146
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return void 0;
|
|
1147
|
+
if (parsed.username !== "" || parsed.password !== "") return void 0;
|
|
1148
|
+
if (parsed.pathname !== "/" || parsed.search !== "" || parsed.hash !== "") return void 0;
|
|
1149
|
+
if (parsed.hostname.length === 0) return void 0;
|
|
1150
|
+
if (parsed.port !== "" && (!/^\d+$/u.test(parsed.port) || Number(parsed.port) < 1 || Number(parsed.port) > 65535)) return void 0;
|
|
1151
|
+
return parsed.origin;
|
|
1152
|
+
} catch {
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
/** Whether a value is a supported, canonical HTTP(S) proxy origin. */
|
|
1157
|
+
function isValidOpenAICodexProxyUrl(value) {
|
|
1158
|
+
return normalizeOpenAICodexProxyUrl(value) !== void 0;
|
|
1159
|
+
}
|
|
1160
|
+
/** Default model used by the standalone search endpoint. */
|
|
1161
|
+
const DEFAULT_OPENAI_CODEX_SEARCH_MODEL = "gpt-5.6-sol";
|
|
1162
|
+
/** Default search mode, matching the official local Codex client. */
|
|
1163
|
+
const DEFAULT_OPENAI_CODEX_SEARCH_MODE = "cached";
|
|
1164
|
+
/** Default provider search-context size. */
|
|
1165
|
+
const DEFAULT_OPENAI_CODEX_SEARCH_CONTEXT_SIZE = "medium";
|
|
1166
|
+
/** Default output budget for the standalone search response. */
|
|
1167
|
+
const DEFAULT_OPENAI_CODEX_SEARCH_MAX_OUTPUT_TOKENS = 1e4;
|
|
1168
|
+
const DEFAULT_OPENAI_CODEX_SETTINGS = Object.freeze({
|
|
1169
|
+
models: void 0,
|
|
1170
|
+
enableProxy: false,
|
|
1171
|
+
proxyUrl: DEFAULT_OPENAI_CODEX_PROXY_URL,
|
|
1172
|
+
enableSearch: false,
|
|
1173
|
+
enableImageTool: false,
|
|
1174
|
+
enableImageGeneration: false,
|
|
1175
|
+
searchModel: DEFAULT_OPENAI_CODEX_SEARCH_MODEL,
|
|
1176
|
+
searchMode: DEFAULT_OPENAI_CODEX_SEARCH_MODE,
|
|
1177
|
+
searchContextSize: DEFAULT_OPENAI_CODEX_SEARCH_CONTEXT_SIZE,
|
|
1178
|
+
searchMaxOutputTokens: DEFAULT_OPENAI_CODEX_SEARCH_MAX_OUTPUT_TOKENS
|
|
1179
|
+
});
|
|
1180
|
+
/** Fill the schema defaults even when called without Cordis validation. */
|
|
1181
|
+
function resolveOpenAICodexSettings(value) {
|
|
1182
|
+
const resolved = {
|
|
1183
|
+
...DEFAULT_OPENAI_CODEX_SETTINGS,
|
|
1184
|
+
...value
|
|
1185
|
+
};
|
|
1186
|
+
if (!isValidOpenAICodexProxyUrl(resolved.proxyUrl)) throw new TypeError("OpenAI Codex proxyUrl must be an HTTP(S) origin without credentials or a path");
|
|
1187
|
+
return resolved;
|
|
1188
|
+
}
|
|
1189
|
+
/** Resolve the active proxy without treating a disabled value as a route. */
|
|
1190
|
+
function resolveOpenAICodexProxyUrl(value) {
|
|
1191
|
+
const resolved = resolveOpenAICodexSettings(value);
|
|
1192
|
+
return resolved.enableProxy ? normalizeOpenAICodexProxyUrl(resolved.proxyUrl) : void 0;
|
|
1193
|
+
}
|
|
1194
|
+
function isRecord$1(value) {
|
|
1195
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1196
|
+
}
|
|
1197
|
+
/** Narrow the redacted settings wire payload before it enters React state. */
|
|
1198
|
+
function decodeOpenAICodexSettings(value) {
|
|
1199
|
+
if (!isRecord$1(value)) return void 0;
|
|
1200
|
+
const models = value["models"];
|
|
1201
|
+
const enableProxy = value["enableProxy"];
|
|
1202
|
+
const proxyUrl = value["proxyUrl"];
|
|
1203
|
+
const enableSearch = value["enableSearch"];
|
|
1204
|
+
const enableImageTool = value["enableImageTool"];
|
|
1205
|
+
const enableImageGeneration = value["enableImageGeneration"];
|
|
1206
|
+
const searchModel = value["searchModel"];
|
|
1207
|
+
const searchMode = value["searchMode"];
|
|
1208
|
+
const searchContextSize = value["searchContextSize"];
|
|
1209
|
+
const searchMaxOutputTokens = value["searchMaxOutputTokens"];
|
|
1210
|
+
if (models !== void 0 && (!Array.isArray(models) || models.some((model) => typeof model !== "string"))) return void 0;
|
|
1211
|
+
if (enableProxy !== void 0 && typeof enableProxy !== "boolean") return void 0;
|
|
1212
|
+
if (proxyUrl !== void 0 && (typeof proxyUrl !== "string" || !isValidOpenAICodexProxyUrl(proxyUrl))) return void 0;
|
|
1213
|
+
if (typeof enableSearch !== "boolean" || typeof enableImageTool !== "boolean") return void 0;
|
|
1214
|
+
if (enableImageGeneration !== void 0 && typeof enableImageGeneration !== "boolean") return void 0;
|
|
1215
|
+
if (typeof searchModel !== "string" || searchModel.trim().length === 0) return void 0;
|
|
1216
|
+
if (searchMode !== "cached" && searchMode !== "indexed" && searchMode !== "live") return void 0;
|
|
1217
|
+
if (searchContextSize !== "low" && searchContextSize !== "medium" && searchContextSize !== "high") return void 0;
|
|
1218
|
+
if (typeof searchMaxOutputTokens !== "number" || !Number.isInteger(searchMaxOutputTokens) || searchMaxOutputTokens < 1) return void 0;
|
|
1219
|
+
return {
|
|
1220
|
+
models: models === void 0 ? void 0 : [...new Set(models)],
|
|
1221
|
+
enableProxy: enableProxy ?? false,
|
|
1222
|
+
proxyUrl: proxyUrl === void 0 ? DEFAULT_OPENAI_CODEX_PROXY_URL : normalizeOpenAICodexProxyUrl(proxyUrl),
|
|
1223
|
+
enableSearch,
|
|
1224
|
+
enableImageTool,
|
|
1225
|
+
enableImageGeneration: enableImageGeneration ?? false,
|
|
1226
|
+
searchModel,
|
|
1227
|
+
searchMode,
|
|
1228
|
+
searchContextSize,
|
|
1229
|
+
searchMaxOutputTokens
|
|
1230
|
+
};
|
|
1231
|
+
}
|
|
1232
|
+
//#endregion
|
|
1233
|
+
//#region src/provider-proxy.ts
|
|
1234
|
+
/** Explicit Codex-only HTTP(S) proxying, probing, and lifecycle ownership. */
|
|
1235
|
+
/** Canonical first-party endpoint used for a no-auth, no-model reachability probe. */
|
|
1236
|
+
const OPENAI_CODEX_PROXY_PROBE_URL = "https://chatgpt.com/backend-api/codex";
|
|
1237
|
+
/** Upper bound for one candidate probe, including CONNECT and response headers. */
|
|
1238
|
+
const OPENAI_CODEX_PROXY_PROBE_TIMEOUT_MS = 3e3;
|
|
1239
|
+
/** Maximum number of candidates considered by automatic detection. */
|
|
1240
|
+
const OPENAI_CODEX_PROXY_CANDIDATE_LIMIT = 8;
|
|
1241
|
+
/** Bounded local candidates documented by the settings UI. */
|
|
1242
|
+
const OPENAI_CODEX_LOCAL_PROXY_CANDIDATES = [
|
|
1243
|
+
"http://127.0.0.1:7890",
|
|
1244
|
+
"http://127.0.0.1:7897",
|
|
1245
|
+
"http://127.0.0.1:10809"
|
|
1246
|
+
];
|
|
1247
|
+
const proxyScope = new AsyncLocalStorage();
|
|
1248
|
+
const activeOwners = /* @__PURE__ */ new Set();
|
|
1249
|
+
var ScopedProxyDispatcher = class extends Dispatcher {
|
|
1250
|
+
fallback;
|
|
1251
|
+
constructor(fallback) {
|
|
1252
|
+
super();
|
|
1253
|
+
this.fallback = fallback;
|
|
1254
|
+
}
|
|
1255
|
+
setFallback(fallback) {
|
|
1256
|
+
this.fallback = fallback;
|
|
1257
|
+
}
|
|
1258
|
+
dispatch(options, handler) {
|
|
1259
|
+
return (proxyScope.getStore() ?? this.fallback).dispatch(options, handler);
|
|
1260
|
+
}
|
|
1261
|
+
};
|
|
1262
|
+
let installedDispatcher;
|
|
1263
|
+
let previousDispatcher;
|
|
1264
|
+
function ensureInstalled(owner) {
|
|
1265
|
+
if (activeOwners.has(owner)) return;
|
|
1266
|
+
const current = getGlobalDispatcher();
|
|
1267
|
+
if (installedDispatcher === void 0) {
|
|
1268
|
+
previousDispatcher = current;
|
|
1269
|
+
installedDispatcher = new ScopedProxyDispatcher(current);
|
|
1270
|
+
setGlobalDispatcher(installedDispatcher);
|
|
1271
|
+
} else if (current !== installedDispatcher) {
|
|
1272
|
+
installedDispatcher.setFallback(current);
|
|
1273
|
+
setGlobalDispatcher(installedDispatcher);
|
|
1274
|
+
}
|
|
1275
|
+
activeOwners.add(owner);
|
|
1276
|
+
}
|
|
1277
|
+
function removeOwner(owner) {
|
|
1278
|
+
activeOwners.delete(owner);
|
|
1279
|
+
if (activeOwners.size !== 0 || installedDispatcher === void 0) return;
|
|
1280
|
+
const installed = installedDispatcher;
|
|
1281
|
+
const previous = previousDispatcher;
|
|
1282
|
+
installedDispatcher = void 0;
|
|
1283
|
+
previousDispatcher = void 0;
|
|
1284
|
+
if (getGlobalDispatcher() === installed && previous !== void 0) setGlobalDispatcher(previous);
|
|
1285
|
+
}
|
|
1286
|
+
function isPromiseLike(value) {
|
|
1287
|
+
return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
|
|
1288
|
+
}
|
|
1289
|
+
function errorCode(error) {
|
|
1290
|
+
if (typeof error !== "object" || error === null) return void 0;
|
|
1291
|
+
const record = error;
|
|
1292
|
+
return typeof record["code"] === "string" ? record["code"] : errorCode(record["cause"]);
|
|
1293
|
+
}
|
|
1294
|
+
function classifyProbeError(error) {
|
|
1295
|
+
const code = errorCode(error);
|
|
1296
|
+
if (code === "ENOTFOUND" || code === "EAI_AGAIN") return "dns-failure";
|
|
1297
|
+
if (code === "ECONNREFUSED") return "connection-refused";
|
|
1298
|
+
if (code === "ETIMEDOUT" || code === "UND_ERR_CONNECT_TIMEOUT" || code === "ABORT_ERR") return "timeout";
|
|
1299
|
+
if (code === "ERR_TLS_CERT_ALTNAME_INVALID" || code === "UNABLE_TO_VERIFY_LEAF_SIGNATURE" || code === "DEPTH_ZERO_SELF_SIGNED_CERT" || code === "ERR_TLS_CERT_SIGNATURE_ALGORITHM_UNSUPPORTED") return "tls-failure";
|
|
1300
|
+
return "connect-failure";
|
|
1301
|
+
}
|
|
1302
|
+
function classifyResponse(status) {
|
|
1303
|
+
if (status === 407) return "proxy-authentication-required";
|
|
1304
|
+
if (status === 401 || status === 403) return "upstream-authentication-required";
|
|
1305
|
+
return "reachable";
|
|
1306
|
+
}
|
|
1307
|
+
function candidateEnvironmentValues() {
|
|
1308
|
+
return [
|
|
1309
|
+
process.env["HTTPS_PROXY"],
|
|
1310
|
+
process.env["https_proxy"],
|
|
1311
|
+
process.env["HTTP_PROXY"],
|
|
1312
|
+
process.env["http_proxy"],
|
|
1313
|
+
process.env["ALL_PROXY"],
|
|
1314
|
+
process.env["all_proxy"]
|
|
1315
|
+
].filter((value) => value !== void 0);
|
|
1316
|
+
}
|
|
1317
|
+
/** Return a small, deterministic candidate set; this never scans LAN hosts or ports. */
|
|
1318
|
+
function listOpenAICodexProxyCandidates() {
|
|
1319
|
+
const candidates = [];
|
|
1320
|
+
for (const value of [...candidateEnvironmentValues(), ...OPENAI_CODEX_LOCAL_PROXY_CANDIDATES]) {
|
|
1321
|
+
const normalized = normalizeOpenAICodexProxyUrl(value);
|
|
1322
|
+
if (normalized !== void 0 && !candidates.includes(normalized)) candidates.push(normalized);
|
|
1323
|
+
if (candidates.length >= 8) break;
|
|
1324
|
+
}
|
|
1325
|
+
return candidates;
|
|
1326
|
+
}
|
|
1327
|
+
/** One plugin instance owns its proxy agents and contributes one global wrapper owner. */
|
|
1328
|
+
var OpenAICodexProxyManager = class {
|
|
1329
|
+
agents = /* @__PURE__ */ new Map();
|
|
1330
|
+
activeOperations = 0;
|
|
1331
|
+
idleWaiters = [];
|
|
1332
|
+
disposed = false;
|
|
1333
|
+
disposePromise;
|
|
1334
|
+
async waitForIdle() {
|
|
1335
|
+
if (this.activeOperations === 0) return;
|
|
1336
|
+
await new Promise((resolve) => {
|
|
1337
|
+
this.idleWaiters.push(resolve);
|
|
1338
|
+
});
|
|
1339
|
+
}
|
|
1340
|
+
async closeAgents() {
|
|
1341
|
+
removeOwner(this);
|
|
1342
|
+
const agents = [...this.agents.values()];
|
|
1343
|
+
this.agents.clear();
|
|
1344
|
+
await Promise.allSettled(agents.map((agent) => agent.close()));
|
|
1345
|
+
}
|
|
1346
|
+
agentFor(proxyUrl) {
|
|
1347
|
+
let agent = this.agents.get(proxyUrl);
|
|
1348
|
+
if (agent !== void 0) return agent;
|
|
1349
|
+
agent = new ProxyAgent(proxyUrl);
|
|
1350
|
+
this.agents.set(proxyUrl, agent);
|
|
1351
|
+
return agent;
|
|
1352
|
+
}
|
|
1353
|
+
acquire(proxyUrl) {
|
|
1354
|
+
if (this.disposed) throw new Error("OpenAI Codex proxy manager has been disposed");
|
|
1355
|
+
ensureInstalled(this);
|
|
1356
|
+
this.activeOperations += 1;
|
|
1357
|
+
let released = false;
|
|
1358
|
+
return {
|
|
1359
|
+
agent: this.agentFor(proxyUrl),
|
|
1360
|
+
release: () => {
|
|
1361
|
+
if (released) return;
|
|
1362
|
+
released = true;
|
|
1363
|
+
this.activeOperations -= 1;
|
|
1364
|
+
if (this.activeOperations === 0) for (const resolve of this.idleWaiters.splice(0)) resolve();
|
|
1365
|
+
}
|
|
1366
|
+
};
|
|
1367
|
+
}
|
|
1368
|
+
/** Run a synchronous or asynchronous Codex operation in the selected proxy scope. */
|
|
1369
|
+
run(proxyUrl, operation) {
|
|
1370
|
+
if (proxyUrl === void 0) return operation();
|
|
1371
|
+
const normalized = normalizeOpenAICodexProxyUrl(proxyUrl);
|
|
1372
|
+
if (!isValidOpenAICodexProxyUrl(normalized)) throw new TypeError("OpenAI Codex proxy URL is invalid");
|
|
1373
|
+
const lease = this.acquire(normalized);
|
|
1374
|
+
try {
|
|
1375
|
+
const value = proxyScope.run(lease.agent, operation);
|
|
1376
|
+
if (isPromiseLike(value)) return Promise.resolve(value).finally(lease.release);
|
|
1377
|
+
lease.release();
|
|
1378
|
+
return value;
|
|
1379
|
+
} catch (error) {
|
|
1380
|
+
lease.release();
|
|
1381
|
+
throw error;
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
/** Run a streaming operation and keep the proxy lease until its final event. */
|
|
1385
|
+
runStream(proxyUrl, operation) {
|
|
1386
|
+
if (proxyUrl === void 0) return operation();
|
|
1387
|
+
const normalized = normalizeOpenAICodexProxyUrl(proxyUrl);
|
|
1388
|
+
if (!isValidOpenAICodexProxyUrl(normalized)) throw new TypeError("OpenAI Codex proxy URL is invalid");
|
|
1389
|
+
const lease = this.acquire(normalized);
|
|
1390
|
+
try {
|
|
1391
|
+
const stream = proxyScope.run(lease.agent, operation);
|
|
1392
|
+
Promise.resolve(stream.result()).finally(lease.release);
|
|
1393
|
+
return stream;
|
|
1394
|
+
} catch (error) {
|
|
1395
|
+
lease.release();
|
|
1396
|
+
throw error;
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
/** Probe one proxy without credentials, model calls, quota calls, or settings writes. */
|
|
1400
|
+
async probe(proxyUrl) {
|
|
1401
|
+
const normalized = normalizeOpenAICodexProxyUrl(proxyUrl);
|
|
1402
|
+
if (normalized === void 0) return {
|
|
1403
|
+
proxyUrl,
|
|
1404
|
+
reachable: false,
|
|
1405
|
+
classification: "invalid"
|
|
1406
|
+
};
|
|
1407
|
+
try {
|
|
1408
|
+
const response = await this.run(normalized, () => fetch(OPENAI_CODEX_PROXY_PROBE_URL, {
|
|
1409
|
+
method: "GET",
|
|
1410
|
+
redirect: "manual",
|
|
1411
|
+
headers: { accept: "application/json" },
|
|
1412
|
+
signal: AbortSignal.timeout(OPENAI_CODEX_PROXY_PROBE_TIMEOUT_MS)
|
|
1413
|
+
}));
|
|
1414
|
+
await response.body?.cancel();
|
|
1415
|
+
return {
|
|
1416
|
+
proxyUrl: normalized,
|
|
1417
|
+
reachable: true,
|
|
1418
|
+
classification: classifyResponse(response.status),
|
|
1419
|
+
status: response.status
|
|
1420
|
+
};
|
|
1421
|
+
} catch (error) {
|
|
1422
|
+
return {
|
|
1423
|
+
proxyUrl: normalized,
|
|
1424
|
+
reachable: false,
|
|
1425
|
+
classification: classifyProbeError(error)
|
|
1426
|
+
};
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
/** Close owned pools only after all scoped operations have become quiescent. */
|
|
1430
|
+
async dispose() {
|
|
1431
|
+
if (this.disposePromise !== void 0) return this.disposePromise;
|
|
1432
|
+
this.disposed = true;
|
|
1433
|
+
this.disposePromise = (async () => {
|
|
1434
|
+
await this.waitForIdle();
|
|
1435
|
+
await this.closeAgents();
|
|
1436
|
+
})();
|
|
1437
|
+
return this.disposePromise;
|
|
1438
|
+
}
|
|
1439
|
+
/** Release the process wrapper and pools after the user disables the proxy. */
|
|
1440
|
+
async deactivate() {
|
|
1441
|
+
if (this.disposed) return;
|
|
1442
|
+
await this.waitForIdle();
|
|
1443
|
+
await this.closeAgents();
|
|
1444
|
+
}
|
|
1445
|
+
};
|
|
1446
|
+
/** Probe the bounded automatic candidate set in parallel. */
|
|
1447
|
+
async function detectOpenAICodexProxies(manager) {
|
|
1448
|
+
return Promise.all(listOpenAICodexProxyCandidates().map((candidate) => manager.probe(candidate)));
|
|
1449
|
+
}
|
|
1450
|
+
//#endregion
|
|
1451
|
+
//#region src/proxy-paths.ts
|
|
1452
|
+
/** Node-free route constants shared by the Host and browser plugin halves. */
|
|
1453
|
+
/** Detect bounded local/environment proxy candidates without changing settings. */
|
|
1454
|
+
const OPENAI_CODEX_PROXY_DETECT_PATH = "/plugins/dsh-openai-codex/proxy/detect";
|
|
1455
|
+
/** Test one manually entered proxy origin without changing settings. */
|
|
1456
|
+
const OPENAI_CODEX_PROXY_TEST_PATH = "/plugins/dsh-openai-codex/proxy/test";
|
|
1457
|
+
//#endregion
|
|
1458
|
+
//#region src/proxy-routes.ts
|
|
1459
|
+
function json$3(res, status, value) {
|
|
1460
|
+
res.writeHead(status, {
|
|
1461
|
+
"content-type": "application/json; charset=utf-8",
|
|
1462
|
+
"cache-control": "no-store",
|
|
1463
|
+
"x-content-type-options": "nosniff"
|
|
1464
|
+
});
|
|
1465
|
+
res.end(JSON.stringify(value));
|
|
1466
|
+
}
|
|
1467
|
+
function proxyUrlFromQuery(req) {
|
|
1468
|
+
if (typeof req.url !== "string") return void 0;
|
|
1469
|
+
try {
|
|
1470
|
+
const values = new URL(req.url, "http://dsh.invalid").searchParams.getAll("proxyUrl");
|
|
1471
|
+
return values.length === 1 && values[0].length <= 2e3 ? values[0] : void 0;
|
|
1472
|
+
} catch {
|
|
1473
|
+
return;
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
/** Register bounded detection and draft-probe routes; neither route mutates settings. */
|
|
1477
|
+
function registerOpenAICodexProxyRoutes(ctx, trustedOrigins, manager) {
|
|
1478
|
+
ctx.effect(() => {
|
|
1479
|
+
const authorize = async (req, res) => {
|
|
1480
|
+
const decision = await trustedRequestDecision(req, trustedOrigins);
|
|
1481
|
+
if (decision.trusted) return true;
|
|
1482
|
+
json$3(res, 403, { error: decision.error });
|
|
1483
|
+
return false;
|
|
1484
|
+
};
|
|
1485
|
+
const routes = [ctx.webServer.register({
|
|
1486
|
+
kind: "exact",
|
|
1487
|
+
path: OPENAI_CODEX_PROXY_DETECT_PATH,
|
|
1488
|
+
handler: async (req, res) => {
|
|
1489
|
+
if (req.method !== "POST") return json$3(res, 405, { error: "method not allowed" });
|
|
1490
|
+
if (!await authorize(req, res)) return;
|
|
1491
|
+
const results = await detectOpenAICodexProxies(manager);
|
|
1492
|
+
return json$3(res, 200, {
|
|
1493
|
+
candidates: results.filter((result) => result.reachable),
|
|
1494
|
+
results
|
|
1495
|
+
});
|
|
1496
|
+
}
|
|
1497
|
+
}), ctx.webServer.register({
|
|
1498
|
+
kind: "exact",
|
|
1499
|
+
path: OPENAI_CODEX_PROXY_TEST_PATH,
|
|
1500
|
+
handler: async (req, res) => {
|
|
1501
|
+
if (req.method !== "POST") return json$3(res, 405, { error: "method not allowed" });
|
|
1502
|
+
if (!await authorize(req, res)) return;
|
|
1503
|
+
const proxyUrl = proxyUrlFromQuery(req);
|
|
1504
|
+
if (proxyUrl === void 0) return json$3(res, 400, { error: "invalid proxy URL" });
|
|
1505
|
+
return json$3(res, 200, await manager.probe(proxyUrl));
|
|
1506
|
+
}
|
|
1507
|
+
})];
|
|
1508
|
+
return () => {
|
|
1509
|
+
for (const dispose of routes) dispose();
|
|
1510
|
+
};
|
|
1511
|
+
}, "dsh-codex-connect: proxy detection routes");
|
|
1512
|
+
}
|
|
1513
|
+
//#endregion
|
|
1108
1514
|
//#region src/compatibility.ts
|
|
1109
1515
|
const COMPATIBILITY_SCHEMA_VERSION = 1;
|
|
1110
1516
|
const SUPPORTED_NODE_RANGE = "^22.19.0 || >=24.0.0";
|
|
@@ -1211,7 +1617,12 @@ function evaluateCompatibility(input = {}) {
|
|
|
1211
1617
|
}
|
|
1212
1618
|
/** Alias for callers that prefer assessment terminology. */
|
|
1213
1619
|
const assessCompatibility = evaluateCompatibility;
|
|
1214
|
-
|
|
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) {
|
|
1215
1626
|
let entry;
|
|
1216
1627
|
try {
|
|
1217
1628
|
const resolved = import.meta.resolve(name);
|
|
@@ -1234,7 +1645,7 @@ async function readPackageVersionFromEntry(name) {
|
|
|
1234
1645
|
}
|
|
1235
1646
|
/** Read installed package metadata and return only versions and statuses. */
|
|
1236
1647
|
async function detectCompatibility(options = {}) {
|
|
1237
|
-
const readVersion = options.readPackageVersion ??
|
|
1648
|
+
const readVersion = options.readPackageVersion ?? readInstalledPackageVersion;
|
|
1238
1649
|
const packageVersions = options.packageVersions ?? options.packages ?? options.installed?.packages;
|
|
1239
1650
|
const resolvedPackages = packageVersions === void 0 ? Object.fromEntries(await Promise.all(COMPATIBILITY_PACKAGES.map(async (name) => [name, await readVersion(name)]))) : packageVersions;
|
|
1240
1651
|
return evaluateCompatibility({
|
|
@@ -1259,7 +1670,8 @@ const HIGHLIGHT_KINDS = [
|
|
|
1259
1670
|
"search-stability",
|
|
1260
1671
|
"image-generation",
|
|
1261
1672
|
"oauth-history",
|
|
1262
|
-
"model-visibility"
|
|
1673
|
+
"model-visibility",
|
|
1674
|
+
"proxy-connection"
|
|
1263
1675
|
];
|
|
1264
1676
|
function isHighlightKind(value) {
|
|
1265
1677
|
return typeof value === "string" && HIGHLIGHT_KINDS.includes(value);
|
|
@@ -1688,7 +2100,7 @@ async function detectCurrentDshVersion() {
|
|
|
1688
2100
|
const adapter = report.packages["@deepseek-ai/dsh-llm-pi-ai"].installed;
|
|
1689
2101
|
return llm !== null && llm === adapter ? llm : void 0;
|
|
1690
2102
|
}
|
|
1691
|
-
function json$
|
|
2103
|
+
function json$2(res, status, value) {
|
|
1692
2104
|
res.writeHead(status, {
|
|
1693
2105
|
"content-type": "application/json; charset=utf-8",
|
|
1694
2106
|
"cache-control": "no-store",
|
|
@@ -1703,14 +2115,14 @@ function registerOpenAICodexUpdateRoutes(ctx, options, trustedOrigins) {
|
|
|
1703
2115
|
kind: "exact",
|
|
1704
2116
|
path: OPENAI_CODEX_RUNTIME_PATH,
|
|
1705
2117
|
handler: async (req, res) => {
|
|
1706
|
-
if (req.method !== "GET") return json$
|
|
2118
|
+
if (req.method !== "GET") return json$2(res, 405, { error: "method not allowed" });
|
|
1707
2119
|
const decision = await trustedRequestDecision(req, trustedOrigins);
|
|
1708
|
-
if (!decision.trusted) return json$
|
|
2120
|
+
if (!decision.trusted) return json$2(res, 403, { error: decision.error });
|
|
1709
2121
|
try {
|
|
1710
2122
|
const currentDshVersion = await (options.resolveCurrentDshVersion ?? detectCurrentDshVersion)();
|
|
1711
|
-
return json$
|
|
2123
|
+
return json$2(res, 200, currentDshVersion === void 0 ? {} : { currentDshVersion });
|
|
1712
2124
|
} catch {
|
|
1713
|
-
return json$
|
|
2125
|
+
return json$2(res, 200, {});
|
|
1714
2126
|
}
|
|
1715
2127
|
}
|
|
1716
2128
|
});
|
|
@@ -1718,9 +2130,9 @@ function registerOpenAICodexUpdateRoutes(ctx, options, trustedOrigins) {
|
|
|
1718
2130
|
kind: "exact",
|
|
1719
2131
|
path: OPENAI_CODEX_UPDATE_PATH,
|
|
1720
2132
|
handler: async (req, res) => {
|
|
1721
|
-
if (req.method !== "GET") return json$
|
|
2133
|
+
if (req.method !== "GET") return json$2(res, 405, { error: "method not allowed" });
|
|
1722
2134
|
const decision = await trustedRequestDecision(req, trustedOrigins);
|
|
1723
|
-
if (!decision.trusted) return json$
|
|
2135
|
+
if (!decision.trusted) return json$2(res, 403, { error: decision.error });
|
|
1724
2136
|
let result;
|
|
1725
2137
|
let currentDshVersion;
|
|
1726
2138
|
try {
|
|
@@ -1743,7 +2155,7 @@ function registerOpenAICodexUpdateRoutes(ctx, options, trustedOrigins) {
|
|
|
1743
2155
|
reason: "registry-unavailable"
|
|
1744
2156
|
};
|
|
1745
2157
|
}
|
|
1746
|
-
return json$
|
|
2158
|
+
return json$2(res, 200, result);
|
|
1747
2159
|
}
|
|
1748
2160
|
});
|
|
1749
2161
|
return () => {
|
|
@@ -1759,7 +2171,7 @@ function registerOpenAICodexUpdateRoutes(ctx, options, trustedOrigins) {
|
|
|
1759
2171
|
const OPENAI_CODEX_MODEL_CATALOG_PATH = "/plugins/dsh-codex-connect/models";
|
|
1760
2172
|
//#endregion
|
|
1761
2173
|
//#region src/model-routes.ts
|
|
1762
|
-
function json(res, status, value) {
|
|
2174
|
+
function json$1(res, status, value) {
|
|
1763
2175
|
res.writeHead(status, {
|
|
1764
2176
|
"content-type": "application/json; charset=utf-8",
|
|
1765
2177
|
"cache-control": "no-store",
|
|
@@ -1772,17 +2184,162 @@ function registerOpenAICodexModelCatalogRoute(ctx, resolveCatalog, trustedOrigin
|
|
|
1772
2184
|
ctx.effect(() => ctx.webServer.register({
|
|
1773
2185
|
kind: "exact",
|
|
1774
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,
|
|
1775
2320
|
handler: async (req, res) => {
|
|
1776
2321
|
if (req.method !== "GET") return json(res, 405, { error: "method not allowed" });
|
|
1777
2322
|
const decision = await trustedRequestDecision(req, trustedOrigins);
|
|
1778
2323
|
if (!decision.trusted) return json(res, 403, { error: decision.error });
|
|
1779
|
-
|
|
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));
|
|
1780
2337
|
}
|
|
1781
|
-
}), "dsh-codex-connect:
|
|
2338
|
+
}), "dsh-codex-connect: original image download route");
|
|
1782
2339
|
}
|
|
1783
2340
|
//#endregion
|
|
1784
2341
|
//#region src/version.ts
|
|
1785
|
-
const CODEX_CONNECT_VERSION = "0.1.0-alpha.4.
|
|
2342
|
+
const CODEX_CONNECT_VERSION = "0.1.0-alpha.4.21";
|
|
1786
2343
|
//#endregion
|
|
1787
2344
|
//#region src/doctor.ts
|
|
1788
2345
|
/** Secret-free diagnostics and duplicate-provider guidance. */
|
|
@@ -1964,10 +2521,6 @@ function detectEncodedImage(data) {
|
|
|
1964
2521
|
}
|
|
1965
2522
|
}
|
|
1966
2523
|
//#endregion
|
|
1967
|
-
//#region src/image-presentation.ts
|
|
1968
|
-
/** Stable metadata marker for generated image result views. */
|
|
1969
|
-
const IMAGE_PRESENTATION_KIND = "codex-connect-images";
|
|
1970
|
-
//#endregion
|
|
1971
2524
|
//#region src/image-tool.ts
|
|
1972
2525
|
/** Stable model-callable tool name. */
|
|
1973
2526
|
const IMAGE_GENERATE_TOOL_NAME = "codex_connect_image_generate";
|
|
@@ -1999,29 +2552,41 @@ function extension(mediaType) {
|
|
|
1999
2552
|
return mediaType === "image/jpeg" ? "jpg" : mediaType.slice(6);
|
|
2000
2553
|
}
|
|
2001
2554
|
function outputContent(value) {
|
|
2002
|
-
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}`);
|
|
2003
2556
|
return [{
|
|
2004
2557
|
type: "text",
|
|
2005
2558
|
text: `Generated ${String(value.images.length)} image${value.images.length === 1 ? "" : "s"}:\n${lines.join("\n")}`
|
|
2006
|
-
}, ...value.images.map((
|
|
2559
|
+
}, ...value.images.map(({ preview }) => ({
|
|
2007
2560
|
type: "image",
|
|
2008
2561
|
attachment: {
|
|
2009
|
-
attachmentId: AttachmentId(
|
|
2010
|
-
mediaType:
|
|
2011
|
-
width:
|
|
2012
|
-
height:
|
|
2013
|
-
bytes:
|
|
2014
|
-
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
|
|
2015
2568
|
}
|
|
2016
2569
|
}))];
|
|
2017
2570
|
}
|
|
2018
|
-
function
|
|
2019
|
-
|
|
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
|
+
};
|
|
2020
2585
|
}
|
|
2021
2586
|
function executionKey(exec) {
|
|
2022
2587
|
return `${String(exec.agent?.id ?? "<no-agent>")}\u0000${String(exec.rootCallId)}\u0000${String(exec.callId)}`;
|
|
2023
2588
|
}
|
|
2024
|
-
async function generate(ctx, transport, prompt, exec) {
|
|
2589
|
+
async function generate(ctx, transport, assets, prompt, exec) {
|
|
2025
2590
|
let response;
|
|
2026
2591
|
try {
|
|
2027
2592
|
response = await transport.generateImages({ prompt }, { signal: exec.signal });
|
|
@@ -2057,28 +2622,49 @@ async function generate(ctx, transport, prompt, exec) {
|
|
|
2057
2622
|
name
|
|
2058
2623
|
});
|
|
2059
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
|
+
}
|
|
2060
2643
|
let refs;
|
|
2061
2644
|
try {
|
|
2062
2645
|
refs = await ctx.attachments.saveImages(inputs);
|
|
2063
2646
|
} catch {
|
|
2647
|
+
await assets.removeImages(originals);
|
|
2064
2648
|
failure("The generated images could not be saved; no attachment references were returned.");
|
|
2065
2649
|
}
|
|
2066
|
-
if (refs.length !== inputs.length
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
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
|
+
}
|
|
2082
2668
|
}
|
|
2083
2669
|
function appendAbortNote(result) {
|
|
2084
2670
|
if (!result.isError || result.error.info?.code !== TOOL_ABORTED) return void 0;
|
|
@@ -2089,11 +2675,11 @@ function appendAbortNote(result) {
|
|
|
2089
2675
|
}];
|
|
2090
2676
|
}
|
|
2091
2677
|
/** Build one fiber-owned image tool, including in-flight call deduplication. */
|
|
2092
|
-
function imageGenerateTool(ctx) {
|
|
2678
|
+
function imageGenerateTool(ctx, assets) {
|
|
2093
2679
|
const inFlight = /* @__PURE__ */ new Map();
|
|
2094
2680
|
return defineTool({
|
|
2095
2681
|
name: IMAGE_GENERATE_TOOL_NAME,
|
|
2096
|
-
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.",
|
|
2097
2683
|
parameters: { prompt: {
|
|
2098
2684
|
type: "string",
|
|
2099
2685
|
required: true,
|
|
@@ -2110,34 +2696,81 @@ function imageGenerateTool(ctx) {
|
|
|
2110
2696
|
type: "object",
|
|
2111
2697
|
additionalProperties: false,
|
|
2112
2698
|
properties: {
|
|
2113
|
-
|
|
2114
|
-
type: "
|
|
2115
|
-
required: true
|
|
2116
|
-
},
|
|
2117
|
-
mediaType: {
|
|
2118
|
-
type: "string",
|
|
2699
|
+
original: {
|
|
2700
|
+
type: "object",
|
|
2119
2701
|
required: true,
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
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
|
+
}
|
|
2137
2738
|
},
|
|
2138
|
-
|
|
2139
|
-
type: "
|
|
2140
|
-
required: true
|
|
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
|
+
}
|
|
2141
2774
|
}
|
|
2142
2775
|
}
|
|
2143
2776
|
}
|
|
@@ -2146,6 +2779,7 @@ function imageGenerateTool(ctx) {
|
|
|
2146
2779
|
render: (_args, value) => outputContent(value),
|
|
2147
2780
|
presentationMeta: (args, value) => ({
|
|
2148
2781
|
kind: IMAGE_PRESENTATION_KIND,
|
|
2782
|
+
schemaVersion: 1,
|
|
2149
2783
|
prompt: args.prompt.trim(),
|
|
2150
2784
|
images: value.images
|
|
2151
2785
|
})
|
|
@@ -2161,7 +2795,7 @@ function imageGenerateTool(ctx) {
|
|
|
2161
2795
|
const key = executionKey(exec);
|
|
2162
2796
|
const current = inFlight.get(key);
|
|
2163
2797
|
if (current !== void 0) return current;
|
|
2164
|
-
const pending = generate(ctx, transport, prompt, exec).catch((error) => {
|
|
2798
|
+
const pending = generate(ctx, transport, assets, prompt, exec).catch((error) => {
|
|
2165
2799
|
if (error instanceof SafeToolError) throw error;
|
|
2166
2800
|
failure(fixedTransportMessage(error));
|
|
2167
2801
|
}).finally(() => {
|
|
@@ -2692,15 +3326,23 @@ function parseSuccess(bytes) {
|
|
|
2692
3326
|
/** Core-owned Cordis service for the optional image package. */
|
|
2693
3327
|
var OpenAICodexTransport = class extends Service {
|
|
2694
3328
|
credentials;
|
|
3329
|
+
proxyManager;
|
|
3330
|
+
resolveProxyUrl;
|
|
2695
3331
|
apiVersion = 1;
|
|
2696
3332
|
models;
|
|
2697
|
-
constructor(ctx, credentials) {
|
|
3333
|
+
constructor(ctx, credentials, proxyManager, resolveProxyUrl = () => void 0) {
|
|
2698
3334
|
super(ctx, OPENAI_CODEX_TRANSPORT_SERVICE);
|
|
2699
3335
|
this.credentials = credentials;
|
|
3336
|
+
this.proxyManager = proxyManager;
|
|
3337
|
+
this.resolveProxyUrl = resolveProxyUrl;
|
|
2700
3338
|
this.models = createModels({ credentials });
|
|
2701
3339
|
this.models.setProvider(openaiCodexProvider());
|
|
2702
3340
|
}
|
|
2703
3341
|
async generateImages(input, context) {
|
|
3342
|
+
const operation = () => this.generateImagesWithoutProxy(input, context);
|
|
3343
|
+
return this.proxyManager?.run(this.resolveProxyUrl(), operation) ?? operation();
|
|
3344
|
+
}
|
|
3345
|
+
async generateImagesWithoutProxy(input, context) {
|
|
2704
3346
|
if (typeof input?.prompt !== "string" || input.prompt.trim().length === 0 || input.prompt.length > 32e3) throw new OpenAICodexTransportError(OPENAI_CODEX_TRANSPORT_ERROR_CODES.invalidRequest);
|
|
2705
3347
|
if (isAborted(context.signal)) throw new OpenAICodexTransportError(OPENAI_CODEX_TRANSPORT_ERROR_CODES.canceled);
|
|
2706
3348
|
if ((await this.credentials.read("openai-codex"))?.type !== "oauth") throw new OpenAICodexTransportError(OPENAI_CODEX_TRANSPORT_ERROR_CODES.signedOut);
|
|
@@ -2771,67 +3413,168 @@ var OpenAICodexTransport = class extends Service {
|
|
|
2771
3413
|
}
|
|
2772
3414
|
};
|
|
2773
3415
|
//#endregion
|
|
2774
|
-
//#region src/
|
|
2775
|
-
/**
|
|
2776
|
-
/**
|
|
2777
|
-
const
|
|
2778
|
-
|
|
2779
|
-
const
|
|
2780
|
-
|
|
2781
|
-
const
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
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
|
|
2801
3451
|
};
|
|
2802
3452
|
}
|
|
2803
|
-
function
|
|
2804
|
-
|
|
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");
|
|
2805
3458
|
}
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
const
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
if (typeof enableSearch !== "boolean" || typeof enableImageTool !== "boolean") return void 0;
|
|
2819
|
-
if (enableImageGeneration !== void 0 && typeof enableImageGeneration !== "boolean") return void 0;
|
|
2820
|
-
if (typeof searchModel !== "string" || searchModel.trim().length === 0) return void 0;
|
|
2821
|
-
if (searchMode !== "cached" && searchMode !== "indexed" && searchMode !== "live") return void 0;
|
|
2822
|
-
if (searchContextSize !== "low" && searchContextSize !== "medium" && searchContextSize !== "high") return void 0;
|
|
2823
|
-
if (typeof searchMaxOutputTokens !== "number" || !Number.isInteger(searchMaxOutputTokens) || searchMaxOutputTokens < 1) return void 0;
|
|
2824
|
-
return {
|
|
2825
|
-
models: models === void 0 ? void 0 : [...new Set(models)],
|
|
2826
|
-
enableSearch,
|
|
2827
|
-
enableImageTool,
|
|
2828
|
-
enableImageGeneration: enableImageGeneration ?? false,
|
|
2829
|
-
searchModel,
|
|
2830
|
-
searchMode,
|
|
2831
|
-
searchContextSize,
|
|
2832
|
-
searchMaxOutputTokens
|
|
2833
|
-
};
|
|
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
|
+
}
|
|
2834
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
|
+
};
|
|
2835
3578
|
//#endregion
|
|
2836
3579
|
//#region src/search.ts
|
|
2837
3580
|
/**
|
|
@@ -2974,6 +3717,10 @@ var OpenAICodexSearchProvider = class {
|
|
|
2974
3717
|
}
|
|
2975
3718
|
/** @inheritdoc */
|
|
2976
3719
|
async search(request, signal) {
|
|
3720
|
+
const operation = () => this.searchWithoutProxy(request, signal);
|
|
3721
|
+
return this.options.proxyManager?.run(this.options.resolveProxyUrl?.(), operation) ?? operation();
|
|
3722
|
+
}
|
|
3723
|
+
async searchWithoutProxy(request, signal) {
|
|
2977
3724
|
throwIfSearchAborted(signal);
|
|
2978
3725
|
let auth;
|
|
2979
3726
|
try {
|
|
@@ -3319,6 +4066,8 @@ const inject = ["llm"];
|
|
|
3319
4066
|
const OPENAI_CODEX_SETTINGS_NS = settingsNamespace(OPENAI_CODEX_SETTINGS_NAMESPACE);
|
|
3320
4067
|
const Config = z.object({
|
|
3321
4068
|
models: z.union([z.const(void 0), z.array(z.string())]),
|
|
4069
|
+
enableProxy: z.boolean().default(false),
|
|
4070
|
+
proxyUrl: z.string().default(DEFAULT_OPENAI_CODEX_PROXY_URL),
|
|
3322
4071
|
enableSearch: z.boolean().default(false),
|
|
3323
4072
|
enableImageTool: z.boolean().default(false),
|
|
3324
4073
|
enableImageGeneration: z.boolean().default(false),
|
|
@@ -3344,16 +4093,22 @@ const Config = z.object({
|
|
|
3344
4093
|
*/
|
|
3345
4094
|
function apply(ctx, config) {
|
|
3346
4095
|
let current = () => config;
|
|
4096
|
+
const proxyManager = new OpenAICodexProxyManager();
|
|
4097
|
+
const resolveProviderProxyUrl = () => resolveOpenAICodexProxyUrl(resolveOpenAICodexSettings(current()));
|
|
4098
|
+
let proxyWasActive = resolveProviderProxyUrl() !== void 0;
|
|
3347
4099
|
const credentials = new OpenAICodexCredentialStore();
|
|
4100
|
+
const imageAssets = new OpenAICodexImageAssetStore();
|
|
3348
4101
|
const trustedOrigins = new OpenAICodexTrustedOriginsStore(join(dirname(credentials.filename), OPENAI_CODEX_TRUSTED_ORIGINS_FILENAME));
|
|
3349
4102
|
const fastMode = new FastModeRegistry();
|
|
3350
4103
|
assertNoOpenAICodexProviderConflict(ctx.llm.listProviders().map((provider) => provider.id));
|
|
3351
|
-
new OpenAICodexTransport(ctx, credentials);
|
|
3352
|
-
ctx.llm.registerAdapter([OPENAI_CODEX_PROVIDER], createOpenAICodexAdapter(credentials, () => ctx.get("attachments"), fastMode, () => resolveOpenAICodexSettings(current()).models));
|
|
4104
|
+
new OpenAICodexTransport(ctx, credentials, proxyManager, resolveProviderProxyUrl);
|
|
4105
|
+
ctx.llm.registerAdapter([OPENAI_CODEX_PROVIDER], createOpenAICodexAdapter(credentials, () => ctx.get("attachments"), fastMode, () => resolveOpenAICodexSettings(current()).models, proxyManager, resolveProviderProxyUrl));
|
|
3353
4106
|
ctx.inject(["webServer"], (webCtx) => {
|
|
3354
|
-
registerOpenAICodexAuthRoutes(webCtx, credentials, trustedOrigins, fastMode);
|
|
4107
|
+
registerOpenAICodexAuthRoutes(webCtx, credentials, trustedOrigins, fastMode, proxyManager, resolveProviderProxyUrl);
|
|
4108
|
+
registerOpenAICodexProxyRoutes(webCtx, trustedOrigins, proxyManager);
|
|
3355
4109
|
registerOpenAICodexUpdateRoutes(webCtx, { currentVersion: CODEX_CONNECT_VERSION }, trustedOrigins);
|
|
3356
4110
|
registerOpenAICodexModelCatalogRoute(webCtx, openAICodexModelCatalog, trustedOrigins);
|
|
4111
|
+
registerOpenAICodexOriginalImageRoute(webCtx, trustedOrigins, imageAssets);
|
|
3357
4112
|
});
|
|
3358
4113
|
let stopped = false;
|
|
3359
4114
|
let searchFiber;
|
|
@@ -3384,7 +4139,9 @@ function apply(ctx, config) {
|
|
|
3384
4139
|
mode: nextRegistration.mode,
|
|
3385
4140
|
contextSize: nextRegistration.contextSize,
|
|
3386
4141
|
maxOutputTokens: nextRegistration.maxOutputTokens,
|
|
3387
|
-
resolveRequestId: () => String(webCtx.get("agents")?.currentInitiator()?.session.id ?? randomUUID())
|
|
4142
|
+
resolveRequestId: () => String(webCtx.get("agents")?.currentInitiator()?.session.id ?? randomUUID()),
|
|
4143
|
+
proxyManager,
|
|
4144
|
+
resolveProxyUrl: resolveProviderProxyUrl
|
|
3388
4145
|
})));
|
|
3389
4146
|
searchFiber = fiber;
|
|
3390
4147
|
searchRegistration = nextRegistration;
|
|
@@ -3425,7 +4182,7 @@ function apply(ctx, config) {
|
|
|
3425
4182
|
imageGenerationFiber = void 0;
|
|
3426
4183
|
if (previous !== void 0) await previous.dispose();
|
|
3427
4184
|
if (stopped || !enabled) return;
|
|
3428
|
-
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)));
|
|
3429
4186
|
imageGenerationFiber = fiber;
|
|
3430
4187
|
Promise.resolve(fiber).catch((error) => {
|
|
3431
4188
|
if (imageGenerationFiber === fiber) imageGenerationFiber = void 0;
|
|
@@ -3465,14 +4222,27 @@ function apply(ctx, config) {
|
|
|
3465
4222
|
image?.dispose() ?? Promise.resolve(),
|
|
3466
4223
|
imageGeneration?.dispose() ?? Promise.resolve()
|
|
3467
4224
|
]);
|
|
4225
|
+
await proxyManager.dispose();
|
|
3468
4226
|
}, "dsh-codex-connect: optional capability lifecycle");
|
|
3469
4227
|
installSettingsSection(ctx, OPENAI_CODEX_SETTINGS_NS, Config, config, {
|
|
4228
|
+
validate(value) {
|
|
4229
|
+
resolveOpenAICodexSettings(value);
|
|
4230
|
+
if (value.enableProxy === true && !isValidOpenAICodexProxyUrl(value.proxyUrl)) throw new TypeError("OpenAI Codex proxyUrl must be an HTTP(S) origin without credentials or a path");
|
|
4231
|
+
},
|
|
3470
4232
|
setSource(source) {
|
|
3471
4233
|
current = source;
|
|
3472
4234
|
},
|
|
3473
|
-
onChange
|
|
4235
|
+
onChange() {
|
|
4236
|
+
const proxyIsActive = resolveProviderProxyUrl() !== void 0;
|
|
4237
|
+
if (proxyWasActive && !proxyIsActive) proxyManager.deactivate().catch((error) => {
|
|
4238
|
+
ctx.logger.error("dsh-codex-connect: could not deactivate the provider proxy");
|
|
4239
|
+
ctx.logger.error(error);
|
|
4240
|
+
});
|
|
4241
|
+
proxyWasActive = proxyIsActive;
|
|
4242
|
+
scheduleCapabilities();
|
|
4243
|
+
}
|
|
3474
4244
|
});
|
|
3475
4245
|
scheduleCapabilities();
|
|
3476
4246
|
}
|
|
3477
4247
|
//#endregion
|
|
3478
|
-
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 };
|