@ai-sdk/harness-codex 1.0.126 → 1.0.129

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
@@ -15,6 +15,7 @@ import {
15
15
  createSandboxCredentialEnvironment,
16
16
  createBridgeErrorHandler,
17
17
  createBridgeStartupError,
18
+ experimental_createBridgeUserMessageSubmitter,
18
19
  drainBridgeProcessStream,
19
20
  forwardBridgeProcessStream,
20
21
  getRestrictedSandboxSession,
@@ -27,6 +28,9 @@ import {
27
28
  withBridgeToken,
28
29
  writeSkills
29
30
  } from "@ai-sdk/harness/utils";
31
+ import {
32
+ tool
33
+ } from "@ai-sdk/provider-utils";
30
34
  import { WebSocket } from "ws";
31
35
  import { z as z2 } from "zod/v4";
32
36
 
@@ -516,10 +520,10 @@ var startMessageSchema = harnessV1BridgeStartBaseSchema.extend({
516
520
  codexConfig: z.record(z.string(), z.unknown()).optional(),
517
521
  mcpServers: z.record(z.string(), z.unknown()).optional(),
518
522
  headers: z.record(z.string(), z.string()).optional(),
519
- // Resume signal. When supplied, the bridge calls
520
- // `codex.resumeThread(resumeThreadId, …)` instead of starting a fresh thread.
521
- // The host sources the id from lifecycle state `data` cached from a prior
522
- // `agent.detach`.
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
+ */
523
527
  resumeThreadId: z.string().optional(),
524
528
  restartThread: z.boolean().optional()
525
529
  });
@@ -528,11 +532,8 @@ var inboundMessageSchema = z.discriminatedUnion("type", [
528
532
  ...harnessV1BridgeInboundCommandSchemas
529
533
  ]);
530
534
 
531
- // src/bridge/cli-relay.ts
532
- var CLI_SHIM_FILENAME = "harness-tool.mjs";
533
-
534
535
  // src/version.ts
535
- var VERSION = true ? "1.0.126" : "0.0.0-test";
536
+ var VERSION = true ? "1.0.129" : "0.0.0-test";
536
537
 
537
538
  // src/codex-harness.ts
538
539
  var DEFAULT_CODEX_MODEL = "gpt-5.5";
@@ -549,7 +550,25 @@ var CODEX_BUILTIN_TOOLS = {
549
550
  toolUseKind: "readonly",
550
551
  description: "Search the web",
551
552
  inputSchema: z2.object({ query: z2.string() })
552
- })
553
+ }),
554
+ apply_patch: {
555
+ ...tool({
556
+ description: "Apply a patch to files",
557
+ inputSchema: z2.string()
558
+ }),
559
+ toolUseKind: "edit"
560
+ },
561
+ view_image: {
562
+ ...tool({
563
+ description: "View a local image file",
564
+ inputSchema: z2.object({
565
+ path: z2.string(),
566
+ detail: z2.enum(["high", "original"]).optional(),
567
+ environment_id: z2.string().optional()
568
+ })
569
+ }),
570
+ toolUseKind: "readonly"
571
+ }
553
572
  };
