Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 70 additions & 11 deletions apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,8 @@ describe("CodexAdapterV2 runtime policy", () => {
},
});
assert.equal(params.effort, reasoningEffort);
// Some Responses providers reject `detailed`; `auto` is the most detailed a model supports.
assert.equal(params.summary, "auto");
assert.equal(params.additionalContext !== undefined, hasT3Mcp === true);
}
}
Expand Down Expand Up @@ -683,7 +685,7 @@ function codexReplayPreamble(input: {
approvalPolicy: "never",
approvalsReviewer: "user",
sandboxPolicy: { type: "dangerFullAccess" },
summary: "detailed",
summary: "auto",
...(input.effort === undefined ? {} : { effort: input.effort }),
collaborationMode: {
mode: "default",
Expand Down Expand Up @@ -717,6 +719,61 @@ function codexReplayPreamble(input: {
];
}

/** Every turn/start follows a config/read; inserts that exchange and shifts later client ids. */
function withConfigReadBeforeTurnStart(
entries: ReadonlyArray<CodexReplay.CodexAppServerReplayEntry>,
): Array<CodexReplay.CodexAppServerReplayEntry> {
const result: Array<CodexReplay.CodexAppServerReplayEntry> = [];
let offset = 0;
let clientIds = new Map<number, number>();
for (const entry of entries) {
const frame = entry.type === "runtime_exit" ? undefined : entry.frame;
if (!Predicate.isObject(frame) || typeof frame.id !== "number") {
result.push(entry);
continue;
}
if (entry.type === "expect_outbound" && typeof frame.method === "string") {
if (frame.method === "initialize") {
offset = 0;
clientIds = new Map();
}
if (frame.method === "turn/start") {
const id = frame.id + offset;
const params = Predicate.isObject(frame.params) ? frame.params : {};
result.push(
{
type: "expect_outbound",
label: "config/read",
frame: { id, method: "config/read", params: { cwd: params.cwd, includeLayers: false } },
},
{
type: "emit_inbound",
label: "config/read",
frame: { id, result: { config: {}, origins: {} } },
},
);
offset += 1;
}
clientIds.set(frame.id, frame.id + offset);
result.push(withReplayRequestId(entry, frame.id + offset));
continue;
}
const mapped = clientIds.get(frame.id);
result.push(
entry.type === "emit_inbound" && frame.method === undefined && mapped !== undefined
? withReplayRequestId(entry, mapped)
: entry,
);
}
return result;
}

function withConfigReads(
transcript: CodexReplay.CodexAppServerReplayTranscript,
): CodexReplay.CodexAppServerReplayTranscript {
return { ...transcript, entries: withConfigReadBeforeTurnStart(transcript.entries) };
}

function makeCodexReplayTranscript(input: {
readonly scenario: string;
readonly entries: ReadonlyArray<CodexReplay.CodexAppServerReplayEntry>;
Expand Down Expand Up @@ -757,7 +814,9 @@ describe("CodexAdapterV2 process exit", () => {
const turnStarted = yield* Deferred.make<void>();
const turnCompleted = yield* Deferred.make<void>();
const events: Array<ProviderAdapter.ProviderAdapterV2Event> = [];
const preamble = codexReplayPreamble({ nativeThreadId, nativeTurnId, prompt: "work" });
const preamble = withConfigReadBeforeTurnStart(
codexReplayPreamble({ nativeThreadId, nativeTurnId, prompt: "work" }),
);
const terminationError =
exit === "numeric"
? new CodexError.CodexAppServerProcessExitedError({ code: 17, pid: 52 })
Expand Down Expand Up @@ -962,7 +1021,7 @@ describe("CodexAdapterV2 session initialize", () => {
) =>
Effect.gen(function* () {
const driver = yield* CodexReplay.makeReplayDriver(
transcript,
withConfigReads(transcript),
beforeEmitInbound === undefined ? {} : { beforeEmitInbound },
);
let initializeRequests = 0;
Expand Down Expand Up @@ -1254,7 +1313,7 @@ describe("CodexAdapterV2 post-settle continuation", () => {
const continuationRequests: Array<ProviderContinuationRequests.ProviderContinuationRequest> =
[];
const driver = yield* CodexReplay.makeReplayDriver(
transcript,
withConfigReads(transcript),
beforeEmitInbound === undefined ? {} : { beforeEmitInbound },
);
const clientFactory: CodexAdapterV2.CodexAppServerClientFactoryShape = {
Expand Down Expand Up @@ -2465,7 +2524,7 @@ describe("CodexAdapterV2 post-settle continuation", () => {
cwd: "/workspace",
model: "gpt-5.4",
...turnPolicies[turnModes[index]!],
summary: "detailed",
summary: "auto",
collaborationMode: { mode: "default", settings: { model: "gpt-5.4" } },
},
},
Expand Down Expand Up @@ -3553,7 +3612,7 @@ describe("CodexAdapterV2 post-settle continuation", () => {
approvalPolicy: "never",
approvalsReviewer: "user",
sandboxPolicy: { type: "dangerFullAccess" },
summary: "detailed",
summary: "auto",
collaborationMode: { mode: "default", settings: { model: "gpt-5.4" } },
},
},
Expand Down Expand Up @@ -3807,7 +3866,7 @@ describe("CodexAdapterV2 post-settle continuation", () => {
makeCodexReplayTranscript({ scenario, entries }),
)).replaceAll(yield* encodeStringJson("/workspace"), yield* encodeStringJson(cwd)),
);
const driver = yield* CodexReplay.makeReplayDriver(localTranscript);
const driver = yield* CodexReplay.makeReplayDriver(withConfigReads(localTranscript));
yield* Effect.gen(function* () {
const orchestrator = yield* Orchestrator.OrchestratorV2;
const worker = yield* EffectWorker.OrchestrationEffectWorkerV2;
Expand Down Expand Up @@ -5443,7 +5502,7 @@ describe("CodexAdapterV2 post-settle continuation", () => {
yield* encodeStringJson(cwd),
),
);
const replayDriver = yield* CodexReplay.makeReplayDriver(localTranscript);
const replayDriver = yield* CodexReplay.makeReplayDriver(withConfigReads(localTranscript));
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
assert.equal(
Number(yield* spawner.exitCode(ChildProcess.make("git", ["init", "--quiet"], { cwd }))),
Expand Down Expand Up @@ -5666,7 +5725,7 @@ describe("CodexAdapterV2 post-settle continuation", () => {
Effect.provide(
ProviderReplayHarness.layerWithRegistry(
{ name: "codex-background-stop-untracked", runtimePolicyOverride: { cwd } },
CodexAdapterV2Testkit.layer({ transcript: localTranscript }),
CodexAdapterV2Testkit.layer({ transcript: withConfigReads(localTranscript) }),
{ runEffectWorker: false },
),
),
Expand Down Expand Up @@ -7932,7 +7991,7 @@ describe("CodexAdapterV2 post-settle continuation", () => {
approvalPolicy: "never",
approvalsReviewer: "user",
sandboxPolicy: { type: "dangerFullAccess" },
summary: "detailed",
summary: "auto",
collaborationMode: { mode: "default", settings: { model: "gpt-5.4" } },
},
},
Expand Down Expand Up @@ -8080,7 +8139,7 @@ describe("CodexAdapterV2 post-settle continuation", () => {
approvalPolicy: "never",
approvalsReviewer: "user",
sandboxPolicy: { type: "dangerFullAccess" },
summary: "detailed",
summary: "auto",
collaborationMode: { mode: "default", settings: { model: "gpt-5.4" } },
},
},
Expand Down
16 changes: 14 additions & 2 deletions apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,8 @@ export const buildCodexTurnStartParams = Effect.fn("CodexAdapterV2.buildCodexTur
/** ChatGPT token sharing does not accept service tiers. */
readonly omitServiceTier?: boolean;
readonly appContext?: ProviderAdapter.ProviderAdapterV2TurnInput["appContext"];
/** The user's `model_reasoning_summary` from the Codex config, when set. */
readonly configuredReasoningSummary?: CodexSchema.V2TurnStartParams__ReasoningSummary;
}) {
const runtimeModeDefaults = codexRuntimeModeTurnDefaults(input.runtimePolicy.runtimeMode);
const approvalPolicy =
Expand Down Expand Up @@ -828,8 +830,9 @@ export const buildCodexTurnStartParams = Effect.fn("CodexAdapterV2.buildCodexTur
...(additionalContext ? { additionalContext } : {}),
cwd: input.runtimePolicy.cwd,
model: input.modelSelection.model,
// Catalogues can default summaries to "none"; request them explicitly on every turn.
summary: "detailed",
// Catalogues can default summaries to "none", and turn/start overrides the user's config,
// so pass theirs through or `auto`: some providers (Amazon Bedrock) reject `detailed`.
summary: input.configuredReasoningSummary ?? "auto",
// Omitting the reviewer on resume preserves the previous Auto reviewer.
approvalsReviewer: runtimeModeDefaults.approvalsReviewer,
...(approvalPolicy === undefined ? {} : { approvalPolicy }),
Expand Down Expand Up @@ -7237,6 +7240,14 @@ export const makeCodexAdapterV2 = Effect.fn("makeCodexAdapterV2")(function* (
) {
const threadId = yield* getNativeThreadId(turnInput.providerThread);
const mcpSession = yield* mcpSessions.read(turnInput.threadId);
const configuredReasoningSummary = yield* client
.request("config/read", { cwd: turnInput.runtimePolicy.cwd, includeLayers: false })
.pipe(
Effect.map((response) => response.config.model_reasoning_summary ?? undefined),
Effect.catch(() =>
Effect.logDebug("Codex config read failed.").pipe(Effect.as(undefined)),
),
);
const turnStartParams = yield* buildCodexTurnStartParams({
nativeThreadId: threadId,
codexInput,
Expand All @@ -7247,6 +7258,7 @@ export const makeCodexAdapterV2 = Effect.fn("makeCodexAdapterV2")(function* (
deviceToolsAvailable: mcpSession?.capabilities?.has("device") ?? false,
omitServiceTier: adapterOptions.resolveRuntime !== undefined,
...(turnInput.appContext === undefined ? {} : { appContext: turnInput.appContext }),
...(configuredReasoningSummary === undefined ? {} : { configuredReasoningSummary }),
});
yield* Ref.update(pendingRootTurns, (current) => {
const updated = new Map(current);
Expand Down
Loading
Loading