@ai-sdk/harness-codex 1.0.134 → 1.0.136
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/dist/bridge/index.mjs +1983 -2194
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/index.d.ts +1478 -1476
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1103 -1371
- package/dist/index.js.map +1 -1
- package/package.json +8 -7
- package/src/codex-harness.ts +5 -4
- package/src/index.ts +1 -0
package/dist/index.js
CHANGED
|
@@ -1,459 +1,330 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
HarnessCapabilityUnsupportedError,
|
|
7
|
-
harnessV1DiagnosticFromBridgeFrame,
|
|
8
|
-
harnessStateDirectoryPath,
|
|
9
|
-
harnessSessionDataDirectoryPath
|
|
10
|
-
} from "@ai-sdk/harness";
|
|
11
|
-
import {
|
|
12
|
-
applyCredentialForwarding,
|
|
13
|
-
classifyDiskLog,
|
|
14
|
-
createBridgeToken,
|
|
15
|
-
createSandboxCredentialEnvironment,
|
|
16
|
-
createBridgeErrorHandler,
|
|
17
|
-
createBridgeStartupError,
|
|
18
|
-
experimental_createBridgeUserMessageSubmitter,
|
|
19
|
-
drainBridgeProcessStream,
|
|
20
|
-
forwardBridgeProcessStream,
|
|
21
|
-
getRestrictedSandboxSession,
|
|
22
|
-
markBridgeStarting,
|
|
23
|
-
resolveSandboxHomeDir,
|
|
24
|
-
SandboxChannel,
|
|
25
|
-
shellQuote,
|
|
26
|
-
warnCredentialBrokeringUnavailable,
|
|
27
|
-
waitForBridgeReady,
|
|
28
|
-
withBridgeToken,
|
|
29
|
-
writeSkills
|
|
30
|
-
} from "@ai-sdk/harness/utils";
|
|
31
|
-
import {
|
|
32
|
-
tool
|
|
33
|
-
} from "@ai-sdk/provider-utils";
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import path, { dirname, join, resolve } from "node:path";
|
|
3
|
+
import { HarnessCapabilityUnsupportedError, commonTool, harnessSessionDataDirectoryPath, harnessStateDirectoryPath, harnessV1BridgeInboundCommandSchemas, harnessV1BridgeOutboundMessageSchema, harnessV1BridgeStartBaseSchema, harnessV1DiagnosticFromBridgeFrame } from "@ai-sdk/harness";
|
|
4
|
+
import { SandboxChannel, applyCredentialForwarding, classifyDiskLog, createBridgeErrorHandler, createBridgeStartupError, createBridgeToken, createCredentialRequestTransformation, createReadBridgeAsset, drainBridgeProcessStream, experimental_createBridgeUserMessageSubmitter, forwardBridgeProcessStream, getAiGatewayAuthFromEnv, getJwtExpiresAt, getRestrictedSandboxSession, isAccessTokenExpiringSoon, isHarnessAuthenticationEnvironment, isLinux, isMacOS, isWindows, markBridgeStarting, readLinuxSecretServicePassword, readMacOSKeychainPassword, readWindowsCredentialManagerPassword, refreshOAuthAccessToken, resolveSandboxCredentialEnvironment, resolveSandboxHomeDir, shellQuote, shouldResolveNativeSubscription, waitForBridgeReady, warnCredentialBrokeringUnavailable, withBridgeToken, writeSkills } from "@ai-sdk/harness/utils";
|
|
5
|
+
import { isRecord, safeParseJSON, tool } from "@ai-sdk/provider-utils";
|
|
34
6
|
import { WebSocket } from "ws";
|
|
35
|
-
import { z
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
import {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
7
|
+
import { z } from "zod/v4";
|
|
8
|
+
import { spawn } from "node:child_process";
|
|
9
|
+
import { chmod, mkdir, readFile, realpath, rename, writeFile } from "node:fs/promises";
|
|
10
|
+
import { homedir } from "node:os";
|
|
11
|
+
//#region src/codex-bootstrap.ts
|
|
12
|
+
const readBridgeAsset = createReadBridgeAsset({
|
|
13
|
+
"package.json": new URL("./bridge/package.json", import.meta.url),
|
|
14
|
+
"pnpm-lock.yaml": new URL("./bridge/pnpm-lock.yaml", import.meta.url),
|
|
15
|
+
"index.mjs": new URL("./bridge/index.mjs", import.meta.url)
|
|
43
16
|
});
|
|
44
|
-
|
|
45
|
-
|
|
17
|
+
const CODEX_BOOTSTRAP_DIR = ".harness-bootstrap/codex";
|
|
18
|
+
let cachedBootstrap;
|
|
46
19
|
async function getCodexBootstrap() {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
20
|
+
if (cachedBootstrap != null) return cachedBootstrap;
|
|
21
|
+
const [pkg, lock, bridge] = await Promise.all([
|
|
22
|
+
readBridgeAsset("package.json"),
|
|
23
|
+
readBridgeAsset("pnpm-lock.yaml"),
|
|
24
|
+
readBridgeAsset("index.mjs")
|
|
25
|
+
]);
|
|
26
|
+
cachedBootstrap = {
|
|
27
|
+
harnessId: "codex",
|
|
28
|
+
bootstrapDir: CODEX_BOOTSTRAP_DIR,
|
|
29
|
+
files: [
|
|
30
|
+
{
|
|
31
|
+
path: `${CODEX_BOOTSTRAP_DIR}/package.json`,
|
|
32
|
+
content: pkg
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
path: `${CODEX_BOOTSTRAP_DIR}/pnpm-lock.yaml`,
|
|
36
|
+
content: lock
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
path: `${CODEX_BOOTSTRAP_DIR}/bridge.mjs`,
|
|
40
|
+
content: bridge
|
|
41
|
+
}
|
|
42
|
+
],
|
|
43
|
+
commands: [{ command: "pnpm install --frozen-lockfile --store-dir .pnpm-store" }]
|
|
44
|
+
};
|
|
45
|
+
return cachedBootstrap;
|
|
68
46
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
];
|
|
82
|
-
function createCodexRequestTransformations({
|
|
83
|
-
env: environment,
|
|
84
|
-
sandboxEnv: sandboxEnvironment,
|
|
85
|
-
auth: authenticationMode
|
|
86
|
-
}) {
|
|
87
|
-
if (!environment.CODEX_API_KEY || !sandboxEnvironment.CODEX_API_KEY) {
|
|
88
|
-
return [];
|
|
89
|
-
}
|
|
90
|
-
return [
|
|
91
|
-
createCredentialRequestTransformation({
|
|
92
|
-
matchUrl: environment.OPENAI_BASE_URL ?? (authenticationMode === "ai-gateway" ? DEFAULT_AI_GATEWAY_BASE_URL : DEFAULT_OPENAI_BASE_URL),
|
|
93
|
-
matchHeaders: {
|
|
94
|
-
Authorization: `Bearer ${sandboxEnvironment.CODEX_API_KEY}`
|
|
95
|
-
},
|
|
96
|
-
transformHeaders: {
|
|
97
|
-
Authorization: `Bearer ${environment.CODEX_API_KEY}`
|
|
98
|
-
}
|
|
99
|
-
})
|
|
100
|
-
];
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region src/codex-auth.ts
|
|
49
|
+
const DEFAULT_AI_GATEWAY_BASE_URL = "https://ai-gateway.vercel.sh/v1";
|
|
50
|
+
const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1";
|
|
51
|
+
const CODEX_CREDENTIAL_ENVIRONMENT_VARIABLES = ["AI_GATEWAY_API_KEY", "CODEX_API_KEY"];
|
|
52
|
+
function createCodexRequestTransformations({ env: environment, sandboxEnv: sandboxEnvironment, auth: authenticationMode }) {
|
|
53
|
+
if (!environment.CODEX_API_KEY || !sandboxEnvironment.CODEX_API_KEY) return [];
|
|
54
|
+
return [createCredentialRequestTransformation({
|
|
55
|
+
matchUrl: environment.OPENAI_BASE_URL ?? (authenticationMode === "ai-gateway" ? DEFAULT_AI_GATEWAY_BASE_URL : "https://api.openai.com/v1"),
|
|
56
|
+
matchHeaders: { Authorization: `Bearer ${sandboxEnvironment.CODEX_API_KEY}` },
|
|
57
|
+
transformHeaders: { Authorization: `Bearer ${environment.CODEX_API_KEY}` }
|
|
58
|
+
})];
|
|
101
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Resolve the environment-variable blob the codex bridge needs. Precedence:
|
|
62
|
+
*
|
|
63
|
+
* 1. An explicit authentication mode pins the selected route.
|
|
64
|
+
* 2. Auto-detect from the host process env: gateway first
|
|
65
|
+
* (`AI_GATEWAY_API_KEY` / `VERCEL_OIDC_TOKEN`), then `CODEX_API_KEY` /
|
|
66
|
+
* `OPENAI_API_KEY`.
|
|
67
|
+
*/
|
|
102
68
|
function resolveCodexEnv(auth, processEnv = process.env) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const gatewayAuthFromEnv = getAiGatewayAuthFromEnv({
|
|
109
|
-
env: authenticationEnvironment
|
|
110
|
-
});
|
|
111
|
-
if (auth === "ai-gateway" || gatewayAuthFromEnv.apiKey) {
|
|
112
|
-
return pickGateway({ gatewayAuthFromEnv });
|
|
113
|
-
}
|
|
114
|
-
return pickOpenAI({ processEnv: authenticationEnvironment });
|
|
69
|
+
const authenticationEnvironment = isHarnessAuthenticationEnvironment(auth) ? auth : processEnv;
|
|
70
|
+
if (auth === "direct") return pickOpenAI({ processEnv: authenticationEnvironment });
|
|
71
|
+
const gatewayAuthFromEnv = getAiGatewayAuthFromEnv({ env: authenticationEnvironment });
|
|
72
|
+
if (auth === "ai-gateway" || gatewayAuthFromEnv.apiKey) return pickGateway({ gatewayAuthFromEnv });
|
|
73
|
+
return pickOpenAI({ processEnv: authenticationEnvironment });
|
|
115
74
|
}
|
|
116
75
|
function resolveCodexAuthenticationMode(auth, processEnv = process.env) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
return "direct";
|
|
122
|
-
}
|
|
123
|
-
if (auth === "ai-gateway") {
|
|
124
|
-
return "ai-gateway";
|
|
125
|
-
}
|
|
126
|
-
return getAiGatewayAuthFromEnv({ env: processEnv }).apiKey ? "ai-gateway" : "direct";
|
|
76
|
+
if (isHarnessAuthenticationEnvironment(auth)) return getAiGatewayAuthFromEnv({ env: auth }).apiKey ? "ai-gateway" : "direct";
|
|
77
|
+
if (auth === "direct") return "direct";
|
|
78
|
+
if (auth === "ai-gateway") return "ai-gateway";
|
|
79
|
+
return getAiGatewayAuthFromEnv({ env: processEnv }).apiKey ? "ai-gateway" : "direct";
|
|
127
80
|
}
|
|
128
|
-
function pickOpenAI({
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
if (project) env.OPENAI_PROJECT = project;
|
|
140
|
-
return env;
|
|
81
|
+
function pickOpenAI({ processEnv }) {
|
|
82
|
+
const env = {};
|
|
83
|
+
const apiKey = processEnv.OPENAI_API_KEY ?? processEnv.CODEX_API_KEY;
|
|
84
|
+
if (apiKey) env.CODEX_API_KEY = apiKey;
|
|
85
|
+
const baseUrl = processEnv.OPENAI_BASE_URL;
|
|
86
|
+
if (baseUrl) env.OPENAI_BASE_URL = baseUrl;
|
|
87
|
+
const organization = processEnv.OPENAI_ORGANIZATION;
|
|
88
|
+
if (organization) env.OPENAI_ORGANIZATION = organization;
|
|
89
|
+
const project = processEnv.OPENAI_PROJECT;
|
|
90
|
+
if (project) env.OPENAI_PROJECT = project;
|
|
91
|
+
return env;
|
|
141
92
|
}
|
|
142
|
-
function pickGateway({
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
env.OPENAI_BASE_URL = baseUrl;
|
|
154
|
-
return env;
|
|
93
|
+
function pickGateway({ gatewayAuthFromEnv }) {
|
|
94
|
+
const apiKey = gatewayAuthFromEnv.apiKey;
|
|
95
|
+
const baseUrl = toCodexGatewayBaseUrl(gatewayAuthFromEnv.baseUrl);
|
|
96
|
+
const env = {};
|
|
97
|
+
if (apiKey) {
|
|
98
|
+
env.AI_GATEWAY_API_KEY = apiKey;
|
|
99
|
+
env.CODEX_API_KEY = apiKey;
|
|
100
|
+
}
|
|
101
|
+
env.AI_GATEWAY_BASE_URL = baseUrl;
|
|
102
|
+
env.OPENAI_BASE_URL = baseUrl;
|
|
103
|
+
return env;
|
|
155
104
|
}
|
|
156
105
|
function toCodexGatewayBaseUrl(baseUrl) {
|
|
157
|
-
|
|
158
|
-
|
|
106
|
+
const trimmed = baseUrl.replace(/\/+$/, "");
|
|
107
|
+
return trimmed.endsWith("/v1") ? trimmed : `${trimmed}/v1`;
|
|
159
108
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
isLinux,
|
|
179
|
-
isMacOS,
|
|
180
|
-
isWindows,
|
|
181
|
-
readLinuxSecretServicePassword,
|
|
182
|
-
readMacOSKeychainPassword,
|
|
183
|
-
readWindowsCredentialManagerPassword,
|
|
184
|
-
refreshOAuthAccessToken,
|
|
185
|
-
shouldResolveNativeSubscription
|
|
186
|
-
} from "@ai-sdk/harness/utils";
|
|
187
|
-
import { isRecord, safeParseJSON } from "@ai-sdk/provider-utils";
|
|
188
|
-
var CHATGPT_CODEX_BASE_URL = "https://chatgpt.com/backend-api/codex";
|
|
189
|
-
var OPENAI_TOKEN_URL = "https://auth.openai.com/oauth/token";
|
|
190
|
-
var OPENAI_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
191
|
-
function createCodexSubscriptionRequestTransformations({
|
|
192
|
-
requestHeaders,
|
|
193
|
-
...sources
|
|
194
|
-
}) {
|
|
195
|
-
const transformations = createCodexRequestTransformations(sources);
|
|
196
|
-
if (requestHeaders == null) return transformations;
|
|
197
|
-
return transformations.map((transformation) => ({
|
|
198
|
-
...transformation,
|
|
199
|
-
transform: {
|
|
200
|
-
...transformation.transform,
|
|
201
|
-
headers: {
|
|
202
|
-
...transformation.transform.headers,
|
|
203
|
-
...requestHeaders
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}));
|
|
109
|
+
//#endregion
|
|
110
|
+
//#region src/codex-subscription.ts
|
|
111
|
+
const CHATGPT_CODEX_BASE_URL = "https://chatgpt.com/backend-api/codex";
|
|
112
|
+
const OPENAI_TOKEN_URL = "https://auth.openai.com/oauth/token";
|
|
113
|
+
const OPENAI_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
114
|
+
function createCodexSubscriptionRequestTransformations({ requestHeaders, ...sources }) {
|
|
115
|
+
const transformations = createCodexRequestTransformations(sources);
|
|
116
|
+
if (requestHeaders == null) return transformations;
|
|
117
|
+
return transformations.map((transformation) => ({
|
|
118
|
+
...transformation,
|
|
119
|
+
transform: {
|
|
120
|
+
...transformation.transform,
|
|
121
|
+
headers: {
|
|
122
|
+
...transformation.transform.headers,
|
|
123
|
+
...requestHeaders
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}));
|
|
207
127
|
}
|
|
208
|
-
async function resolveCodexAuthentication({
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
}
|
|
218
|
-
if (!shouldResolveNativeSubscription({
|
|
219
|
-
auth,
|
|
220
|
-
env: environment,
|
|
221
|
-
hasDirectCredential: environment.CODEX_API_KEY != null
|
|
222
|
-
})) {
|
|
223
|
-
return { environment };
|
|
224
|
-
}
|
|
225
|
-
return await readSubscription({ authCredentialsStoreMode }) ?? { environment };
|
|
128
|
+
async function resolveCodexAuthentication({ auth, processEnv = process.env, authCredentialsStoreMode, readSubscription = readCodexSubscription }) {
|
|
129
|
+
const environment = resolveCodexEnv(auth, processEnv);
|
|
130
|
+
if (isHarnessAuthenticationEnvironment(auth)) return { environment };
|
|
131
|
+
if (!shouldResolveNativeSubscription({
|
|
132
|
+
auth,
|
|
133
|
+
env: environment,
|
|
134
|
+
hasDirectCredential: environment.CODEX_API_KEY != null
|
|
135
|
+
})) return { environment };
|
|
136
|
+
return await readSubscription({ authCredentialsStoreMode }) ?? { environment };
|
|
226
137
|
}
|
|
227
|
-
async function readCodexSubscription({
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
return {
|
|
268
|
-
environment: {
|
|
269
|
-
CODEX_API_KEY: accessToken,
|
|
270
|
-
OPENAI_BASE_URL: CHATGPT_CODEX_BASE_URL
|
|
271
|
-
},
|
|
272
|
-
...credential.accountId == null ? {} : {
|
|
273
|
-
requestHeaders: {
|
|
274
|
-
"ChatGPT-Account-ID": credential.accountId
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
};
|
|
138
|
+
async function readCodexSubscription({ env = process.env, homeDirectory = homedir(), authCredentialsStoreMode, platform = process.platform, keyring, fetch } = {}) {
|
|
139
|
+
const codexHome = resolve(env.CODEX_HOME ?? join(homeDirectory, ".codex"));
|
|
140
|
+
const stored = await readCodexAuthStore({
|
|
141
|
+
codexHome,
|
|
142
|
+
authPath: join(codexHome, "auth.json"),
|
|
143
|
+
storageMode: toCodexAuthCredentialsStoreMode(authCredentialsStoreMode) ?? await readCodexAuthCredentialsStoreMode({ codexHome }) ?? "file",
|
|
144
|
+
keyring: keyring ?? createCodexKeyring({ platform })
|
|
145
|
+
});
|
|
146
|
+
if (stored == null) return void 0;
|
|
147
|
+
const credential = await toCodexCredential(stored.value);
|
|
148
|
+
if (credential == null) return void 0;
|
|
149
|
+
let accessToken = credential.accessToken;
|
|
150
|
+
if (isAccessTokenExpiringSoon({ expiresAt: credential.expiresAt })) {
|
|
151
|
+
const refreshed = await refreshOAuthAccessToken({
|
|
152
|
+
tokenUrl: OPENAI_TOKEN_URL,
|
|
153
|
+
clientId: OPENAI_CLIENT_ID,
|
|
154
|
+
refreshToken: credential.refreshToken,
|
|
155
|
+
requestFormat: "json",
|
|
156
|
+
...fetch == null ? {} : { fetch }
|
|
157
|
+
});
|
|
158
|
+
accessToken = refreshed.accessToken;
|
|
159
|
+
await stored.write({
|
|
160
|
+
...stored.value,
|
|
161
|
+
last_refresh: (/* @__PURE__ */ new Date()).toISOString(),
|
|
162
|
+
tokens: {
|
|
163
|
+
...stored.value.tokens,
|
|
164
|
+
access_token: refreshed.accessToken,
|
|
165
|
+
refresh_token: refreshed.refreshToken ?? credential.refreshToken
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
environment: {
|
|
171
|
+
CODEX_API_KEY: accessToken,
|
|
172
|
+
OPENAI_BASE_URL: CHATGPT_CODEX_BASE_URL
|
|
173
|
+
},
|
|
174
|
+
...credential.accountId == null ? {} : { requestHeaders: { "ChatGPT-Account-ID": credential.accountId } }
|
|
175
|
+
};
|
|
278
176
|
}
|
|
279
|
-
async function readCodexAuthStore({
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
177
|
+
async function readCodexAuthStore({ codexHome, authPath, storageMode, keyring }) {
|
|
178
|
+
if (storageMode === "ephemeral") return void 0;
|
|
179
|
+
const readFileStore = async () => {
|
|
180
|
+
const value = await readCodexAuthFile(authPath);
|
|
181
|
+
return value == null ? void 0 : {
|
|
182
|
+
value,
|
|
183
|
+
write: (updated) => writeCodexAuthFile({
|
|
184
|
+
authPath,
|
|
185
|
+
value: updated
|
|
186
|
+
})
|
|
187
|
+
};
|
|
188
|
+
};
|
|
189
|
+
const readKeyringStore = async () => {
|
|
190
|
+
if (keyring == null) return void 0;
|
|
191
|
+
const canonicalHome = await realpath(codexHome).catch(() => codexHome);
|
|
192
|
+
const account = `cli|${createHash("sha256").update(canonicalHome).digest("hex").slice(0, 16)}`;
|
|
193
|
+
const text = await keyring.read({
|
|
194
|
+
service: "Codex Auth",
|
|
195
|
+
account
|
|
196
|
+
});
|
|
197
|
+
if (text == null) return void 0;
|
|
198
|
+
const value = await parseCodexAuthFile(text);
|
|
199
|
+
return value == null ? void 0 : {
|
|
200
|
+
value,
|
|
201
|
+
write: (updated) => keyring.write({
|
|
202
|
+
service: "Codex Auth",
|
|
203
|
+
account,
|
|
204
|
+
value: JSON.stringify(updated)
|
|
205
|
+
})
|
|
206
|
+
};
|
|
207
|
+
};
|
|
208
|
+
if (storageMode === "file") return readFileStore();
|
|
209
|
+
if (storageMode === "keyring") return readKeyringStore();
|
|
210
|
+
return await readKeyringStore() ?? readFileStore();
|
|
312
211
|
}
|
|
313
|
-
async function readCodexAuthCredentialsStoreMode({
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
if (line.trimStart().startsWith("[")) return void 0;
|
|
322
|
-
const match = /^\s*cli_auth_credentials_store\s*=\s*["'](file|keyring|auto|ephemeral)["']\s*(?:#.*)?$/.exec(
|
|
323
|
-
line
|
|
324
|
-
);
|
|
325
|
-
if (match != null) {
|
|
326
|
-
return toCodexAuthCredentialsStoreMode(match[1]);
|
|
327
|
-
}
|
|
328
|
-
}
|
|
212
|
+
async function readCodexAuthCredentialsStoreMode({ codexHome }) {
|
|
213
|
+
const text = await readFile(join(codexHome, "config.toml"), "utf8").catch(() => void 0);
|
|
214
|
+
if (text == null) return void 0;
|
|
215
|
+
for (const line of text.split(/\r?\n/)) {
|
|
216
|
+
if (line.trimStart().startsWith("[")) return void 0;
|
|
217
|
+
const match = /^\s*cli_auth_credentials_store\s*=\s*["'](file|keyring|auto|ephemeral)["']\s*(?:#.*)?$/.exec(line);
|
|
218
|
+
if (match != null) return toCodexAuthCredentialsStoreMode(match[1]);
|
|
219
|
+
}
|
|
329
220
|
}
|
|
330
221
|
function toCodexAuthCredentialsStoreMode(value) {
|
|
331
|
-
|
|
222
|
+
return value === "file" || value === "keyring" || value === "auto" || value === "ephemeral" ? value : void 0;
|
|
332
223
|
}
|
|
333
224
|
async function readCodexAuthFile(authPath) {
|
|
334
|
-
|
|
335
|
-
|
|
225
|
+
const text = await readFile(authPath, "utf8").catch(() => void 0);
|
|
226
|
+
return text == null ? void 0 : parseCodexAuthFile(text);
|
|
336
227
|
}
|
|
337
228
|
async function parseCodexAuthFile(text) {
|
|
338
|
-
|
|
339
|
-
|
|
229
|
+
const parsed = await safeParseJSON({ text });
|
|
230
|
+
return parsed.success && isRecord(parsed.value) ? parsed.value : void 0;
|
|
340
231
|
}
|
|
341
232
|
async function toCodexCredential(value) {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
refreshToken,
|
|
356
|
-
expiresAt,
|
|
357
|
-
...typeof accountId === "string" ? { accountId } : {}
|
|
358
|
-
};
|
|
233
|
+
if (value.auth_mode !== "chatgpt" || !isRecord(value.tokens)) return;
|
|
234
|
+
const accessToken = value.tokens.access_token;
|
|
235
|
+
const refreshToken = value.tokens.refresh_token;
|
|
236
|
+
if (typeof accessToken !== "string" || typeof refreshToken !== "string") return;
|
|
237
|
+
const expiresAt = await getJwtExpiresAt({ token: accessToken });
|
|
238
|
+
if (expiresAt == null) return void 0;
|
|
239
|
+
const accountId = value.tokens.account_id;
|
|
240
|
+
return {
|
|
241
|
+
accessToken,
|
|
242
|
+
refreshToken,
|
|
243
|
+
expiresAt,
|
|
244
|
+
...typeof accountId === "string" ? { accountId } : {}
|
|
245
|
+
};
|
|
359
246
|
}
|
|
360
|
-
async function writeCodexAuthFile({
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
await writeFile(temporaryPath, `${JSON.stringify(value, null, 2)}
|
|
367
|
-
`, {
|
|
368
|
-
mode: 384
|
|
369
|
-
});
|
|
370
|
-
await chmod(temporaryPath, 384);
|
|
371
|
-
await rename(temporaryPath, authPath);
|
|
247
|
+
async function writeCodexAuthFile({ authPath, value }) {
|
|
248
|
+
await mkdir(dirname(authPath), { recursive: true });
|
|
249
|
+
const temporaryPath = `${authPath}.${process.pid}.tmp`;
|
|
250
|
+
await writeFile(temporaryPath, `${JSON.stringify(value, null, 2)}\n`, { mode: 384 });
|
|
251
|
+
await chmod(temporaryPath, 384);
|
|
252
|
+
await rename(temporaryPath, authPath);
|
|
372
253
|
}
|
|
373
|
-
function createCodexKeyring({
|
|
374
|
-
|
|
375
|
-
}) {
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
],
|
|
419
|
-
input: value
|
|
420
|
-
});
|
|
421
|
-
}
|
|
422
|
-
};
|
|
423
|
-
}
|
|
424
|
-
if (isWindows(platform)) {
|
|
425
|
-
return createWindowsCodexKeyring();
|
|
426
|
-
}
|
|
254
|
+
function createCodexKeyring({ platform }) {
|
|
255
|
+
if (isMacOS(platform)) return {
|
|
256
|
+
async read({ service, account }) {
|
|
257
|
+
return readMacOSKeychainPassword({
|
|
258
|
+
service,
|
|
259
|
+
account
|
|
260
|
+
});
|
|
261
|
+
},
|
|
262
|
+
async write({ service, account, value }) {
|
|
263
|
+
await runCommandWithInput({
|
|
264
|
+
command: "/usr/bin/security",
|
|
265
|
+
args: ["-i"],
|
|
266
|
+
input: `add-generic-password -U -s ${shellQuoteForSecurity(service)} -a ${shellQuoteForSecurity(account)} -X ${Buffer.from(value, "utf8").toString("hex")}\n`
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
if (isLinux(platform)) return {
|
|
271
|
+
async read({ service, account }) {
|
|
272
|
+
return readLinuxSecretServicePassword({ attributes: {
|
|
273
|
+
service,
|
|
274
|
+
username: account,
|
|
275
|
+
target: "default"
|
|
276
|
+
} });
|
|
277
|
+
},
|
|
278
|
+
async write({ service, account, value }) {
|
|
279
|
+
await runCommandWithInput({
|
|
280
|
+
command: "secret-tool",
|
|
281
|
+
args: [
|
|
282
|
+
"store",
|
|
283
|
+
"--label",
|
|
284
|
+
`${account}@${service}:default`,
|
|
285
|
+
"service",
|
|
286
|
+
service,
|
|
287
|
+
"username",
|
|
288
|
+
account,
|
|
289
|
+
"target",
|
|
290
|
+
"default",
|
|
291
|
+
"application",
|
|
292
|
+
"rust-keyring"
|
|
293
|
+
],
|
|
294
|
+
input: value
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
if (isWindows(platform)) return createWindowsCodexKeyring();
|
|
427
299
|
}
|
|
428
300
|
function shellQuoteForSecurity(value) {
|
|
429
|
-
|
|
301
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
|
|
430
302
|
}
|
|
431
|
-
async function runCommandWithInput({
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
});
|
|
303
|
+
async function runCommandWithInput({ command, args, input, env }) {
|
|
304
|
+
await new Promise((resolvePromise, reject) => {
|
|
305
|
+
const child = spawn(command, args, {
|
|
306
|
+
stdio: [
|
|
307
|
+
"pipe",
|
|
308
|
+
"ignore",
|
|
309
|
+
"pipe"
|
|
310
|
+
],
|
|
311
|
+
...env == null ? {} : { env }
|
|
312
|
+
});
|
|
313
|
+
let stderr = "";
|
|
314
|
+
child.stderr.setEncoding("utf8");
|
|
315
|
+
child.stderr.on("data", (chunk) => {
|
|
316
|
+
stderr += chunk;
|
|
317
|
+
});
|
|
318
|
+
child.once("error", reject);
|
|
319
|
+
child.once("close", (code) => {
|
|
320
|
+
if (code === 0) resolvePromise();
|
|
321
|
+
else reject(new Error(stderr || `${command} exited with code ${code}.`));
|
|
322
|
+
});
|
|
323
|
+
child.stdin.end(input);
|
|
324
|
+
});
|
|
454
325
|
}
|
|
455
326
|
function createWindowsCodexKeyring() {
|
|
456
|
-
|
|
327
|
+
const source = `
|
|
457
328
|
using System;
|
|
458
329
|
using System.Runtime.InteropServices;
|
|
459
330
|
using System.Text;
|
|
@@ -481,967 +352,828 @@ public static class CodexCredentialManager {
|
|
|
481
352
|
} finally { Marshal.FreeHGlobal(blob); }
|
|
482
353
|
}
|
|
483
354
|
}`;
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
355
|
+
return {
|
|
356
|
+
async read({ service, account }) {
|
|
357
|
+
return readWindowsCredentialManagerPassword({ targetName: `${account}.${service}` });
|
|
358
|
+
},
|
|
359
|
+
async write({ service, account, value }) {
|
|
360
|
+
await runCommandWithInput({
|
|
361
|
+
command: "powershell.exe",
|
|
362
|
+
args: [
|
|
363
|
+
"-NoProfile",
|
|
364
|
+
"-NonInteractive",
|
|
365
|
+
"-Command",
|
|
366
|
+
`Add-Type -TypeDefinition $env:AI_SDK_CODEX_CREDENTIAL_SOURCE; $value = [Console]::In.ReadToEnd(); [CodexCredentialManager]::Write($env:AI_SDK_CODEX_CREDENTIAL_TARGET, $env:AI_SDK_CODEX_CREDENTIAL_USER, $value)`
|
|
367
|
+
],
|
|
368
|
+
input: value,
|
|
369
|
+
env: {
|
|
370
|
+
...process.env,
|
|
371
|
+
AI_SDK_CODEX_CREDENTIAL_SOURCE: source,
|
|
372
|
+
AI_SDK_CODEX_CREDENTIAL_TARGET: `${account}.${service}`,
|
|
373
|
+
AI_SDK_CODEX_CREDENTIAL_USER: account
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
};
|
|
505
378
|
}
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
* The host carries this app-server thread ID in lifecycle state. Supplying it
|
|
525
|
-
* makes the bridge call `thread/resume` instead of starting a fresh thread.
|
|
526
|
-
*/
|
|
527
|
-
resumeThreadId: z.string().optional(),
|
|
528
|
-
restartThread: z.boolean().optional()
|
|
379
|
+
//#endregion
|
|
380
|
+
//#region src/codex-bridge-protocol.ts
|
|
381
|
+
const outboundMessageSchema = harnessV1BridgeOutboundMessageSchema;
|
|
382
|
+
const startMessageSchema = harnessV1BridgeStartBaseSchema.extend({
|
|
383
|
+
instructions: z.string().optional(),
|
|
384
|
+
reasoningEffort: z.enum([
|
|
385
|
+
"low",
|
|
386
|
+
"medium",
|
|
387
|
+
"high",
|
|
388
|
+
"xhigh",
|
|
389
|
+
"max"
|
|
390
|
+
]).optional(),
|
|
391
|
+
webSearch: z.boolean().optional(),
|
|
392
|
+
codexConfig: z.record(z.string(), z.unknown()).optional(),
|
|
393
|
+
mcpServers: z.record(z.string(), z.unknown()).optional(),
|
|
394
|
+
headers: z.record(z.string(), z.string()).optional(),
|
|
395
|
+
resumeThreadId: z.string().optional(),
|
|
396
|
+
restartThread: z.boolean().optional()
|
|
529
397
|
});
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
398
|
+
z.discriminatedUnion("type", [startMessageSchema, ...harnessV1BridgeInboundCommandSchemas]);
|
|
399
|
+
//#endregion
|
|
400
|
+
//#region src/version.ts
|
|
401
|
+
const VERSION = "1.0.136";
|
|
402
|
+
//#endregion
|
|
403
|
+
//#region src/codex-harness.ts
|
|
404
|
+
const DEFAULT_CODEX_MODEL = "gpt-5.5";
|
|
405
|
+
/**
|
|
406
|
+
* Value to use in User-Agent and `x-client-app` headers.
|
|
407
|
+
*/
|
|
408
|
+
const CODEX_CLIENT_APP = `ai-sdk-harness-codex/${VERSION}`;
|
|
409
|
+
const CODEX_BUILTIN_TOOLS = {
|
|
410
|
+
bash: commonTool("bash", {
|
|
411
|
+
nativeName: "shell",
|
|
412
|
+
toolUseKind: "bash",
|
|
413
|
+
description: "Execute a shell command",
|
|
414
|
+
inputSchema: z.object({ command: z.string() })
|
|
415
|
+
}),
|
|
416
|
+
webSearch: commonTool("webSearch", {
|
|
417
|
+
nativeName: "web_search",
|
|
418
|
+
toolUseKind: "readonly",
|
|
419
|
+
description: "Search the web",
|
|
420
|
+
inputSchema: z.object({ query: z.string() })
|
|
421
|
+
}),
|
|
422
|
+
apply_patch: {
|
|
423
|
+
...tool({
|
|
424
|
+
description: "Apply a patch to files",
|
|
425
|
+
inputSchema: z.string()
|
|
426
|
+
}),
|
|
427
|
+
toolUseKind: "edit"
|
|
428
|
+
},
|
|
429
|
+
view_image: {
|
|
430
|
+
...tool({
|
|
431
|
+
description: "View a local image file",
|
|
432
|
+
inputSchema: z.object({
|
|
433
|
+
path: z.string(),
|
|
434
|
+
detail: z.enum(["high", "original"]).optional(),
|
|
435
|
+
environment_id: z.string().optional()
|
|
436
|
+
})
|
|
437
|
+
}),
|
|
438
|
+
toolUseKind: "readonly"
|
|
439
|
+
}
|
|
572
440
|
};
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
441
|
+
/**
|
|
442
|
+
* Live bridge coordinates returned by `doDetach()` and `doSuspendTurn()`. A
|
|
443
|
+
* future process uses them to reopen a socket to the still-running bridge
|
|
444
|
+
* (`attach`) instead of re-spawning it. Absent on a `doStop()` payload.
|
|
445
|
+
*/
|
|
446
|
+
const codexBridgeCoordsSchema = z.object({
|
|
447
|
+
port: z.number(),
|
|
448
|
+
token: z.string(),
|
|
449
|
+
lastSeenEventId: z.number(),
|
|
450
|
+
sandboxId: z.string().optional()
|
|
578
451
|
});
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
452
|
+
/**
|
|
453
|
+
* Schema for the adapter-specific lifecycle `data` payload Codex produces.
|
|
454
|
+
* `threadId` is what app-server's `thread/resume` method requires for the replay/rerun
|
|
455
|
+
* rungs; the sandbox lookup is handled separately via
|
|
456
|
+
* `provider.resumeSession({ sessionId })`. `bridge` carries live coordinates
|
|
457
|
+
* for cross-process `attach` (present on `doDetach()` and `doSuspendTurn()`
|
|
458
|
+
* payloads).
|
|
459
|
+
*/
|
|
460
|
+
const codexResumeStateSchema = z.object({
|
|
461
|
+
threadId: z.string().optional(),
|
|
462
|
+
turnConfigurationFingerprint: z.string().optional(),
|
|
463
|
+
bridge: codexBridgeCoordsSchema.optional(),
|
|
464
|
+
sandboxCredentialEnvironment: z.record(z.string(), z.string()).optional()
|
|
584
465
|
});
|
|
585
466
|
function createCodex(settings = {}) {
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
builtinToolFiltering: startOpts.builtinToolFiltering,
|
|
843
|
-
codexConfig: settings.codexConfig,
|
|
844
|
-
mcpServers: settings.mcpServers,
|
|
845
|
-
headers: startOpts.headers,
|
|
846
|
-
resumeThreadId: resumeThreadIdString,
|
|
847
|
-
isResume: respawnStrategy !== void 0,
|
|
848
|
-
seedResumeThreadOnFirstPrompt: respawnStrategy !== void 0,
|
|
849
|
-
rerunContinue: respawnStrategy === "rerun",
|
|
850
|
-
bridgePort: boundPort,
|
|
851
|
-
bridgeToken: token,
|
|
852
|
-
sandboxId,
|
|
853
|
-
sandboxCredentialEnvironment,
|
|
854
|
-
debug: startOpts.observability?.debug,
|
|
855
|
-
permissionMode: startOpts.permissionMode,
|
|
856
|
-
sandbox: toolSafeSandboxSession,
|
|
857
|
-
sandboxHomeDir,
|
|
858
|
-
turnConfigurationFingerprint
|
|
859
|
-
});
|
|
860
|
-
}
|
|
861
|
-
};
|
|
467
|
+
return {
|
|
468
|
+
specificationVersion: "harness-v1",
|
|
469
|
+
harnessId: "codex",
|
|
470
|
+
builtinTools: CODEX_BUILTIN_TOOLS,
|
|
471
|
+
supportsBuiltinToolFiltering: true,
|
|
472
|
+
supportsBuiltinToolApprovals: false,
|
|
473
|
+
lifecycleStateSchema: codexResumeStateSchema,
|
|
474
|
+
getBootstrap: getCodexBootstrap,
|
|
475
|
+
doStart: async (startOpts) => {
|
|
476
|
+
const model = DEFAULT_CODEX_MODEL;
|
|
477
|
+
if (startOpts.permissionMode != null && startOpts.permissionMode !== "allow-all") throw new HarnessCapabilityUnsupportedError({
|
|
478
|
+
message: "Harness 'codex' does not support built-in tool approval requests; use permissionMode: 'allow-all'.",
|
|
479
|
+
harnessId: "codex"
|
|
480
|
+
});
|
|
481
|
+
const sandboxSession = startOpts.sandboxSession;
|
|
482
|
+
const toolSafeSandboxSession = getRestrictedSandboxSession(sandboxSession);
|
|
483
|
+
const sandboxId = "id" in sandboxSession ? sandboxSession.id : void 0;
|
|
484
|
+
validateBasicSandboxSettings({
|
|
485
|
+
sandboxSession,
|
|
486
|
+
port: settings.port,
|
|
487
|
+
portEndpoint: settings.portEndpoint
|
|
488
|
+
});
|
|
489
|
+
if (settings.mintBridgeToken != null && sandboxId == null) throw new HarnessCapabilityUnsupportedError({
|
|
490
|
+
harnessId: "codex",
|
|
491
|
+
message: "The codex harness cannot use `mintBridgeToken` with a sandbox session that does not expose an id."
|
|
492
|
+
});
|
|
493
|
+
const lifecycleState = startOpts.continueFrom ?? startOpts.resumeFrom;
|
|
494
|
+
const isResume = lifecycleState != null;
|
|
495
|
+
const isContinue = startOpts.continueFrom != null;
|
|
496
|
+
const resumeData = isResume && typeof lifecycleState?.data === "object" ? lifecycleState.data : void 0;
|
|
497
|
+
const resumeThreadId = resumeData?.threadId;
|
|
498
|
+
const resumeThreadIdString = typeof resumeThreadId === "string" && resumeThreadId.length > 0 ? resumeThreadId : void 0;
|
|
499
|
+
const turnConfigurationFingerprint = typeof resumeData?.turnConfigurationFingerprint === "string" ? resumeData.turnConfigurationFingerprint : void 0;
|
|
500
|
+
const coords = resumeData?.bridge;
|
|
501
|
+
const authenticationMode = resolveCodexAuthenticationMode(settings.auth);
|
|
502
|
+
const resolvedAuthentication = await resolveCodexAuthentication({
|
|
503
|
+
auth: settings.auth,
|
|
504
|
+
authCredentialsStoreMode: settings.codexConfig?.cli_auth_credentials_store
|
|
505
|
+
});
|
|
506
|
+
const resolvedAuthEnvironment = resolvedAuthentication.environment;
|
|
507
|
+
let sandboxAuthEnvironment = resolvedAuthEnvironment;
|
|
508
|
+
let sandboxCredentialEnvironment;
|
|
509
|
+
let credentialsBrokered = false;
|
|
510
|
+
if ("addRequestTransformations" in sandboxSession && sandboxSession.addRequestTransformations != null) {
|
|
511
|
+
sandboxCredentialEnvironment = await resolveSandboxCredentialEnvironment({
|
|
512
|
+
environment: resolvedAuthEnvironment,
|
|
513
|
+
credentialEnvironmentVariables: CODEX_CREDENTIAL_ENVIRONMENT_VARIABLES,
|
|
514
|
+
credentialForwarding: settings.credentialForwarding,
|
|
515
|
+
previousSandboxCredentialEnvironment: resumeData?.sandboxCredentialEnvironment
|
|
516
|
+
});
|
|
517
|
+
sandboxAuthEnvironment = {
|
|
518
|
+
...resolvedAuthEnvironment,
|
|
519
|
+
...sandboxCredentialEnvironment
|
|
520
|
+
};
|
|
521
|
+
const requestTransformations = createCodexSubscriptionRequestTransformations({
|
|
522
|
+
env: resolvedAuthEnvironment,
|
|
523
|
+
sandboxEnv: sandboxAuthEnvironment,
|
|
524
|
+
auth: authenticationMode,
|
|
525
|
+
requestHeaders: resolvedAuthentication.requestHeaders
|
|
526
|
+
});
|
|
527
|
+
if (requestTransformations.length > 0) await sandboxSession.addRequestTransformations(requestTransformations);
|
|
528
|
+
credentialsBrokered = true;
|
|
529
|
+
if (requestTransformations.length > 0 && authenticationMode === "direct" && resolvedAuthEnvironment.OPENAI_BASE_URL == null) sandboxAuthEnvironment.OPENAI_BASE_URL = DEFAULT_OPENAI_BASE_URL;
|
|
530
|
+
}
|
|
531
|
+
const sandboxHomeDir = await resolveSandboxHomeDir({
|
|
532
|
+
sandbox: toolSafeSandboxSession,
|
|
533
|
+
abortSignal: startOpts.abortSignal
|
|
534
|
+
});
|
|
535
|
+
const stateDir = harnessStateDirectoryPath({ sandboxHomeDir });
|
|
536
|
+
const bootstrapDir = path.posix.resolve(stateDir, CODEX_BOOTSTRAP_DIR);
|
|
537
|
+
const workDir = startOpts.sessionWorkDir;
|
|
538
|
+
const bridgeStateDir = `${harnessSessionDataDirectoryPath({
|
|
539
|
+
stateDirectory: stateDir,
|
|
540
|
+
sessionId: startOpts.sessionId
|
|
541
|
+
})}/bridge`;
|
|
542
|
+
const timeoutMs = settings.startupTimeoutMs ?? 12e4;
|
|
543
|
+
const report = startOpts.observability?.report;
|
|
544
|
+
const onDiagnostic = report ? (frame) => report(harnessV1DiagnosticFromBridgeFrame(frame, {
|
|
545
|
+
sessionId: startOpts.sessionId,
|
|
546
|
+
timestamp: Date.now()
|
|
547
|
+
})) : void 0;
|
|
548
|
+
const onBridgeError = createBridgeErrorHandler({
|
|
549
|
+
harnessId: "codex",
|
|
550
|
+
sessionId: startOpts.sessionId
|
|
551
|
+
});
|
|
552
|
+
if (coords) try {
|
|
553
|
+
const endpoint = await resolveBridgeEndpoint({
|
|
554
|
+
sandboxSession,
|
|
555
|
+
override: settings.portEndpoint,
|
|
556
|
+
port: coords.port
|
|
557
|
+
});
|
|
558
|
+
const attachEndpoint = withBridgeToken({
|
|
559
|
+
endpoint,
|
|
560
|
+
token: coords.token
|
|
561
|
+
});
|
|
562
|
+
const attachChannel = new SandboxChannel({
|
|
563
|
+
connect: ({ abortSignal }) => openWebSocket({
|
|
564
|
+
...attachEndpoint,
|
|
565
|
+
abortSignal
|
|
566
|
+
}),
|
|
567
|
+
outboundSchema: outboundMessageSchema,
|
|
568
|
+
initialLastSeenEventId: coords.lastSeenEventId,
|
|
569
|
+
onDiagnostic,
|
|
570
|
+
onBridgeError,
|
|
571
|
+
reconnect: settings.reconnect
|
|
572
|
+
});
|
|
573
|
+
await attachChannel.open(isContinue ? { resume: true } : void 0);
|
|
574
|
+
return createSession({
|
|
575
|
+
sessionId: startOpts.sessionId,
|
|
576
|
+
channel: attachChannel,
|
|
577
|
+
proc: void 0,
|
|
578
|
+
model,
|
|
579
|
+
reasoningEffort: settings.reasoningEffort,
|
|
580
|
+
webSearch: settings.webSearch,
|
|
581
|
+
builtinToolFiltering: startOpts.builtinToolFiltering,
|
|
582
|
+
codexConfig: settings.codexConfig,
|
|
583
|
+
mcpServers: settings.mcpServers,
|
|
584
|
+
headers: startOpts.headers,
|
|
585
|
+
resumeThreadId: resumeThreadIdString,
|
|
586
|
+
isResume: true,
|
|
587
|
+
seedResumeThreadOnFirstPrompt: false,
|
|
588
|
+
rerunContinue: false,
|
|
589
|
+
bridgePort: coords.port,
|
|
590
|
+
bridgeToken: coords.token,
|
|
591
|
+
sandboxId,
|
|
592
|
+
sandboxCredentialEnvironment,
|
|
593
|
+
debug: startOpts.observability?.debug,
|
|
594
|
+
permissionMode: startOpts.permissionMode,
|
|
595
|
+
sandbox: toolSafeSandboxSession,
|
|
596
|
+
sandboxHomeDir,
|
|
597
|
+
turnConfigurationFingerprint
|
|
598
|
+
});
|
|
599
|
+
} catch {}
|
|
600
|
+
let respawnStrategy = isResume ? "rerun" : void 0;
|
|
601
|
+
if (coords && isContinue) {
|
|
602
|
+
const logRaw = await Promise.resolve(toolSafeSandboxSession.readTextFile({
|
|
603
|
+
path: `${bridgeStateDir}/event-log.ndjson`,
|
|
604
|
+
abortSignal: startOpts.abortSignal
|
|
605
|
+
})).catch(() => null);
|
|
606
|
+
if (await classifyDiskLog(logRaw) === "replay") respawnStrategy = "replay";
|
|
607
|
+
}
|
|
608
|
+
const port = resolveBridgePort({
|
|
609
|
+
sandboxSession,
|
|
610
|
+
override: settings.port
|
|
611
|
+
});
|
|
612
|
+
const token = settings.mintBridgeToken == null ? createBridgeToken() : settings.mintBridgeToken(sandboxId);
|
|
613
|
+
const forwardedAuthEnvironment = credentialsBrokered ? sandboxAuthEnvironment : await applyCredentialForwarding({
|
|
614
|
+
environment: sandboxAuthEnvironment,
|
|
615
|
+
credentialEnvironmentVariables: CODEX_CREDENTIAL_ENVIRONMENT_VARIABLES,
|
|
616
|
+
credentialForwarding: settings.credentialForwarding
|
|
617
|
+
});
|
|
618
|
+
if (!credentialsBrokered) warnCredentialBrokeringUnavailable({
|
|
619
|
+
environment: resolvedAuthEnvironment,
|
|
620
|
+
forwardedEnvironment: forwardedAuthEnvironment,
|
|
621
|
+
credentialEnvironmentVariables: CODEX_CREDENTIAL_ENVIRONMENT_VARIABLES
|
|
622
|
+
});
|
|
623
|
+
const env = {
|
|
624
|
+
...forwardedAuthEnvironment,
|
|
625
|
+
AI_SDK_HARNESS_CLIENT_APP: CODEX_CLIENT_APP,
|
|
626
|
+
BRIDGE_CHANNEL_TOKEN: token,
|
|
627
|
+
BRIDGE_WS_PORT: String(port),
|
|
628
|
+
...respawnStrategy === "replay" ? { BRIDGE_REPLAY_FROM_DISK: "1" } : {}
|
|
629
|
+
};
|
|
630
|
+
if (respawnStrategy === void 0) await toolSafeSandboxSession.run({
|
|
631
|
+
command: `mkdir -p ${shellQuote(workDir)} ${shellQuote(bridgeStateDir)}`,
|
|
632
|
+
abortSignal: startOpts.abortSignal
|
|
633
|
+
});
|
|
634
|
+
await markBridgeStarting({
|
|
635
|
+
sandbox: toolSafeSandboxSession,
|
|
636
|
+
bridgeStateDir,
|
|
637
|
+
bridgeType: "codex",
|
|
638
|
+
abortSignal: startOpts.abortSignal
|
|
639
|
+
});
|
|
640
|
+
const proc = await toolSafeSandboxSession.spawn({
|
|
641
|
+
command: `node ${shellQuote(`${bootstrapDir}/bridge.mjs`)} --workdir ${shellQuote(workDir)} --bridge-state-dir ${shellQuote(bridgeStateDir)}`,
|
|
642
|
+
env,
|
|
643
|
+
abortSignal: startOpts.abortSignal
|
|
644
|
+
});
|
|
645
|
+
const stderrTail = [];
|
|
646
|
+
const bridgeStderrDone = forwardBridgeProcessStream({
|
|
647
|
+
stream: proc.stderr,
|
|
648
|
+
streamName: "stderr",
|
|
649
|
+
source: "codex",
|
|
650
|
+
collectTail: stderrTail
|
|
651
|
+
});
|
|
652
|
+
const { port: boundPort } = await waitForBridgeReady({
|
|
653
|
+
proc,
|
|
654
|
+
sandbox: toolSafeSandboxSession,
|
|
655
|
+
bridgeStateDir,
|
|
656
|
+
bridgeType: "codex",
|
|
657
|
+
timeoutMs,
|
|
658
|
+
abortSignal: startOpts.abortSignal,
|
|
659
|
+
createTimeoutError: ({ proc, stdoutTail }) => createBridgeStartupError({
|
|
660
|
+
message: "codex bridge did not become ready in time.",
|
|
661
|
+
proc,
|
|
662
|
+
stdoutTail,
|
|
663
|
+
stderrTail,
|
|
664
|
+
stderrDone: bridgeStderrDone
|
|
665
|
+
}),
|
|
666
|
+
createExitError: ({ proc, stdoutTail }) => createBridgeStartupError({
|
|
667
|
+
message: "codex bridge exited before becoming ready.",
|
|
668
|
+
proc,
|
|
669
|
+
stdoutTail,
|
|
670
|
+
stderrTail,
|
|
671
|
+
stderrDone: bridgeStderrDone
|
|
672
|
+
})
|
|
673
|
+
});
|
|
674
|
+
drainBridgeProcessStream(proc.stdout);
|
|
675
|
+
const endpoint = await resolveBridgeEndpoint({
|
|
676
|
+
sandboxSession,
|
|
677
|
+
override: settings.portEndpoint,
|
|
678
|
+
port: boundPort
|
|
679
|
+
});
|
|
680
|
+
const bridgeEndpoint = withBridgeToken({
|
|
681
|
+
endpoint,
|
|
682
|
+
token
|
|
683
|
+
});
|
|
684
|
+
const channel = new SandboxChannel({
|
|
685
|
+
connect: ({ abortSignal }) => openWebSocket({
|
|
686
|
+
...bridgeEndpoint,
|
|
687
|
+
abortSignal
|
|
688
|
+
}),
|
|
689
|
+
outboundSchema: outboundMessageSchema,
|
|
690
|
+
onDiagnostic,
|
|
691
|
+
onBridgeError,
|
|
692
|
+
reconnect: settings.reconnect,
|
|
693
|
+
...respawnStrategy === "replay" ? { initialLastSeenEventId: coords?.lastSeenEventId ?? 0 } : {}
|
|
694
|
+
});
|
|
695
|
+
await channel.open(respawnStrategy === "replay" ? { resume: true } : void 0);
|
|
696
|
+
return createSession({
|
|
697
|
+
sessionId: startOpts.sessionId,
|
|
698
|
+
channel,
|
|
699
|
+
proc,
|
|
700
|
+
model,
|
|
701
|
+
reasoningEffort: settings.reasoningEffort,
|
|
702
|
+
webSearch: settings.webSearch,
|
|
703
|
+
builtinToolFiltering: startOpts.builtinToolFiltering,
|
|
704
|
+
codexConfig: settings.codexConfig,
|
|
705
|
+
mcpServers: settings.mcpServers,
|
|
706
|
+
headers: startOpts.headers,
|
|
707
|
+
resumeThreadId: resumeThreadIdString,
|
|
708
|
+
isResume: respawnStrategy !== void 0,
|
|
709
|
+
seedResumeThreadOnFirstPrompt: respawnStrategy !== void 0,
|
|
710
|
+
rerunContinue: respawnStrategy === "rerun",
|
|
711
|
+
bridgePort: boundPort,
|
|
712
|
+
bridgeToken: token,
|
|
713
|
+
sandboxId,
|
|
714
|
+
sandboxCredentialEnvironment,
|
|
715
|
+
debug: startOpts.observability?.debug,
|
|
716
|
+
permissionMode: startOpts.permissionMode,
|
|
717
|
+
sandbox: toolSafeSandboxSession,
|
|
718
|
+
sandboxHomeDir,
|
|
719
|
+
turnConfigurationFingerprint
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
};
|
|
862
723
|
}
|
|
863
|
-
function resolveBridgePort({
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
}
|
|
871
|
-
throw new HarnessCapabilityUnsupportedError({
|
|
872
|
-
harnessId: "codex",
|
|
873
|
-
message: "The codex harness needs a TCP port exposed by the sandbox. Create the sandbox with `ports: [<port>]` or pass `createCodex({ port })`."
|
|
874
|
-
});
|
|
724
|
+
function resolveBridgePort({ sandboxSession, override }) {
|
|
725
|
+
if (override !== void 0) return override;
|
|
726
|
+
if ("ports" in sandboxSession && sandboxSession.ports.length > 0) return sandboxSession.ports[0];
|
|
727
|
+
throw new HarnessCapabilityUnsupportedError({
|
|
728
|
+
harnessId: "codex",
|
|
729
|
+
message: "The codex harness needs a TCP port exposed by the sandbox. Create the sandbox with `ports: [<port>]` or pass `createCodex({ port })`."
|
|
730
|
+
});
|
|
875
731
|
}
|
|
876
|
-
function validateBasicSandboxSettings({
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
});
|
|
887
|
-
}
|
|
888
|
-
if (portEndpoint == null) {
|
|
889
|
-
throw new HarnessCapabilityUnsupportedError({
|
|
890
|
-
harnessId: "codex",
|
|
891
|
-
message: "The codex harness requires an explicit `portEndpoint` when using a basic sandbox session."
|
|
892
|
-
});
|
|
893
|
-
}
|
|
732
|
+
function validateBasicSandboxSettings({ sandboxSession, port, portEndpoint }) {
|
|
733
|
+
if ("getPortEndpoint" in sandboxSession) return;
|
|
734
|
+
if (port == null) throw new HarnessCapabilityUnsupportedError({
|
|
735
|
+
harnessId: "codex",
|
|
736
|
+
message: "The codex harness requires an explicit `port` when using a basic sandbox session."
|
|
737
|
+
});
|
|
738
|
+
if (portEndpoint == null) throw new HarnessCapabilityUnsupportedError({
|
|
739
|
+
harnessId: "codex",
|
|
740
|
+
message: "The codex harness requires an explicit `portEndpoint` when using a basic sandbox session."
|
|
741
|
+
});
|
|
894
742
|
}
|
|
895
|
-
async function resolveBridgeEndpoint({
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
harnessId: "codex",
|
|
906
|
-
message: "The codex harness requires an explicit `portEndpoint` when using a basic sandbox session."
|
|
907
|
-
});
|
|
743
|
+
async function resolveBridgeEndpoint({ sandboxSession, override, port }) {
|
|
744
|
+
if (override != null) return override;
|
|
745
|
+
if ("getPortEndpoint" in sandboxSession) return sandboxSession.getPortEndpoint({
|
|
746
|
+
port,
|
|
747
|
+
protocol: "ws"
|
|
748
|
+
});
|
|
749
|
+
throw new HarnessCapabilityUnsupportedError({
|
|
750
|
+
harnessId: "codex",
|
|
751
|
+
message: "The codex harness requires an explicit `portEndpoint` when using a basic sandbox session."
|
|
752
|
+
});
|
|
908
753
|
}
|
|
909
|
-
function openWebSocket({
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
const onError = (err) => {
|
|
951
|
-
rejectWithCleanup(err);
|
|
952
|
-
};
|
|
953
|
-
const onAbort = () => rejectWithCleanup(
|
|
954
|
-
abortSignal.reason ?? new Error("WebSocket connection aborted")
|
|
955
|
-
);
|
|
956
|
-
ws.once("open", onOpen);
|
|
957
|
-
ws.once("error", onError);
|
|
958
|
-
abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
959
|
-
});
|
|
754
|
+
function openWebSocket({ url, headers, abortSignal }) {
|
|
755
|
+
return new Promise((resolve, reject) => {
|
|
756
|
+
if (abortSignal.aborted) {
|
|
757
|
+
reject(abortSignal.reason ?? /* @__PURE__ */ new Error("WebSocket connection aborted"));
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
const ws = new WebSocket(url, { headers: headers == null ? void 0 : { ...headers } });
|
|
761
|
+
let settled = false;
|
|
762
|
+
const cleanup = () => {
|
|
763
|
+
abortSignal.removeEventListener("abort", onAbort);
|
|
764
|
+
ws.off("open", onOpen);
|
|
765
|
+
ws.off("error", onError);
|
|
766
|
+
};
|
|
767
|
+
const rejectWithCleanup = (error) => {
|
|
768
|
+
if (settled) return;
|
|
769
|
+
settled = true;
|
|
770
|
+
cleanup();
|
|
771
|
+
ws.once("error", () => {});
|
|
772
|
+
try {
|
|
773
|
+
ws.terminate();
|
|
774
|
+
} catch {
|
|
775
|
+
try {
|
|
776
|
+
ws.close();
|
|
777
|
+
} catch {}
|
|
778
|
+
}
|
|
779
|
+
reject(error);
|
|
780
|
+
};
|
|
781
|
+
const onOpen = () => {
|
|
782
|
+
if (settled) return;
|
|
783
|
+
settled = true;
|
|
784
|
+
cleanup();
|
|
785
|
+
resolve(ws);
|
|
786
|
+
};
|
|
787
|
+
const onError = (err) => {
|
|
788
|
+
rejectWithCleanup(err);
|
|
789
|
+
};
|
|
790
|
+
const onAbort = () => rejectWithCleanup(abortSignal.reason ?? /* @__PURE__ */ new Error("WebSocket connection aborted"));
|
|
791
|
+
ws.once("open", onOpen);
|
|
792
|
+
ws.once("error", onError);
|
|
793
|
+
abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
794
|
+
});
|
|
960
795
|
}
|
|
961
|
-
function createSession({
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
if (stopTimer) clearTimeout(stopTimer);
|
|
1313
|
-
try {
|
|
1314
|
-
await proc?.kill();
|
|
1315
|
-
} catch {
|
|
1316
|
-
}
|
|
1317
|
-
channel.close();
|
|
1318
|
-
}
|
|
1319
|
-
})();
|
|
1320
|
-
return stopPromise;
|
|
1321
|
-
},
|
|
1322
|
-
doStop: async () => {
|
|
1323
|
-
if (stopped) {
|
|
1324
|
-
throw new Error(
|
|
1325
|
-
`codex session ${sessionId} is already stopped; cannot stop.`
|
|
1326
|
-
);
|
|
1327
|
-
}
|
|
1328
|
-
stopped = true;
|
|
1329
|
-
channel.beginClose();
|
|
1330
|
-
const data = channel.isClosed() ? {} : await new Promise((resolve2, reject) => {
|
|
1331
|
-
const timer = setTimeout(() => {
|
|
1332
|
-
unsub();
|
|
1333
|
-
reject(
|
|
1334
|
-
new Error(
|
|
1335
|
-
`codex session ${sessionId} did not reply to stop within 5s.`
|
|
1336
|
-
)
|
|
1337
|
-
);
|
|
1338
|
-
}, 5e3);
|
|
1339
|
-
timer.unref?.();
|
|
1340
|
-
const unsub = channel.on("bridge-stop", (msg) => {
|
|
1341
|
-
clearTimeout(timer);
|
|
1342
|
-
unsub();
|
|
1343
|
-
resolve2(msg.data);
|
|
1344
|
-
});
|
|
1345
|
-
try {
|
|
1346
|
-
channel.send({ type: "stop" });
|
|
1347
|
-
} catch (err) {
|
|
1348
|
-
clearTimeout(timer);
|
|
1349
|
-
unsub();
|
|
1350
|
-
reject(err);
|
|
1351
|
-
}
|
|
1352
|
-
});
|
|
1353
|
-
let stopTimer;
|
|
1354
|
-
try {
|
|
1355
|
-
if (proc) {
|
|
1356
|
-
await Promise.race([
|
|
1357
|
-
proc.wait(),
|
|
1358
|
-
new Promise((resolve2) => {
|
|
1359
|
-
stopTimer = setTimeout(resolve2, 5e3);
|
|
1360
|
-
stopTimer.unref?.();
|
|
1361
|
-
})
|
|
1362
|
-
]);
|
|
1363
|
-
}
|
|
1364
|
-
} finally {
|
|
1365
|
-
if (stopTimer) clearTimeout(stopTimer);
|
|
1366
|
-
try {
|
|
1367
|
-
await proc?.kill();
|
|
1368
|
-
} catch {
|
|
1369
|
-
}
|
|
1370
|
-
channel.close();
|
|
1371
|
-
}
|
|
1372
|
-
const lifecycleData = data != null && typeof data === "object" && !Array.isArray(data) ? { ...data } : {};
|
|
1373
|
-
const payload = {
|
|
1374
|
-
type: "resume-session",
|
|
1375
|
-
harnessId: "codex",
|
|
1376
|
-
specificationVersion: "harness-v1",
|
|
1377
|
-
data: {
|
|
1378
|
-
...lifecycleData,
|
|
1379
|
-
...latestTurnConfigurationFingerprint ? {
|
|
1380
|
-
turnConfigurationFingerprint: latestTurnConfigurationFingerprint
|
|
1381
|
-
} : {},
|
|
1382
|
-
...sandboxCredentialEnvironment == null ? {} : { sandboxCredentialEnvironment }
|
|
1383
|
-
}
|
|
1384
|
-
};
|
|
1385
|
-
return payload;
|
|
1386
|
-
},
|
|
1387
|
-
doSuspendTurn: async () => {
|
|
1388
|
-
if (stopped) {
|
|
1389
|
-
throw new Error(
|
|
1390
|
-
`codex session ${sessionId} is stopped; cannot suspend.`
|
|
1391
|
-
);
|
|
1392
|
-
}
|
|
1393
|
-
stopped = true;
|
|
1394
|
-
const lastSeenEventId = await channel.suspend();
|
|
1395
|
-
const payload = {
|
|
1396
|
-
type: "continue-turn",
|
|
1397
|
-
harnessId: "codex",
|
|
1398
|
-
specificationVersion: "harness-v1",
|
|
1399
|
-
data: {
|
|
1400
|
-
...latestThreadId ? { threadId: latestThreadId } : {},
|
|
1401
|
-
...latestTurnConfigurationFingerprint ? {
|
|
1402
|
-
turnConfigurationFingerprint: latestTurnConfigurationFingerprint
|
|
1403
|
-
} : {},
|
|
1404
|
-
...sandboxCredentialEnvironment == null ? {} : { sandboxCredentialEnvironment },
|
|
1405
|
-
bridge: {
|
|
1406
|
-
port: bridgePort,
|
|
1407
|
-
token: bridgeToken,
|
|
1408
|
-
lastSeenEventId,
|
|
1409
|
-
...sandboxId == null ? {} : { sandboxId }
|
|
1410
|
-
}
|
|
1411
|
-
}
|
|
1412
|
-
};
|
|
1413
|
-
return payload;
|
|
1414
|
-
}
|
|
1415
|
-
};
|
|
796
|
+
function createSession({ sessionId, channel, proc, model, reasoningEffort, webSearch, builtinToolFiltering, codexConfig, mcpServers, headers, resumeThreadId, isResume, seedResumeThreadOnFirstPrompt, rerunContinue, bridgePort, bridgeToken, sandboxId, sandboxCredentialEnvironment, debug, permissionMode, sandbox, sandboxHomeDir, turnConfigurationFingerprint }) {
|
|
797
|
+
let stopped = false;
|
|
798
|
+
let stopPromise;
|
|
799
|
+
let pendingResumeThreadId = seedResumeThreadOnFirstPrompt ? resumeThreadId : void 0;
|
|
800
|
+
let latestThreadId = resumeThreadId;
|
|
801
|
+
let latestTurnConfigurationFingerprint = turnConfigurationFingerprint;
|
|
802
|
+
channel.on("bridge-thread", (msg) => {
|
|
803
|
+
latestThreadId = msg.threadId;
|
|
804
|
+
});
|
|
805
|
+
const synchronizeTurnConfiguration = async ({ skills, instructions, tools, abortSignal }) => {
|
|
806
|
+
const skillsResult = await writeSkills({
|
|
807
|
+
sandbox,
|
|
808
|
+
homePath: sandboxHomeDir,
|
|
809
|
+
skillsDir: ".agents/skills",
|
|
810
|
+
skills,
|
|
811
|
+
abortSignal,
|
|
812
|
+
invalidSkillNameMessage: ({ name }) => `Invalid Codex skill name: ${name}`,
|
|
813
|
+
invalidSkillFilePathMessage: ({ skillName, filePath }) => `Invalid Codex skill file path for ${skillName}: ${filePath}`
|
|
814
|
+
});
|
|
815
|
+
const nextFingerprint = fingerprintCodexTurnConfiguration({
|
|
816
|
+
instructions,
|
|
817
|
+
tools
|
|
818
|
+
});
|
|
819
|
+
const restartThread = latestThreadId != null && (skillsResult.changed || latestTurnConfigurationFingerprint != null && latestTurnConfigurationFingerprint !== nextFingerprint);
|
|
820
|
+
latestTurnConfigurationFingerprint = nextFingerprint;
|
|
821
|
+
if (restartThread) pendingResumeThreadId = void 0;
|
|
822
|
+
return { restartThread };
|
|
823
|
+
};
|
|
824
|
+
const wireTurn = (turnOpts) => {
|
|
825
|
+
let pendingResolve;
|
|
826
|
+
let pendingReject;
|
|
827
|
+
const done = new Promise((resolve, reject) => {
|
|
828
|
+
pendingResolve = resolve;
|
|
829
|
+
pendingReject = reject;
|
|
830
|
+
});
|
|
831
|
+
const userMessageSubmitter = experimental_createBridgeUserMessageSubmitter({
|
|
832
|
+
send: (message) => channel.send(message),
|
|
833
|
+
onResponse: (listener) => channel.on("user-message-response", listener),
|
|
834
|
+
onReconnect: (listener) => channel.onReconnect(listener)
|
|
835
|
+
});
|
|
836
|
+
const unsubs = [];
|
|
837
|
+
const forward = (event) => {
|
|
838
|
+
try {
|
|
839
|
+
turnOpts.emit(event);
|
|
840
|
+
} catch {}
|
|
841
|
+
};
|
|
842
|
+
const eventTypes = [
|
|
843
|
+
"stream-start",
|
|
844
|
+
"text-start",
|
|
845
|
+
"text-delta",
|
|
846
|
+
"text-end",
|
|
847
|
+
"reasoning-start",
|
|
848
|
+
"reasoning-delta",
|
|
849
|
+
"reasoning-end",
|
|
850
|
+
"tool-call",
|
|
851
|
+
"tool-approval-request",
|
|
852
|
+
"tool-result",
|
|
853
|
+
"file-change",
|
|
854
|
+
"finish-step",
|
|
855
|
+
"raw"
|
|
856
|
+
];
|
|
857
|
+
let isSettled = false;
|
|
858
|
+
const settleSuccess = () => {
|
|
859
|
+
if (isSettled) return;
|
|
860
|
+
isSettled = true;
|
|
861
|
+
userMessageSubmitter.close();
|
|
862
|
+
for (const u of unsubs) u();
|
|
863
|
+
pendingResolve();
|
|
864
|
+
};
|
|
865
|
+
const settleError = (err) => {
|
|
866
|
+
if (isSettled) return;
|
|
867
|
+
isSettled = true;
|
|
868
|
+
userMessageSubmitter.close(err);
|
|
869
|
+
for (const u of unsubs) u();
|
|
870
|
+
pendingReject(err);
|
|
871
|
+
};
|
|
872
|
+
for (const type of eventTypes) unsubs.push(channel.on(type, (msg) => {
|
|
873
|
+
forward(msg);
|
|
874
|
+
}));
|
|
875
|
+
unsubs.push(channel.on("finish", (msg) => {
|
|
876
|
+
forward(msg);
|
|
877
|
+
settleSuccess();
|
|
878
|
+
}));
|
|
879
|
+
unsubs.push(channel.on("error", (msg) => {
|
|
880
|
+
forward(msg);
|
|
881
|
+
settleError(msg.error);
|
|
882
|
+
}));
|
|
883
|
+
const onClose = (_code, reason) => {
|
|
884
|
+
if (isSettled) return;
|
|
885
|
+
if (reason === "suspended") {
|
|
886
|
+
settleSuccess();
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
settleError(/* @__PURE__ */ new Error("codex bridge closed before the turn finished."));
|
|
890
|
+
};
|
|
891
|
+
channel.onClose(onClose);
|
|
892
|
+
const onAbort = () => {
|
|
893
|
+
if (isSettled) return;
|
|
894
|
+
try {
|
|
895
|
+
channel.send({ type: "abort" });
|
|
896
|
+
} catch {}
|
|
897
|
+
settleError(turnOpts.abortSignal?.reason ?? new DOMException("Aborted", "AbortError"));
|
|
898
|
+
};
|
|
899
|
+
if (turnOpts.abortSignal) {
|
|
900
|
+
if (turnOpts.abortSignal.aborted) onAbort();
|
|
901
|
+
else turnOpts.abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
902
|
+
}
|
|
903
|
+
return {
|
|
904
|
+
control: {
|
|
905
|
+
submitToolResult: async (input) => {
|
|
906
|
+
channel.send({
|
|
907
|
+
type: "tool-result",
|
|
908
|
+
toolCallId: input.toolCallId,
|
|
909
|
+
output: input.output,
|
|
910
|
+
isError: input.isError
|
|
911
|
+
});
|
|
912
|
+
},
|
|
913
|
+
submitToolApproval: async (input) => {
|
|
914
|
+
channel.send({
|
|
915
|
+
type: "tool-approval-response",
|
|
916
|
+
approvalId: input.approvalId,
|
|
917
|
+
approved: input.approved,
|
|
918
|
+
reason: input.reason
|
|
919
|
+
});
|
|
920
|
+
},
|
|
921
|
+
submitUserMessage: async (text) => {
|
|
922
|
+
await userMessageSubmitter.submit(text);
|
|
923
|
+
},
|
|
924
|
+
done
|
|
925
|
+
},
|
|
926
|
+
sendStart: (send) => {
|
|
927
|
+
setTimeout(() => {
|
|
928
|
+
if (isSettled) return;
|
|
929
|
+
try {
|
|
930
|
+
send();
|
|
931
|
+
} catch (err) {
|
|
932
|
+
settleError(err);
|
|
933
|
+
}
|
|
934
|
+
}, 0).unref?.();
|
|
935
|
+
}
|
|
936
|
+
};
|
|
937
|
+
};
|
|
938
|
+
return {
|
|
939
|
+
sessionId,
|
|
940
|
+
isResume,
|
|
941
|
+
doPromptTurn: async (promptOpts) => {
|
|
942
|
+
if (promptOpts.responseFormat?.type === "json" && promptOpts.responseFormat.schema == null) throw new HarnessCapabilityUnsupportedError({
|
|
943
|
+
message: "Harness 'codex' requires a JSON schema for structured output.",
|
|
944
|
+
harnessId: "codex"
|
|
945
|
+
});
|
|
946
|
+
const tools = (promptOpts.tools ?? []).map((t) => ({
|
|
947
|
+
name: t.name,
|
|
948
|
+
description: t.description,
|
|
949
|
+
inputSchema: t.inputSchema
|
|
950
|
+
}));
|
|
951
|
+
const { restartThread } = await synchronizeTurnConfiguration({
|
|
952
|
+
skills: promptOpts.skills,
|
|
953
|
+
instructions: promptOpts.instructions,
|
|
954
|
+
tools,
|
|
955
|
+
abortSignal: promptOpts.abortSignal
|
|
956
|
+
});
|
|
957
|
+
const turn = wireTurn({
|
|
958
|
+
emit: promptOpts.emit,
|
|
959
|
+
abortSignal: promptOpts.abortSignal
|
|
960
|
+
});
|
|
961
|
+
const startMessage = {
|
|
962
|
+
type: "start",
|
|
963
|
+
prompt: extractUserText(promptOpts.prompt),
|
|
964
|
+
tools,
|
|
965
|
+
...promptOpts.responseFormat == null ? {} : { responseFormat: promptOpts.responseFormat },
|
|
966
|
+
...promptOpts.instructions ? { instructions: promptOpts.instructions } : {},
|
|
967
|
+
model: promptOpts.model ?? model,
|
|
968
|
+
reasoningEffort,
|
|
969
|
+
webSearch,
|
|
970
|
+
...builtinToolFiltering == null ? {} : { builtinToolFiltering },
|
|
971
|
+
...codexConfig == null ? {} : { codexConfig },
|
|
972
|
+
...mcpServers == null ? {} : { mcpServers },
|
|
973
|
+
...headers == null ? {} : { headers },
|
|
974
|
+
...permissionMode ? { permissionMode } : {},
|
|
975
|
+
...pendingResumeThreadId ? { resumeThreadId: pendingResumeThreadId } : {},
|
|
976
|
+
...restartThread ? { restartThread: true } : {},
|
|
977
|
+
...debug ? { debug } : {}
|
|
978
|
+
};
|
|
979
|
+
pendingResumeThreadId = void 0;
|
|
980
|
+
turn.sendStart(() => channel.send(startMessage));
|
|
981
|
+
return turn.control;
|
|
982
|
+
},
|
|
983
|
+
doContinueTurn: async (continueOpts) => {
|
|
984
|
+
if (continueOpts.responseFormat?.type === "json" && continueOpts.responseFormat.schema == null) throw new HarnessCapabilityUnsupportedError({
|
|
985
|
+
message: "Harness 'codex' requires a JSON schema for structured output.",
|
|
986
|
+
harnessId: "codex"
|
|
987
|
+
});
|
|
988
|
+
const tools = (continueOpts.tools ?? []).map((t) => ({
|
|
989
|
+
name: t.name,
|
|
990
|
+
description: t.description,
|
|
991
|
+
inputSchema: t.inputSchema
|
|
992
|
+
}));
|
|
993
|
+
const { restartThread } = await synchronizeTurnConfiguration({
|
|
994
|
+
skills: continueOpts.skills,
|
|
995
|
+
instructions: continueOpts.instructions,
|
|
996
|
+
tools,
|
|
997
|
+
abortSignal: continueOpts.abortSignal
|
|
998
|
+
});
|
|
999
|
+
const turn = wireTurn({
|
|
1000
|
+
emit: continueOpts.emit,
|
|
1001
|
+
abortSignal: continueOpts.abortSignal
|
|
1002
|
+
});
|
|
1003
|
+
if (rerunContinue) {
|
|
1004
|
+
const threadId = pendingResumeThreadId ?? latestThreadId;
|
|
1005
|
+
pendingResumeThreadId = void 0;
|
|
1006
|
+
turn.sendStart(() => channel.send({
|
|
1007
|
+
type: "start",
|
|
1008
|
+
prompt: "Continue.",
|
|
1009
|
+
tools,
|
|
1010
|
+
...continueOpts.responseFormat == null ? {} : { responseFormat: continueOpts.responseFormat },
|
|
1011
|
+
...continueOpts.instructions ? { instructions: continueOpts.instructions } : {},
|
|
1012
|
+
model: continueOpts.model ?? model,
|
|
1013
|
+
reasoningEffort,
|
|
1014
|
+
webSearch,
|
|
1015
|
+
...builtinToolFiltering == null ? {} : { builtinToolFiltering },
|
|
1016
|
+
...codexConfig == null ? {} : { codexConfig },
|
|
1017
|
+
...mcpServers == null ? {} : { mcpServers },
|
|
1018
|
+
...headers == null ? {} : { headers },
|
|
1019
|
+
...permissionMode ? { permissionMode } : {},
|
|
1020
|
+
...threadId ? { resumeThreadId: threadId } : {},
|
|
1021
|
+
...restartThread ? { restartThread: true } : {},
|
|
1022
|
+
...debug ? { debug } : {}
|
|
1023
|
+
}));
|
|
1024
|
+
}
|
|
1025
|
+
return turn.control;
|
|
1026
|
+
},
|
|
1027
|
+
doCompact: async () => {
|
|
1028
|
+
throw new HarnessCapabilityUnsupportedError({
|
|
1029
|
+
message: "Harness 'codex' does not support manual compaction; Codex auto-compacts its context internally.",
|
|
1030
|
+
harnessId: "codex"
|
|
1031
|
+
});
|
|
1032
|
+
},
|
|
1033
|
+
doDetach: async () => {
|
|
1034
|
+
if (stopped) throw new Error(`codex session ${sessionId} is already stopped; cannot detach.`);
|
|
1035
|
+
stopped = true;
|
|
1036
|
+
const lastSeenEventId = await channel.suspend();
|
|
1037
|
+
return {
|
|
1038
|
+
type: "resume-session",
|
|
1039
|
+
harnessId: "codex",
|
|
1040
|
+
specificationVersion: "harness-v1",
|
|
1041
|
+
data: {
|
|
1042
|
+
...latestThreadId ? { threadId: latestThreadId } : {},
|
|
1043
|
+
...latestTurnConfigurationFingerprint ? { turnConfigurationFingerprint: latestTurnConfigurationFingerprint } : {},
|
|
1044
|
+
...sandboxCredentialEnvironment == null ? {} : { sandboxCredentialEnvironment },
|
|
1045
|
+
bridge: {
|
|
1046
|
+
port: bridgePort,
|
|
1047
|
+
token: bridgeToken,
|
|
1048
|
+
lastSeenEventId,
|
|
1049
|
+
...sandboxId == null ? {} : { sandboxId }
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
};
|
|
1053
|
+
},
|
|
1054
|
+
doDestroy: async () => {
|
|
1055
|
+
if (stopped) return stopPromise;
|
|
1056
|
+
stopped = true;
|
|
1057
|
+
stopPromise = (async () => {
|
|
1058
|
+
channel.beginClose();
|
|
1059
|
+
try {
|
|
1060
|
+
if (!channel.isClosed()) channel.send({ type: "destroy" });
|
|
1061
|
+
} catch {}
|
|
1062
|
+
let stopTimer;
|
|
1063
|
+
try {
|
|
1064
|
+
if (proc) await Promise.race([proc.wait(), new Promise((resolve) => {
|
|
1065
|
+
stopTimer = setTimeout(resolve, 5e3);
|
|
1066
|
+
stopTimer.unref?.();
|
|
1067
|
+
})]);
|
|
1068
|
+
} finally {
|
|
1069
|
+
if (stopTimer) clearTimeout(stopTimer);
|
|
1070
|
+
try {
|
|
1071
|
+
await proc?.kill();
|
|
1072
|
+
} catch {}
|
|
1073
|
+
channel.close();
|
|
1074
|
+
}
|
|
1075
|
+
})();
|
|
1076
|
+
return stopPromise;
|
|
1077
|
+
},
|
|
1078
|
+
doStop: async () => {
|
|
1079
|
+
if (stopped) throw new Error(`codex session ${sessionId} is already stopped; cannot stop.`);
|
|
1080
|
+
stopped = true;
|
|
1081
|
+
channel.beginClose();
|
|
1082
|
+
const data = channel.isClosed() ? {} : await new Promise((resolve, reject) => {
|
|
1083
|
+
const timer = setTimeout(() => {
|
|
1084
|
+
unsub();
|
|
1085
|
+
reject(/* @__PURE__ */ new Error(`codex session ${sessionId} did not reply to stop within 5s.`));
|
|
1086
|
+
}, 5e3);
|
|
1087
|
+
timer.unref?.();
|
|
1088
|
+
const unsub = channel.on("bridge-stop", (msg) => {
|
|
1089
|
+
clearTimeout(timer);
|
|
1090
|
+
unsub();
|
|
1091
|
+
resolve(msg.data);
|
|
1092
|
+
});
|
|
1093
|
+
try {
|
|
1094
|
+
channel.send({ type: "stop" });
|
|
1095
|
+
} catch (err) {
|
|
1096
|
+
clearTimeout(timer);
|
|
1097
|
+
unsub();
|
|
1098
|
+
reject(err);
|
|
1099
|
+
}
|
|
1100
|
+
});
|
|
1101
|
+
let stopTimer;
|
|
1102
|
+
try {
|
|
1103
|
+
if (proc) await Promise.race([proc.wait(), new Promise((resolve) => {
|
|
1104
|
+
stopTimer = setTimeout(resolve, 5e3);
|
|
1105
|
+
stopTimer.unref?.();
|
|
1106
|
+
})]);
|
|
1107
|
+
} finally {
|
|
1108
|
+
if (stopTimer) clearTimeout(stopTimer);
|
|
1109
|
+
try {
|
|
1110
|
+
await proc?.kill();
|
|
1111
|
+
} catch {}
|
|
1112
|
+
channel.close();
|
|
1113
|
+
}
|
|
1114
|
+
return {
|
|
1115
|
+
type: "resume-session",
|
|
1116
|
+
harnessId: "codex",
|
|
1117
|
+
specificationVersion: "harness-v1",
|
|
1118
|
+
data: {
|
|
1119
|
+
...data != null && typeof data === "object" && !Array.isArray(data) ? { ...data } : {},
|
|
1120
|
+
...latestTurnConfigurationFingerprint ? { turnConfigurationFingerprint: latestTurnConfigurationFingerprint } : {},
|
|
1121
|
+
...sandboxCredentialEnvironment == null ? {} : { sandboxCredentialEnvironment }
|
|
1122
|
+
}
|
|
1123
|
+
};
|
|
1124
|
+
},
|
|
1125
|
+
doSuspendTurn: async () => {
|
|
1126
|
+
if (stopped) throw new Error(`codex session ${sessionId} is stopped; cannot suspend.`);
|
|
1127
|
+
stopped = true;
|
|
1128
|
+
const lastSeenEventId = await channel.suspend();
|
|
1129
|
+
return {
|
|
1130
|
+
type: "continue-turn",
|
|
1131
|
+
harnessId: "codex",
|
|
1132
|
+
specificationVersion: "harness-v1",
|
|
1133
|
+
data: {
|
|
1134
|
+
...latestThreadId ? { threadId: latestThreadId } : {},
|
|
1135
|
+
...latestTurnConfigurationFingerprint ? { turnConfigurationFingerprint: latestTurnConfigurationFingerprint } : {},
|
|
1136
|
+
...sandboxCredentialEnvironment == null ? {} : { sandboxCredentialEnvironment },
|
|
1137
|
+
bridge: {
|
|
1138
|
+
port: bridgePort,
|
|
1139
|
+
token: bridgeToken,
|
|
1140
|
+
lastSeenEventId,
|
|
1141
|
+
...sandboxId == null ? {} : { sandboxId }
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
};
|
|
1416
1147
|
}
|
|
1417
|
-
function fingerprintCodexTurnConfiguration({
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1148
|
+
function fingerprintCodexTurnConfiguration({ instructions, tools }) {
|
|
1149
|
+
return createHash("sha256").update(JSON.stringify({
|
|
1150
|
+
instructions: instructions ?? null,
|
|
1151
|
+
tools
|
|
1152
|
+
})).digest("hex");
|
|
1422
1153
|
}
|
|
1423
1154
|
function extractUserText(prompt) {
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
}
|
|
1437
|
-
return parts.join("\n\n");
|
|
1155
|
+
if (typeof prompt === "string") return prompt;
|
|
1156
|
+
const { content } = prompt;
|
|
1157
|
+
if (typeof content === "string") return content;
|
|
1158
|
+
const parts = [];
|
|
1159
|
+
for (const part of content) {
|
|
1160
|
+
if (part.type !== "text") throw new HarnessCapabilityUnsupportedError({
|
|
1161
|
+
harnessId: "codex",
|
|
1162
|
+
message: `The codex harness does not yet support user message parts of type '${part.type}'. Pass a string or a user message whose content contains only text parts.`
|
|
1163
|
+
});
|
|
1164
|
+
parts.push(part.text);
|
|
1165
|
+
}
|
|
1166
|
+
return parts.join("\n\n");
|
|
1438
1167
|
}
|
|
1168
|
+
//#endregion
|
|
1169
|
+
//#region src/index.ts
|
|
1170
|
+
/**
|
|
1171
|
+
* Default `codex` harness instance with no overrides — suitable for the
|
|
1172
|
+
* common case where the underlying `codex` CLI's defaults are fine.
|
|
1173
|
+
* Equivalent to `createCodex()`.
|
|
1174
|
+
*/
|
|
1175
|
+
const codex = createCodex();
|
|
1176
|
+
//#endregion
|
|
1177
|
+
export { VERSION, codex, createCodex };
|
|
1439
1178
|
|
|
1440
|
-
// src/index.ts
|
|
1441
|
-
var codex = createCodex();
|
|
1442
|
-
export {
|
|
1443
|
-
VERSION,
|
|
1444
|
-
codex,
|
|
1445
|
-
createCodex
|
|
1446
|
-
};
|
|
1447
1179
|
//# sourceMappingURL=index.js.map
|