@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.
- package/CHANGELOG.md +26 -0
- package/dist/bridge/index.mjs +32 -6
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/bridge/package.json +1 -1
- package/dist/bridge/pnpm-lock.yaml +34 -34
- package/dist/index.d.ts +3 -1
- package/dist/index.js +95 -42
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/bridge/index.ts +3 -1
- package/src/bridge/package.json +1 -1
- package/src/bridge/pnpm-lock.yaml +34 -34
- package/src/codex-bridge-protocol.ts +2 -1
- package/src/codex-harness.ts +141 -51
package/src/codex-harness.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { randomBytes } from 'node:crypto';
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import {
|
|
4
4
|
commonTool,
|
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
warnCredentialBrokeringUnavailable,
|
|
37
37
|
waitForBridgeReady,
|
|
38
38
|
writeSkills as writeHarnessSkills,
|
|
39
|
+
type WriteSkillsResult,
|
|
39
40
|
} from '@ai-sdk/harness/utils';
|
|
40
41
|
import {
|
|
41
42
|
type Experimental_SandboxProcess,
|
|
@@ -66,11 +67,6 @@ import { VERSION } from './version';
|
|
|
66
67
|
type CodexChannel = SandboxChannel<OutboundMessage, InboundMessage>;
|
|
67
68
|
type CodexRespawnStrategy = 'replay' | 'rerun';
|
|
68
69
|
|
|
69
|
-
type WriteSkillsResult = {
|
|
70
|
-
readonly homeDir: string;
|
|
71
|
-
readonly codexHomeDir: string;
|
|
72
|
-
};
|
|
73
|
-
|
|
74
70
|
/*
|
|
75
71
|
* This intentionally is not the latest Codex model. Newer GPT-5.6 models use
|
|
76
72
|
* Responses Lite, which does not expose their code-mode tools as callable
|
|
@@ -110,13 +106,15 @@ export type CodexHarnessSettings = {
|
|
|
110
106
|
/**
|
|
111
107
|
* OpenAI model id the underlying `codex` CLI should use. Leaving this unset
|
|
112
108
|
* pins the adapter default (`DEFAULT_CODEX_MODEL`).
|
|
109
|
+
*
|
|
110
|
+
* @deprecated Use `model` on `HarnessAgent` instead.
|
|
113
111
|
*/
|
|
114
112
|
readonly model?: string;
|
|
115
113
|
/**
|
|
116
114
|
* Reasoning effort for reasoning-capable models. Leaving this unset
|
|
117
115
|
* defers to the CLI's default.
|
|
118
116
|
*/
|
|
119
|
-
readonly reasoningEffort?: 'low' | 'medium' | 'high';
|
|
117
|
+
readonly reasoningEffort?: 'low' | 'medium' | 'high' | 'xhigh' | 'max';
|
|
120
118
|
/**
|
|
121
119
|
* When `true`, allow the underlying runtime to use live web search.
|
|
122
120
|
*/
|
|
@@ -189,6 +187,7 @@ const codexBridgeCoordsSchema = z.object({
|
|
|
189
187
|
*/
|
|
190
188
|
const codexResumeStateSchema = z.object({
|
|
191
189
|
threadId: z.string().optional(),
|
|
190
|
+
turnConfigurationFingerprint: z.string().optional(),
|
|
192
191
|
bridge: codexBridgeCoordsSchema.optional(),
|
|
193
192
|
sandboxCredentialEnvironment: z.record(z.string(), z.string()).optional(),
|
|
194
193
|
});
|
|
@@ -206,6 +205,7 @@ export function createCodex(
|
|
|
206
205
|
lifecycleStateSchema: codexResumeStateSchema,
|
|
207
206
|
getBootstrap: getCodexBootstrap,
|
|
208
207
|
doStart: async startOpts => {
|
|
208
|
+
const model = settings.model ?? DEFAULT_CODEX_MODEL;
|
|
209
209
|
if (startOpts.builtinToolFiltering != null) {
|
|
210
210
|
throw new HarnessCapabilityUnsupportedError({
|
|
211
211
|
message:
|
|
@@ -251,6 +251,7 @@ export function createCodex(
|
|
|
251
251
|
isResume && typeof lifecycleState?.data === 'object'
|
|
252
252
|
? (lifecycleState.data as {
|
|
253
253
|
threadId?: unknown;
|
|
254
|
+
turnConfigurationFingerprint?: unknown;
|
|
254
255
|
bridge?: CodexBridgeCoords;
|
|
255
256
|
sandboxCredentialEnvironment?: Record<string, string>;
|
|
256
257
|
})
|
|
@@ -260,6 +261,10 @@ export function createCodex(
|
|
|
260
261
|
typeof resumeThreadId === 'string' && resumeThreadId.length > 0
|
|
261
262
|
? resumeThreadId
|
|
262
263
|
: undefined;
|
|
264
|
+
const turnConfigurationFingerprint =
|
|
265
|
+
typeof resumeData?.turnConfigurationFingerprint === 'string'
|
|
266
|
+
? resumeData.turnConfigurationFingerprint
|
|
267
|
+
: undefined;
|
|
263
268
|
const coords = resumeData?.bridge;
|
|
264
269
|
const authenticationMode = resolveCodexAuthenticationMode(settings.auth);
|
|
265
270
|
const resolvedAuthEnvironment = resolveCodexEnv(settings.auth);
|
|
@@ -315,6 +320,10 @@ export function createCodex(
|
|
|
315
320
|
);
|
|
316
321
|
|
|
317
322
|
const workDir = startOpts.sessionWorkDir;
|
|
323
|
+
const sandboxHomeDir = await resolveSandboxHomeDir({
|
|
324
|
+
sandbox: toolSafeSandboxSession,
|
|
325
|
+
abortSignal: startOpts.abortSignal,
|
|
326
|
+
});
|
|
318
327
|
const sessionDataDir = `${defaultWorkingDirectory}/.agent-runs/${startOpts.sessionId}`;
|
|
319
328
|
const bridgeStateDir = `${sessionDataDir}/bridge`;
|
|
320
329
|
const cliShimDir = `${sessionDataDir}/codex`;
|
|
@@ -372,7 +381,7 @@ export function createCodex(
|
|
|
372
381
|
cliShimPath,
|
|
373
382
|
// The live bridge was spawned by another process; no process handle.
|
|
374
383
|
proc: undefined,
|
|
375
|
-
model
|
|
384
|
+
model,
|
|
376
385
|
reasoningEffort: settings.reasoningEffort,
|
|
377
386
|
webSearch: settings.webSearch,
|
|
378
387
|
codexConfig: settings.codexConfig,
|
|
@@ -387,6 +396,9 @@ export function createCodex(
|
|
|
387
396
|
sandboxCredentialEnvironment,
|
|
388
397
|
debug: startOpts.observability?.debug,
|
|
389
398
|
permissionMode: startOpts.permissionMode,
|
|
399
|
+
sandbox: toolSafeSandboxSession,
|
|
400
|
+
sandboxHomeDir,
|
|
401
|
+
turnConfigurationFingerprint,
|
|
390
402
|
});
|
|
391
403
|
} catch {
|
|
392
404
|
// Bridge no longer reachable — recover by respawning below.
|
|
@@ -432,25 +444,11 @@ export function createCodex(
|
|
|
432
444
|
CODEX_CREDENTIAL_ENVIRONMENT_VARIABLES,
|
|
433
445
|
credentialForwarding: settings.credentialForwarding,
|
|
434
446
|
});
|
|
435
|
-
const codexSkillSetup =
|
|
436
|
-
startOpts.skills && startOpts.skills.length > 0
|
|
437
|
-
? await writeCodexSkills({
|
|
438
|
-
sandbox: toolSafeSandboxSession,
|
|
439
|
-
skills: startOpts.skills,
|
|
440
|
-
abortSignal: startOpts.abortSignal,
|
|
441
|
-
})
|
|
442
|
-
: undefined;
|
|
443
447
|
const env = {
|
|
444
448
|
...forwardedAuthEnvironment,
|
|
445
449
|
AI_SDK_HARNESS_CLIENT_APP: CODEX_CLIENT_APP,
|
|
446
450
|
BRIDGE_CHANNEL_TOKEN: token,
|
|
447
451
|
BRIDGE_WS_PORT: String(port),
|
|
448
|
-
...(codexSkillSetup
|
|
449
|
-
? {
|
|
450
|
-
HOME: codexSkillSetup.homeDir,
|
|
451
|
-
CODEX_HOME: codexSkillSetup.codexHomeDir,
|
|
452
|
-
}
|
|
453
|
-
: {}),
|
|
454
452
|
...(respawnStrategy === 'replay'
|
|
455
453
|
? { BRIDGE_REPLAY_FROM_DISK: '1' }
|
|
456
454
|
: {}),
|
|
@@ -537,7 +535,7 @@ export function createCodex(
|
|
|
537
535
|
channel,
|
|
538
536
|
cliShimPath,
|
|
539
537
|
proc,
|
|
540
|
-
model
|
|
538
|
+
model,
|
|
541
539
|
reasoningEffort: settings.reasoningEffort,
|
|
542
540
|
webSearch: settings.webSearch,
|
|
543
541
|
codexConfig: settings.codexConfig,
|
|
@@ -552,6 +550,9 @@ export function createCodex(
|
|
|
552
550
|
sandboxCredentialEnvironment,
|
|
553
551
|
debug: startOpts.observability?.debug,
|
|
554
552
|
permissionMode: startOpts.permissionMode,
|
|
553
|
+
sandbox: toolSafeSandboxSession,
|
|
554
|
+
sandboxHomeDir,
|
|
555
|
+
turnConfigurationFingerprint,
|
|
555
556
|
});
|
|
556
557
|
},
|
|
557
558
|
};
|
|
@@ -624,22 +625,17 @@ async function resolveBridgeEndpoint({
|
|
|
624
625
|
|
|
625
626
|
async function writeCodexSkills({
|
|
626
627
|
sandbox,
|
|
628
|
+
sandboxHomeDir,
|
|
627
629
|
skills,
|
|
628
630
|
abortSignal,
|
|
629
631
|
}: {
|
|
630
632
|
sandbox: SandboxSession;
|
|
633
|
+
sandboxHomeDir: string;
|
|
631
634
|
skills: ReadonlyArray<HarnessV1Skill>;
|
|
632
635
|
abortSignal?: AbortSignal;
|
|
633
636
|
}): Promise<WriteSkillsResult> {
|
|
634
|
-
const
|
|
635
|
-
|
|
636
|
-
await sandbox.run({
|
|
637
|
-
command: `mkdir -p ${shellQuote(codexHomeDir)}`,
|
|
638
|
-
abortSignal,
|
|
639
|
-
});
|
|
640
|
-
|
|
641
|
-
const rootDir = path.posix.join(homeDir, '.agents', 'skills');
|
|
642
|
-
await writeHarnessSkills({
|
|
637
|
+
const rootDir = path.posix.join(sandboxHomeDir, '.agents', 'skills');
|
|
638
|
+
return writeHarnessSkills({
|
|
643
639
|
sandbox,
|
|
644
640
|
rootDir,
|
|
645
641
|
skills,
|
|
@@ -648,11 +644,6 @@ async function writeCodexSkills({
|
|
|
648
644
|
invalidSkillFilePathMessage: ({ skillName, filePath }) =>
|
|
649
645
|
`Invalid Codex skill file path for ${skillName}: ${filePath}`,
|
|
650
646
|
});
|
|
651
|
-
|
|
652
|
-
return {
|
|
653
|
-
homeDir,
|
|
654
|
-
codexHomeDir,
|
|
655
|
-
};
|
|
656
647
|
}
|
|
657
648
|
|
|
658
649
|
function openWebSocket({
|
|
@@ -708,6 +699,9 @@ function createSession({
|
|
|
708
699
|
sandboxCredentialEnvironment,
|
|
709
700
|
debug,
|
|
710
701
|
permissionMode,
|
|
702
|
+
sandbox,
|
|
703
|
+
sandboxHomeDir,
|
|
704
|
+
turnConfigurationFingerprint,
|
|
711
705
|
}: {
|
|
712
706
|
sessionId: string;
|
|
713
707
|
channel: CodexChannel;
|
|
@@ -715,7 +709,7 @@ function createSession({
|
|
|
715
709
|
/** Undefined on `attach` — the live bridge was spawned by another process. */
|
|
716
710
|
proc: Experimental_SandboxProcess | undefined;
|
|
717
711
|
model: string | undefined;
|
|
718
|
-
reasoningEffort: 'low' | 'medium' | 'high' | undefined;
|
|
712
|
+
reasoningEffort: 'low' | 'medium' | 'high' | 'xhigh' | 'max' | undefined;
|
|
719
713
|
webSearch: boolean | undefined;
|
|
720
714
|
codexConfig: Record<string, unknown> | undefined;
|
|
721
715
|
mcpServers: Record<string, unknown> | undefined;
|
|
@@ -729,6 +723,9 @@ function createSession({
|
|
|
729
723
|
sandboxCredentialEnvironment: Record<string, string> | undefined;
|
|
730
724
|
debug: HarnessV1DebugConfig | undefined;
|
|
731
725
|
permissionMode: HarnessV1PermissionMode | undefined;
|
|
726
|
+
sandbox: SandboxSession;
|
|
727
|
+
sandboxHomeDir: string;
|
|
728
|
+
turnConfigurationFingerprint: string | undefined;
|
|
732
729
|
}): HarnessV1Session {
|
|
733
730
|
let stopped = false;
|
|
734
731
|
let stopPromise: Promise<void> | undefined;
|
|
@@ -755,10 +752,55 @@ function createSession({
|
|
|
755
752
|
* can include a thread id even before this process has run a turn.
|
|
756
753
|
*/
|
|
757
754
|
let latestThreadId = resumeThreadId;
|
|
755
|
+
let latestTurnConfigurationFingerprint = turnConfigurationFingerprint;
|
|
758
756
|
channel.on('bridge-thread', msg => {
|
|
759
757
|
latestThreadId = msg.threadId;
|
|
760
758
|
});
|
|
761
759
|
|
|
760
|
+
const synchronizeTurnConfiguration = async ({
|
|
761
|
+
skills,
|
|
762
|
+
instructions,
|
|
763
|
+
tools,
|
|
764
|
+
abortSignal,
|
|
765
|
+
}: {
|
|
766
|
+
skills: ReadonlyArray<HarnessV1Skill>;
|
|
767
|
+
instructions: string | undefined;
|
|
768
|
+
tools: ReadonlyArray<{
|
|
769
|
+
name: string;
|
|
770
|
+
description?: string;
|
|
771
|
+
inputSchema: unknown;
|
|
772
|
+
}>;
|
|
773
|
+
abortSignal?: AbortSignal;
|
|
774
|
+
}): Promise<{ restartThread: boolean }> => {
|
|
775
|
+
const skillsResult = await writeCodexSkills({
|
|
776
|
+
sandbox,
|
|
777
|
+
sandboxHomeDir,
|
|
778
|
+
skills,
|
|
779
|
+
abortSignal,
|
|
780
|
+
});
|
|
781
|
+
const nextFingerprint = fingerprintCodexTurnConfiguration({
|
|
782
|
+
instructions,
|
|
783
|
+
tools,
|
|
784
|
+
});
|
|
785
|
+
const restartThread =
|
|
786
|
+
latestThreadId != null &&
|
|
787
|
+
(skillsResult.changed ||
|
|
788
|
+
(latestTurnConfigurationFingerprint != null &&
|
|
789
|
+
latestTurnConfigurationFingerprint !== nextFingerprint));
|
|
790
|
+
latestTurnConfigurationFingerprint = nextFingerprint;
|
|
791
|
+
if (restartThread) {
|
|
792
|
+
/*
|
|
793
|
+
* `codex exec resume` retains the native thread's original developer
|
|
794
|
+
* instructions and skill catalog. A fresh native thread is therefore
|
|
795
|
+
* required for replacement semantics. Host-tool guidance is framed
|
|
796
|
+
* again because that guidance also belongs to the new native thread.
|
|
797
|
+
*/
|
|
798
|
+
initialPromptGuidanceApplied = false;
|
|
799
|
+
pendingResumeThreadId = undefined;
|
|
800
|
+
}
|
|
801
|
+
return { restartThread };
|
|
802
|
+
};
|
|
803
|
+
|
|
762
804
|
/*
|
|
763
805
|
* Wire the channel into one turn's worth of events and return the control
|
|
764
806
|
* surface. Shared by `doPromptTurn` (which sends a `start` afterwards) and
|
|
@@ -916,7 +958,6 @@ function createSession({
|
|
|
916
958
|
return {
|
|
917
959
|
sessionId,
|
|
918
960
|
isResume,
|
|
919
|
-
modelId: model,
|
|
920
961
|
doPromptTurn: async promptOpts => {
|
|
921
962
|
if (
|
|
922
963
|
promptOpts.responseFormat?.type === 'json' &&
|
|
@@ -928,16 +969,22 @@ function createSession({
|
|
|
928
969
|
harnessId: 'codex',
|
|
929
970
|
});
|
|
930
971
|
}
|
|
931
|
-
const turn = wireTurn({
|
|
932
|
-
emit: promptOpts.emit,
|
|
933
|
-
abortSignal: promptOpts.abortSignal,
|
|
934
|
-
});
|
|
935
|
-
|
|
936
972
|
const tools = (promptOpts.tools ?? []).map(t => ({
|
|
937
973
|
name: t.name,
|
|
938
974
|
description: t.description,
|
|
939
975
|
inputSchema: t.inputSchema,
|
|
940
976
|
}));
|
|
977
|
+
const { restartThread } = await synchronizeTurnConfiguration({
|
|
978
|
+
skills: promptOpts.skills,
|
|
979
|
+
instructions: promptOpts.instructions,
|
|
980
|
+
tools,
|
|
981
|
+
abortSignal: promptOpts.abortSignal,
|
|
982
|
+
});
|
|
983
|
+
const turn = wireTurn({
|
|
984
|
+
emit: promptOpts.emit,
|
|
985
|
+
abortSignal: promptOpts.abortSignal,
|
|
986
|
+
});
|
|
987
|
+
|
|
941
988
|
let promptText = extractUserText(promptOpts.prompt);
|
|
942
989
|
if (!initialPromptGuidanceApplied) {
|
|
943
990
|
promptText = frameInitialPromptGuidance({
|
|
@@ -963,7 +1010,7 @@ function createSession({
|
|
|
963
1010
|
...(promptOpts.instructions
|
|
964
1011
|
? { instructions: promptOpts.instructions }
|
|
965
1012
|
: {}),
|
|
966
|
-
model,
|
|
1013
|
+
model: promptOpts.model ?? model,
|
|
967
1014
|
reasoningEffort,
|
|
968
1015
|
webSearch,
|
|
969
1016
|
...(codexConfig == null ? {} : { codexConfig }),
|
|
@@ -972,6 +1019,7 @@ function createSession({
|
|
|
972
1019
|
...(pendingResumeThreadId
|
|
973
1020
|
? { resumeThreadId: pendingResumeThreadId }
|
|
974
1021
|
: {}),
|
|
1022
|
+
...(restartThread ? { restartThread: true } : {}),
|
|
975
1023
|
...(debug ? { debug } : {}),
|
|
976
1024
|
};
|
|
977
1025
|
pendingResumeThreadId = undefined;
|
|
@@ -990,6 +1038,17 @@ function createSession({
|
|
|
990
1038
|
harnessId: 'codex',
|
|
991
1039
|
});
|
|
992
1040
|
}
|
|
1041
|
+
const tools = (continueOpts.tools ?? []).map(t => ({
|
|
1042
|
+
name: t.name,
|
|
1043
|
+
description: t.description,
|
|
1044
|
+
inputSchema: t.inputSchema,
|
|
1045
|
+
}));
|
|
1046
|
+
const { restartThread } = await synchronizeTurnConfiguration({
|
|
1047
|
+
skills: continueOpts.skills,
|
|
1048
|
+
instructions: continueOpts.instructions,
|
|
1049
|
+
tools,
|
|
1050
|
+
abortSignal: continueOpts.abortSignal,
|
|
1051
|
+
});
|
|
993
1052
|
const turn = wireTurn({
|
|
994
1053
|
emit: continueOpts.emit,
|
|
995
1054
|
abortSignal: continueOpts.abortSignal,
|
|
@@ -1021,24 +1080,21 @@ function createSession({
|
|
|
1021
1080
|
* empty text block trips the Anthropic API's `cache_control` rule).
|
|
1022
1081
|
*/
|
|
1023
1082
|
prompt: 'Continue.',
|
|
1024
|
-
tools
|
|
1025
|
-
name: t.name,
|
|
1026
|
-
description: t.description,
|
|
1027
|
-
inputSchema: t.inputSchema,
|
|
1028
|
-
})),
|
|
1083
|
+
tools,
|
|
1029
1084
|
...(continueOpts.responseFormat == null
|
|
1030
1085
|
? {}
|
|
1031
1086
|
: { responseFormat: continueOpts.responseFormat }),
|
|
1032
1087
|
...(continueOpts.instructions
|
|
1033
1088
|
? { instructions: continueOpts.instructions }
|
|
1034
1089
|
: {}),
|
|
1035
|
-
model,
|
|
1090
|
+
model: continueOpts.model ?? model,
|
|
1036
1091
|
reasoningEffort,
|
|
1037
1092
|
webSearch,
|
|
1038
1093
|
...(codexConfig == null ? {} : { codexConfig }),
|
|
1039
1094
|
...(mcpServers == null ? {} : { mcpServers }),
|
|
1040
1095
|
...(permissionMode ? { permissionMode } : {}),
|
|
1041
1096
|
...(threadId ? { resumeThreadId: threadId } : {}),
|
|
1097
|
+
...(restartThread ? { restartThread: true } : {}),
|
|
1042
1098
|
...(debug ? { debug } : {}),
|
|
1043
1099
|
}),
|
|
1044
1100
|
);
|
|
@@ -1074,6 +1130,12 @@ function createSession({
|
|
|
1074
1130
|
specificationVersion: 'harness-v1',
|
|
1075
1131
|
data: {
|
|
1076
1132
|
...(latestThreadId ? { threadId: latestThreadId } : {}),
|
|
1133
|
+
...(latestTurnConfigurationFingerprint
|
|
1134
|
+
? {
|
|
1135
|
+
turnConfigurationFingerprint:
|
|
1136
|
+
latestTurnConfigurationFingerprint,
|
|
1137
|
+
}
|
|
1138
|
+
: {}),
|
|
1077
1139
|
...(sandboxCredentialEnvironment == null
|
|
1078
1140
|
? {}
|
|
1079
1141
|
: { sandboxCredentialEnvironment }),
|
|
@@ -1195,6 +1257,12 @@ function createSession({
|
|
|
1195
1257
|
specificationVersion: 'harness-v1',
|
|
1196
1258
|
data: {
|
|
1197
1259
|
...lifecycleData,
|
|
1260
|
+
...(latestTurnConfigurationFingerprint
|
|
1261
|
+
? {
|
|
1262
|
+
turnConfigurationFingerprint:
|
|
1263
|
+
latestTurnConfigurationFingerprint,
|
|
1264
|
+
}
|
|
1265
|
+
: {}),
|
|
1198
1266
|
...(sandboxCredentialEnvironment == null
|
|
1199
1267
|
? {}
|
|
1200
1268
|
: { sandboxCredentialEnvironment }),
|
|
@@ -1224,6 +1292,12 @@ function createSession({
|
|
|
1224
1292
|
specificationVersion: 'harness-v1',
|
|
1225
1293
|
data: {
|
|
1226
1294
|
...(latestThreadId ? { threadId: latestThreadId } : {}),
|
|
1295
|
+
...(latestTurnConfigurationFingerprint
|
|
1296
|
+
? {
|
|
1297
|
+
turnConfigurationFingerprint:
|
|
1298
|
+
latestTurnConfigurationFingerprint,
|
|
1299
|
+
}
|
|
1300
|
+
: {}),
|
|
1227
1301
|
...(sandboxCredentialEnvironment == null
|
|
1228
1302
|
? {}
|
|
1229
1303
|
: { sandboxCredentialEnvironment }),
|
|
@@ -1240,6 +1314,22 @@ function createSession({
|
|
|
1240
1314
|
};
|
|
1241
1315
|
}
|
|
1242
1316
|
|
|
1317
|
+
function fingerprintCodexTurnConfiguration({
|
|
1318
|
+
instructions,
|
|
1319
|
+
tools,
|
|
1320
|
+
}: {
|
|
1321
|
+
instructions: string | undefined;
|
|
1322
|
+
tools: ReadonlyArray<{
|
|
1323
|
+
name: string;
|
|
1324
|
+
description?: string;
|
|
1325
|
+
inputSchema: unknown;
|
|
1326
|
+
}>;
|
|
1327
|
+
}): string {
|
|
1328
|
+
return createHash('sha256')
|
|
1329
|
+
.update(JSON.stringify({ instructions: instructions ?? null, tools }))
|
|
1330
|
+
.digest('hex');
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1243
1333
|
/*
|
|
1244
1334
|
* Frame host-tool relay guidance and the user's text so Codex treats the
|
|
1245
1335
|
* prepended block as operating guidance rather than user prose. Applied only
|