@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/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 { getAiGatewayAuthFromEnv } from "@ai-sdk/harness/utils";
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
- if (auth?.openaiCompatible) {
31
- return pickOpenAICompatible(auth.openaiCompatible, processEnv);
94
+ const normalizedAuth = normalizeCodexAuthToLegacyAuth(auth);
95
+ if (normalizedAuth?.openaiCompatible) {
96
+ return pickOpenAICompatible(normalizedAuth.openaiCompatible, processEnv);
32
97
  }
33
- if (auth?.openai) {
34
- return pickOpenAI({ explicit: auth.openai, processEnv });
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 (auth?.gateway) {
104
+ if (normalizedAuth?.gateway) {
40
105
  return pickGateway({
41
- explicit: auth.gateway,
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.72" : "0.0.0-test";
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: async () => {
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
- BOOTSTRAP_DIR
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 attachUrl = await sandboxSession.getPortUrl({
333
+ const endpoint2 = await sandboxSession.getPortEndpoint({
237
334
  port: coords.port,
238
335
  protocol: "ws"
239
- }) + `?agent_bridge_token=${encodeURIComponent(coords.token)}`;
336
+ });
337
+ const attachEndpoint = withBridgeToken({
338
+ endpoint: endpoint2,
339
+ token: coords.token
340
+ });
240
341
  const attachChannel = new SandboxChannel({
241
- connect: () => openWebSocket(attachUrl),
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
- ...resolveCodexEnv(settings.auth),
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 wsUrl = await sandboxSession.getPortUrl({
450
+ const endpoint = await sandboxSession.getPortEndpoint({
349
451
  port: boundPort,
350
452
  protocol: "ws"
351
- }) + `?agent_bridge_token=${encodeURIComponent(token)}`;
453
+ });
454
+ const bridgeEndpoint = withBridgeToken({ endpoint, token });
352
455
  const channel = new SandboxChannel({
353
- connect: () => openWebSocket(wsUrl),
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(url) {
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 } : {},