export interface AppServerAgentMessageItem { type: 'agentMessage'; text?: string | null; phase?: 'commentary' | 'final_answer' | string | null; } export interface AppServerContextCompactionItem { type: 'contextCompaction'; id?: string; } export type AppServerItem = | AppServerAgentMessageItem | AppServerContextCompactionItem | { type: string; [key: string]: unknown; }; export interface AppServerTurnState { turnId?: string; status: 'pending' | 'inProgress' | 'completed' | 'failed' | 'interrupted'; finalAnswer: string | null; latestAgentMessage: string | null; errorMessage: string | null; compactionCompleted: boolean; } export type AppServerTurnEvent = | { method: 'turn/started'; params?: { turn?: { id?: string | null; status?: string | null } }; } | { method: 'turn/completed'; params?: { turn?: { id?: string | null; status?: string | null; error?: { message?: string | null } | string | null; }; }; } | { method: 'item/completed'; params?: { item?: AppServerItem | null }; } | { method: 'error'; params?: { error?: { message?: string | null; codexErrorInfo?: { httpStatusCode?: number | null; type?: string | null; } | null; } | null; }; }; export function createInitialAppServerTurnState(): AppServerTurnState { return { status: 'pending', finalAnswer: null, latestAgentMessage: null, errorMessage: null, compactionCompleted: false, }; } export function reduceAppServerTurnState( state: AppServerTurnState, event: AppServerTurnEvent, ): AppServerTurnState { if (event.method === 'turn/started') { return { ...state, turnId: event.params?.turn?.id || state.turnId, status: 'inProgress', }; } if (event.method === 'item/completed') { const item = event.params?.item; if (!item) return state; if (item.type === 'agentMessage') { const text = typeof item.text === 'string' && item.text.trim().length > 0 ? item.text : null; if (!text) return state; if (item.phase === 'final_answer') { return { ...state, finalAnswer: text, latestAgentMessage: text, }; } return { ...state, latestAgentMessage: text, }; } if (item.type === 'contextCompaction') { return { ...state, compactionCompleted: true, }; } return state; } if (event.method === 'error') { const error = event.params?.error; const message = typeof error?.message === 'string' && error.message.trim().length > 0 ? error.message.trim() : 'Codex app-server turn failed.'; const httpStatusCode = error?.codexErrorInfo?.httpStatusCode; return { ...state, errorMessage: typeof httpStatusCode === 'number' ? `${message} (HTTP ${httpStatusCode})` : message, }; } if (event.method === 'turn/completed') { const turn = event.params?.turn; const status = turn?.status === 'completed' || turn?.status === 'failed' || turn?.status === 'interrupted' ? turn.status : 'completed'; const turnError = typeof turn?.error === 'string' ? turn.error : turn?.error?.message || null; return { ...state, turnId: turn?.id || state.turnId, status, errorMessage: turnError || state.errorMessage, }; } return state; } export function isAppServerTurnFinished(state: AppServerTurnState): boolean { return ( state.status === 'completed' || state.status === 'failed' || state.status === 'interrupted' ); } export function getAppServerTurnResult( state: AppServerTurnState, ): string | null { return state.finalAnswer; }