@ai-sdk/harness-codex 1.0.72 → 1.0.74
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/CHANGELOG.md +21 -0
- package/README.md +10 -1
- package/dist/bridge/index.mjs +1 -0
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/index.d.ts +14 -2
- package/dist/index.js +165 -62
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/bridge/index.ts +1 -0
- package/src/codex-auth.ts +93 -11
- package/src/codex-bootstrap.ts +60 -0
- package/src/codex-bridge-protocol.ts +1 -0
- package/src/codex-harness.ts +97 -75
- package/src/index.ts +1 -1
package/dist/index.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
// src/codex-harness.ts
|
|
2
2
|
import { randomBytes } from "crypto";
|
|
3
|
-
import { readFile } from "fs/promises";
|
|
4
3
|
import path from "path";
|
|
5
|
-
import { fileURLToPath } from "url";
|
|
6
4
|
import {
|
|
7
5
|
commonTool,
|
|
8
6
|
HarnessCapabilityUnsupportedError,
|
|
@@ -15,30 +13,97 @@ import {
|
|
|
15
13
|
drainBridgeProcessStream,
|
|
16
14
|
forwardBridgeProcessStream,
|
|
17
15
|
markBridgeStarting,
|
|
16
|
+
maskSandboxCredentials,
|
|
18
17
|
resolveSandboxHomeDir,
|
|
19
18
|
SandboxChannel,
|
|
20
19
|
shellQuote,
|
|
20
|
+
warnCredentialBrokeringUnavailable,
|
|
21
21
|
waitForBridgeReady,
|
|
22
22
|
writeSkills as writeHarnessSkills
|
|
23
23
|
} from "@ai-sdk/harness/utils";
|
|
24
24
|
import { WebSocket } from "ws";
|
|
25
25
|
import { z as z2 } from "zod/v4";
|
|
26
26
|
|
|
27
|
+
// src/codex-bootstrap.ts
|
|
28
|
+
import { readFile } from "fs/promises";
|
|
29
|
+
import { fileURLToPath } from "url";
|
|
30
|
+
var CODEX_BOOTSTRAP_DIR = ".harness-bootstrap/codex";
|
|
31
|
+
var cachedBootstrap;
|
|
32
|
+
async function getCodexBootstrap() {
|
|
33
|
+
if (cachedBootstrap != null) return cachedBootstrap;
|
|
34
|
+
const [pkg, lock, bridge] = await Promise.all([
|
|
35
|
+
readBridgeAsset("package.json"),
|
|
36
|
+
readBridgeAsset("pnpm-lock.yaml"),
|
|
37
|
+
readBridgeAsset("index.mjs")
|
|
38
|
+
]);
|
|
39
|
+
cachedBootstrap = {
|
|
40
|
+
harnessId: "codex",
|
|
41
|
+
bootstrapDir: CODEX_BOOTSTRAP_DIR,
|
|
42
|
+
files: [
|
|
43
|
+
{ path: `${CODEX_BOOTSTRAP_DIR}/package.json`, content: pkg },
|
|
44
|
+
{ path: `${CODEX_BOOTSTRAP_DIR}/pnpm-lock.yaml`, content: lock },
|
|
45
|
+
{ path: `${CODEX_BOOTSTRAP_DIR}/bridge.mjs`, content: bridge }
|
|
46
|
+
],
|
|
47
|
+
commands: [
|
|
48
|
+
{
|
|
49
|
+
command: "pnpm install --frozen-lockfile --store-dir .pnpm-store"
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
};
|
|
53
|
+
return cachedBootstrap;
|
|
54
|
+
}
|
|
55
|
+
async function readBridgeAsset(name) {
|
|
56
|
+
const candidates = [
|
|
57
|
+
new URL(`./bridge/${name}`, import.meta.url),
|
|
58
|
+
new URL(`../bridge/${name}`, import.meta.url)
|
|
59
|
+
];
|
|
60
|
+
let lastErr;
|
|
61
|
+
for (const url of candidates) {
|
|
62
|
+
try {
|
|
63
|
+
return await readFile(fileURLToPath(url), "utf8");
|
|
64
|
+
} catch (err) {
|
|
65
|
+
const code = err.code;
|
|
66
|
+
if (code !== "ENOENT") throw err;
|
|
67
|
+
lastErr = err;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
throw lastErr ?? new Error(`bridge asset not found: ${name}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
27
73
|
// src/codex-auth.ts
|
|
28
|
-
import {
|
|
74
|
+
import {
|
|
75
|
+
createCredentialRequestTransformation,
|
|
76
|
+
getAiGatewayAuthFromEnv
|
|
77
|
+
} from "@ai-sdk/harness/utils";
|
|
78
|
+
var DEFAULT_AI_GATEWAY_BASE_URL = "https://ai-gateway.vercel.sh/v1";
|
|
79
|
+
var DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1";
|
|
80
|
+
var CODEX_CREDENTIAL_ENVIRONMENT_VARIABLES = [
|
|
81
|
+
"AI_GATEWAY_API_KEY",
|
|
82
|
+
"CODEX_API_KEY"
|
|
83
|
+
];
|
|
84
|
+
function createCodexRequestTransformations(env, auth) {
|
|
85
|
+
if (!env.CODEX_API_KEY) return [];
|
|
86
|
+
return [
|
|
87
|
+
createCredentialRequestTransformation({
|
|
88
|
+
baseUrl: env.OPENAI_BASE_URL ?? (auth === "ai-gateway" ? DEFAULT_AI_GATEWAY_BASE_URL : DEFAULT_OPENAI_BASE_URL),
|
|
89
|
+
headers: { Authorization: `Bearer ${env.CODEX_API_KEY}` }
|
|
90
|
+
})
|
|
91
|
+
];
|
|
92
|
+
}
|
|
29
93
|
function resolveCodexEnv(auth, processEnv = process.env) {
|
|
30
|
-
|
|
31
|
-
|
|
94
|
+
const normalizedAuth = normalizeCodexAuthToLegacyAuth(auth);
|
|
95
|
+
if (normalizedAuth?.openaiCompatible) {
|
|
96
|
+
return pickOpenAICompatible(normalizedAuth.openaiCompatible, processEnv);
|
|
32
97
|
}
|
|
33
|
-
if (
|
|
34
|
-
return pickOpenAI({ explicit:
|
|
98
|
+
if (normalizedAuth?.openai) {
|
|
99
|
+
return pickOpenAI({ explicit: normalizedAuth.openai, processEnv });
|
|
35
100
|
}
|
|
36
101
|
const gatewayAuthFromEnv = getAiGatewayAuthFromEnv({
|
|
37
102
|
env: processEnv
|
|
38
103
|
});
|
|
39
|
-
if (
|
|
104
|
+
if (normalizedAuth?.gateway) {
|
|
40
105
|
return pickGateway({
|
|
41
|
-
explicit:
|
|
106
|
+
explicit: normalizedAuth.gateway,
|
|
42
107
|
gatewayAuthFromEnv
|
|
43
108
|
});
|
|
44
109
|
}
|
|
@@ -50,6 +115,37 @@ function resolveCodexEnv(auth, processEnv = process.env) {
|
|
|
50
115
|
}
|
|
51
116
|
return pickOpenAI({ processEnv });
|
|
52
117
|
}
|
|
118
|
+
function resolveCodexAuthenticationMode(auth, processEnv = process.env) {
|
|
119
|
+
if (typeof auth !== "string" && auth?.openaiCompatible) {
|
|
120
|
+
return "direct";
|
|
121
|
+
}
|
|
122
|
+
if (auth === "direct" || typeof auth !== "string" && auth?.openai) {
|
|
123
|
+
return "direct";
|
|
124
|
+
}
|
|
125
|
+
if (auth === "ai-gateway" || typeof auth !== "string" && auth?.gateway) {
|
|
126
|
+
return "ai-gateway";
|
|
127
|
+
}
|
|
128
|
+
return getAiGatewayAuthFromEnv({ env: processEnv }).apiKey ? "ai-gateway" : "direct";
|
|
129
|
+
}
|
|
130
|
+
function normalizeCodexAuthToLegacyAuth(auth) {
|
|
131
|
+
if (auth == null || auth === "auto") {
|
|
132
|
+
return void 0;
|
|
133
|
+
}
|
|
134
|
+
if (typeof auth === "string") {
|
|
135
|
+
switch (auth) {
|
|
136
|
+
case "direct":
|
|
137
|
+
return { openai: {} };
|
|
138
|
+
case "ai-gateway":
|
|
139
|
+
return { gateway: {} };
|
|
140
|
+
default:
|
|
141
|
+
return void 0;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
console.warn(
|
|
145
|
+
'[codex] Passing an object to auth options is deprecated. Use a string mode ("auto" | "direct" | "ai-gateway") instead, and pass credentials via environment variables.'
|
|
146
|
+
);
|
|
147
|
+
return auth;
|
|
148
|
+
}
|
|
53
149
|
function pickOpenAICompatible(explicit, processEnv) {
|
|
54
150
|
const env = {};
|
|
55
151
|
const apiKey = explicit.apiKey ?? processEnv.OPENAI_API_KEY ?? processEnv.CODEX_API_KEY;
|
|
@@ -90,6 +186,7 @@ function pickGateway({
|
|
|
90
186
|
env.CODEX_API_KEY = apiKey;
|
|
91
187
|
}
|
|
92
188
|
env.AI_GATEWAY_BASE_URL = baseUrl;
|
|
189
|
+
env.OPENAI_BASE_URL = baseUrl;
|
|
93
190
|
return env;
|
|
94
191
|
}
|
|
95
192
|
function toCodexGatewayBaseUrl(baseUrl) {
|
|
@@ -110,6 +207,7 @@ var startMessageSchema = harnessV1BridgeStartBaseSchema.extend({
|
|
|
110
207
|
instructions: z.string().optional(),
|
|
111
208
|
reasoningEffort: z.enum(["low", "medium", "high"]).optional(),
|
|
112
209
|
webSearch: z.boolean().optional(),
|
|
210
|
+
codexConfig: z.record(z.string(), z.unknown()).optional(),
|
|
113
211
|
mcpServers: z.record(z.string(), z.unknown()).optional(),
|
|
114
212
|
// Resume signal. When supplied, the bridge calls
|
|
115
213
|
// `codex.resumeThread(resumeThreadId, …)` instead of starting a fresh thread.
|
|
@@ -126,7 +224,7 @@ var inboundMessageSchema = z.discriminatedUnion("type", [
|
|
|
126
224
|
var CLI_SHIM_FILENAME = "harness-tool.mjs";
|
|
127
225
|
|
|
128
226
|
// src/version.ts
|
|
129
|
-
var VERSION = true ? "1.0.
|
|
227
|
+
var VERSION = true ? "1.0.74" : "0.0.0-test";
|
|
130
228
|
|
|
131
229
|
// src/codex-harness.ts
|
|
132
230
|
var DEFAULT_CODEX_MODEL = "gpt-5.5";
|
|
@@ -145,7 +243,6 @@ var CODEX_BUILTIN_TOOLS = {
|
|
|
145
243
|
inputSchema: z2.object({ query: z2.string() })
|
|
146
244
|
})
|
|
147
245
|
};
|
|
148
|
-
var BOOTSTRAP_DIR = ".harness-bootstrap/codex";
|
|
149
246
|
var codexBridgeCoordsSchema = z2.object({
|
|
150
247
|
port: z2.number(),
|
|
151
248
|
token: z2.string(),
|
|
@@ -157,36 +254,13 @@ var codexResumeStateSchema = z2.object({
|
|
|
157
254
|
bridge: codexBridgeCoordsSchema.optional()
|
|
158
255
|
});
|
|
159
256
|
function createCodex(settings = {}) {
|
|
160
|
-
let cachedBootstrap;
|
|
161
257
|
return {
|
|
162
258
|
specificationVersion: "harness-v1",
|
|
163
259
|
harnessId: "codex",
|
|
164
260
|
builtinTools: CODEX_BUILTIN_TOOLS,
|
|
165
261
|
supportsBuiltinToolApprovals: false,
|
|
166
262
|
lifecycleStateSchema: codexResumeStateSchema,
|
|
167
|
-
getBootstrap:
|
|
168
|
-
if (cachedBootstrap != null) return cachedBootstrap;
|
|
169
|
-
const [pkg, lock, bridge] = await Promise.all([
|
|
170
|
-
readBridgeAsset("package.json"),
|
|
171
|
-
readBridgeAsset("pnpm-lock.yaml"),
|
|
172
|
-
readBridgeAsset("index.mjs")
|
|
173
|
-
]);
|
|
174
|
-
cachedBootstrap = {
|
|
175
|
-
harnessId: "codex",
|
|
176
|
-
bootstrapDir: BOOTSTRAP_DIR,
|
|
177
|
-
files: [
|
|
178
|
-
{ path: `${BOOTSTRAP_DIR}/package.json`, content: pkg },
|
|
179
|
-
{ path: `${BOOTSTRAP_DIR}/pnpm-lock.yaml`, content: lock },
|
|
180
|
-
{ path: `${BOOTSTRAP_DIR}/bridge.mjs`, content: bridge }
|
|
181
|
-
],
|
|
182
|
-
commands: [
|
|
183
|
-
{
|
|
184
|
-
command: "pnpm install --frozen-lockfile --store-dir .pnpm-store"
|
|
185
|
-
}
|
|
186
|
-
]
|
|
187
|
-
};
|
|
188
|
-
return cachedBootstrap;
|
|
189
|
-
},
|
|
263
|
+
getBootstrap: getCodexBootstrap,
|
|
190
264
|
doStart: async (startOpts) => {
|
|
191
265
|
if (startOpts.builtinToolFiltering != null) {
|
|
192
266
|
throw new HarnessCapabilityUnsupportedError({
|
|
@@ -201,11 +275,34 @@ function createCodex(settings = {}) {
|
|
|
201
275
|
});
|
|
202
276
|
}
|
|
203
277
|
const sandboxSession = startOpts.sandboxSession;
|
|
278
|
+
const authenticationMode = resolveCodexAuthenticationMode(settings.auth);
|
|
279
|
+
const resolvedAuthEnvironment = resolveCodexEnv(settings.auth);
|
|
280
|
+
let sandboxAuthEnvironment = resolvedAuthEnvironment;
|
|
281
|
+
if (sandboxSession.addRequestTransformations != null) {
|
|
282
|
+
const requestTransformations = createCodexRequestTransformations(
|
|
283
|
+
resolvedAuthEnvironment,
|
|
284
|
+
authenticationMode
|
|
285
|
+
);
|
|
286
|
+
if (requestTransformations.length > 0) {
|
|
287
|
+
await sandboxSession.addRequestTransformations(
|
|
288
|
+
requestTransformations
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
sandboxAuthEnvironment = maskSandboxCredentials({
|
|
292
|
+
environment: resolvedAuthEnvironment,
|
|
293
|
+
credentialEnvironmentVariables: CODEX_CREDENTIAL_ENVIRONMENT_VARIABLES
|
|
294
|
+
});
|
|
295
|
+
if (requestTransformations.length > 0 && authenticationMode === "direct" && resolvedAuthEnvironment.OPENAI_BASE_URL == null) {
|
|
296
|
+
sandboxAuthEnvironment.OPENAI_BASE_URL = DEFAULT_OPENAI_BASE_URL;
|
|
297
|
+
}
|
|
298
|
+
} else {
|
|
299
|
+
warnCredentialBrokeringUnavailable();
|
|
300
|
+
}
|
|
204
301
|
const session = sandboxSession.restricted();
|
|
205
302
|
const sandboxId = sandboxSession.id;
|
|
206
303
|
const bootstrapDir = path.posix.resolve(
|
|
207
304
|
sandboxSession.defaultWorkingDirectory,
|
|
208
|
-
|
|
305
|
+
CODEX_BOOTSTRAP_DIR
|
|
209
306
|
);
|
|
210
307
|
const lifecycleState = startOpts.continueFrom ?? startOpts.resumeFrom;
|
|
211
308
|
const isResume = lifecycleState != null;
|
|
@@ -233,12 +330,16 @@ function createCodex(settings = {}) {
|
|
|
233
330
|
});
|
|
234
331
|
if (coords) {
|
|
235
332
|
try {
|
|
236
|
-
const
|
|
333
|
+
const endpoint2 = await sandboxSession.getPortEndpoint({
|
|
237
334
|
port: coords.port,
|
|
238
335
|
protocol: "ws"
|
|
239
|
-
})
|
|
336
|
+
});
|
|
337
|
+
const attachEndpoint = withBridgeToken({
|
|
338
|
+
endpoint: endpoint2,
|
|
339
|
+
token: coords.token
|
|
340
|
+
});
|
|
240
341
|
const attachChannel = new SandboxChannel({
|
|
241
|
-
connect: () => openWebSocket(
|
|
342
|
+
connect: () => openWebSocket(attachEndpoint),
|
|
242
343
|
outboundSchema: outboundMessageSchema,
|
|
243
344
|
initialLastSeenEventId: coords.lastSeenEventId,
|
|
244
345
|
onDiagnostic,
|
|
@@ -254,6 +355,7 @@ function createCodex(settings = {}) {
|
|
|
254
355
|
model: settings.model ?? DEFAULT_CODEX_MODEL,
|
|
255
356
|
reasoningEffort: settings.reasoningEffort,
|
|
256
357
|
webSearch: settings.webSearch,
|
|
358
|
+
codexConfig: settings.codexConfig,
|
|
257
359
|
mcpServers: settings.mcpServers,
|
|
258
360
|
resumeThreadId: resumeThreadIdString,
|
|
259
361
|
isResume: true,
|
|
@@ -288,7 +390,7 @@ function createCodex(settings = {}) {
|
|
|
288
390
|
abortSignal: startOpts.abortSignal
|
|
289
391
|
}) : void 0;
|
|
290
392
|
const env = {
|
|
291
|
-
...
|
|
393
|
+
...sandboxAuthEnvironment,
|
|
292
394
|
AI_SDK_HARNESS_CLIENT_APP: CODEX_CLIENT_APP,
|
|
293
395
|
BRIDGE_CHANNEL_TOKEN: token,
|
|
294
396
|
BRIDGE_WS_PORT: String(port),
|
|
@@ -345,12 +447,13 @@ function createCodex(settings = {}) {
|
|
|
345
447
|
})
|
|
346
448
|
});
|
|
347
449
|
void drainBridgeProcessStream(proc.stdout);
|
|
348
|
-
const
|
|
450
|
+
const endpoint = await sandboxSession.getPortEndpoint({
|
|
349
451
|
port: boundPort,
|
|
350
452
|
protocol: "ws"
|
|
351
|
-
})
|
|
453
|
+
});
|
|
454
|
+
const bridgeEndpoint = withBridgeToken({ endpoint, token });
|
|
352
455
|
const channel = new SandboxChannel({
|
|
353
|
-
connect: () => openWebSocket(
|
|
456
|
+
connect: () => openWebSocket(bridgeEndpoint),
|
|
354
457
|
outboundSchema: outboundMessageSchema,
|
|
355
458
|
onDiagnostic,
|
|
356
459
|
onBridgeError,
|
|
@@ -370,6 +473,7 @@ function createCodex(settings = {}) {
|
|
|
370
473
|
model: settings.model ?? DEFAULT_CODEX_MODEL,
|
|
371
474
|
reasoningEffort: settings.reasoningEffort,
|
|
372
475
|
webSearch: settings.webSearch,
|
|
476
|
+
codexConfig: settings.codexConfig,
|
|
373
477
|
mcpServers: settings.mcpServers,
|
|
374
478
|
resumeThreadId: resumeThreadIdString,
|
|
375
479
|
isResume: respawnStrategy !== void 0,
|
|
@@ -392,23 +496,6 @@ function resolveBridgePort(sandboxSession, override) {
|
|
|
392
496
|
message: "The codex harness needs a TCP port exposed by the sandbox. Create the sandbox with `ports: [<port>]` or pass `createCodex({ port })`."
|
|
393
497
|
});
|
|
394
498
|
}
|
|
395
|
-
async function readBridgeAsset(name) {
|
|
396
|
-
const candidates = [
|
|
397
|
-
new URL(`./bridge/${name}`, import.meta.url),
|
|
398
|
-
new URL(`../bridge/${name}`, import.meta.url)
|
|
399
|
-
];
|
|
400
|
-
let lastErr;
|
|
401
|
-
for (const url of candidates) {
|
|
402
|
-
try {
|
|
403
|
-
return await readFile(fileURLToPath(url), "utf8");
|
|
404
|
-
} catch (err) {
|
|
405
|
-
const code = err.code;
|
|
406
|
-
if (code !== "ENOENT") throw err;
|
|
407
|
-
lastErr = err;
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
throw lastErr ?? new Error(`bridge asset not found: ${name}`);
|
|
411
|
-
}
|
|
412
499
|
async function writeCodexSkills({
|
|
413
500
|
sandbox,
|
|
414
501
|
skills,
|
|
@@ -434,9 +521,14 @@ async function writeCodexSkills({
|
|
|
434
521
|
codexHomeDir
|
|
435
522
|
};
|
|
436
523
|
}
|
|
437
|
-
function openWebSocket(
|
|
524
|
+
function openWebSocket({
|
|
525
|
+
url,
|
|
526
|
+
headers
|
|
527
|
+
}) {
|
|
438
528
|
return new Promise((resolve, reject) => {
|
|
439
|
-
const ws = new WebSocket(url
|
|
529
|
+
const ws = new WebSocket(url, {
|
|
530
|
+
headers: headers == null ? void 0 : { ...headers }
|
|
531
|
+
});
|
|
440
532
|
const onOpen = () => {
|
|
441
533
|
ws.off("error", onError);
|
|
442
534
|
resolve(ws);
|
|
@@ -449,6 +541,14 @@ function openWebSocket(url) {
|
|
|
449
541
|
ws.once("error", onError);
|
|
450
542
|
});
|
|
451
543
|
}
|
|
544
|
+
function withBridgeToken({
|
|
545
|
+
endpoint,
|
|
546
|
+
token
|
|
547
|
+
}) {
|
|
548
|
+
const bridgeUrl = new URL(endpoint.url);
|
|
549
|
+
bridgeUrl.searchParams.set("agent_bridge_token", token);
|
|
550
|
+
return { ...endpoint, url: bridgeUrl.toString() };
|
|
551
|
+
}
|
|
452
552
|
function createSession({
|
|
453
553
|
sessionId,
|
|
454
554
|
channel,
|
|
@@ -457,6 +557,7 @@ function createSession({
|
|
|
457
557
|
model,
|
|
458
558
|
reasoningEffort,
|
|
459
559
|
webSearch,
|
|
560
|
+
codexConfig,
|
|
460
561
|
mcpServers,
|
|
461
562
|
resumeThreadId,
|
|
462
563
|
isResume,
|
|
@@ -635,6 +736,7 @@ function createSession({
|
|
|
635
736
|
model,
|
|
636
737
|
reasoningEffort,
|
|
637
738
|
webSearch,
|
|
739
|
+
...codexConfig == null ? {} : { codexConfig },
|
|
638
740
|
...mcpServers == null ? {} : { mcpServers },
|
|
639
741
|
...permissionMode ? { permissionMode } : {},
|
|
640
742
|
...pendingResumeThreadId ? { resumeThreadId: pendingResumeThreadId } : {},
|
|
@@ -672,6 +774,7 @@ function createSession({
|
|
|
672
774
|
model,
|
|
673
775
|
reasoningEffort,
|
|
674
776
|
webSearch,
|
|
777
|
+
...codexConfig == null ? {} : { codexConfig },
|
|
675
778
|
...mcpServers == null ? {} : { mcpServers },
|
|
676
779
|
...permissionMode ? { permissionMode } : {},
|
|
677
780
|
...threadId ? { resumeThreadId: threadId } : {},
|