554
573
  var codexBridgeCoordsSchema = z2.object({
555
574
  port: z2.number(),
@@ -568,17 +587,12 @@ function createCodex(settings = {}) {
568
587
  specificationVersion: "harness-v1",
569
588
  harnessId: "codex",
570
589
  builtinTools: CODEX_BUILTIN_TOOLS,
590
+ supportsBuiltinToolFiltering: true,
571
591
  supportsBuiltinToolApprovals: false,
572
592
  lifecycleStateSchema: codexResumeStateSchema,
573
593
  getBootstrap: getCodexBootstrap,
574
594
  doStart: async (startOpts) => {
575
595
  const model = DEFAULT_CODEX_MODEL;
576
- if (startOpts.builtinToolFiltering != null) {
577
- throw new HarnessCapabilityUnsupportedError({
578
- message: "Harness 'codex' does not support built-in tool filtering controls.",
579
- harnessId: "codex"
580
- });
581
- }
582
596
  if (startOpts.permissionMode != null && startOpts.permissionMode !== "allow-all") {
583
597
  throw new HarnessCapabilityUnsupportedError({
584
598
  message: "Harness 'codex' does not support built-in tool approval requests; use permissionMode: 'allow-all'.",
@@ -654,8 +668,6 @@ function createCodex(settings = {}) {
654
668
  sessionId: startOpts.sessionId
655
669
  });
656
670
  const bridgeStateDir = `${sessionDataDir}/bridge`;
657
- const cliShimDir = `${sessionDataDir}/codex`;
658
- const cliShimPath = `${cliShimDir}/${CLI_SHIM_FILENAME}`;
659
671
  const timeoutMs = settings.startupTimeoutMs ?? 12e4;
660
672
  const report = startOpts.observability?.report;
661
673
  const onDiagnostic = report ? (frame) => report(
@@ -691,12 +703,12 @@ function createCodex(settings = {}) {
691
703
  return createSession({
692
704
  sessionId: startOpts.sessionId,
693
705
  channel: attachChannel,
694
- cliShimPath,
695
706
  // The live bridge was spawned by another process; no process handle.
696
707
  proc: void 0,
697
708
  model,
698
709
  reasoningEffort: settings.reasoningEffort,
699
710
  webSearch: settings.webSearch,
711
+ builtinToolFiltering: startOpts.builtinToolFiltering,
700
712
  codexConfig: settings.codexConfig,
701
713
  mcpServers: settings.mcpServers,
702
714
  headers: startOpts.headers,
@@ -766,7 +778,7 @@ function createCodex(settings = {}) {
766
778
  abortSignal: startOpts.abortSignal
767
779
  });
768
780
  const proc = await toolSafeSandboxSession.spawn({
769
- command: `node ${shellQuote(`${bootstrapDir}/bridge.mjs`)} --workdir ${shellQuote(workDir)} --bridge-state-dir ${shellQuote(bridgeStateDir)} --cli-shim-dir ${shellQuote(cliShimDir)}`,
781
+ command: `node ${shellQuote(`${bootstrapDir}/bridge.mjs`)} --workdir ${shellQuote(workDir)} --bridge-state-dir ${shellQuote(bridgeStateDir)}`,
770
782
  env,
771
783
  abortSignal: startOpts.abortSignal
772
784
  });
@@ -823,11 +835,11 @@ function createCodex(settings = {}) {
823
835
  return createSession({
824
836
  sessionId: startOpts.sessionId,
825
837
  channel,
826
- cliShimPath,
827
838
  proc,
828
839
  model,
829
840
  reasoningEffort: settings.reasoningEffort,
830
841
  webSearch: settings.webSearch,
842
+ builtinToolFiltering: startOpts.builtinToolFiltering,
831
843
  codexConfig: settings.codexConfig,
832
844
  mcpServers: settings.mcpServers,
833
845
  headers: startOpts.headers,
@@ -949,11 +961,11 @@ function openWebSocket({
949
961
  function createSession({
950
962
  sessionId,
951
963
  channel,
952
- cliShimPath,
953
964
  proc,
954
965
  model,
955
966
  reasoningEffort,
956
967
  webSearch,
968
+ builtinToolFiltering,
957
969
  codexConfig,
958
970
  mcpServers,
959
971
  headers,
@@ -974,7 +986,6 @@ function createSession({
974
986
  let stopped = false;
975
987
  let stopPromise;
976
988
  let pendingResumeThreadId = seedResumeThreadOnFirstPrompt ? resumeThreadId : void 0;
977
- let initialPromptGuidanceApplied = isResume;
978
989
  let latestThreadId = resumeThreadId;
979
990
  let latestTurnConfigurationFingerprint = turnConfigurationFingerprint;
980
991
  channel.on("bridge-thread", (msg) => {
@@ -1002,7 +1013,6 @@ function createSession({
1002
1013
  const restartThread = latestThreadId != null && (skillsResult.changed || latestTurnConfigurationFingerprint != null && latestTurnConfigurationFingerprint !== nextFingerprint);
1003
1014
  latestTurnConfigurationFingerprint = nextFingerprint;
1004
1015
  if (restartThread) {
1005
- initialPromptGuidanceApplied = false;
1006
1016
  pendingResumeThreadId = void 0;
1007
1017
  }
1008
1018
  return { restartThread };
@@ -1014,6 +1024,11 @@ function createSession({
1014
1024
  pendingResolve = resolve2;
1015
1025
  pendingReject = reject;
1016
1026
  });
1027
+ const userMessageSubmitter = experimental_createBridgeUserMessageSubmitter({
1028
+ send: (message) => channel.send(message),
1029
+ onResponse: (listener) => channel.on("user-message-response", listener),
1030
+ onReconnect: (listener) => channel.onReconnect(listener)
1031
+ });
1017
1032
  const unsubs = [];
1018
1033
  const forward = (event) => {
1019
1034
  try {
@@ -1040,12 +1055,14 @@ function createSession({
1040
1055
  const settleSuccess = () => {
1041
1056
  if (isSettled) return;
1042
1057
  isSettled = true;
1058
+ userMessageSubmitter.close();
1043
1059
  for (const u of unsubs) u();
1044
1060
  pendingResolve();
1045
1061
  };
1046
1062
  const settleError = (err) => {
1047
1063
  if (isSettled) return;
1048
1064
  isSettled = true;
1065
+ userMessageSubmitter.close(err);
1049
1066
  for (const u of unsubs) u();
1050
1067
  pendingReject(err);
1051
1068
  };
@@ -1113,6 +1130,9 @@ function createSession({
1113
1130
  reason: input.reason
1114
1131
  });
1115
1132
  },
1133
+ submitUserMessage: async (text) => {
1134
+ await userMessageSubmitter.submit(text);
1135
+ },
1116
1136
  done
1117
1137
  };
1118
1138
  return {
@@ -1155,26 +1175,16 @@ function createSession({
1155
1175
  emit: promptOpts.emit,
1156
1176
  abortSignal: promptOpts.abortSignal
1157
1177
  });
1158
- let promptText = extractUserText(promptOpts.prompt);
1159
- if (!initialPromptGuidanceApplied) {
1160
- promptText = frameInitialPromptGuidance({
1161
- toolUsageBlock: tools.length > 0 ? composeToolUsageInstructions({
1162
- tools,
1163
- cliShimPath
1164
- }) : void 0,
1165
- userText: promptText
1166
- });
1167
- }
1168
- initialPromptGuidanceApplied = true;
1169
1178
  const startMessage = {
1170
1179
  type: "start",
1171
- prompt: promptText,
1180
+ prompt: extractUserText(promptOpts.prompt),
1172
1181
  tools,
1173
1182
  ...promptOpts.responseFormat == null ? {} : { responseFormat: promptOpts.responseFormat },
1174
1183
  ...promptOpts.instructions ? { instructions: promptOpts.instructions } : {},
1175
1184
  model: promptOpts.model ?? model,
1176
1185
  reasoningEffort,
1177
1186
  webSearch,
1187
+ ...builtinToolFiltering == null ? {} : { builtinToolFiltering },
1178
1188
  ...codexConfig == null ? {} : { codexConfig },
1179
1189
  ...mcpServers == null ? {} : { mcpServers },
1180
1190
  ...headers == null ? {} : { headers },
@@ -1229,6 +1239,7 @@ function createSession({
1229
1239
  model: continueOpts.model ?? model,
1230
1240
  reasoningEffort,
1231
1241
  webSearch,
1242
+ ...builtinToolFiltering == null ? {} : { builtinToolFiltering },
1232
1243
  ...codexConfig == null ? {} : { codexConfig },
1233
1244
  ...mcpServers == null ? {} : { mcpServers },
1234
1245
  ...headers == null ? {} : { headers },
@@ -1409,44 +1420,6 @@ function fingerprintCodexTurnConfiguration({
1409
1420
  }) {
1410
1421
  return createHash2("sha256").update(JSON.stringify({ instructions: instructions ?? null, tools })).digest("hex");
1411
1422
  }
1412
- function frameInitialPromptGuidance({
1413
- toolUsageBlock,
1414
- userText
1415
- }) {
1416
- const blocks = [];
1417
- if (toolUsageBlock) blocks.push(toolUsageBlock);
1418
- if (blocks.length === 0) return userText;
1419
- return `${blocks.join("\n\n")}
1420
-
1421
- <user-message>
1422
- ${userText}
1423
- </user-message>`;
1424
- }
1425
- function composeToolUsageInstructions({
1426
- tools,
1427
- cliShimPath
1428
- }) {
1429
- const lines = [
1430
- "<host-tool-instructions>",
1431
- "You have access to the following host-provided tools. To use one, run the following command via your built-in `bash` tool:",
1432
- "",
1433
- ` node ${cliShimPath} <toolName> '<jsonInput>'`,
1434
- "",
1435
- "The script prints the JSON result to stdout. Do not invent another way to call these tools \u2014 only this CLI invocation will work. Pass the JSON input as a single-quoted argument.",
1436
- "For every user request that depends on a host-provided tool, run a separate CLI invocation for each needed tool call in the current turn before answering. Do not reuse previous tool results, and do not say you used a host tool unless the command has completed in the current turn.",
1437
- ""
1438
- ];
1439
- for (const toolSpec of tools) {
1440
- lines.push(
1441
- `- **${toolSpec.name}**${toolSpec.description ? ": " + toolSpec.description : ""}`
1442
- );
1443
- lines.push(
1444
- ` - Input schema: \`${JSON.stringify(toolSpec.inputSchema ?? {})}\``
1445
- );
1446
- }
1447
- lines.push("</host-tool-instructions>");
1448
- return lines.join("\n");
1449
- }
1450
1423
  function extractUserText(prompt) {
1451
1424
  if (typeof prompt === "string") return prompt;
1452
1425
  const { content } = prompt;