Skip to content

Commit f623fc0

Browse files
committed
fix(codex): surface pre-start compaction metadata
1 parent 8810fc9 commit f623fc0

2 files changed

Lines changed: 43 additions & 30 deletions

File tree

extensions/codex/src/app-server/run-attempt.context-engine.test.ts

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1448,9 +1448,9 @@ describe("runCodexAppServerAttempt context-engine lifecycle", () => {
14481448
expect(assemble).toHaveBeenCalledTimes(2);
14491449
const inputText = getRequestInputText(harness);
14501450
expect(inputText).toContain("successor compacted context");
1451-
expect(optionalString(requireRequestParams(harness, "thread/start").developerInstructions)).toContain(
1452-
"compacted context system",
1453-
);
1451+
expect(
1452+
optionalString(requireRequestParams(harness, "thread/start").developerInstructions),
1453+
).toContain("compacted context system");
14541454

14551455
await harness.completeTurn();
14561456
await run;
@@ -1473,14 +1473,13 @@ describe("runCodexAppServerAttempt context-engine lifecycle", () => {
14731473
summary: "summary",
14741474
firstKeptEntryId: "entry-1",
14751475
tokensBefore: 100_000,
1476+
tokensAfter: 12_345.8,
14761477
sessionId: "session-1-compacted",
14771478
sessionFile: successorSessionFile,
14781479
},
14791480
}));
14801481
const assemble = vi.fn<ContextEngine["assemble"]>().mockResolvedValue({
1481-
messages: Array.from({ length: 8 }, (_, index) =>
1482-
toolResultMessage(hugePayload, index + 1),
1483-
),
1482+
messages: Array.from({ length: 8 }, (_, index) => toolResultMessage(hugePayload, index + 1)),
14841483
estimatedTokens: 100_000,
14851484
contextProjection: { mode: "thread_bootstrap", epoch: "epoch-before" },
14861485
});
@@ -1498,6 +1497,8 @@ describe("runCodexAppServerAttempt context-engine lifecycle", () => {
14981497
expect(result.preflightRecovery?.route).not.toBe("fits");
14991498
expect(result.sessionIdUsed).toBe("session-1-compacted");
15001499
expect(result.sessionFileUsed).toBe(successorSessionFile);
1500+
expect(result.compactionCount).toBe(1);
1501+
expect(result.compactionTokensAfter).toBe(12_345);
15011502
expect(compact).toHaveBeenCalledTimes(1);
15021503
expect(assemble).toHaveBeenCalledTimes(2);
15031504
expect(harness.requests.map((request) => request.method)).not.toContain("turn/start");
@@ -1518,9 +1519,7 @@ describe("runCodexAppServerAttempt context-engine lifecycle", () => {
15181519
result: { summary: "unchanged", firstKeptEntryId: "entry-1", tokensBefore: 100_000 },
15191520
}));
15201521
const assemble = vi.fn<ContextEngine["assemble"]>().mockResolvedValue({
1521-
messages: Array.from({ length: 8 }, (_, index) =>
1522-
toolResultMessage(hugePayload, index + 1),
1523-
),
1522+
messages: Array.from({ length: 8 }, (_, index) => toolResultMessage(hugePayload, index + 1)),
15241523
estimatedTokens: 100_000,
15251524
contextProjection: { mode: "thread_bootstrap", epoch: "epoch-before" },
15261525
});
@@ -1572,9 +1571,7 @@ describe("runCodexAppServerAttempt context-engine lifecycle", () => {
15721571
}),
15731572
);
15741573
const assemble = vi.fn<ContextEngine["assemble"]>().mockResolvedValue({
1575-
messages: Array.from({ length: 8 }, (_, index) =>
1576-
toolResultMessage(hugePayload, index + 1),
1577-
),
1574+
messages: Array.from({ length: 8 }, (_, index) => toolResultMessage(hugePayload, index + 1)),
15781575
estimatedTokens: 100_000,
15791576
contextProjection: { mode: "thread_bootstrap", epoch: "epoch-before" },
15801577
});

extensions/codex/src/app-server/run-attempt.ts

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1097,7 +1097,9 @@ function shouldAwaitCodexAgentEndHook(params: EmbeddedRunAttemptParams): boolean
10971097
return !params.messageChannel && !params.messageProvider;
10981098
}
10991099

