diff --git a/src/main/codex/codex-structured-compact.ts b/src/main/codex/codex-structured-compact.ts new file mode 100644 index 00000000000..d1bacf5f80b --- /dev/null +++ b/src/main/codex/codex-structured-compact.ts @@ -0,0 +1,46 @@ +import { isCodexAppServerRequestError } from './codex-app-server-connection' +import type { StructuredSessionCompaction } from '../native-chat/agent-session-wire/structured-session-compaction' +import type { CodexStructuredTurnCancellation } from './codex-structured-turn-cancellation' +import type { CodexSession } from './codex-structured-session-state' +import type { StructuredAgentSessionAdapter } from '../native-chat/agent-session-wire/structured-agent-session-adapter' + +/** + * Runs a compaction against the app-server, through the shared compaction + * bookkeeping that dedupes and settles late results. + * + * Lives outside the adapter for the same reason the background-terminal and + * rewind surfaces do: the adapter is a delegation hub, and a method with a body + * this size is the odd one out rather than the norm. + */ +export function compactCodexSession(args: { + compactions: StructuredSessionCompaction + turnCancellation: CodexStructuredTurnCancellation + session: CodexSession + requestTimeoutMs: number | undefined + input: Parameters>[0] +}): ReturnType { + const { compactions, input, requestTimeoutMs, session, turnCancellation } = args + return compactions.run( + input.sessionId, + session.threadId, + async () => { + await turnCancellation.captureBaseline(session) + return session.connection + .request( + 'thread/compact/start', + { threadId: session.threadId }, + { timeoutMs: requestTimeoutMs } + ) + .catch((error) => { + // A refusal is a compaction outcome, not a transport failure: report + // it back rather than tearing down the session. + if (isCodexAppServerRequestError(error)) { + return { error: error.message } + } + throw error + }) + }, + input.onLateResult, + input.turnId + ) +} diff --git a/src/main/codex/codex-structured-session-adapter.ts b/src/main/codex/codex-structured-session-adapter.ts index 89e9ba8378c..b49a66738ce 100644 --- a/src/main/codex/codex-structured-session-adapter.ts +++ b/src/main/codex/codex-structured-session-adapter.ts @@ -1,10 +1,10 @@ +import { compactCodexSession } from './codex-structured-compact' import * as codexRewind from './codex-structured-rewind' import type { AgentJournalMessageItem, AgentSessionJournalIdentity } from '../../shared/agent-session-journal-types' import { StructuredSessionCompaction } from '../native-chat/agent-session-wire/structured-session-compaction' -import { isCodexAppServerRequestError } from './codex-app-server-connection' import { createCodexBackgroundTerminalChannel, type CodexBackgroundTerminalChannel @@ -212,9 +212,8 @@ export class CodexStructuredSessionAdapter implements StructuredAgentSessionAdap backgroundTaskState = (sessionId: string) => this.backgroundTerminals.state(sessionId) - stopBackgroundTasks: NonNullable = ( - input - ) => this.backgroundTerminals.stop(input) + stopBackgroundTasks: CodexBackgroundTerminalChannel['stop'] = (input) => + this.backgroundTerminals.stop(input) rewindSupport: NonNullable = (sessionId) => this.sessions.get(sessionId)?.historyMode === 'legacy' @@ -227,30 +226,14 @@ export class CodexStructuredSessionAdapter implements StructuredAgentSessionAdap recoverRewind: NonNullable = (input) => codexRewind.recoverCodexRewind(this.session(input.sessionId), input, this.deps.requestTimeoutMs) - compact: NonNullable = (input) => { - const session = this.session(input.sessionId) - return this.compactions.run( - input.sessionId, - session.threadId, - async () => { - await this.turnCancellation.captureBaseline(session) - return session.connection - .request( - 'thread/compact/start', - { threadId: session.threadId }, - { timeoutMs: this.deps.requestTimeoutMs } - ) - .catch((error) => { - if (isCodexAppServerRequestError(error)) { - return { error: error.message } - } - throw error - }) - }, - input.onLateResult, - input.turnId - ) - } + compact: NonNullable = (input) => + compactCodexSession({ + compactions: this.compactions, + turnCancellation: this.turnCancellation, + session: this.session(input.sessionId), + requestTimeoutMs: this.deps.requestTimeoutMs, + input + }) async answerPrompt(input: { sessionId: string