codex-openai-proxy 0.1.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +253 -0
- package/dist/app-server/app-server.d.ts +39 -0
- package/dist/app-server/app-server.js +261 -0
- package/dist/app-server/app-server.js.map +1 -0
- package/dist/app-server/auth.d.ts +16 -0
- package/dist/app-server/auth.js +102 -0
- package/dist/app-server/auth.js.map +1 -0
- package/dist/app-server/json-rpc.d.ts +29 -0
- package/dist/app-server/json-rpc.js +141 -0
- package/dist/app-server/json-rpc.js.map +1 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +11 -0
- package/dist/bin.js.map +1 -0
- package/dist/cli/cli.d.ts +6 -0
- package/dist/cli/cli.js +319 -0
- package/dist/cli/cli.js.map +1 -0
- package/dist/continuation/state.d.ts +71 -0
- package/dist/continuation/state.js +460 -0
- package/dist/continuation/state.js.map +1 -0
- package/dist/core/abort.d.ts +13 -0
- package/dist/core/abort.js +74 -0
- package/dist/core/abort.js.map +1 -0
- package/dist/core/canonical.d.ts +6 -0
- package/dist/core/canonical.js +22 -0
- package/dist/core/canonical.js.map +1 -0
- package/dist/core/config.d.ts +33 -0
- package/dist/core/config.js +139 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/logger.d.ts +17 -0
- package/dist/core/logger.js +44 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/policy.d.ts +98 -0
- package/dist/core/policy.js +242 -0
- package/dist/core/policy.js.map +1 -0
- package/dist/core/redact.d.ts +2 -0
- package/dist/core/redact.js +35 -0
- package/dist/core/redact.js.map +1 -0
- package/dist/http/chat-execute.d.ts +24 -0
- package/dist/http/chat-execute.js +491 -0
- package/dist/http/chat-execute.js.map +1 -0
- package/dist/http/chat-normalize.d.ts +100 -0
- package/dist/http/chat-normalize.js +379 -0
- package/dist/http/chat-normalize.js.map +1 -0
- package/dist/http/chat-sse.d.ts +14 -0
- package/dist/http/chat-sse.js +52 -0
- package/dist/http/chat-sse.js.map +1 -0
- package/dist/http/chat-validate.d.ts +37 -0
- package/dist/http/chat-validate.js +190 -0
- package/dist/http/chat-validate.js.map +1 -0
- package/dist/http/chat.d.ts +8 -0
- package/dist/http/chat.js +137 -0
- package/dist/http/chat.js.map +1 -0
- package/dist/http/errors.d.ts +19 -0
- package/dist/http/errors.js +56 -0
- package/dist/http/errors.js.map +1 -0
- package/dist/http/server.d.ts +19 -0
- package/dist/http/server.js +254 -0
- package/dist/http/server.js.map +1 -0
- package/package.json +67 -0
- package/protocol/VERSION.json +10 -0
- package/protocol/schemas/response-mapping.schema.json +38 -0
- package/protocol/schemas/x-codex.schema.json +12 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { createServer, } from "node:http";
|
|
3
|
+
import { HttpError, writeError, writeJson } from "./errors.js";
|
|
4
|
+
import { UNRESTRICTED_POLICY_REQUIREMENTS, } from "../core/policy.js";
|
|
5
|
+
import { handleChatCompletion } from "./chat.js";
|
|
6
|
+
import { ContinuationCoordinator, ResponseStore, } from "../continuation/state.js";
|
|
7
|
+
/** Creates a loopback proxy with bounded concurrency and request lifetimes. */
|
|
8
|
+
export function createProxyServer(options, log) {
|
|
9
|
+
let ready = false;
|
|
10
|
+
let transport;
|
|
11
|
+
let requirements = UNRESTRICTED_POLICY_REQUIREMENTS;
|
|
12
|
+
let continuations;
|
|
13
|
+
const continuationStore = new ResponseStore(options.stateDir);
|
|
14
|
+
let active = 0;
|
|
15
|
+
const controllers = new Set();
|
|
16
|
+
const sockets = new Set();
|
|
17
|
+
const server = createServer((request, response) => {
|
|
18
|
+
const started = Date.now();
|
|
19
|
+
const requestId = randomUUID();
|
|
20
|
+
response.setHeader("x-request-id", requestId);
|
|
21
|
+
// Parse the request target once; routing and every log line reuse it.
|
|
22
|
+
let url;
|
|
23
|
+
try {
|
|
24
|
+
url = new URL(request.url ?? "/", "http://loopback.invalid");
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
url = undefined;
|
|
28
|
+
}
|
|
29
|
+
const logRequest = (status) => {
|
|
30
|
+
log("info", "http_request", {
|
|
31
|
+
request_id: requestId,
|
|
32
|
+
method: request.method,
|
|
33
|
+
path: url?.pathname ?? "[invalid-path]",
|
|
34
|
+
status,
|
|
35
|
+
duration_ms: Date.now() - started,
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
const authorityError = validateRequestAuthority(request);
|
|
39
|
+
if (authorityError) {
|
|
40
|
+
writeError(response, authorityError);
|
|
41
|
+
logRequest(authorityError.status);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
// Reject before allocating per-request resources when capacity is full.
|
|
45
|
+
if (active >= options.maxRequests) {
|
|
46
|
+
const overloaded = new HttpError(429, "The proxy is handling too many requests.", "rate_limit_error", "overloaded");
|
|
47
|
+
writeError(response, overloaded);
|
|
48
|
+
logRequest(overloaded.status);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
active += 1;
|
|
52
|
+
const controller = new AbortController();
|
|
53
|
+
controllers.add(controller);
|
|
54
|
+
const timer = setTimeout(() => controller.abort(new Error("request timeout")), options.requestTimeoutMs);
|
|
55
|
+
timer.unref();
|
|
56
|
+
let finished = false;
|
|
57
|
+
const finish = () => {
|
|
58
|
+
if (finished)
|
|
59
|
+
return;
|
|
60
|
+
finished = true;
|
|
61
|
+
request.off("aborted", abortRequest);
|
|
62
|
+
clearTimeout(timer);
|
|
63
|
+
controllers.delete(controller);
|
|
64
|
+
active -= 1;
|
|
65
|
+
logRequest(response.statusCode);
|
|
66
|
+
};
|
|
67
|
+
const abortRequest = () => {
|
|
68
|
+
controller.abort(new Error("client disconnected"));
|
|
69
|
+
finish();
|
|
70
|
+
};
|
|
71
|
+
request.once("aborted", abortRequest);
|
|
72
|
+
response.once("finish", finish);
|
|
73
|
+
response.once("close", () => {
|
|
74
|
+
if (!response.writableFinished)
|
|
75
|
+
abortRequest();
|
|
76
|
+
});
|
|
77
|
+
void route(request, response, ready, options.bodyLimitBytes, controller.signal, transport, continuations, options.root, requirements, options.implicitToolContinuation, log, requestId, url).catch((cause) => {
|
|
78
|
+
const error = cause instanceof HttpError
|
|
79
|
+
? cause
|
|
80
|
+
: controller.signal.aborted
|
|
81
|
+
? new HttpError(408, "The request timed out.", "invalid_request_error", "request_timeout")
|
|
82
|
+
: new HttpError(500, "An internal error occurred.", "server_error", "internal_error");
|
|
83
|
+
if (!(cause instanceof HttpError))
|
|
84
|
+
log.failure("request_failed", { request_id: requestId }, cause);
|
|
85
|
+
writeError(response, error);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
server.requestTimeout = options.requestTimeoutMs;
|
|
89
|
+
server.headersTimeout = Math.min(options.requestTimeoutMs, 60_000);
|
|
90
|
+
server.keepAliveTimeout = 5_000;
|
|
91
|
+
server.on("connection", (socket) => {
|
|
92
|
+
sockets.add(socket);
|
|
93
|
+
socket.once("close", () => sockets.delete(socket));
|
|
94
|
+
});
|
|
95
|
+
return {
|
|
96
|
+
server,
|
|
97
|
+
setReady(value) {
|
|
98
|
+
ready = value;
|
|
99
|
+
},
|
|
100
|
+
setTransport(value, nextRequirements) {
|
|
101
|
+
// Update requirements before the same-transport short-circuit so a refresh
|
|
102
|
+
// of managed policy against an unchanged transport still takes effect.
|
|
103
|
+
requirements = nextRequirements ?? UNRESTRICTED_POLICY_REQUIREMENTS;
|
|
104
|
+
if (transport === value)
|
|
105
|
+
return;
|
|
106
|
+
continuations?.dispose();
|
|
107
|
+
if (transport && transport !== value)
|
|
108
|
+
transport.close(new Error("app-server transport replaced"));
|
|
109
|
+
transport = value;
|
|
110
|
+
continuations = value
|
|
111
|
+
? new ContinuationCoordinator(continuationStore, value, options.toolTimeoutMs)
|
|
112
|
+
: undefined;
|
|
113
|
+
},
|
|
114
|
+
listen: () => new Promise((resolve, reject) => {
|
|
115
|
+
const onError = (error) => reject(error);
|
|
116
|
+
server.once("error", onError);
|
|
117
|
+
server.listen({ host: options.host, port: options.port, exclusive: true }, () => {
|
|
118
|
+
server.off("error", onError);
|
|
119
|
+
const address = server.address();
|
|
120
|
+
if (address === null || typeof address === "string")
|
|
121
|
+
return reject(new Error("Listener did not return a TCP address."));
|
|
122
|
+
resolve({ address: address.address, port: address.port });
|
|
123
|
+
});
|
|
124
|
+
}),
|
|
125
|
+
close: () => new Promise((resolve, reject) => {
|
|
126
|
+
continuations?.dispose();
|
|
127
|
+
transport?.close(new Error("proxy shutting down"));
|
|
128
|
+
continuations = undefined;
|
|
129
|
+
transport = undefined;
|
|
130
|
+
controllers.forEach((controller) => controller.abort(new Error("server shutting down")));
|
|
131
|
+
server.close((error) => (error ? reject(error) : resolve()));
|
|
132
|
+
server.closeIdleConnections();
|
|
133
|
+
const force = setTimeout(() => {
|
|
134
|
+
sockets.forEach((socket) => socket.destroy());
|
|
135
|
+
server.closeAllConnections();
|
|
136
|
+
}, options.shutdownTimeoutMs);
|
|
137
|
+
force.unref();
|
|
138
|
+
}),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/** Routes the intentionally small public HTTP surface. */
|
|
142
|
+
async function route(request, response, ready, bodyLimit, signal, transport, continuations, root, requirements, implicitToolContinuation, log, requestId, url) {
|
|
143
|
+
if (request.method === "GET" && url?.pathname === "/health") {
|
|
144
|
+
writeJson(response, 200, { status: "ok" });
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (request.method === "GET" && url?.pathname === "/ready") {
|
|
148
|
+
writeJson(response, ready ? 200 : 503, {
|
|
149
|
+
status: ready ? "ready" : "not_ready",
|
|
150
|
+
});
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (request.method === "POST" && url?.pathname === "/v1/chat/completions") {
|
|
154
|
+
const contentType = request.headers["content-type"]
|
|
155
|
+
?.split(";", 1)[0]
|
|
156
|
+
?.trim()
|
|
157
|
+
.toLowerCase();
|
|
158
|
+
if (contentType !== "application/json")
|
|
159
|
+
throw new HttpError(415, "Content-Type must be application/json.", "invalid_request_error", "unsupported_media_type");
|
|
160
|
+
const body = await readJsonBody(request, bodyLimit, signal);
|
|
161
|
+
if (!ready)
|
|
162
|
+
throw new HttpError(503, "The app-server is not ready.", "server_error", "app_server_not_ready");
|
|
163
|
+
if (!transport || !continuations)
|
|
164
|
+
throw new HttpError(503, "The app-server transport is unavailable.", "server_error", "app_server_not_ready");
|
|
165
|
+
await handleChatCompletion(body, response, {
|
|
166
|
+
rpc: transport,
|
|
167
|
+
log,
|
|
168
|
+
requestId,
|
|
169
|
+
signal,
|
|
170
|
+
continuations,
|
|
171
|
+
root,
|
|
172
|
+
requirements,
|
|
173
|
+
implicitToolContinuation,
|
|
174
|
+
});
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
throw new HttpError(404, "The requested route was not found.", "not_found_error", "route_not_found");
|
|
178
|
+
}
|
|
179
|
+
/** Rejects hostile authorities and every browser-originated request. */
|
|
180
|
+
function validateRequestAuthority(request) {
|
|
181
|
+
if (!isAllowedHost(request.headers.host))
|
|
182
|
+
return new HttpError(403, "The Host header must identify a loopback address.", "invalid_request_error", "invalid_host_header", "host");
|
|
183
|
+
// The proxy has no browser authentication surface. Rejecting Origin entirely
|
|
184
|
+
// keeps cross-origin and browser form traffic fail-closed, while ordinary CLI
|
|
185
|
+
// and server-side HTTP clients (which omit Origin) remain compatible.
|
|
186
|
+
if (request.headers.origin !== undefined)
|
|
187
|
+
return new HttpError(403, "Browser-originated requests are not accepted.", "invalid_request_error", "invalid_origin_header", "origin");
|
|
188
|
+
return undefined;
|
|
189
|
+
}
|
|
190
|
+
/** Accepts only explicit loopback HTTP authorities with an optional valid port. */
|
|
191
|
+
function isAllowedHost(host) {
|
|
192
|
+
if (host === undefined)
|
|
193
|
+
return false;
|
|
194
|
+
const match = /^(localhost|127\.0\.0\.1|\[::1\])(?::([0-9]+))?$/i.exec(host);
|
|
195
|
+
if (!match)
|
|
196
|
+
return false;
|
|
197
|
+
if (match[2] === undefined)
|
|
198
|
+
return true;
|
|
199
|
+
const port = Number(match[2]);
|
|
200
|
+
return Number.isInteger(port) && port >= 1 && port <= 65_535;
|
|
201
|
+
}
|
|
202
|
+
/** Creates the stable error returned when a request body exceeds its limit. */
|
|
203
|
+
function bodyTooLargeError() {
|
|
204
|
+
return new HttpError(413, "Request body is too large.", "invalid_request_error", "body_too_large");
|
|
205
|
+
}
|
|
206
|
+
/** Reads and parses a size-limited, abortable JSON request body. */
|
|
207
|
+
async function readJsonBody(request, limit, signal) {
|
|
208
|
+
const declared = Number(request.headers["content-length"]);
|
|
209
|
+
if (Number.isFinite(declared) && declared > limit)
|
|
210
|
+
throw bodyTooLargeError();
|
|
211
|
+
const chunks = await new Promise((resolve, reject) => {
|
|
212
|
+
const result = [];
|
|
213
|
+
let size = 0;
|
|
214
|
+
const cleanup = () => {
|
|
215
|
+
request.off("data", onData);
|
|
216
|
+
request.off("end", onEnd);
|
|
217
|
+
request.off("error", onError);
|
|
218
|
+
signal.removeEventListener("abort", onAbort);
|
|
219
|
+
};
|
|
220
|
+
const fail = (error) => {
|
|
221
|
+
cleanup();
|
|
222
|
+
request.pause();
|
|
223
|
+
reject(error);
|
|
224
|
+
};
|
|
225
|
+
const onData = (raw) => {
|
|
226
|
+
const chunk = Buffer.isBuffer(raw) ? raw : Buffer.from(raw);
|
|
227
|
+
size += chunk.length;
|
|
228
|
+
if (size > limit) {
|
|
229
|
+
fail(bodyTooLargeError());
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
result.push(chunk);
|
|
233
|
+
};
|
|
234
|
+
const onEnd = () => {
|
|
235
|
+
cleanup();
|
|
236
|
+
resolve(result);
|
|
237
|
+
};
|
|
238
|
+
const onError = () => fail(new HttpError(400, "The request body could not be read.", "invalid_request_error", "invalid_body"));
|
|
239
|
+
const onAbort = () => fail(signal.reason);
|
|
240
|
+
request.on("data", onData);
|
|
241
|
+
request.once("end", onEnd);
|
|
242
|
+
request.once("error", onError);
|
|
243
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
244
|
+
if (signal.aborted)
|
|
245
|
+
onAbort();
|
|
246
|
+
});
|
|
247
|
+
try {
|
|
248
|
+
return JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
throw new HttpError(400, "The request body is not valid JSON.", "invalid_request_error", "invalid_json");
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/http/server.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "codex-openai-proxy",
|
|
3
|
+
"version": "0.1.0-rc.0",
|
|
4
|
+
"description": "A localhost-only OpenAI Chat Completions proxy for Codex app-server",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "John Chen",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/CIVITAS-John/codex-app-server-to-proxy.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/CIVITAS-John/codex-app-server-to-proxy#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/CIVITAS-John/codex-app-server-to-proxy/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"codex",
|
|
17
|
+
"openai",
|
|
18
|
+
"chat-completions",
|
|
19
|
+
"proxy",
|
|
20
|
+
"cli",
|
|
21
|
+
"app-server"
|
|
22
|
+
],
|
|
23
|
+
"type": "module",
|
|
24
|
+
"bin": {
|
|
25
|
+
"codex-openai-proxy": "dist/bin.js"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"README.md",
|
|
30
|
+
"protocol/schemas",
|
|
31
|
+
"protocol/VERSION.json"
|
|
32
|
+
],
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "node scripts/clean-dist.mjs && tsc -p tsconfig.json",
|
|
35
|
+
"check": "npm run format:check && npm run lint && npm run check:protocol && npm test",
|
|
36
|
+
"check:protocol": "node scripts/check-protocol-clean.mjs",
|
|
37
|
+
"format": "prettier --write .",
|
|
38
|
+
"format:check": "prettier --check .",
|
|
39
|
+
"generate:protocol": "node scripts/generate-protocol.mjs",
|
|
40
|
+
"lint": "eslint .",
|
|
41
|
+
"prepack": "npm run build",
|
|
42
|
+
"serve": "node dist/bin.js serve",
|
|
43
|
+
"start": "npm run build && npm run serve",
|
|
44
|
+
"test": "npm run build --silent && npm run test:typecheck --silent && vitest run",
|
|
45
|
+
"test:package": "node scripts/package-smoke.mjs",
|
|
46
|
+
"test:live": "npm run build --silent && npm run test:typecheck --silent && vitest run --config vitest.live.config.js",
|
|
47
|
+
"test:typecheck": "tsc -p tsconfig.test.json",
|
|
48
|
+
"spike:offline": "node scripts/offline-spike.mjs"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@eslint/js": "^9.0.0",
|
|
52
|
+
"@types/node": "^20.19.43",
|
|
53
|
+
"@vitest/coverage-v8": "^4.1.10",
|
|
54
|
+
"eslint": "^9.0.0",
|
|
55
|
+
"fast-check": "^4.9.0",
|
|
56
|
+
"prettier": "^3.0.0",
|
|
57
|
+
"typescript": "^5.0.0",
|
|
58
|
+
"typescript-eslint": "^8.0.0",
|
|
59
|
+
"vitest": "^4.1.10"
|
|
60
|
+
},
|
|
61
|
+
"engines": {
|
|
62
|
+
"node": ">=20"
|
|
63
|
+
},
|
|
64
|
+
"dependencies": {
|
|
65
|
+
"@openai/codex": "0.144.5"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"codexPackage": "@openai/codex",
|
|
3
|
+
"codexVersion": "0.144.5",
|
|
4
|
+
"versionSource": "package.json dependencies.@openai/codex",
|
|
5
|
+
"generatedAt": "2026-07-16",
|
|
6
|
+
"experimental": true,
|
|
7
|
+
"typescriptCommand": "@openai/codex app-server generate-ts --experimental --out protocol/generated/typescript",
|
|
8
|
+
"jsonSchemaCommand": "@openai/codex app-server generate-json-schema --experimental --out protocol/generated/json-schema",
|
|
9
|
+
"executableSource": "The exact @openai/codex runtime dependency owns the executable used for generation and default startup; --codex-path overrides must report the same version."
|
|
10
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://codex-openai-proxy.invalid/response-mapping.schema.json",
|
|
4
|
+
"title": "Durable continuation store",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": ["version", "records"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"version": { "const": 0 },
|
|
10
|
+
"records": {
|
|
11
|
+
"type": "array",
|
|
12
|
+
"items": { "$ref": "#/$defs/record" }
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"$defs": {
|
|
16
|
+
"record": {
|
|
17
|
+
"type": "object",
|
|
18
|
+
"additionalProperties": false,
|
|
19
|
+
"required": ["responseId", "threadId", "state", "model", "cwd", "toolsHash", "policyHash", "createdAt", "expiresAt"],
|
|
20
|
+
"properties": {
|
|
21
|
+
"responseId": { "type": "string", "minLength": 1 },
|
|
22
|
+
"threadId": { "type": "string", "minLength": 1 },
|
|
23
|
+
"state": { "enum": ["ready", "pending_tool", "expired", "superseded", "corrupt"] },
|
|
24
|
+
"model": { "type": "string", "minLength": 1 },
|
|
25
|
+
"cwd": { "type": "string", "minLength": 1 },
|
|
26
|
+
"toolsHash": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
|
|
27
|
+
"policyHash": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
|
|
28
|
+
"createdAt": { "type": "number" },
|
|
29
|
+
"expiresAt": { "type": "number" },
|
|
30
|
+
"callIds": {
|
|
31
|
+
"type": "array",
|
|
32
|
+
"uniqueItems": true,
|
|
33
|
+
"items": { "type": "string" }
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://codex-openai-proxy.invalid/x-codex.schema.json",
|
|
4
|
+
"title": "x_codex request extension",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"properties": {
|
|
8
|
+
"cwd": { "type": "string", "minLength": 1 },
|
|
9
|
+
"sandbox": { "enum": ["read-only", "workspace-write", "danger-full-access"] },
|
|
10
|
+
"web_search": { "enum": ["disabled", "cached", "indexed", "live"] }
|
|
11
|
+
}
|
|
12
|
+
}
|