import type { AgentError, AgentEvent } from "@agent-native/agentkit/protocol"; import { type AutoContinueRefusalCode } from "../../agent/auto-continue.js"; /** * Everything a user can be told about an agent run. The server owns which one * applies; a browser stream that closed is evidence for none of them. * * - `running`: the agent is still working (including handing off to a * successor run, or still busy with an earlier message). * - `succeeded`: the run finished and reported its result. * - `stopped`: someone asked it to stop. * - `interrupted`: the run ended before finishing for an infrastructure reason * (time budget, lost worker, cut stream). Continuing or retrying can work. * - `failed`: the run reported a real failure (provider, credentials, server). * - `unverified`: the browser cannot reach the server to find out yet. */ export declare const RUN_OUTCOMES: readonly ["running", "succeeded", "stopped", "interrupted", "failed", "unverified"]; export type RunOutcome = (typeof RUN_OUTCOMES)[number]; /** * Codes that described "the agent stopped / is stuck" one incident at a time. * They survive only as telemetry detail; the user sees their outcome. A new * code is added here with its outcome or it does not type-check. */ export declare const LEGACY_RUN_CODES: readonly ["agent_run_stream_ended", "stream_ended", "action_not_started", "background_run_lost", "run_budget_exhausted", "aborted_abort_check_unavailable", "aborted_adapter-dispose", "aborted_protocol-cancel", "connection_error", "run_stream_failed", "run_timeout", "builder_gateway_stream_ended", "thread_not_found", "not_found", "interrupted_before_reporting", "run_slot_busy", "http_409", "stale_run", "background_worker_never_started", "background_worker_failed", "background_continuation_dispatch_failed", "background_continuation_dispatch_deferred", "turn_continuation_budget_exhausted", "turn_wall_clock_budget_exhausted", "turn_budget_unreadable", "run_record_missing", "unknown_run_status", "run_terminal_lookup_failed", "run_subscription_poll_failed", "run_event_persistence_failed", "run_missing_terminal", "run_terminated", "completion_error", "runtime_error", "loop_limit", "no_progress", "max_tokens", "gateway_timeout", "network_interrupted", "rate_limited", "missing_api_key", "missing_credentials", "run_events_unreachable", "run_state_unreadable", "auto_continue_cap_reached", "auto_continue_unavailable", "auto_continue_history_unreadable"]; export type LegacyRunCode = (typeof LEGACY_RUN_CODES)[number]; /** * The user-visible outcome of a run error code. A code nobody mapped is a * real failure carrying the server's own message, never a quiet success. */ export declare function runOutcomeForCode(code: string | undefined): RunOutcome; /** The outcome a protocol event log currently shows its reader. */ export declare function runOutcomeOfEvents(events: readonly AgentEvent[]): RunOutcome; /** * Where a run's terminal event came from: the run's own stream delivered it * (`stream`), the browser read the server's record of the run after the stream * closed and ended it from that (`authority`), or a runtime with no such record * ended it because its stream could not be resumed (`pipe`: not verified), or * the person's own action ended it here (`local`: Stop, a refused connection). */ export type RunTerminalSource = "stream" | "authority" | "pipe" | "local"; /** One run's outcome as the browser ended it, for telemetry. */ export interface RunOutcomeReport { readonly runId: string; readonly threadId: string; /** * The turn the server stored the run under; absent when this page did not * start it, or for a replay whose turn the server did not name. */ readonly turnId?: string; readonly outcome: RunOutcome; /** The legacy code that named this outcome one incident at a time. */ readonly code?: string; /** The server's message for a failed run. Never sent as it is. */ readonly message?: string; readonly retryable?: boolean; readonly terminalSource: RunTerminalSource; /** * A stream closed with no terminal event, and the outcome then came from the * server's own record or terminal event (never the browser's guess). */ readonly verifiedAfterPipeClosed: boolean; /** Stream resumes tried without new events (authority-less runtimes). */ readonly resumeAttempts: number; /** Authority reads that produced no new run events. */ readonly quietReads: number; /** Re-reads of a finished run to receive its own terminal event. */ readonly drainAttempts: number; } /** The server's record of the newest run carrying a turn. */ export type ServerRunState = { readonly status: "running" | "completed" | "truncated" | "errored" | "aborted"; readonly runId: string; readonly turnId?: string; readonly startMessageId?: string; /** Server clock, epoch ms. */ readonly startedAt?: number; readonly dispatchMode?: string; readonly terminalReason?: string | null; /** Present only when the caller requested a durable connection check. */ readonly pendingConnectionRequestId?: string | null; } | { readonly status: "missing"; readonly pendingConnectionRequestId?: null; }; export type RunAuthorityRead = { readonly kind: "read"; readonly state: ServerRunState; } | { readonly kind: "unreachable"; readonly error: unknown; } /** * The server answered, but not with the run's state: a definitive refusal * (signed out, no access) or a body nobody can read. Asking again will not * change it, and it says nothing about whether the run is still going. */ | { readonly kind: "refused"; readonly status?: number; }; /** Re-reads of a run the server already ended, to receive its own terminal event. */ export interface TerminalDrain { readonly runId: string; readonly attempts: number; /** A drain stream was read to its end without a connection failure. */ readonly readToEnd: boolean; } export interface StreamClosedContext { /** The runtime run the closed stream was reading. */ readonly runId: string; readonly drain?: TerminalDrain; /** When the current wait for a successor run began. */ readonly waitingSinceMs?: number; /** Consecutive authority reads that produced no new run events. */ readonly quietReads: number; /** Consecutive attempts to open the run's event stream that failed. */ readonly subscribeFailures: number; readonly nowMs: number; /** * Time-limit stops this reader already asked the server to continue, with * the server's refusal when it refused. Absent: this reader cannot continue * a turn, so a time-limit stop ends as interrupted. */ readonly autoContinue?: { readonly asked: ReadonlyMap; }; } export type StreamClosedDecision = { readonly type: "resubscribe"; readonly runId: string; readonly turnId?: string; readonly delayMs: number; /** Re-reading a run the server already ended, to receive its own terminal event. */ readonly drain: boolean; } | { readonly type: "wait"; readonly delayMs: number; } /** Ask the server to continue the turn from the run it stopped at its time limit. */ | { readonly type: "continue"; readonly runId: string; readonly turnId: string; } | { readonly type: "terminal"; readonly outcome: "succeeded" | "stopped"; } | { readonly type: "terminal"; readonly outcome: "interrupted" | "failed" | "unverified"; readonly error: AgentError; }; /** Copy for outcomes the browser ends from the server's record of a run. */ export declare const RUN_INTERRUPTED_MESSAGE = "The agent stopped before finishing."; export declare const RUN_FAILED_MESSAGE = "The agent run failed."; export declare const RUN_UNVERIFIED_MESSAGE = "This chat lost track of the agent, which may still be running. Reload to see its progress."; export declare const RUN_SIGNED_OUT_MESSAGE = "You're signed out, so this chat can't follow the agent. Sign in again, then reload."; /** * Failed attempts to open a running run's event stream (about two minutes of * backoff) before the browser says it can no longer follow the run. Without a * bound, a successor whose events endpoint keeps refusing is re-requested * back to back for as long as the run lives. */ export declare const MAX_SUBSCRIBE_FAILURES = 12; export declare function authorityBackoffMs(quietReads: number): number; /** * What to do after a run's event stream closed without a terminal event, * decided only from the server's record of the run. A closed stream is a fact * about the connection: it never ends the run by itself. */ export declare function decideAfterStreamClosed(read: RunAuthorityRead, context: StreamClosedContext): StreamClosedDecision;