/** * Session Reuse Utilities * * ARCHITECTURAL NOTE: * True session reuse (keeping a single bidirectional stream open across multiple * OpenAI API requests) is not possible due to a fundamental mismatch: * * - OpenAI API: Request/response model. Must close HTTP response to return tool_calls * to the client, then receive a new HTTP request with tool results. * * - Cursor's bidirectional streaming: Keeps a single stream open. Tool execution * happens locally while the stream stays open. Results are sent via bidiAppend, * and the server continues generating automatically. * * Our workaround: When tool results come back in a new request, we close any * existing session and start completely fresh. The messagesToPrompt() function * formats the full conversation history including prior tool calls and results, * so the server has full context even though we're starting a new stream. * * The session infrastructure (SessionLike, pendingExecs, etc.) is retained for: * 1. Potential future improvements if we find a way to bridge the gap * 2. Internal read handling during edit flows (single-request scope) */ import type { ExecRequest, McpExecRequest } from "./api/agent-service"; export interface OpenAIToolCallLite { id?: string; function?: { name?: string; arguments?: string; }; } export interface OpenAIMessageLite { role: "system" | "user" | "assistant" | "tool"; content: unknown; tool_calls?: OpenAIToolCallLite[]; tool_call_id?: string; } export interface SessionClient { sendToolResult: (execRequest: McpExecRequest & { type: "mcp"; }, result: { success?: { content: string; isError?: boolean; }; error?: string; }) => Promise; sendShellResult: (id: number, execId: string | undefined, command: string, cwd: string, stdout: string, stderr: string, exitCode: number, executionTimeMs?: number) => Promise; sendReadResult: (id: number, execId: string | undefined, content: string, path: string, totalLines?: number, fileSize?: bigint, truncated?: boolean) => Promise; sendLsResult: (id: number, execId: string | undefined, filesString: string) => Promise; sendGrepResult: (id: number, execId: string | undefined, pattern: string, path: string, files: string[]) => Promise; sendWriteResult: (id: number, execId: string | undefined, result: { success?: { path: string; linesCreated: number; fileSize: number; fileContentAfterWrite?: string; }; error?: { path: string; error: string; }; }) => Promise; sendResumeAction?: () => Promise; } export interface SessionLike { id: string; iterator: AsyncIterator; pendingExecs: Map; createdAt: number; lastActivity: number; state: "running" | "waiting_tool"; client: SessionClient; } export declare function createSessionId(): string; export declare function makeToolCallId(sessionId: string, callBase: string): string; export declare function parseSessionIdFromToolCallId(toolCallId: string | null | undefined): string | null; export declare function findSessionIdInMessages(messages: OpenAIMessageLite[]): string | null; export declare function collectToolMessages(messages: OpenAIMessageLite[]): OpenAIMessageLite[]; export declare function extractMessageContent(message: OpenAIMessageLite): string; export declare function selectCallBase(execReq: ExecRequest): string; export declare function mapExecRequestToTool(execReq: ExecRequest): { toolName: string | null; toolArgs: Record | null; }; export declare function safeParseJson(input: string): Record | null; export declare function sendToolResultsToCursor(session: SessionLike, toolMessages: OpenAIMessageLite[]): Promise; export declare function cleanupExpiredSessions(sessionMap: Map; lastActivity: number; }>, timeoutMs: number, now?: number): Promise;