@ai-sdk/harness-codex 1.0.94 → 1.0.96

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.
@@ -4,7 +4,7 @@
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "dependencies": {
7
- "@openai/codex-sdk": "0.144.5",
7
+ "@openai/codex-sdk": "0.149.1",
8
8
  "ws": "8.21.0"
9
9
  }
10
10
  }
@@ -9,55 +9,55 @@ importers:
9
9
  .:
10
10
  dependencies:
11
11
  '@openai/codex-sdk':
12
- specifier: 0.144.5
13
- version: 0.144.5
12
+ specifier: 0.149.1
13
+ version: 0.149.1
14
14
  ws:
15
15
  specifier: 8.21.0
16
16
  version: 8.21.0
17
17
 
18
18
  packages:
19
19
 
20
- '@openai/codex-sdk@0.144.5':
21
- resolution: {integrity: sha512-90wHPEGyk74On6gwQPNtw+wzuDJ2zYpiVADDxw43S1cWn3QbBh/21zFS2xlAWWCrdY0gE90yXfmmrzwdUDlBGw==}
20
+ '@openai/codex-sdk@0.149.1':
21
+ resolution: {integrity: sha512-R00Rz5327LefZggAxl28r7vFQq1vxa91OxtjZJOsQfAM/MyH8InW5qwwRu6pzUmRGp1E29XrOzm7u1TeV5Yz2A==}
22
22
  engines: {node: '>=18'}
23
23
 
24
- '@openai/codex@0.144.5':
25
- resolution: {integrity: sha512-jjB+K+OMv572mKhS+2QuLxWXDJNdpwbPenf+V+8bdq7wg4Scqt3cn6WEekD8wPqDVZqck0HSX17K9rD9kbDJQA==}
24
+ '@openai/codex@0.149.1':
25
+ resolution: {integrity: sha512-6q5pbcpFbJbqOpkubSDBwXmktQ55aD8eUzGzBF1zASob2DjwhBKDSNGtdZKalfrNJUdTDTPDMmzCXEXs5tMBYA==}
26
26
  engines: {node: '>=16'}
27
27
  hasBin: true
28
28
 
29
- '@openai/codex@0.144.5-darwin-arm64':
30
- resolution: {integrity: sha512-zcT6NfBCqLFt+BReNSETTZW6v6PdbH0dzNtm9j7l7mDGqwPbKZDGJdnpkBao2389I0ZacyIKgSZoI0vez1d4Dw==}
29
+ '@openai/codex@0.149.1-darwin-arm64':
30
+ resolution: {integrity: sha512-6X84kTCbnTgPIJ2EdcPsrvwS0Wxsqpa+bCswGmRf4BjhcQ5nPMnBC6yCAaCMj+vrbXQHj+L6sa9FaR4QkmA1qw==}
31
31
  engines: {node: '>=16'}
32
32
  cpu: [arm64]
33
33
  os: [darwin]
34
34
 
