import type { AgentSuggestion } from "@agent-native/agentkit/protocol"; import type { A2AAgentActivitySnapshot } from "../a2a/activity.js"; import type { ActionChatUIConfig } from "../action-ui.js"; import type { AgentChatRichEventEnvelope } from "../agent/types.js"; import type { ArtifactReceipt } from "../artifacts/detect.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; approvalPause?: true; approvalPauseCandidate?: true; approvalResumeCandidate?: true; /** * Set when the stream ended while this tool was still in flight. We know * it started and NOT whether its side effect landed, so it is deliberately * separate from `isError` — an email that WAS delivered must never render * as a failure just because the transport dropped before `tool_done`. */ outcome?: "unknown"; completedSideEffect?: boolean; artifacts?: ArtifactReceipt[]; mcpApp?: AgentMcpAppPayload; chatUI?: ActionChatUIConfig; chatUIResult?: unknown; 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. * `askId` identifies THIS gate hit; it changes when a failed resume * re-emits `approval_required` for the same call, which is how the UI * tells that apart from the same ask simply re-rendering (see * `ApprovalAffordance` in chat/tool-call-display.tsx). */ approval?: { approvalKey: string; dismissed?: boolean; askId?: string; allowPersistentApproval?: false; }; /** * 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; }; type AgentChatStreamResult = { readonly content?: readonly ContentPart[]; readonly status?: { readonly type: "complete"; readonly reason: "stop"; } | { readonly type: "incomplete"; readonly reason: "error"; }; readonly metadata?: { readonly custom?: Record; }; }; export interface SSEEvent { type: string; text?: string; suggestions?: AgentSuggestion[]; event?: AgentChatRichEventEnvelope; tool?: string; /** Server-assigned call identifier emitted on tool_start / tool_done events. */ id?: string; /** Stable transport identity, preserved when a durable turn is replayed. */ eventId?: string; label?: string; progressBytes?: number; input?: Record; result?: string; isError?: boolean; completedSideEffect?: boolean; approvalPause?: boolean; artifacts?: ArtifactReceipt[]; mcpApp?: AgentMcpAppPayload; chatUI?: ActionChatUIConfig; chatUIResult?: unknown; /** Stable key the client echoes back in `approvedToolCalls` to approve a * paused `needsApproval` tool call. Present on `approval_required` events. */ approvalKey?: string; /** Model-side tool-call id for `approval_required` (mirrors AgentChatEvent). */ toolCallId?: string; /** Identifies this `approval_required` gate hit (mirrors AgentChatEvent). */ askId?: string; /** False when this action requires a fresh approval for every call. */ allowPersistentApproval?: false; /** Host-resolved connection request. URLs and scopes are never streamed. */ requestId?: string; provider?: string; connectionReason?: "connect" | "grant" | "reauthorize" | "admin_required"; appId?: string; source?: { id: string; kind?: string; label?: string; uri?: string; }; error?: string; message?: string; seq?: number; agent?: string; status?: string; state?: string; elapsedSeconds?: number; detail?: string; agentCallId?: string; durationMs?: number; terminalCode?: string; snapshot?: A2AAgentActivitySnapshot; reason?: string; taskId?: string; threadId?: string; description?: string; preview?: string; currentStep?: string; summary?: string; errorCode?: string; upgradeUrl?: string; details?: string; recoverable?: boolean; /** The engine said another attempt may succeed — see `AgentChatEvent`. */ providerRetryable?: 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; } /** * Kept verbatim in sync with `INTERRUPTED_TOOL_RESULT_MARKER` * (agent/production-agent.ts): the server matches this substring in replayed * history to count how many times a write tool was interrupted, which is the * only thing that tells "we do not know if it landed" apart from "it failed". */ export declare const INTERRUPTED_TOOL_RESULT = "Interrupted before this tool returned a result."; export declare function settleInterruptedToolCalls(content: ContentPart[], result?: string, options?: { includeActivity?: boolean; activityResult?: string; userStopped?: boolean; }): 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 reader-local watchdog override. A background follow reader can * use a shorter value because a timeout only detaches that read; the follow * loop immediately re-checks the server-owned run state. */ noProgressTimeoutMs?: number; /** Reader-local counterpart to `noProgressTimeoutMs` for action preparation. */ actionPreparationStallTimeoutMs?: number; /** Mark the adapter's final `done` snapshot terminal before it is yielded. */ markTerminalResults?: 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; /** Run identity attached to processor-generated error events. */ runId?: string; /** Logical turn identity attached to processor-generated error events. */ turnId?: string; /** * Caller-owned sequence admission shared by every read of one run. Durable * reconnects can replay the last persisted frame after a dropped response; * admit it once before any progress accounting or content folding. */ seenEventSeqs?: Set; /** Caller-owned identity admission shared by every read of one logical turn. */ seenEventIds?: Set; } export declare function admitSSEEvent(event: SSEEvent, seenEventSeqs?: Set, seenEventIds?: Set): boolean; 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; }; /** * The browser's durable liveness cursor must mirror the server's * `last_progress_at` predicate. The stream watchdog intentionally has a * broader background policy, so it cannot be reused for this cursor: raw * keepalives and repeated zero-byte preparation are not proof of real work. */ export declare function isDurableProgressEvent(ev: SSEEvent, actionPreparationProgress?: boolean): boolean; /** * Locate the tool call an `approval_required` event refers to. * * Stricter than `findPendingToolCallIndex`: when the server supplies a call id * we never fall back to "newest pending call with this name". A paused * `tool_done` resolves the call right after the gate fires, so a replayed or * reordered approval would otherwise miss on id and silently attach this call's * approvalKey to a *different* in-flight call of the same action — putting the * wrong key behind a visible Approve button. A resumed paused call is eligible * only after its exact `tool_start` marks it as an approval-resume candidate. * * Without an event id, the required input must identify one pending call or * explicitly resumed pause by itself. A name-only match can attach a stale * approval to a different same-name call. With an event id, the only tolerated * fallback is a single unambiguous reader-local (`tc_N`) placeholder, which is * how a call looks when an older server omitted the id on `tool_start`. */ export declare function findApprovalToolCallIndex(content: readonly ContentPart[], toolName: string, toolCallId?: string, approvalInput?: unknown): number; export declare function appendMissingFinalResponseWarning(content: ContentPart[], completedToolNames?: Iterable): { message: string; errorCode: string; recoverable: true; failedTools?: string[]; } | 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, context?: { runId?: string; turnId?: string; }): { action: "continue" | "done" | "yield" | "error" | "missing_api_key" | "auto_continue"; result?: AgentChatStreamResult; autoContinue?: { reason: AgentAutoContinueReason; maxIterations?: number; errorInfo?: AgentAutoContinueErrorInfo; }; }; /** * Drop the draft the server is about to re-emit — the narration since the last * completed tool, not the whole turn. A `clear` arrives on a final-answer-guard * retry or a continuation, both of which resume AFTER the last completed tool * call, so anything before that boundary is settled multi-step narration the * retry will never re-send. Wiping it read to users as "it deleted its reply * and started over", and `thread-data-builder` replays this same scoping on * rebuild, so the loss survived a reload. */ export declare function clearAssistantDraftContent(content: ContentPart[]): void; /** * Read and process SSE events from a ReadableStream response body. * Yields Core-owned stream results 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, isProgress?: boolean) => 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 Core-owned stream results — 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, isProgress?: boolean) => void, options?: SSEStreamOptions): Promise; export {};