import type { JsonRpcSession, JsonRpcStdioClient } from './JsonRpcStdioClient'; import type { CodexAppServerInitializeResponse } from './protocol'; const DEFAULT_INITIALIZE_TIMEOUT_MS = 6_000; const DEFAULT_REQUEST_TIMEOUT_MS = 3_000; const DEFAULT_TOTAL_TIMEOUT_MS = 8_000; export const DEFAULT_CODEX_APP_SERVER_SUPPRESSED_NOTIFICATION_METHODS = [ 'thread/started', 'thread/status/changed', 'thread/archived', 'thread/unarchived', 'thread/closed', 'thread/name/updated', 'turn/started', 'turn/completed', 'item/agentMessage/delta', 'item/agentReasoning/delta', 'item/execCommandOutputDelta', ]; export interface CodexAppServerSession extends JsonRpcSession { readonly initializeResponse: CodexAppServerInitializeResponse; } export class CodexAppServerSessionFactory { constructor(private readonly rpcClient: JsonRpcStdioClient) {} async withSession( options: { binaryPath: string; env?: NodeJS.ProcessEnv; requestTimeoutMs?: number; initializeTimeoutMs?: number; totalTimeoutMs?: number; label: string; experimentalApi?: boolean; optOutNotificationMethods?: string[]; }, handler: (session: CodexAppServerSession) => Promise ): Promise { const requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS; const initializeTimeoutMs = Math.max( options.initializeTimeoutMs ?? DEFAULT_INITIALIZE_TIMEOUT_MS, requestTimeoutMs ); return this.rpcClient.withSession( { binaryPath: options.binaryPath, args: ['app-server'], env: options.env, requestTimeoutMs, totalTimeoutMs: options.totalTimeoutMs ?? DEFAULT_TOTAL_TIMEOUT_MS, label: options.label, }, async (session) => { const initializedSession = await this.initializeSession(session, { initializeTimeoutMs, experimentalApi: options.experimentalApi ?? false, optOutNotificationMethods: options.optOutNotificationMethods ?? DEFAULT_CODEX_APP_SERVER_SUPPRESSED_NOTIFICATION_METHODS, }); return handler(initializedSession); } ); } async openSession(options: { binaryPath: string; env?: NodeJS.ProcessEnv; requestTimeoutMs?: number; initializeTimeoutMs?: number; experimentalApi?: boolean; optOutNotificationMethods?: string[]; }): Promise { const requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS; const initializeTimeoutMs = Math.max( options.initializeTimeoutMs ?? DEFAULT_INITIALIZE_TIMEOUT_MS, requestTimeoutMs ); const session = await this.rpcClient.openSession({ binaryPath: options.binaryPath, args: ['app-server'], env: options.env, requestTimeoutMs, }); try { return await this.initializeSession(session, { initializeTimeoutMs, experimentalApi: options.experimentalApi ?? false, optOutNotificationMethods: options.optOutNotificationMethods ?? DEFAULT_CODEX_APP_SERVER_SUPPRESSED_NOTIFICATION_METHODS, }); } catch (error) { await session.close().catch(() => undefined); throw error; } } private async initializeSession( session: JsonRpcSession, options: { initializeTimeoutMs: number; experimentalApi: boolean; optOutNotificationMethods: string[]; } ): Promise { const initializeResponse = await session.request( 'initialize', { clientInfo: { name: 'claude-agent-teams-ui', title: 'Agent Teams UI', version: '0.1.0', }, capabilities: { experimentalApi: options.experimentalApi, optOutNotificationMethods: options.optOutNotificationMethods, }, }, options.initializeTimeoutMs ); await session.notify('initialized'); return { ...session, initializeResponse, }; } }