35
- '@openai/codex@0.144.5-darwin-x64':
36
- resolution: {integrity: sha512-//Mo0m1MwaoT6psu5xsmofXpKx4/0irIkeq10xJvk59+886EG355ibjA+ZmlRcKhE3bLjsKD7p81nTbAdRL/bw==}
35
+ '@openai/codex@0.149.1-darwin-x64':
36
+ resolution: {integrity: sha512-MfLBQLfcElJL9tvj6y45qVHHMGSXCPnQOixuD3/Zq0g1BW/eFizkrGLdn48cFpc+l8cK+gt5nYG5pQYwVs6g4A==}
37
37
  engines: {node: '>=16'}
38
38
  cpu: [x64]
39
39
  os: [darwin]
40
40
 
41
- '@openai/codex@0.144.5-linux-arm64':
42
- resolution: {integrity: sha512-zAHggxVwR2TBxKmybXY7ZMiB0G8DMonY2YPdwNNjwXcf+LOIqNGgswwNCDMbP/HEe6r8j+R9ZX/yYoo8f+n/RQ==}
41
+ '@openai/codex@0.149.1-linux-arm64':
42
+ resolution: {integrity: sha512-OqxUfZ1TVvHd18zHPKK/8ZRlpk8Vy11mg5CMHaLxNWldTbwVImDKtSLWT+m8m4NM5Sz4PbjtZMrVT/RfpBW/mQ==}
43
43
  engines: {node: '>=16'}
44
44
  cpu: [arm64]
45
45
  os: [linux]
46
46
 
47
- '@openai/codex@0.144.5-linux-x64':
48
- resolution: {integrity: sha512-FalLJlBQGFdK8Gc3kj9sa/ekNdgkHhUawLaKkvy5CtB18JaP2YxtTP/Pe1pD2iBiq8mMUliRnafpF6AdBdQMbg==}
47
+ '@openai/codex@0.149.1-linux-x64':
48
+ resolution: {integrity: sha512-Of5fGYgr7tAMsyj6vhXb4/RM/UoA3Zq8BLegUBDC09UNy1XTLGYP/2XD+UX8z3qh0NDwxYdCjFIWdDNijKZggQ==}
49
49
  engines: {node: '>=16'}
50
50
  cpu: [x64]
51
51
  os: [linux]
52
52
 
53
- '@openai/codex@0.144.5-win32-arm64':
54
- resolution: {integrity: sha512-0Pj7iqjEOEvPQPO3kFfCy9vGX4BTu76ChFFZHr2eNNIfVc3FOENAv/X98u4L+iIUtDOK9DbqmfUudW3DPapshg==}
53
+ '@openai/codex@0.149.1-win32-arm64':
54
+ resolution: {integrity: sha512-5K0DmOKGK9Bos627p8sK8ATHjovPK0sDyT6h9Cb+4v+5CW5SGw1HLgjGxoLfJ8g3cg6mtg/pRCXXo2L/j71UVA==}
55
55
  engines: {node: '>=16'}
56
56
  cpu: [arm64]
57
57
  os: [win32]
58
58
 
59
- '@openai/codex@0.144.5-win32-x64':
60
- resolution: {integrity: sha512-DnsSTlnnzleTxvLwIGnBitKInscxn2I7qASqosS8Fv+qysBygd+ZiBn/SQsRCgQ28PAlsNzmd3Gf3ZTecolAmg==}
59
+ '@openai/codex@0.149.1-win32-x64':
60
+ resolution: {integrity: sha512-G3QXGAg7nyyhqOeooAMUekBCeHd8a1QByhKcVAFyzNBaI06t6Ft7nsF+1SzFS0spuIdU4YyMi5YD26ukADBQUQ==}
61
61
  engines: {node: '>=16'}
62
62
  cpu: [x64]
63
63
  os: [win32]
@@ -76,35 +76,35 @@ packages:
76
76
 
77
77
  snapshots:
78
78
 
79
- '@openai/codex-sdk@0.144.5':
79
+ '@openai/codex-sdk@0.149.1':
80
80
  dependencies:
81
- '@openai/codex': 0.144.5
81
+ '@openai/codex': 0.149.1
82
82
 
83
- '@openai/codex@0.144.5':
83
+ '@openai/codex@0.149.1':
84
84
  optionalDependencies:
85
- '@openai/codex-darwin-arm64': '@openai/codex@0.144.5-darwin-arm64'
86
- '@openai/codex-darwin-x64': '@openai/codex@0.144.5-darwin-x64'
87
- '@openai/codex-linux-arm64': '@openai/codex@0.144.5-linux-arm64'
88
- '@openai/codex-linux-x64': '@openai/codex@0.144.5-linux-x64'
89
- '@openai/codex-win32-arm64': '@openai/codex@0.144.5-win32-arm64'
90
- '@openai/codex-win32-x64': '@openai/codex@0.144.5-win32-x64'
91
-
92
- '@openai/codex@0.144.5-darwin-arm64':
85
+ '@openai/codex-darwin-arm64': '@openai/codex@0.149.1-darwin-arm64'
86
+ '@openai/codex-darwin-x64': '@openai/codex@0.149.1-darwin-x64'
87
+ '@openai/codex-linux-arm64': '@openai/codex@0.149.1-linux-arm64'
88
+ '@openai/codex-linux-x64': '@openai/codex@0.149.1-linux-x64'
89
+ '@openai/codex-win32-arm64': '@openai/codex@0.149.1-win32-arm64'
90
+ '@openai/codex-win32-x64': '@openai/codex@0.149.1-win32-x64'
91
+
92
+ '@openai/codex@0.149.1-darwin-arm64':
93
93
  optional: true
94
94
 
95
- '@openai/codex@0.144.5-darwin-x64':
95
+ '@openai/codex@0.149.1-darwin-x64':
96
96
  optional: true
97
97
 
98
- '@openai/codex@0.144.5-linux-arm64':
98
+ '@openai/codex@0.149.1-linux-arm64':
99
99
  optional: true
100
100
 
101
- '@openai/codex@0.144.5-linux-x64':
101
+ '@openai/codex@0.149.1-linux-x64':
102
102
  optional: true
103
103
 
104
- '@openai/codex@0.144.5-win32-arm64':
104
+ '@openai/codex@0.149.1-win32-arm64':
105
105
  optional: true
106
106
 
107
- '@openai/codex@0.144.5-win32-x64':
107
+ '@openai/codex@0.149.1-win32-x64':
108
108
  optional: true
109
109
 
110
110
  ws@8.21.0: {}
package/dist/index.d.ts CHANGED
@@ -25,13 +25,15 @@ type CodexHarnessSettings = {
25
25
  /**
26
26
  * OpenAI model id the underlying `codex` CLI should use. Leaving this unset
27
27
  * pins the adapter default (`DEFAULT_CODEX_MODEL`).
28
+ *
29
+ * @deprecated Use `model` on `HarnessAgent` instead.
28
30
  */
29
31
  readonly model?: string;
30
32
  /**
31
33
  * Reasoning effort for reasoning-capable models. Leaving this unset
32
34
  * defers to the CLI's default.
33
35
  */
34
- readonly reasoningEffort?: 'low' | 'medium' | 'high';
36
+ readonly reasoningEffort?: 'low' | 'medium' | 'high' | 'xhigh' | 'max';
35
37
  /**
36
38
  * When `true`, allow the underlying runtime to use live web search.
37
39
  */
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/codex-harness.ts
2
- import { randomBytes } from "crypto";
2
+ import { createHash, randomBytes } from "crypto";
3
3
  import path from "path";
4
4
  import {
5
5
  commonTool,
@@ -175,7 +175,7 @@ import { z } from "zod/v4";
175
175
  var outboundMessageSchema = harnessV1BridgeOutboundMessageSchema;
176
176
  var startMessageSchema = harnessV1BridgeStartBaseSchema.extend({
177
177
  instructions: z.string().optional(),
178
- reasoningEffort: z.enum(["low", "medium", "high"]).optional(),
178
+ reasoningEffort: z.enum(["low", "medium", "high", "xhigh", "max"]).optional(),
179
179
  webSearch: z.boolean().optional(),
180
180
  codexConfig: z.record(z.string(), z.unknown()).optional(),
181
181
  mcpServers: z.record(z.string(), z.unknown()).optional(),
@@ -183,7 +183,8 @@ var startMessageSchema = harnessV1BridgeStartBaseSchema.extend({
183
183
  // `codex.resumeThread(resumeThreadId, …)` instead of starting a fresh thread.
184
184
  // The host sources the id from lifecycle state `data` cached from a prior
185
185
  // `agent.detach`.
186
- resumeThreadId: z.string().optional()
186
+ resumeThreadId: z.string().optional(),
187
+ restartThread: z.boolean().optional()
187
188
  });
188
189
  var inboundMessageSchema = z.discriminatedUnion("type", [
189
190
  startMessageSchema,
@@ -194,7 +195,7 @@ var inboundMessageSchema = z.discriminatedUnion("type", [
194
195
  var CLI_SHIM_FILENAME = "harness-tool.mjs";
195
196
 
196
197
  // src/version.ts
197
- var VERSION = true ? "1.0.94" : "0.0.0-test";
198
+ var VERSION = true ? "1.0.96" : "0.0.0-test";
198
199
 
199
200
  // src/codex-harness.ts
200
201
  var DEFAULT_CODEX_MODEL = "gpt-5.5";
@@ -221,6 +222,7 @@ var codexBridgeCoordsSchema = z2.object({
221
222
  });
222
223
  var codexResumeStateSchema = z2.object({
223
224
  threadId: z2.string().optional(),
225
+ turnConfigurationFingerprint: z2.string().optional(),
224
226
  bridge: codexBridgeCoordsSchema.optional(),
225
227
  sandboxCredentialEnvironment: z2.record(z2.string(), z2.string()).optional()
226
228
  });
@@ -233,6 +235,7 @@ function createCodex(settings = {}) {
233
235
  lifecycleStateSchema: codexResumeStateSchema,
234
236
  getBootstrap: getCodexBootstrap,
235
237
  doStart: async (startOpts) => {
238
+ const model = settings.model ?? DEFAULT_CODEX_MODEL;
236
239
  if (startOpts.builtinToolFiltering != null) {
237
240
  throw new HarnessCapabilityUnsupportedError({
238
241
  message: "Harness 'codex' does not support built-in tool filtering controls.",
@@ -269,6 +272,7 @@ function createCodex(settings = {}) {
269
272
  const resumeData = isResume && typeof lifecycleState?.data === "object" ? lifecycleState.data : void 0;
270
273
  const resumeThreadId = resumeData?.threadId;
271
274
  const resumeThreadIdString = typeof resumeThreadId === "string" && resumeThreadId.length > 0 ? resumeThreadId : void 0;
275
+ const turnConfigurationFingerprint = typeof resumeData?.turnConfigurationFingerprint === "string" ? resumeData.turnConfigurationFingerprint : void 0;
272
276
  const coords = resumeData?.bridge;
273
277
  const authenticationMode = resolveCodexAuthenticationMode(settings.auth);
274
278
  const resolvedAuthEnvironment = resolveCodexEnv(settings.auth);
@@ -307,6 +311,10 @@ function createCodex(settings = {}) {
307
311
  CODEX_BOOTSTRAP_DIR
308
312
  );
309
313
  const workDir = startOpts.sessionWorkDir;
314
+ const sandboxHomeDir = await resolveSandboxHomeDir({
315
+ sandbox: toolSafeSandboxSession,
316
+ abortSignal: startOpts.abortSignal
317
+ });
310
318
  const sessionDataDir = `${defaultWorkingDirectory}/.agent-runs/${startOpts.sessionId}`;
311
319
  const bridgeStateDir = `${sessionDataDir}/bridge`;
312
320
  const cliShimDir = `${sessionDataDir}/codex`;
@@ -348,7 +356,7 @@ function createCodex(settings = {}) {
348
356
  cliShimPath,
349
357
  // The live bridge was spawned by another process; no process handle.
350
358
  proc: void 0,
351
- model: settings.model ?? DEFAULT_CODEX_MODEL,
359
+ model,
352
360
  reasoningEffort: settings.reasoningEffort,
353
361
  webSearch: settings.webSearch,
354
362
  codexConfig: settings.codexConfig,
@@ -362,7 +370,10 @@ function createCodex(settings = {}) {
362
370
  sandboxId,
363
371
  sandboxCredentialEnvironment,
364
372
  debug: startOpts.observability?.debug,
365
- permissionMode: startOpts.permissionMode
373
+ permissionMode: startOpts.permissionMode,
374
+ sandbox: toolSafeSandboxSession,
375
+ sandboxHomeDir,
376
+ turnConfigurationFingerprint
366
377
  });
367
378
  } catch {
368
379
  }
@@ -389,20 +400,11 @@ function createCodex(settings = {}) {
389
400
  credentialEnvironmentVariables: CODEX_CREDENTIAL_ENVIRONMENT_VARIABLES,
390
401
  credentialForwarding: settings.credentialForwarding
391
402
  });
392
- const codexSkillSetup = startOpts.skills && startOpts.skills.length > 0 ? await writeCodexSkills({
393
- sandbox: toolSafeSandboxSession,
394
- skills: startOpts.skills,
395
- abortSignal: startOpts.abortSignal
396
- }) : void 0;
397
403
  const env = {
398
404
  ...forwardedAuthEnvironment,
399
405
  AI_SDK_HARNESS_CLIENT_APP: CODEX_CLIENT_APP,
400
406
  BRIDGE_CHANNEL_TOKEN: token,
401
407
  BRIDGE_WS_PORT: String(port),
402
- ...codexSkillSetup ? {
403
- HOME: codexSkillSetup.homeDir,
404
- CODEX_HOME: codexSkillSetup.codexHomeDir
405
- } : {},
406
408
  ...respawnStrategy === "replay" ? { BRIDGE_REPLAY_FROM_DISK: "1" } : {}
407
409
  };
408
410
  if (respawnStrategy === void 0) {
@@ -476,7 +478,7 @@ function createCodex(settings = {}) {
476
478
  channel,
477
479
  cliShimPath,
478
480
  proc,
479
- model: settings.model ?? DEFAULT_CODEX_MODEL,
481
+ model,
480
482
  reasoningEffort: settings.reasoningEffort,
481
483
  webSearch: settings.webSearch,
482
484
  codexConfig: settings.codexConfig,
@@ -490,7 +492,10 @@ function createCodex(settings = {}) {
490
492
  sandboxId,
491
493
  sandboxCredentialEnvironment,
492
494
  debug: startOpts.observability?.debug,
493
- permissionMode: startOpts.permissionMode
495
+ permissionMode: startOpts.permissionMode,
496
+ sandbox: toolSafeSandboxSession,
497
+ sandboxHomeDir,
498
+ turnConfigurationFingerprint
494
499
  });
495
500
  }
496
501
  };
@@ -543,17 +548,12 @@ async function resolveBridgeEndpoint({
543
548
  }
544
549
  async function writeCodexSkills({
545
550
  sandbox,
551
+ sandboxHomeDir,
546
552
  skills,
547
553
  abortSignal
548
554
  }) {
549
- const homeDir = await resolveSandboxHomeDir({ sandbox, abortSignal });
550
- const codexHomeDir = path.posix.join(homeDir, ".codex");
551
- await sandbox.run({
552
- command: `mkdir -p ${shellQuote(codexHomeDir)}`,
553
- abortSignal
554
- });
555
- const rootDir = path.posix.join(homeDir, ".agents", "skills");
556
- await writeHarnessSkills({
555
+ const rootDir = path.posix.join(sandboxHomeDir, ".agents", "skills");
556
+ return writeHarnessSkills({
557
557
  sandbox,
558
558
  rootDir,
559
559
  skills,
@@ -561,10 +561,6 @@ async function writeCodexSkills({
561
561
  invalidSkillNameMessage: ({ name }) => `Invalid Codex skill name: ${name}`,
562
562
  invalidSkillFilePathMessage: ({ skillName, filePath }) => `Invalid Codex skill file path for ${skillName}: ${filePath}`
563
563
  });
564
- return {
565
- homeDir,
566
- codexHomeDir
567
- };
568
564
  }
569
565
  function openWebSocket({
570
566
  url,
@@ -613,16 +609,44 @@ function createSession({
613
609
  sandboxId,
614
610
  sandboxCredentialEnvironment,
615
611
  debug,
616
- permissionMode
612
+ permissionMode,
613
+ sandbox,
614
+ sandboxHomeDir,
615
+ turnConfigurationFingerprint
617
616
  }) {
618
617
  let stopped = false;
619
618
  let stopPromise;
620
619
  let pendingResumeThreadId = seedResumeThreadOnFirstPrompt ? resumeThreadId : void 0;
621
620
  let initialPromptGuidanceApplied = isResume;
622
621
  let latestThreadId = resumeThreadId;
622
+ let latestTurnConfigurationFingerprint = turnConfigurationFingerprint;
623
623
  channel.on("bridge-thread", (msg) => {
624
624
  latestThreadId = msg.threadId;
625
625
  });
626
+ const synchronizeTurnConfiguration = async ({
627
+ skills,
628
+ instructions,
629
+ tools,
630
+ abortSignal
631
+ }) => {
632
+ const skillsResult = await writeCodexSkills({
633
+ sandbox,
634
+ sandboxHomeDir,
635
+ skills,
636
+ abortSignal
637
+ });
638
+ const nextFingerprint = fingerprintCodexTurnConfiguration({
639
+ instructions,
640
+ tools
641
+ });
642
+ const restartThread = latestThreadId != null && (skillsResult.changed || latestTurnConfigurationFingerprint != null && latestTurnConfigurationFingerprint !== nextFingerprint);
643
+ latestTurnConfigurationFingerprint = nextFingerprint;
644
+ if (restartThread) {
645
+ initialPromptGuidanceApplied = false;
646
+ pendingResumeThreadId = void 0;
647
+ }
648
+ return { restartThread };
649
+ };
626
650
  const wireTurn = (turnOpts) => {
627
651
  let pendingResolve;
628
652
  let pendingReject;
@@ -749,7 +773,6 @@ function createSession({
749
773
  return {
750
774
  sessionId,
751
775
  isResume,
752
- modelId: model,
753
776
  doPromptTurn: async (promptOpts) => {
754
777
  if (promptOpts.responseFormat?.type === "json" && promptOpts.responseFormat.schema == null) {
755
778
  throw new HarnessCapabilityUnsupportedError({
@@ -757,15 +780,21 @@ function createSession({
757
780
  harnessId: "codex"
758
781
  });
759
782
  }
760
- const turn = wireTurn({
761
- emit: promptOpts.emit,
762
- abortSignal: promptOpts.abortSignal
763
- });
764
783
  const tools = (promptOpts.tools ?? []).map((t) => ({
765
784
  name: t.name,
766
785
  description: t.description,
767
786
  inputSchema: t.inputSchema
768
787
  }));
788
+ const { restartThread } = await synchronizeTurnConfiguration({
789
+ skills: promptOpts.skills,
790
+ instructions: promptOpts.instructions,
791
+ tools,
792
+ abortSignal: promptOpts.abortSignal
793
+ });
794
+ const turn = wireTurn({
795
+ emit: promptOpts.emit,
796
+ abortSignal: promptOpts.abortSignal
797
+ });
769
798
  let promptText = extractUserText(promptOpts.prompt);
770
799
  if (!initialPromptGuidanceApplied) {
771
800
  promptText = frameInitialPromptGuidance({
@@ -783,13 +812,14 @@ function createSession({
783
812
  tools,
784
813
  ...promptOpts.responseFormat == null ? {} : { responseFormat: promptOpts.responseFormat },
785
814
  ...promptOpts.instructions ? { instructions: promptOpts.instructions } : {},
786
- model,
815
+ model: promptOpts.model ?? model,
787
816
  reasoningEffort,
788
817
  webSearch,
789
818
  ...codexConfig == null ? {} : { codexConfig },
790
819
  ...mcpServers == null ? {} : { mcpServers },
791
820
  ...permissionMode ? { permissionMode } : {},
792
821
  ...pendingResumeThreadId ? { resumeThreadId: pendingResumeThreadId } : {},
822
+ ...restartThread ? { restartThread: true } : {},
793
823
  ...debug ? { debug } : {}
794
824
  };
795
825
  pendingResumeThreadId = void 0;
@@ -803,6 +833,17 @@ function createSession({
803
833
  harnessId: "codex"
804
834
  });
805
835
  }
836
+ const tools = (continueOpts.tools ?? []).map((t) => ({
837
+ name: t.name,
838
+ description: t.description,
839
+ inputSchema: t.inputSchema
840
+ }));
841
+ const { restartThread } = await synchronizeTurnConfiguration({
842
+ skills: continueOpts.skills,
843
+ instructions: continueOpts.instructions,
844
+ tools,
845
+ abortSignal: continueOpts.abortSignal
846
+ });
806
847
  const turn = wireTurn({
807
848
  emit: continueOpts.emit,
808
849
  abortSignal: continueOpts.abortSignal
@@ -821,20 +862,17 @@ function createSession({
821
862
  * empty text block trips the Anthropic API's `cache_control` rule).
822
863
  */
823
864
  prompt: "Continue.",
824
- tools: (continueOpts.tools ?? []).map((t) => ({
825
- name: t.name,
826
- description: t.description,
827
- inputSchema: t.inputSchema
828
- })),
865
+ tools,
829
866
  ...continueOpts.responseFormat == null ? {} : { responseFormat: continueOpts.responseFormat },
830
867
  ...continueOpts.instructions ? { instructions: continueOpts.instructions } : {},
831
- model,
868
+ model: continueOpts.model ?? model,
832
869
  reasoningEffort,
833
870
  webSearch,
834
871
  ...codexConfig == null ? {} : { codexConfig },
835
872
  ...mcpServers == null ? {} : { mcpServers },
836
873
  ...permissionMode ? { permissionMode } : {},
837
874
  ...threadId ? { resumeThreadId: threadId } : {},
875
+ ...restartThread ? { restartThread: true } : {},
838
876
  ...debug ? { debug } : {}
839
877
  })
840
878
  );
@@ -861,6 +899,9 @@ function createSession({
861
899
  specificationVersion: "harness-v1",
862
900
  data: {
863
901
  ...latestThreadId ? { threadId: latestThreadId } : {},
902
+ ...latestTurnConfigurationFingerprint ? {
903
+ turnConfigurationFingerprint: latestTurnConfigurationFingerprint
904
+ } : {},
864
905
  ...sandboxCredentialEnvironment == null ? {} : { sandboxCredentialEnvironment },
865
906
  bridge: {
866
907
  port: bridgePort,
@@ -962,6 +1003,9 @@ function createSession({
962
1003
  specificationVersion: "harness-v1",
963
1004
  data: {
964
1005
  ...lifecycleData,
1006
+ ...latestTurnConfigurationFingerprint ? {
1007
+ turnConfigurationFingerprint: latestTurnConfigurationFingerprint
1008
+ } : {},
965
1009
  ...sandboxCredentialEnvironment == null ? {} : { sandboxCredentialEnvironment }
966
1010
  }
967
1011
  };
@@ -981,6 +1025,9 @@ function createSession({
981
1025
  specificationVersion: "harness-v1",
982
1026
  data: {
983
1027
  ...latestThreadId ? { threadId: latestThreadId } : {},
1028
+ ...latestTurnConfigurationFingerprint ? {
1029
+ turnConfigurationFingerprint: latestTurnConfigurationFingerprint
1030
+ } : {},
984
1031
  ...sandboxCredentialEnvironment == null ? {} : { sandboxCredentialEnvironment },
985
1032
  bridge: {
986
1033
  port: bridgePort,
@@ -994,6 +1041,12 @@ function createSession({
994
1041
  }
995
1042
  };
996
1043
  }
1044
+ function fingerprintCodexTurnConfiguration({
1045
+ instructions,
1046
+ tools
1047
+ }) {
1048
+ return createHash("sha256").update(JSON.stringify({ instructions: instructions ?? null, tools })).digest("hex");
1049
+ }
997
1050
  function frameInitialPromptGuidance({
998
1051
  toolUsageBlock,
999
1052
  userText