import type { ChatModelRunResult } from "@assistant-ui/react"; import type { A2AAgentActivitySnapshot } from "../a2a/activity.js"; import type { ActionChatUIConfig } from "../action-ui.js"; import type { AgentMcpAppPayload } from "../mcp-client/app-result.js"; export type ContentPart = { type: "text"; text: string; } | { /** * Model chain-of-thought / extended-thinking prose. Streamed from * server `thinking` SSE events (and code-agent thinking transcript * items). Rendered as a collapsible plain-English cell — not a tool * call. */ type: "reasoning"; text: string; } | { type: "tool-call"; toolCallId: string; toolName: string; argsText: string; args: Record; result?: string; isError?: boolean; completedSideEffect?: boolean; mcpApp?: AgentMcpAppPayload; chatUI?: ActionChatUIConfig; activity?: boolean; repeatCount?: number; /** * Set when the server emitted an `approval_required` event for this tool * call (opt-in `needsApproval` actions). The action did NOT run; the UI * renders an Approve/Deny affordance. `approvalKey` is echoed back in * `approvedToolCalls` to approve, `dismissed` records a local Deny. */ approval?: { approvalKey: string; dismissed?: boolean; }; /** * Structured metadata from the coding-tools executor side-channel. * Present only on code-agent tool calls from executors new enough to * emit it. The `toolKind` discriminant identifies the shape. */ structuredMeta?: Record; }; export interface SSEEvent { type: string; text?: string; tool?: string; /** Server-assigned call identifier emitted on tool_start / tool_done events. */ id?: string; label?: string; progressBytes?: number; input?: Record; result?: string; isError?: boolean; completedSideEffect?: boolean; mcpApp?: AgentMcpAppPayload; chatUI?: ActionChatUIConfig; /** Stable key the client echoes back in `approvedToolCalls` to approve a * paused `needsApproval` tool call. Present on `approval_required` events. */ approvalKey?: string; error?: string; seq?: number; agent?: string; status?: string; state?: string; elapsedSeconds?: number; detail?: string; agentCallId?: string; durationMs?: number; snapshot?: A2AAgentActivitySnapshot; reason?: string; taskId?: string; threadId?: string; description?: string; preview?: string; currentStep?: string; summary?: string; errorCode?: string; upgradeUrl?: string; details?: string; recoverable?: boolean; maxIterations?: number; } export type AgentAutoContinueReason = "run_timeout" | "loop_limit" | "no_progress" | "stream_ended" | "stale_run"; export type AgentActivityTrailEntry = { label: string; tool?: string; }; export interface AgentCallProgress { state: string; elapsedSeconds: number; detail?: string; } export interface AgentAutoContinueErrorInfo { message: string; details?: string; errorCode?: string; recoverable?: boolean; upgradeUrl?: string; } export declare function settleInterruptedToolCalls(content: ContentPart[], result?: string, options?: { includeActivity?: boolean; activityResult?: string; }): boolean; export declare class AgentAutoContinueSignal extends Error { readonly reason: AgentAutoContinueReason; readonly maxIterations?: number; readonly activityTrail: AgentActivityTrailEntry[]; readonly errorInfo?: AgentAutoContinueErrorInfo; /** * True when a CLIENT watchdog produced this signal rather than the server * asking for a continuation. The two need opposite handling: a server * `auto_continue` wants a fresh POST, a client watchdog only means "the * browser stopped seeing bytes" and must reattach instead. */ readonly clientWatchdog: boolean; constructor(options: { reason: AgentAutoContinueReason; maxIterations?: number; activityTrail?: AgentActivityTrailEntry[]; errorInfo?: AgentAutoContinueErrorInfo; clientWatchdog?: boolean; }); } /** * Client no-progress window for foreground runs. MUST stay ABOVE the server's * authoritative backstop (`RUN_NO_PROGRESS_HARD_TIMEOUT_MS`, 150s in * agent/run-manager.ts) so the server's recovery ladder always gets first * chance and the browser is never the primary stall detector. At the old 75s * this fired below every server bound, so the whole server ladder was dead * code and the median hosted foreground turn ended as a client-declared stall. * Progress accounting here deliberately mirrors the server's * `shouldBumpProgressForEvent` (keepalives and zero-byte prep activity do not * count) so the two never disagree about what "progress" means. */ export declare const SSE_NO_PROGRESS_TIMEOUT_MS = 180000; export declare const SSE_ACTION_PREPARATION_STALL_TIMEOUT_MS = 90000; /** * Window applied instead of the normal no-progress budget while a tool call or * A2A delegation is open. The server deliberately suspends its own backstop for * exactly this case — tool execution legitimately emits nothing for minutes — * so without the mirror here the browser silently caps every long tool at the * shorter window and kills a run the server believes is healthy. */ export declare const SSE_IN_FLIGHT_WORK_TIMEOUT_MS: number; /** * Open-work delta for one event: +1 when a tool call or A2A delegation starts, * -1 when it settles. Mirrors the server's `in_flight_since` marker. */ export declare function sseInFlightWorkDelta(ev: SSEEvent): number; /** * Widened client watchdog windows for durable background runs. The SERVER is * the recovery brain for these runs: its run-manager no-progress backstop * emits `auto_continue` over the same stream the client is already reading, * and its unclaimed-run sweep reaps dead workers into loud terminal errors. * The client watchdogs therefore sit ABOVE the server's durable-background * backstop so a healthy background run never trips them — the server's own * recovery event arrives first over the wire. When one does fire, the thrown * signal only means "reattach the read" (the adapter's background follow loop * re-polls /runs/active); it never escalates to a client-declared error or a * synthetic continuation POST. Progress ACCOUNTING (what counts as a * meaningful event) is unchanged — only the client-initiated recovery timing * is relaxed. */ export declare const SSE_DURABLE_NO_PROGRESS_TIMEOUT_MS: number; export declare const SSE_DURABLE_ACTION_PREPARATION_STALL_TIMEOUT_MS: number; export declare function sseNoProgressTimeoutMs(options?: SSEStreamOptions): number; export interface SSEStreamOptions { /** * Durable background runs have their own server-side liveness budget and * heartbeat. While one is active, generic keepalive-only periods keep the * client attached. Tool-input preparation is stricter: real byte progress * keeps long payloads alive, but zero-byte/silent preparation still recovers * so one stuck action cannot pin the chat forever — just on the wider * durable windows above (behind the server's own 150s backstop) instead of * the tight foreground 75s/90s windows. */ durableBackgroundRun?: boolean; /** * Optional caller-owned preparation watchdog state. Passing the same object * across reconnect reads keeps a stuck action preparation from getting a * fresh stall budget every time the browser reattaches to the same run. */ preparingActionState?: PreparingActionState; } type PreparingActionEntry = { tool: string; startedAt?: number; lastProgressBytes?: number; /** * Timestamp of the last real streaming progress for the in-preparation tool * input. The server emits a throttled `activity` heartbeat per * `tool-input-delta`, so while the model is actively streaming a (possibly * very large) tool argument this keeps advancing. The stall guard measures * silence from HERE — not from `startedAt` — so a legitimately large, still- * streaming input is never aborted; only genuine silence (keepalive-only, no * further deltas) can trip it. */ lastProgressAt?: number; }; export type PreparingActionState = { entries?: Map; toolEntries?: Map; }; export declare function appendMissingFinalResponseWarning(content: ContentPart[], completedToolNames?: Iterable): { message: string; errorCode: string; recoverable: true; } | null; interface ProcessEventState { completedToolsAfterLastAssistantText: Set; /** Set once `agent-chat:stream-progress` has been dispatched for the * current chunk, so a burst of per-token text/reasoning deltas only fires * it once. Cleared by `resetProcessEventState` on a server `clear` retry * so the next batch of real output re-arms it. */ streamProgressDispatched: boolean; } /** * Process a single SSE event and update the content accumulator. * Returns: "continue" to keep going, "done" to stop, or a yield-ready result. */ export declare function processEvent(ev: SSEEvent, content: ContentPart[], toolCallCounter: { value: number; }, tabId: string | undefined, state?: ProcessEventState): { action: "continue" | "done" | "yield" | "error" | "missing_api_key" | "auto_continue"; result?: ChatModelRunResult; autoContinue?: { reason: AgentAutoContinueReason; maxIterations?: number; errorInfo?: AgentAutoContinueErrorInfo; }; }; /** * Read and process SSE events from a ReadableStream response body. * Yields ChatModelRunResult for each meaningful event. * * When `runId` is provided, every yielded result carries * `metadata.custom.runId` so the UI can expose the trace ID via * "Copy Request ID" — including mid-stream, so users can grab it before * the run completes (or if the run hangs / ends prematurely). */ export declare function readSSEStream(body: ReadableStream, content: ContentPart[], toolCallCounter: { value: number; }, tabId: string | undefined, onSeq?: (seq: number) => void, runId?: string | null, options?: SSEStreamOptions): AsyncGenerator; /** * Read raw SSE events from a ReadableStream and process them into ContentPart[]. * Unlike readSSEStream, this doesn't yield ChatModelRunResult — it updates the * content array in-place and calls onUpdate for each meaningful change. * Designed for reconnection scenarios where we render outside assistant-ui's runtime. */ export declare function readSSEStreamRaw(body: ReadableStream, content: ContentPart[], toolCallCounter: { value: number; }, tabId: string | undefined, onUpdate: (content: ContentPart[]) => void, onSeq?: (seq: number) => void, options?: SSEStreamOptions): Promise; export {}; //# sourceMappingURL=sse-event-processor.d.ts.map