1100-
type ContextEngineProviderBoundaryPrecheck = ReturnType<typeof shouldPreemptivelyCompactBeforePrompt>;
1100+
type ContextEngineProviderBoundaryPrecheck = ReturnType<
1101+
typeof shouldPreemptivelyCompactBeforePrompt
1102+
>;
11011103
type ContextEngineProviderBoundaryOverflowPrecheck = ContextEngineProviderBoundaryPrecheck & {
11021104
route: Exclude<ContextEngineProviderBoundaryPrecheck["route"], "fits">;
11031105
};
@@ -1439,12 +1441,16 @@ export async function runCodexAppServerAttempt(
14391441
contextEnginePluginId: activeContextEnginePluginId,
14401442
tokenBudget: params.contextTokenBudget,
14411443
});
1444+
type ContextEngineOverflowCompactionResult = Pick<
1445+
EmbeddedRunAttemptResult,
1446+
"compactionCount" | "compactionTokensAfter"
1447+
>;
14421448
const forceContextEngineCompactionForCodexOverflow = async (
14431449
error: unknown,
14441450
options: { threadId?: string } = {},
1445-
): Promise<boolean> => {
1451+
): Promise<ContextEngineOverflowCompactionResult | undefined> => {
14461452
if (!activeContextEngine?.info.ownsCompaction) {
1447-
return false;
1453+
return undefined;
14481454
}
14491455
embeddedAgentLog.warn(
14501456
"codex app-server context-engine prompt overflowed; forcing context-engine compaction",
@@ -1491,7 +1497,7 @@ export async function runCodexAppServerAttempt(
14911497
tokensAfter: compactResult.result?.tokensAfter,
14921498
});
14931499
if (!compactResult.ok || !compactResult.compacted) {
1494-
return false;
1500+
return undefined;
14951501
}
14961502
adoptContextEngineCompactionTranscript(compactResult);
14971503
await runHarnessContextEngineMaintenance({
@@ -1503,7 +1509,13 @@ export async function runCodexAppServerAttempt(
15031509
runtimeContext: buildActiveContextEngineRuntimeContext(),
15041510
config: params.config,
15051511
});
1506-
return true;
1512+
const tokensAfter = compactResult.result?.tokensAfter;
1513+
return {
1514+
compactionCount: 1,
1515+
...(typeof tokensAfter === "number" && Number.isFinite(tokensAfter) && tokensAfter >= 0
1516+
? { compactionTokensAfter: Math.floor(tokensAfter) }
1517+
: {}),
1518+
};
15071519
} catch (compactErr) {
15081520
if (runAbortController.signal.aborted || isAbortLikeError(compactErr)) {
15091521
throw compactErr;
@@ -1514,7 +1526,7 @@ export async function runCodexAppServerAttempt(
15141526
engineId: activeContextEngine.info.id,
15151527
error: formatErrorMessage(compactErr),
15161528
});
1517-
return false;
1529+
return undefined;
15181530
}
15191531
};
15201532
if (activeContextEngine) {
@@ -1820,6 +1832,7 @@ export async function runCodexAppServerAttempt(
18201832
const buildPreStartPromptErrorResult = (
18211833
message: string,
18221834
preflightRecovery?: EmbeddedRunAttemptResult["preflightRecovery"],
1835+
compactionResult?: ContextEngineOverflowCompactionResult,
18231836
): EmbeddedRunAttemptResult => {
18241837
const activeAttempt = buildActiveRunAttemptParams();
18251838
return {
@@ -1832,9 +1845,8 @@ export async function runCodexAppServerAttempt(
18321845
],
18331846
systemPromptReport: buildCurrentSystemPromptReport(activeAttempt),
18341847
}),
1835-
...(preflightRecovery
1836-
? { promptErrorSource: "precheck" as const, preflightRecovery }
1837-
: {}),
1848+
...compactionResult,
1849+
...(preflightRecovery ? { promptErrorSource: "precheck" as const, preflightRecovery } : {}),
18381850
};
18391851
};
18401852
const buildPreStartAbortResult = (error: unknown): EmbeddedRunAttemptResult => ({
@@ -1844,8 +1856,9 @@ export async function runCodexAppServerAttempt(
18441856
promptError: undefined,
18451857
promptErrorSource: null,
18461858
});
1847-
const maybeCompactContextEngineForProviderBoundaryPrecheck =
1848-
async (): Promise<EmbeddedRunAttemptResult | undefined> => {
1859+
const maybeCompactContextEngineForProviderBoundaryPrecheck = async (): Promise<
1860+
EmbeddedRunAttemptResult | undefined
1861+
> => {
18491862
if (!activeContextEngine?.info.ownsCompaction) {
18501863
return undefined;
18511864
}
@@ -1879,10 +1892,10 @@ export async function runCodexAppServerAttempt(
18791892
route: precheck.route,
18801893
});
18811894
}
1882-
const compacted = await forceContextEngineCompactionForCodexOverflow(
1895+
const compactionResult = await forceContextEngineCompactionForCodexOverflow(
18831896
PREEMPTIVE_OVERFLOW_ERROR_TEXT,
18841897
);
1885-
if (!compacted) {
1898+
if (!compactionResult) {
18861899
return buildPreStartPromptErrorResult(PREEMPTIVE_OVERFLOW_ERROR_TEXT, {
18871900
route: precheck.route,
18881901
});
@@ -1903,14 +1916,17 @@ export async function runCodexAppServerAttempt(
19031916
overflowTokens: afterCompactionPrecheck.overflowTokens,
19041917
},
19051918
);
1906-
return buildPreStartPromptErrorResult(PREEMPTIVE_OVERFLOW_ERROR_TEXT, {
1907-
route: afterCompactionPrecheck.route,
1908-
});
1919+
return buildPreStartPromptErrorResult(
1920+
PREEMPTIVE_OVERFLOW_ERROR_TEXT,
1921+
{
1922+
route: afterCompactionPrecheck.route,
1923+
},
1924+
compactionResult,
1925+
);
19091926
};
19101927
let providerBoundaryPrecheckFailure: EmbeddedRunAttemptResult | undefined;
19111928
try {
1912-
providerBoundaryPrecheckFailure =
1913-
await maybeCompactContextEngineForProviderBoundaryPrecheck();
1929+
providerBoundaryPrecheckFailure = await maybeCompactContextEngineForProviderBoundaryPrecheck();
19141930
} catch (error) {
19151931
params.abortSignal?.removeEventListener("abort", abortFromUpstream);
19161932
if (runAbortController.signal.aborted || isAbortLikeError(error)) {

0 commit comments

Comments
 (0)