@ai-sdk/harness-codex 1.0.109 → 1.0.110

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,5 +1,5 @@
1
1
  // src/codex-harness.ts
2
- import { createHash } from "crypto";
2
+ import { createHash as createHash2 } from "crypto";
3
3
  import path from "path";
4
4
  import {
5
5
  commonTool,
@@ -151,6 +151,354 @@ function toCodexGatewayBaseUrl(baseUrl) {
151
151
  return trimmed.endsWith("/v1") ? trimmed : `${trimmed}/v1`;
152
152
  }
153
153
 
154
+ // src/codex-subscription.ts
155
+ import { spawn } from "child_process";
156
+ import { createHash } from "crypto";
157
+ import {
158
+ chmod,
159
+ mkdir,
160
+ readFile,
161
+ realpath,
162
+ rename,
163
+ writeFile
164
+ } from "fs/promises";
165
+ import { homedir } from "os";
166
+ import { dirname, join, resolve } from "path";
167
+ import {
168
+ getJwtExpiresAt,
169
+ isAccessTokenExpiringSoon,
170
+ isHarnessAuthenticationEnvironment as isHarnessAuthenticationEnvironment2,
171
+ isLinux,
172
+ isMacOS,
173
+ isWindows,
174
+ readLinuxSecretServicePassword,
175
+ readMacOSKeychainPassword,
176
+ readWindowsCredentialManagerPassword,
177
+ refreshOAuthAccessToken,
178
+ shouldResolveNativeSubscription
179
+ } from "@ai-sdk/harness/utils";
180
+ import { isRecord, safeParseJSON } from "@ai-sdk/provider-utils";
181
+ var CHATGPT_CODEX_BASE_URL = "https://chatgpt.com/backend-api/codex";
182
+ var OPENAI_TOKEN_URL = "https://auth.openai.com/oauth/token";
183
+ var OPENAI_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
184
+ function createCodexSubscriptionRequestTransformations({
185
+ requestHeaders,
186
+ ...sources
187
+ }) {
188
+ const transformations = createCodexRequestTransformations(sources);
189
+ if (requestHeaders == null) return transformations;
190
+ return transformations.map((transformation) => ({
191
+ ...transformation,
192
+ transform: {
193
+ ...transformation.transform,
194
+ headers: {
195
+ ...transformation.transform.headers,
196
+ ...requestHeaders
197
+ }
198
+ }
199
+ }));
200
+ }
201
+ async function resolveCodexAuthentication({
202
+ auth,
203
+ processEnv = process.env,
204
+ authCredentialsStoreMode,
205
+ readSubscription = readCodexSubscription
206
+ }) {
207
+ const environment = resolveCodexEnv(auth, processEnv);
208
+ if (isHarnessAuthenticationEnvironment2(auth)) {
209
+ return { environment };
210
+ }
211
+ if (!shouldResolveNativeSubscription({
212
+ auth,
213
+ env: environment,
214
+ hasDirectCredential: environment.CODEX_API_KEY != null
215
+ })) {
216
+ return { environment };
217
+ }
218
+ return await readSubscription({ authCredentialsStoreMode }) ?? { environment };
219
+ }
220
+ async function readCodexSubscription({
221
+ env = process.env,
222
+ homeDirectory = homedir(),
223
+ authCredentialsStoreMode,
224
+ platform = process.platform,
225
+ keyring,
226
+ fetch
227
+ } = {}) {
228
+ const codexHome = resolve(env.CODEX_HOME ?? join(homeDirectory, ".codex"));
229
+ const authPath = join(codexHome, "auth.json");
230
+ const storageMode = toCodexAuthCredentialsStoreMode(authCredentialsStoreMode) ?? await readCodexAuthCredentialsStoreMode({ codexHome }) ?? "file";
231
+ const stored = await readCodexAuthStore({
232
+ codexHome,
233
+ authPath,
234
+ storageMode,
235
+ keyring: keyring ?? createCodexKeyring({ platform })
236
+ });
237
+ if (stored == null) return void 0;
238
+ const credential = await toCodexCredential(stored.value);
239
+ if (credential == null) return void 0;
240
+ let accessToken = credential.accessToken;
241
+ if (isAccessTokenExpiringSoon({ expiresAt: credential.expiresAt })) {
242
+ const refreshed = await refreshOAuthAccessToken({
243
+ tokenUrl: OPENAI_TOKEN_URL,
244
+ clientId: OPENAI_CLIENT_ID,
245
+ refreshToken: credential.refreshToken,
246
+ requestFormat: "json",
247
+ ...fetch == null ? {} : { fetch }
248
+ });
249
+ accessToken = refreshed.accessToken;
250
+ await stored.write({
251
+ ...stored.value,
252
+ last_refresh: (/* @__PURE__ */ new Date()).toISOString(),
253
+ tokens: {
254
+ ...stored.value.tokens,
255
+ access_token: refreshed.accessToken,
256
+ refresh_token: refreshed.refreshToken ?? credential.refreshToken
257
+ }
258
+ });
259
+ }
260
+ return {
261
+ environment: {
262
+ CODEX_API_KEY: accessToken,
263
+ OPENAI_BASE_URL: CHATGPT_CODEX_BASE_URL
264
+ },
265
+ ...credential.accountId == null ? {} : {
266
+ requestHeaders: {
267
+ "ChatGPT-Account-ID": credential.accountId
268
+ }
269
+ }
270
+ };
271
+ }
272
+ async function readCodexAuthStore({
273
+ codexHome,
274
+ authPath,
275
+ storageMode,
276
+ keyring
277
+ }) {
278
+ if (storageMode === "ephemeral") return void 0;
279
+ const readFileStore = async () => {
280
+ const value = await readCodexAuthFile(authPath);
281
+ return value == null ? void 0 : {
282
+ value,
283
+ write: (updated) => writeCodexAuthFile({ authPath, value: updated })
284
+ };
285
+ };
286
+ const readKeyringStore = async () => {
287
+ if (keyring == null) return void 0;
288
+ const canonicalHome = await realpath(codexHome).catch(() => codexHome);
289
+ const account = `cli|${createHash("sha256").update(canonicalHome).digest("hex").slice(0, 16)}`;
290
+ const text = await keyring.read({ service: "Codex Auth", account });
291
+ if (text == null) return void 0;
292
+ const value = await parseCodexAuthFile(text);
293
+ return value == null ? void 0 : {
294
+ value,
295
+ write: (updated) => keyring.write({
296
+ service: "Codex Auth",
297
+ account,
298
+ value: JSON.stringify(updated)
299
+ })
300
+ };
301
+ };
302
+ if (storageMode === "file") return readFileStore();
303
+ if (storageMode === "keyring") return readKeyringStore();
304
+ return await readKeyringStore() ?? readFileStore();
305
+ }
306
+ async function readCodexAuthCredentialsStoreMode({
307
+ codexHome
308
+ }) {
309
+ const text = await readFile(join(codexHome, "config.toml"), "utf8").catch(
310
+ () => void 0
311
+ );
312
+ if (text == null) return void 0;
313
+ for (const line of text.split(/\r?\n/)) {
314
+ if (line.trimStart().startsWith("[")) return void 0;
315
+ const match = /^\s*cli_auth_credentials_store\s*=\s*["'](file|keyring|auto|ephemeral)["']\s*(?:#.*)?$/.exec(
316
+ line
317
+ );
318
+ if (match != null) {
319
+ return toCodexAuthCredentialsStoreMode(match[1]);
320
+ }
321
+ }
322
+ return void 0;
323
+ }
324
+ function toCodexAuthCredentialsStoreMode(value) {
325
+ return value === "file" || value === "keyring" || value === "auto" || value === "ephemeral" ? value : void 0;
326
+ }
327
+ async function readCodexAuthFile(authPath) {
328
+ const text = await readFile(authPath, "utf8").catch(() => void 0);
329
+ return text == null ? void 0 : parseCodexAuthFile(text);
330
+ }
331
+ async function parseCodexAuthFile(text) {
332
+ const parsed = await safeParseJSON({ text });
333
+ return parsed.success && isRecord(parsed.value) ? parsed.value : void 0;
334
+ }
335
+ async function toCodexCredential(value) {
336
+ if (value.auth_mode !== "chatgpt" || !isRecord(value.tokens)) {
337
+ return void 0;
338
+ }
339
+ const accessToken = value.tokens.access_token;
340
+ const refreshToken = value.tokens.refresh_token;
341
+ if (typeof accessToken !== "string" || typeof refreshToken !== "string") {
342
+ return void 0;
343
+ }
344
+ const expiresAt = await getJwtExpiresAt({ token: accessToken });
345
+ if (expiresAt == null) return void 0;
346
+ const accountId = value.tokens.account_id;
347
+ return {
348
+ accessToken,
349
+ refreshToken,
350
+ expiresAt,
351
+ ...typeof accountId === "string" ? { accountId } : {}
352
+ };
353
+ }
354
+ async function writeCodexAuthFile({
355
+ authPath,
356
+ value
357
+ }) {
358
+ await mkdir(dirname(authPath), { recursive: true });
359
+ const temporaryPath = `${authPath}.${process.pid}.tmp`;
360
+ await writeFile(temporaryPath, `${JSON.stringify(value, null, 2)}
361
+ `, {
362
+ mode: 384
363
+ });
364
+ await chmod(temporaryPath, 384);
365
+ await rename(temporaryPath, authPath);
366
+ }
367
+ function createCodexKeyring({
368
+ platform
369
+ }) {
370
+ if (isMacOS(platform)) {
371
+ return {
372
+ async read({ service, account }) {
373
+ return readMacOSKeychainPassword({ service, account });
374
+ },
375
+ async write({ service, account, value }) {
376
+ const command = `add-generic-password -U -s ${shellQuoteForSecurity(service)} -a ${shellQuoteForSecurity(account)} -X ${Buffer.from(value, "utf8").toString("hex")}
377
+ `;
378
+ await runCommandWithInput({
379
+ command: "/usr/bin/security",
380
+ args: ["-i"],
381
+ input: command
382
+ });
383
+ }
384
+ };
385
+ }
386
+ if (isLinux(platform)) {
387
+ return {
388
+ async read({ service, account }) {
389
+ return readLinuxSecretServicePassword({
390
+ attributes: {
391
+ service,
392
+ username: account,
393
+ target: "default"
394
+ }
395
+ });
396
+ },
397
+ async write({ service, account, value }) {
398
+ await runCommandWithInput({
399
+ command: "secret-tool",
400
+ args: [
401
+ "store",
402
+ "--label",
403
+ `${account}@${service}:default`,
404
+ "service",
405
+ service,
406
+ "username",
407
+ account,
408
+ "target",
409
+ "default",
410
+ "application",
411
+ "rust-keyring"
412
+ ],
413
+ input: value
414
+ });
415
+ }
416
+ };
417
+ }
418
+ if (isWindows(platform)) {
419
+ return createWindowsCodexKeyring();
420
+ }
421
+ return void 0;
422
+ }
423
+ function shellQuoteForSecurity(value) {
424
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
425
+ }
426
+ async function runCommandWithInput({
427
+ command,
428
+ args,
429
+ input,
430
+ env
431
+ }) {
432
+ await new Promise((resolvePromise, reject) => {
433
+ const child = spawn(command, args, {
434
+ stdio: ["pipe", "ignore", "pipe"],
435
+ ...env == null ? {} : { env }
436
+ });
437
+ let stderr = "";
438
+ child.stderr.setEncoding("utf8");
439
+ child.stderr.on("data", (chunk) => {
440
+ stderr += chunk;
441
+ });
442
+ child.once("error", reject);
443
+ child.once("close", (code) => {
444
+ if (code === 0) resolvePromise();
445
+ else reject(new Error(stderr || `${command} exited with code ${code}.`));
446
+ });
447
+ child.stdin.end(input);
448
+ });
449
+ }
450
+ function createWindowsCodexKeyring() {
451
+ const source = `
452
+ using System;
453
+ using System.Runtime.InteropServices;
454
+ using System.Text;
455
+ public static class CodexCredentialManager {
456
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
457
+ public struct Credential {
458
+ public UInt32 Flags; public UInt32 Type; public string TargetName;
459
+ public string Comment; public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten;
460
+ public UInt32 CredentialBlobSize; public IntPtr CredentialBlob;
461
+ public UInt32 Persist; public UInt32 AttributeCount; public IntPtr Attributes;
462
+ public string TargetAlias; public string UserName;
463
+ }
464
+ [DllImport("advapi32.dll", EntryPoint = "CredWriteW", CharSet = CharSet.Unicode, SetLastError = true)]
465
+ static extern bool CredWrite(ref Credential credential, UInt32 flags);
466
+ public static void Write(string target, string userName, string value) {
467
+ byte[] bytes = Encoding.Unicode.GetBytes(value);
468
+ IntPtr blob = Marshal.AllocHGlobal(bytes.Length);
469
+ try {
470
+ Marshal.Copy(bytes, 0, blob, bytes.Length);
471
+ Credential credential = new Credential {
472
+ Type = 1, TargetName = target, CredentialBlobSize = (UInt32)bytes.Length,
473
+ CredentialBlob = blob, Persist = 3, UserName = userName
474
+ };
475
+ if (!CredWrite(ref credential, 0)) throw new System.ComponentModel.Win32Exception();
476
+ } finally { Marshal.FreeHGlobal(blob); }
477
+ }
478
+ }`;
479
+ return {
480
+ async read({ service, account }) {
481
+ return readWindowsCredentialManagerPassword({
482
+ targetName: `${account}.${service}`
483
+ });
484
+ },
485
+ async write({ service, account, value }) {
486
+ const script = `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)`;
487
+ await runCommandWithInput({
488
+ command: "powershell.exe",
489
+ args: ["-NoProfile", "-NonInteractive", "-Command", script],
490
+ input: value,
491
+ env: {
492
+ ...process.env,
493
+ AI_SDK_CODEX_CREDENTIAL_SOURCE: source,
494
+ AI_SDK_CODEX_CREDENTIAL_TARGET: `${account}.${service}`,
495
+ AI_SDK_CODEX_CREDENTIAL_USER: account
496
+ }
497
+ });
498
+ }
499
+ };
500
+ }
501
+
154
502
  // src/codex-bridge-protocol.ts
155
503
  import {
156
504
  harnessV1BridgeInboundCommandSchemas,
@@ -183,7 +531,7 @@ var inboundMessageSchema = z.discriminatedUnion("type", [
183
531
  var CLI_SHIM_FILENAME = "harness-tool.mjs";
184
532
 
185
533
  // src/version.ts
186
- var VERSION = true ? "1.0.109" : "0.0.0-test";
534
+ var VERSION = true ? "1.0.110" : "0.0.0-test";
187
535
 
188
536
  // src/codex-harness.ts
189
537
  var DEFAULT_CODEX_MODEL = "gpt-5.5";
@@ -263,7 +611,11 @@ function createCodex(settings = {}) {
263
611
  const turnConfigurationFingerprint = typeof resumeData?.turnConfigurationFingerprint === "string" ? resumeData.turnConfigurationFingerprint : void 0;
264
612
  const coords = resumeData?.bridge;
265
613
  const authenticationMode = resolveCodexAuthenticationMode(settings.auth);
266
- const resolvedAuthEnvironment = resolveCodexEnv(settings.auth);
614
+ const resolvedAuthentication = await resolveCodexAuthentication({
615
+ auth: settings.auth,
616
+ authCredentialsStoreMode: settings.codexConfig?.cli_auth_credentials_store
617
+ });
618
+ const resolvedAuthEnvironment = resolvedAuthentication.environment;
267
619
  let sandboxAuthEnvironment = resolvedAuthEnvironment;
268
620
  let sandboxCredentialEnvironment;
269
621
  let credentialsBrokered = false;
@@ -277,10 +629,11 @@ function createCodex(settings = {}) {
277
629
  ...resolvedAuthEnvironment,
278
630
  ...sandboxCredentialEnvironment
279
631
  };
280
- const requestTransformations = createCodexRequestTransformations({
632
+ const requestTransformations = createCodexSubscriptionRequestTransformations({
281
633
  env: resolvedAuthEnvironment,
282
634
  sandboxEnv: sandboxAuthEnvironment,
283
- auth: authenticationMode
635
+ auth: authenticationMode,
636
+ requestHeaders: resolvedAuthentication.requestHeaders
284
637
  });
285
638
  if (requestTransformations.length > 0) {
286
639
  await sandboxSession.addRequestTransformations(
@@ -545,13 +898,13 @@ function openWebSocket({
545
898
  url,
546
899
  headers
547
900
  }) {
548
- return new Promise((resolve, reject) => {
901
+ return new Promise((resolve2, reject) => {
549
902
  const ws = new WebSocket(url, {
550
903
  headers: headers == null ? void 0 : { ...headers }
551
904
  });
552
905
  const onOpen = () => {
553
906
  ws.off("error", onError);
554
- resolve(ws);
907
+ resolve2(ws);
555
908
  };
556
909
  const onError = (err) => {
557
910
  ws.off("open", onOpen);
@@ -625,8 +978,8 @@ function createSession({
625
978
  const wireTurn = (turnOpts) => {
626
979
  let pendingResolve;
627
980
  let pendingReject;
628
- const done = new Promise((resolve, reject) => {
629
- pendingResolve = resolve;
981
+ const done = new Promise((resolve2, reject) => {
982
+ pendingResolve = resolve2;
630
983
  pendingReject = reject;
631
984
  });
632
985
  const unsubs = [];
@@ -906,8 +1259,8 @@ function createSession({
906
1259
  if (proc) {
907
1260
  await Promise.race([
908
1261
  proc.wait(),
909
- new Promise((resolve) => {
910
- stopTimer = setTimeout(resolve, 5e3);
1262
+ new Promise((resolve2) => {
1263
+ stopTimer = setTimeout(resolve2, 5e3);
911
1264
  stopTimer.unref?.();
912
1265
  })
913
1266
  ]);
@@ -931,7 +1284,7 @@ function createSession({
931
1284
  }
932
1285
  stopped = true;
933
1286
  channel.beginClose();
934
- const data = channel.isClosed() ? {} : await new Promise((resolve, reject) => {
1287
+ const data = channel.isClosed() ? {} : await new Promise((resolve2, reject) => {
935
1288
  const timer = setTimeout(() => {
936
1289
  unsub();
937
1290
  reject(
@@ -944,7 +1297,7 @@ function createSession({
944
1297
  const unsub = channel.on("bridge-stop", (msg) => {
945
1298
  clearTimeout(timer);
946
1299
  unsub();
947
- resolve(msg.data);
1300
+ resolve2(msg.data);
948
1301
  });
949
1302
  try {
950
1303
  channel.send({ type: "stop" });
@@ -959,8 +1312,8 @@ function createSession({
959
1312
  if (proc) {
960
1313
  await Promise.race([
961
1314
  proc.wait(),
962
- new Promise((resolve) => {
963
- stopTimer = setTimeout(resolve, 5e3);
1315
+ new Promise((resolve2) => {
1316
+ stopTimer = setTimeout(resolve2, 5e3);
964
1317
  stopTimer.unref?.();
965
1318
  })
966
1319
  ]);
@@ -1022,7 +1375,7 @@ function fingerprintCodexTurnConfiguration({
1022
1375
  instructions,
1023
1376
  tools
1024
1377
  }) {
1025
- return createHash("sha256").update(JSON.stringify({ instructions: instructions ?? null, tools })).digest("hex");
1378
+ return createHash2("sha256").update(JSON.stringify({ instructions: instructions ?? null, tools })).digest("hex");
1026
1379
  }
1027
1380
  function frameInitialPromptGuidance({
1028
1381
  toolUsageBlock,