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
2 changes: 2 additions & 0 deletions components/adapters/codex/src/proxy-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1976,6 +1976,7 @@ export async function startCodexResponsesProxy(params: {
latestModel: model,
latestUpstreamProvider: upstreamProviderName,
disclosedReadPaths: reductionSummary?.disclosedReadPaths,
disclosedReadOwners: reductionSummary?.disclosedReadOwners,
});
if (typeof snapshot.responseId === "string" && snapshot.responseId) {
await indexCodexResponseSession(config.stateDir, snapshot.responseId, sessionId);
Expand Down Expand Up @@ -2127,6 +2128,7 @@ export async function startCodexResponsesProxy(params: {
latestModel: model,
latestUpstreamProvider: upstreamProviderName,
disclosedReadPaths: reductionSummary?.disclosedReadPaths,
disclosedReadOwners: reductionSummary?.disclosedReadOwners,
});
if (typeof responseId === "string" && responseId) {
await indexCodexResponseSession(config.stateDir, responseId, sessionId);
Expand Down
93 changes: 91 additions & 2 deletions components/adapters/codex/src/reduction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type SegmentBinding = {
blockIndex?: number;
blockKey?: "text" | "content";
toolName?: string;
callId?: string;
};

type ReductionInstruction = {
Expand Down Expand Up @@ -82,9 +83,83 @@ export type CodexReductionSummary = {
diagnostics: CodexReductionDiagnostics;
visualSegments?: CodexReductionVisualSegment[];
disclosedReadPaths?: string[];
/** Normalized read path -> call_id of the read that first disclosed it (null: not attributable). */
disclosedReadOwners?: DisclosedReadOwners;
skippedReason?: string;
};

export type DisclosedReadOwners = Record<string, string | null>;

export function normalizeDisclosedReadOwners(value: unknown): DisclosedReadOwners | undefined {
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
const next: DisclosedReadOwners = {};
for (const [path, owner] of Object.entries(value as Record<string, unknown>)) {
const normalized = path.trim().toLowerCase();
if (!normalized) continue;
next[normalized] = typeof owner === "string" && owner ? owner : null;
}
return Object.keys(next).length > 0 ? next : undefined;
}

/**
* Paths to hand the pass as "already disclosed". Codex resends the whole input
* history every request, so a disclosing read that is still in it is re-detected by
* the pass itself. Carrying its path too would make the pass treat that same read as
* a repeat and send it untrimmed, which breaks the prompt cache. Only paths whose
* disclosing read has left the history (compaction) are carried.
*/
export function carriedDisclosedReadPaths(
owners: DisclosedReadOwners | undefined,
presentCallIds: ReadonlySet<string>,
): string[] | undefined {
if (!owners) return undefined;
const carried = Object.entries(owners)
.filter(([, owner]) => owner === null || !presentCallIds.has(owner))
.map(([path]) => path);
return carried.length > 0 ? carried : undefined;
}

/**
* Rebuild owners for the reported path set; new paths belong to a matching read
* actually trimmed in this request.
*/
export function recordDisclosedReadOwners(
owners: DisclosedReadOwners | undefined,
reportedPaths: unknown,
segments: readonly ContextSegment[],
bindings: readonly SegmentBinding[],
trimmedSegmentIds: ReadonlySet<string>,
): DisclosedReadOwners | undefined {
const paths = normalizeDisclosedReadPaths(reportedPaths);
if (!paths) return owners;
const next: DisclosedReadOwners = {};
const bindingBySegment = new Map(bindings.map((binding) => [binding.segmentId, binding]));
for (const path of paths) {
if (owners && Object.hasOwn(owners, path)) {
next[path] = owners[path] ?? null;
continue;
}
const owner = segments.find((segment) => {
if (!trimmedSegmentIds.has(segment.id)) return false;
const binding = bindingBySegment.get(segment.id);
const toolName = binding?.toolName?.trim().toLowerCase();
if (toolName !== "read" && toolName !== "file_read") return false;
const segmentPath = (segment.metadata as Record<string, unknown> | undefined)?.path;
return typeof segmentPath === "string" && segmentPath.trim().toLowerCase() === path;
});
next[path] = (owner && bindingBySegment.get(owner.id)?.callId) || null;
}
return Object.keys(next).length > 0 ? next : undefined;
}

function presentCallIds(payload: any): Set<string> {
const ids = new Set<string>();
for (const item of Array.isArray(payload?.input) ? payload.input : []) {
if (item && typeof item === "object" && typeof item.call_id === "string" && item.call_id) ids.add(item.call_id);
}
return ids;
}

function normalizeDisclosedReadPaths(value: unknown): string[] | undefined {
if (!Array.isArray(value)) return undefined;
const next = new Set<string>();
Expand Down Expand Up @@ -327,6 +402,7 @@ function buildTurnContext(
if (!isToolLikeInputItem(item)) return;
toolLikeItems += 1;
const callHint = typeof item.call_id === "string" ? toolCallHints.get(item.call_id) : undefined;
const callId = typeof item.call_id === "string" && item.call_id ? item.call_id : undefined;
if (typeof item.output === "string") {
const id = `input-${itemIndex}-output`;
segments.push(segmentForText({
Expand All @@ -344,6 +420,7 @@ function buildTurnContext(
itemIndex,
field: "output",
toolName: callHint?.toolName ?? (typeof item?.name === "string" ? item.name : undefined),
...(callId ? { callId } : {}),
});
}
if (typeof item.arguments === "string") {
Expand All @@ -363,6 +440,7 @@ function buildTurnContext(
itemIndex,
field: "arguments",
toolName: callHint?.toolName ?? (typeof item?.name === "string" ? item.name : undefined),
...(callId ? { callId } : {}),
});
}
if (typeof item.content === "string") {
Expand All @@ -382,6 +460,7 @@ function buildTurnContext(
itemIndex,
field: "content",
toolName: callHint?.toolName ?? (typeof item?.name === "string" ? item.name : undefined),
...(callId ? { callId } : {}),
});
}
if (Array.isArray(item.content)) {
Expand All @@ -407,6 +486,7 @@ function buildTurnContext(
blockIndex,
blockKey,
toolName: callHint?.toolName ?? (typeof item?.name === "string" ? item.name : undefined),
...(callId ? { callId } : {}),
});
});
}
Expand Down Expand Up @@ -657,8 +737,11 @@ export async function applyBeforeCallReductionToPayload(params: {
};
}
const snapshot = await loadCodexSessionSnapshot(config.stateDir, sessionId);
// Snapshots written before owners were tracked only have `disclosedReadPaths`; those
// are ignored rather than carried, because carrying them reproduces the cache miss.
const disclosedReadOwners = normalizeDisclosedReadOwners(snapshot?.disclosedReadOwners);
const built = buildTurnContext(payload, sessionId, {
disclosedReadPaths: normalizeDisclosedReadPaths(snapshot?.disclosedReadPaths),
disclosedReadPaths: carriedDisclosedReadPaths(disclosedReadOwners, presentCallIds(payload)),
});
const analyzerInstructions = buildAnalyzerReductionInstructions(built.turnCtx.segments, config);
const fallbackInstructions = buildCodexFallbackReductionInstructions(built.turnCtx.segments, config);
Expand Down Expand Up @@ -688,9 +771,13 @@ export async function applyBeforeCallReductionToPayload(params: {
const { turnCtx: reducedCtx, report } = await runReductionBeforeCall({ turnCtx, passes });
const passEffects = summarizePassEffects(report);
const changedSegmentIds = new Set<string>();
const trimmedSegmentIds = new Set<string>();
for (const entry of report) {
if (!entry.changed) continue;
for (const id of entry.touchedSegmentIds ?? []) changedSegmentIds.add(id);
for (const id of entry.touchedSegmentIds ?? []) {
changedSegmentIds.add(id);
if (entry.id === "tool_payload_trim") trimmedSegmentIds.add(id);
}
}
if (changedSegmentIds.size === 0) {
return {
Expand All @@ -703,6 +790,7 @@ export async function applyBeforeCallReductionToPayload(params: {
passEffects,
diagnostics: built.diagnostics,
disclosedReadPaths: normalizeDisclosedReadPaths(reducedCtx.metadata?.disclosedReadPaths),
disclosedReadOwners: recordDisclosedReadOwners(disclosedReadOwners, reducedCtx.metadata?.disclosedReadPaths, built.turnCtx.segments, built.bindings, trimmedSegmentIds),
skippedReason: "pipeline_no_effect",
};
}
Expand Down Expand Up @@ -761,6 +849,7 @@ export async function applyBeforeCallReductionToPayload(params: {
diagnostics: built.diagnostics,
visualSegments,
disclosedReadPaths: normalizeDisclosedReadPaths(reducedCtx.metadata?.disclosedReadPaths),
disclosedReadOwners: recordDisclosedReadOwners(disclosedReadOwners, reducedCtx.metadata?.disclosedReadPaths, built.turnCtx.segments, built.bindings, trimmedSegmentIds),
};
}

Expand Down
4 changes: 4 additions & 0 deletions components/adapters/codex/src/session-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export type CodexSessionSnapshot = {
workspaceHint?: string;
transcriptPath?: string;
disclosedReadPaths?: string[];
/** Normalized read path -> call_id of the read that first disclosed it (null: not attributable). */
disclosedReadOwners?: Record<string, string | null>;
lastHookEvent?: string;
lastToolName?: string;
lastToolInputChars?: number;
Expand Down Expand Up @@ -105,6 +107,7 @@ export async function upsertCodexSessionSnapshot(
workspaceHint: patch.workspaceHint ?? current?.workspaceHint,
transcriptPath: patch.transcriptPath ?? current?.transcriptPath,
disclosedReadPaths: patch.disclosedReadPaths ?? current?.disclosedReadPaths,
disclosedReadOwners: patch.disclosedReadOwners ?? current?.disclosedReadOwners,
lastHookEvent: patch.lastHookEvent ?? current?.lastHookEvent,
lastToolName: patch.lastToolName ?? current?.lastToolName,
lastToolInputChars: patch.lastToolInputChars ?? current?.lastToolInputChars,
Expand Down Expand Up @@ -145,6 +148,7 @@ export async function mergeCodexSessionSnapshot(
workspaceHint: target?.workspaceHint ?? source.workspaceHint,
transcriptPath: target?.transcriptPath ?? source.transcriptPath,
disclosedReadPaths: target?.disclosedReadPaths ?? source.disclosedReadPaths,
disclosedReadOwners: target?.disclosedReadOwners ?? source.disclosedReadOwners,
lastHookEvent: target?.lastHookEvent ?? source.lastHookEvent,
lastToolName: target?.lastToolName ?? source.lastToolName,
lastToolInputChars: target?.lastToolInputChars ?? source.lastToolInputChars,
Expand Down
Loading
Loading