import { i as ExtensionManifest, t as ExtensionManager } from "./manager-dOcdCX4u.js"; import { D as createWorkspaceTools, h as WorkspaceLike } from "./workspace-BZXdZcuw.js"; import { LanguageModel, ModelMessage, PrepareStepFunction, PrepareStepFunction as PrepareStepFunction$1, PrepareStepResult, PrepareStepResult as PrepareStepResult$1, StepResult, StopCondition, StopCondition as StopCondition$1, StreamTextOnChunkCallback, StreamTextOnStepFinishCallback, TextStreamPart, ToolSet, TypedToolCall, TypedToolCall as TypedToolCall$1, TypedToolResult, UIMessage, streamText } from "ai"; import * as skills from "agents/skills"; import { SkillRunContext, SkillScriptRunner, SkillSource, SkillSource as SkillSource$1 } from "agents/skills"; import { Agent, FiberContext, FiberContext as FiberContext$1, FiberRecoveryContext, FiberRecoveryContext as FiberRecoveryContext$1, FiberRecoveryResult, RetryOptions, StartFiberOptions, SubAgentClass, SubAgentStub } from "agents"; import { ChatRecoveryConfig, ChatRecoveryConfig as ChatRecoveryConfig$1, ChatRecoveryContext, ChatRecoveryContext as ChatRecoveryContext$1, ChatRecoveryExhaustedContext, ChatRecoveryOptions, ChatRecoveryOptions as ChatRecoveryOptions$1, ChatRecoveryProgressContext, ChatResponseResult, ChatResponseResult as ChatResponseResult$1, ClientToolExecutor, ClientToolSchema, MessageConcurrency, MessageConcurrency as MessageConcurrency$1, ResolvedChatRecoveryConfig, ResumableStream, SaveMessagesOptions, SaveMessagesOptions as SaveMessagesOptions$1, SaveMessagesResult, SaveMessagesResult as SaveMessagesResult$1 } from "agents/chat"; import { Session, Session as Session$1 } from "agents/experimental/memory/session"; import { Workspace as Workspace$1 } from "@cloudflare/shell"; import { ActionEvent, Adapter, Attachment, Author, Message, Thread } from "chat"; import { ChatSdkStateAdapterOptions, ChatSdkStateAgent } from "agents/chat-sdk"; import { RpcTarget } from "cloudflare:workers"; //#region src/media-eviction.d.ts interface MediaEvictionConfig { /** * Messages at the tail of the active path that are never evicted. * Think clamps this to at least the read-time truncation window * (4 messages — the recent span the model replays at full fidelity), * so a misconfigured low value can never strip content the model * still sees. * @default 8 */ keepRecentMessages?: number; /** * Minimum serialized size (in characters; base64 is ASCII so this equals * bytes for media) of a single part value to evict. * @default 32 * 1024 */ minPartBytes?: number; /** * Preserve evicted values as workspace files under * `/attachments/evicted/` instead of dropping them. The marker records * the file path. * @default true */ externalizeToWorkspace?: boolean; /** * Maximum oversized stored rows processed per pass. Bounds how long a * single pass can take; remaining rows are picked up by later passes. * @default 64 */ maxRowsPerPass?: number; } //#endregion //#region src/messengers/events.d.ts type MessengerEventKind = | "direct-message" | "mention" | "subscribed-message" | "action" | "delivery-event"; interface MessengerAuthor { fullName?: string; isBot?: boolean | "unknown"; isMe?: boolean; userId: string; userName?: string; } interface MessengerAttachment { data?: ArrayBuffer; fetch?: () => Promise; id?: string; mediaType?: string; name?: string; raw?: unknown; size?: number; text?: string; url?: string; } interface MessengerThread { channelId?: string; channelName?: string; id: string; isDirectMessage: boolean; providerThreadId: string; title?: string; } interface MessengerMessage { attachments: MessengerAttachment[]; author: MessengerAuthor; createdAt?: Date; id: string; isMention?: boolean; providerMessageId: string; raw?: unknown; text: string; } interface MessengerAction { actionId: string; messageId?: string; raw?: unknown; user?: MessengerAuthor; value?: string; } interface MessengerCapabilities { canEditMessages?: boolean; canStream?: boolean; maxMessageLength?: number; supportsActions?: boolean; supportsAttachments?: boolean; supportsEphemeral?: boolean; } interface MessengerContext { action?: MessengerAction; author?: MessengerAuthor; capabilities: MessengerCapabilities; kind: MessengerEventKind; message?: MessengerMessage; messengerId: string; provider: string; thread: MessengerThread; } interface MessengerEvent extends MessengerContext { raw?: unknown; } declare function messengerContextFromEvent( event: MessengerEvent ): MessengerContext; declare function serializableMessengerEvent( event: MessengerEvent ): MessengerEvent; declare function toMessengerUserMessage(event: MessengerEvent): UIMessage; //#endregion //#region src/messengers/delivery.d.ts declare const MESSENGER_REPLY_FIBER_NAME = "think:messenger-reply"; declare const EMPTY_MESSENGER_RESPONSE = "I couldn't produce a text response. Please try again."; declare const ERROR_MESSENGER_RESPONSE = "Sorry, I couldn't answer that right now. Please try again."; declare const INTERRUPTED_MESSENGER_RESPONSE = "Sorry, my reply was interrupted. Please send your message again if you'd like me to retry."; interface TextStreamCallbackOptions { onVisibleStart?: () => Promise | void; visibleSoftLimit?: number; } declare class TextStreamCallback extends RpcTarget implements StreamCallback { private readonly onVisibleStart?; private readonly visibleChunks; private readonly wakeups; private readonly visibleSoftLimit?; private chatRequestId?; private closed; private interrupted; private error?; private text; private visibleClosed; private visibleLimitReachedValue; private visibleStarted; private visibleTextValue; constructor(options?: TextStreamCallbackOptions); onStart(event: ChatStartEvent): void; onEvent(json: string): void; onDone(): void; onError(error: string): void; onInterrupted(): void; wasInterrupted(): boolean; close(): void; fail(error: unknown): void; hasText(): boolean; remainingText(): string; requestId(): string | undefined; textSoFar(): string; visibleLimitReached(): boolean; visibleText(): string; stream(): AsyncIterable; private pushVisibleText; private wake; private markVisibleStarted; } declare function textDeltaFromStreamChunk(json: string): string | null; type MessengerReplyStage = "accepted" | "streaming" | "completed"; interface MessengerReplySnapshot { event: MessengerEvent; stage: MessengerReplyStage; thread?: unknown; type: typeof MESSENGER_REPLY_FIBER_NAME; } declare function messengerReplySnapshot( stage: MessengerReplyStage, event: MessengerEvent, thread?: unknown ): MessengerReplySnapshot; declare function parseMessengerReplySnapshot( snapshot: unknown ): MessengerReplySnapshot | null; declare function messengerReplyRecoveryMode( snapshot: MessengerReplySnapshot ): "answer" | "apologize" | null; declare function messengerReplyFailureMode( hasStreamedText: boolean, completedModelTurn?: boolean, expectedDeliveryCompletion?: boolean ): "apologize" | "error" | null; interface MessengerDeliveryTarget { cancelChat( requestId: string, reason?: string ): boolean | void | Promise; chat( userMessage: string | UIMessage, callback: StreamCallback ): Promise; chatWithMessengerContext?( userMessage: string | UIMessage, callback: StreamCallback, context: MessengerEvent ): Promise; } interface MessengerDeliverySurface { post( message: | string | { markdown: string; } | AsyncIterable ): Promise; startTyping?(status?: string): Promise; } interface MessengerDeliveryPolicy { emptyResponseText?: string; errorResponseText?: string; interruptedResponseText?: string; isExpectedDeliveryCompletion?( error: unknown, callback: TextStreamCallback ): boolean; splitText?(text: string): string[]; visibleSoftLimit?: number; } interface DeliverMessengerReplyOptions { checkpoint?: (snapshot: MessengerReplySnapshot) => Promise | void; event: MessengerEvent; fiber?: FiberContext; policy?: MessengerDeliveryPolicy; snapshotEvent?: MessengerEvent; snapshotThread?: unknown; surface: MessengerDeliverySurface; target: MessengerDeliveryTarget; userMessage?: UIMessage; } declare function deliverMessengerReply( options: DeliverMessengerReplyOptions ): Promise; //#endregion //#region src/messengers/chat-sdk.d.ts declare class ThinkMessengerStateAgent extends ChatSdkStateAgent {} type MessengerRespondTo = | "action" | "direct-message" | "mention" | "subscribed-thread"; type MessengerConversationMode = "self" | "thread"; type MessengerConversationTarget = | { target: "self"; } | { agentClass?: SubAgentClass; name: string; target: "subagent"; }; type MessengerConversationResolver = ( event: MessengerEvent ) => MessengerConversationTarget | Promise; interface MessengerDefinition { adapter: Adapter; adapterName: string; capabilities?: MessengerCapabilities; conversation?: MessengerConversationMode | MessengerConversationResolver; delivery?: MessengerDeliveryPolicy; keyShard?: ChatSdkStateAdapterOptions["keyShard"]; path?: string; provider: string; respondTo?: readonly MessengerRespondTo[]; shardKey?: ChatSdkStateAdapterOptions["shardKey"]; subscribeOnMention?: boolean; toEvent?: ( input: ChatSdkMessengerEventInput ) => MessengerEvent | Promise; userName: string; verifyWebhook?: | false | ((request: Request) => boolean | Response | Promise); } type ThinkMessengers = Record; interface NormalizedMessengerDefinition extends MessengerDefinition { id: string; path: string; respondTo: readonly MessengerRespondTo[]; subscribeOnMention: boolean; verifyWebhook: | false | ((request: Request) => boolean | Response | Promise); } interface ChatSdkMessengerOptions extends Omit< MessengerDefinition, "adapterName" > { adapterName?: string; } interface ChatSdkMessengerEventInput { action?: ActionEvent; eventKind: MessengerEventKind; message?: Message; raw?: unknown; thread: Thread; } interface MessengerThinkTarget { cancelChat( requestId: string, reason?: string ): boolean | void | Promise; chat( userMessage: string | UIMessage, callback: StreamCallback ): Promise; chatWithMessengerContext?( userMessage: string | UIMessage, callback: StreamCallback, context: MessengerEvent ): Promise; } interface MessengerThinkHost extends MessengerThinkTarget { constructor: { name: string; }; name: string; parentPath: ReadonlyArray<{ className: string; name: string; }>; startFiber( name: string, fn: (ctx: FiberContext) => Promise, options?: StartFiberOptions ): Promise; resolveFiber(id: string, result: FiberRecoveryResult): Promise; subAgent( agentClass: SubAgentClass, name: string ): Promise>; } interface MessengerFiberStartResult { accepted: boolean; fiberId: string; snapshot?: unknown; status: string; } declare function defineMessengers(messengers: T): T; declare function chatSdkMessenger( options: ChatSdkMessengerOptions ): MessengerDefinition; declare class ThinkMessengerRuntime { private readonly host; private chat?; private readonly definitionsByAdapterName; private readonly definitionsById; private readonly definitions; constructor(definitions: ThinkMessengers, host: MessengerThinkHost); get size(): number; initialize(): void; handleRequest(request: Request): Promise; handleFiberRecovery(ctx: FiberRecoveryContext): Promise; private createChat; private enqueueReply; private answer; private resolveTarget; private definitionForThread; private definitionForThreadId; private hasUniqueProvider; private shardThread; private shardStateKey; private reviveChatObject; private toEvent; } declare function normalizeMessengers( messengers: ThinkMessengers ): NormalizedMessengerDefinition[]; declare function defaultConversationName(event: MessengerEvent): string; declare function idempotencyKeyForEvent(event: MessengerEvent): string; declare function defaultChatSdkEvent( definition: NormalizedMessengerDefinition, input: ChatSdkMessengerEventInput ): MessengerEvent; declare function toMessengerThread(thread: Thread): MessengerThread; declare function toMessengerMessage(message: Message): MessengerMessage; declare function toMessengerAuthor(author: Author): MessengerAuthor; declare function toMessengerAttachment( attachment: Attachment ): MessengerAttachment; //#endregion //#region src/think.d.ts type ChatRecoveryRetryData = { targetUserId?: string; originalRequestId?: string; incidentId?: string; lastBody?: Record | null; lastClientTools?: ClientToolSchema[] | null; recoveredRequestId?: string; }; type ChatRecoveryContinueData = { targetAssistantId?: string; originalRequestId?: string; incidentId?: string; lastBody?: Record | null; lastClientTools?: ClientToolSchema[] | null; recoveredRequestId?: string; }; /** * A best-effort internal `onStart` step that failed on this wake and was * skipped so the agent could still come up (#1710). * * - `transcript-hydration` — reading the persisted conversation into the * in-memory message cache failed (e.g. `SQLITE_NOMEM` on an oversized, * media-heavy transcript). The agent starts with an empty in-memory view; * persisted history is untouched and the next safe-boundary sync retries. * - `scheduled-task-reconcile` — declarative scheduled tasks were not * reconciled on this wake; the next successful wake reconciles them. * - `durable-work-recovery` — pending submissions / workflow notifications * were not recovered or drained on this wake. */ interface OnStartDegradation { step: | "transcript-hydration" | "scheduled-task-reconcile" | "durable-work-recovery"; error: unknown; } /** * Callback interface for streaming chat events from a Think sub-agent. * * Designed to work across the sub-agent RPC boundary — implement as * an RpcTarget in the parent agent and pass to `chat()`. */ interface ChatStartEvent { requestId: string; } interface StreamCallback { onStart(event: ChatStartEvent): void | Promise; onEvent(json: string): void | Promise; onDone(): void | Promise; onError(error: string): void | Promise; /** * The current attempt was interrupted (a stream-stall watchdog abort routed * into bounded recovery, #1626) and its final outcome will NOT arrive through * this callback. One of two things is true: * - a scheduled continuation — running in a LATER isolate invocation, without * this callback — will produce the answer (delivered to other channels, * e.g. WebSocket connections), OR * - the recovery budget was exhausted, so the turn was already terminalized * out-of-band (the configured `terminalMessage` + `onExhausted`) and is * terminally over — there is NO continuation to come. * * This is NOT `onDone` (this attempt did not complete) and NOT `onError` (the * raw stall is not surfaced as a terminal error here); without it the contract * `onStart → onEvent* → (onDone | onError)` is silently abandoned and a * consumer that treats the clean resolve as success finalizes a truncated * partial. * * Consumers should AVOID finalizing the partial on this signal — surface a * "recovering…" / "interrupted, please retry" state, or re-attach via a * durable channel — but must ALSO NOT block indefinitely waiting for a * continuation: per the exhausted case above, one may never come. Optional → * defaults to a no-op, so this is fully backward-compatible. * * Note: a deploy/eviction interruption kills the isolate (and this callback) * before this can fire — the caller observes a transport break instead. This * fires only for an in-isolate interruption (the stall→recovery path). */ onInterrupted?(): void | Promise; } /** * Minimal interface for the result of the inference loop. * The AI SDK's `streamText()` result satisfies this interface. */ interface StreamableResult { toUIMessageStream(options?: { sendReasoning?: boolean; }): AsyncIterable; output?: PromiseLike; } /** * Options for a chat turn (sub-agent RPC entry point). */ interface ChatOptions { signal?: AbortSignal; /** * Client-defined tool schemas to expose to the model for this turn, mirroring * the `clientTools` carried over the WebSocket chat protocol. Use this when a * parent agent delegates to a Think sub-agent over RPC but the sub-agent still * needs access to tools the client (or parent) defines at runtime. * * On their own these are execute-less — the model's call surfaces as a tool * call through the stream callback. Provide {@link ChatOptions.onClientToolCall} * to also resolve those calls inline so the turn can continue to completion. */ clientTools?: ClientToolSchema[]; /** * Executes a client tool call and returns its output, completing the * round trip for {@link ChatOptions.clientTools} within the same turn. * * Without this, a client-tool call has no result and the turn ends with a * dangling tool call (the RPC stream callback has no inbound result channel). * With it, the model can call a client tool, receive the result, and keep * going — the same multi-step behavior the WebSocket path gets from * `cf_agent_tool_result` messages. */ onClientToolCall?: ClientToolExecutor; } /** Options for {@link Think.addMessages}. */ interface AddMessagesOptions { /** * Parent to attach the first message under. Omitted (`undefined`) attaches to * the latest committed leaf at call time; `null` attaches at the root. An * explicit id that does not exist throws (fail fast rather than silently * misattaching). Subsequent messages in an array chain under the previous one. */ parentId?: string | null; /** * `"append"` (default) inserts new rows, idempotent by message id. * `"upsert"` inserts, or updates in place when the id already exists (in which * case `parentId` is ignored — re-parenting is not supported). * * Idempotency is by id against the whole session tree, not just the target * path: if a message id already exists *anywhere* in history, `"append"` is a * no-op for it (no new row, no re-parent) and `"upsert"` updates it in place * wherever it lives. In both modes the next message in the array chains under * that existing id, so passing already-present ids mid-array threads new * messages onto the existing branch rather than forking a new one. */ mode?: "append" | "upsert"; /** * Broadcast the change to connected clients. Default `true`. Has no effect * when called from inside an active turn (e.g. a tool `execute`), where the * live view is intentionally not touched until the next turn's sync. */ broadcast?: boolean; } type AgentToolChildRunStatus = | "starting" | "running" | "completed" | "error" | "aborted"; type AgentToolRunInspection = { runId: string; status: AgentToolChildRunStatus; requestId?: string; streamId?: string; output?: Output; summary?: string; error?: string; startedAt: number; completedAt?: number; }; type Digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"; type Hour = `0${Digit}` | `1${Digit}` | "20" | "21" | "22" | "23"; type Minute = `${"0" | "1" | "2" | "3" | "4" | "5"}${Digit}`; type ThinkTime = `${Hour}:${Minute}`; type ThinkIntervalSchedule = | `every ${number} minute${"" | "s"}` | `every ${number} hour${"" | "s"}`; type ThinkWallClockSchedule = | `every day at ${ThinkTime}` | `every weekday at ${ThinkTime}` | `every week on ${string} at ${ThinkTime}`; type ThinkScheduledTaskSchedule = | ThinkIntervalSchedule | ThinkWallClockSchedule | `${ThinkWallClockSchedule} in ${string}`; type ThinkScheduledTaskContext = { taskId: string; scheduledFor: number; scheduledForDate: Date; occurrenceKey: string; idempotencyKey: string; schedule: string; scheduleKind: "interval" | "wall-clock"; timezone?: string; metadata?: Record; }; type ThinkScheduledTaskPromptAction = { prompt: string | (() => string | Promise); handler?: never; }; type ThinkScheduledTaskHandlerAction = { handler: (ctx: ThinkScheduledTaskContext) => void | Promise; prompt?: never; }; type ThinkScheduledTaskBase = ( | ThinkScheduledTaskPromptAction | ThinkScheduledTaskHandlerAction ) & { retry?: RetryOptions; metadata?: Record; }; type ThinkScheduledTask = | (ThinkScheduledTaskBase & { schedule: ThinkIntervalSchedule; timezone?: never; }) | (ThinkScheduledTaskBase & { schedule: ThinkWallClockSchedule; timezone?: string; }) | (ThinkScheduledTaskBase & { schedule: `${ThinkWallClockSchedule} in ${string}`; timezone?: string; }); type ThinkScheduledTasks = Record; declare function defineScheduledTasks( tasks: T ): T; type DeclaredScheduledTaskPayload = { taskId: string; scheduleHash: string; scheduledFor: number; }; type AgentToolStoredChunk = { sequence: number; body: string; }; type ThinkSubmissionStatus = | "pending" | "running" | "completed" | "aborted" | "skipped" | "error"; type SubmitMessagesOptions = { submissionId?: string; idempotencyKey?: string; metadata?: Record; }; type ThinkWorkflowPromptContext = { workflow: { name: string; id: string; stepName: string; eventType: string; }; output?: { schema: unknown; }; fingerprint?: string; }; type ThinkSubmissionInspection = { submissionId: string; idempotencyKey?: string; requestId?: string; status: ThinkSubmissionStatus; error?: string; metadata?: Record; createdAt: number; startedAt?: number; completedAt?: number; }; type SubmitMessagesResult = ThinkSubmissionInspection & { accepted: boolean; }; type ListSubmissionsOptions = { status?: ThinkSubmissionStatus | ThinkSubmissionStatus[]; limit?: number; }; type DeleteSubmissionsOptions = { status?: ThinkSubmissionStatus | ThinkSubmissionStatus[]; completedBefore?: Date; limit?: number; }; /** * A chat turn request. Built automatically by each entry path * (WebSocket, chat(), saveMessages, auto-continuation) and passed * to Think's inference loop. */ interface TurnInput { signal?: AbortSignal; /** Client-provided tool schemas for dynamic tool registration. */ clientTools?: ClientToolSchema[]; /** * Executor that resolves client-tool calls inline (RPC `chat()` path). When * present, `clientTools` are built WITH an `execute` that delegates to it, so * the turn completes the tool round trip itself instead of surfacing a * dangling tool call. Not persisted — recovery cannot replay a live executor. */ clientToolExecutor?: ClientToolExecutor; /** Custom body fields from the client request. */ body?: Record; /** Internal workflow prompt configuration, never sourced from client body. */ workflowPrompt?: ThinkWorkflowPromptContext; /** Whether this is a continuation turn (auto-continue after tool result, recovery). */ continuation: boolean; } /** * Context passed to the `beforeTurn` hook. * Contains everything Think assembled — the hook can inspect and override. */ interface TurnContext { /** Assembled system prompt (from context blocks or getSystemPrompt fallback). */ system: string; /** Assembled model messages (truncated, pruned). */ messages: ModelMessage[]; /** Merged tool set (workspace + getTools + session + MCP + client + caller). */ tools: ToolSet; /** The language model from getModel(). */ model: LanguageModel; /** Whether this is a continuation turn. */ continuation: boolean; /** Custom body fields from the client request. */ body?: Record; } /** * Configuration returned by the `beforeTurn` hook to override defaults. * All fields are optional — return only what you want to change. */ interface TurnConfig { /** Override the model for this turn (e.g. cheap model for continuations). */ model?: LanguageModel; /** Override the assembled system prompt. */ system?: string; /** Override the assembled messages. */ messages?: ModelMessage[]; /** Extra tools to merge (additive — spread on top of existing tools). */ tools?: ToolSet; /** Limit which tools the model can call (AI SDK activeTools). */ activeTools?: string[]; /** Force a specific tool call (AI SDK toolChoice). */ toolChoice?: Parameters[0]["toolChoice"]; /** Override maxSteps for this turn. */ maxSteps?: number; /** * Additional AI SDK stop conditions for ending the turn early. * Think always keeps its `maxSteps` stop condition as a safety bound. */ stopWhen?: StopCondition | Array>; /** * Controls whether reasoning chunks are included in the UI message stream * for this turn. Defaults to the instance-level `sendReasoning` setting. */ sendReasoning?: boolean; /** * Override the stream-stall inactivity watchdog timeout for THIS turn only * (ms; `0` disables it for this turn). Defaults to the instance-level * `chatStreamStallTimeoutMs`. Because the watchdog measures the gap between * UI-message-stream chunks — which includes server-side tool execution — a * turn known to invoke a slow tool can raise (or disable) the timeout for * just that turn instead of permanently widening the global window. Auto- * resets after the turn. */ chatStreamStallTimeoutMs?: number; /** Maximum number of tokens to generate for this turn. */ maxOutputTokens?: Parameters[0]["maxOutputTokens"]; /** Temperature setting for this turn. */ temperature?: Parameters[0]["temperature"]; /** Nucleus sampling setting for this turn. */ topP?: Parameters[0]["topP"]; /** Top-K sampling setting for this turn. */ topK?: Parameters[0]["topK"]; /** Presence penalty setting for this turn. */ presencePenalty?: Parameters[0]["presencePenalty"]; /** Frequency penalty setting for this turn. */ frequencyPenalty?: Parameters[0]["frequencyPenalty"]; /** Stop sequences for this turn. */ stopSequences?: Parameters[0]["stopSequences"]; /** Seed for deterministic sampling when supported by the model. */ seed?: Parameters[0]["seed"]; /** Maximum number of retries for this turn. Set to 0 to disable retries. */ maxRetries?: Parameters[0]["maxRetries"]; /** Timeout configuration for this turn. */ timeout?: Parameters[0]["timeout"]; /** Additional HTTP headers for provider requests on this turn. */ headers?: Parameters[0]["headers"]; /** Provider-specific options (AI SDK providerOptions). */ providerOptions?: Record; /** Optional AI SDK telemetry configuration for this turn. */ experimental_telemetry?: Parameters< typeof streamText >[0]["experimental_telemetry"]; /** * Optional AI SDK stream transform(s) for this turn (`experimental_transform`). * Forwarded to `streamText` so callers can inspect/rewrite the stream — e.g. * detecting tool results that carry `{ content, sources }` and enqueuing * additional `source` parts via the transform's controller. Accepts a single * transform or an array applied in order. */ experimental_transform?: Parameters< typeof streamText >[0]["experimental_transform"]; /** * Optional structured-output specification (AI SDK `output`). * Forwarded to `streamText` so the model's final response is parsed * against the supplied schema. Use the AI SDK's `Output.object({ schema })` * / `Output.text()` helpers. Combine with `activeTools: []` on the * terminal turn if your provider strips tools when structured output * is active (e.g. workers-ai-provider). */ output?: Parameters[0]["output"]; } /** * Provider-agnostic semantic classification of a chat-turn error. * * Think ships **no** provider-specific string/code matching — the app owns * that knowledge (it knows which provider/model it talks to), exactly like the * `tokenCounter` it already passes to `compactAfter()`. An app teaches Think * what an error *means* by overriding `classifyChatError()`; Think then reacts * generically (e.g. compact-and-retry on `context_overflow`). * * - `context_overflow` — the prompt exceeded the model's context window * (Anthropic `"prompt is too long"`, OpenAI `context_length_exceeded`, …). * The only category Think currently acts on (auto-compact + retry). * - `rate_limit` / `transient` — reserved for future backoff/retry policies. * - `fatal` — unrecoverable; surface terminally. * - `unknown` — default; Think applies its existing terminal behavior. */ type ChatErrorClassification = | "context_overflow" | "rate_limit" | "transient" | "fatal" | "unknown"; /** * Opt-in handling for a turn that overflows the model's context window * mid-flight. Compaction (`compactAfter()`) is only checked between turns, so a * long, tool-heavy turn can grow past the window before the next check; the * provider then rejects the request (`"prompt is too long"` / * `context_length_exceeded`). Both layers reuse the session's compaction * function and are provider-agnostic — the app maps the error via * {@link Think.classifyChatError}; Think never matches provider strings itself. * * Set `Think.contextOverflow` to enable. Leaving it unset disables both layers * (existing terminal behavior). */ interface ContextOverflowConfig { /** * Reactive backstop. When a turn fails with an error classified as * `"context_overflow"`, discard the truncated partial, run * `session.compact()`, and re-run the turn from the compacted history. The * partial is intentionally not persisted: the turn restarts from scratch, so * keeping the cut-off assistant message would orphan it beside the recovered * answer (and duplicate any tool work the retry re-issues). If compaction * cannot shorten history or the retry budget is spent, the overflow surfaces * terminally through `onChatError` (classified) — it never loops or ends * silently. Default `false`. */ reactive?: boolean; /** * Maximum compact-and-retry attempts for a single overflowing turn (the * reactive backstop). Independent of the proactive guard's cap — see * {@link proactive.maxCompactions}. Default `1`. */ maxRetries?: number; /** * Proactive guard. Before each step, read the previous step's model-reported * `usage.inputTokens` and, if it crosses `maxInputTokens * (headroom ?? 0.9)`, * compact in place and feed the recompacted history into the upcoming step — * heading off the provider rejection before it happens. Keys off usage (every * provider reports it), not provider error strings. Unset disables it. * * If a provider omits `inputTokens`, the guard falls back to `usage.totalTokens` * (input + output) — a safe over-approximation that compacts slightly early * rather than missing the threshold. If neither is reported, the guard does * nothing that step (the reactive backstop still catches a genuine overflow). * * `maxCompactions` caps how many times the guard may compact within a single * step loop (default `1`, floored at `1`). It is independent of * {@link maxRetries} (the reactive budget): a no-op compaction would repeat on * every step, so the cap stops the guard from compacting (and emitting * `chat:context:compacted`) on each one. */ proactive?: { maxInputTokens: number; headroom?: number; maxCompactions?: number; }; } /** * Opt-in default classifier for {@link Think.classifyChatError}. Matches the * context-window-overflow error messages of the common providers (Anthropic, * OpenAI, Google, Bedrock, Mistral, …) and returns `"context_overflow"`. * * Think ships this as an explicitly-imported helper rather than wiring it into * core, so the framework default stays free of provider strings. Assign it (or * delegate to it) when you do not need custom classification: * * @example * ```typescript * import { Think, defaultContextOverflowClassifier } from "@cloudflare/think"; * * export class MyAgent extends Think { * override contextOverflow = { reactive: true }; * override classifyChatError = defaultContextOverflowClassifier; * } * ``` * * Or combine with your own checks: * * @example * ```typescript * override classifyChatError(error: unknown): ChatErrorClassification | void { * if (isMyRateLimit(error)) return "rate_limit"; * return defaultContextOverflowClassifier(error); * } * ``` */ declare function defaultContextOverflowClassifier( error: unknown ): ChatErrorClassification | undefined; interface ChatErrorContext { requestId?: string; stage: "parse" | "persist" | "turn" | "stream" | "recovery" | "transcript"; messagesPersisted?: boolean; /** * App-provided semantic classification (from `classifyChatError`), when * known. Lets `onChatError` overrides and observers distinguish e.g. a * context-overflow from a generic provider failure without re-matching * provider strings. */ classification?: ChatErrorClassification; } /** * Context passed to the `beforeStep` hook before each AI SDK step in * the agentic loop. Backed by the AI SDK's `PrepareStepFunction` * parameter — exposes the previous `steps`, the zero-based `stepNumber`, * the currently selected `model`, the `messages` about to be sent, and * `experimental_context`. * * Pass an explicit `TOOLS` generic for typed previous tool calls / results. * * Limitations (AI SDK boundary, not Think): * - No `abortSignal` is exposed in the context. If you do remote work * inside `beforeStep`, it cannot be cancelled by turn-level abort. * - `experimental_context` is typed `unknown`; users must narrow it. * - `output` cannot be overridden per-step — set it at the turn level * via `TurnConfig.output` (returned from `beforeTurn`). */ type PrepareStepContext = Parameters< PrepareStepFunction >[0]; /** * Configuration returned by `beforeStep` to override defaults for the * current AI SDK step. This is the AI SDK's `PrepareStepResult` — * return only the fields you want to override (`model`, `toolChoice`, * `activeTools`, `system`, `messages`, `experimental_context`, * `providerOptions`). */ type StepConfig = PrepareStepResult; /** * Context passed to the `beforeToolCall` hook **before** the tool's * `execute` function runs. * * Backed by the AI SDK's `OnToolCallStartEvent` (the parameter of * `experimental_onToolCallStart`). The full `TypedToolCall` * fields (`toolName`, `toolCallId`, `input`, `providerMetadata`, the * dynamic/invalid/error discriminators) are spread at the top level for * convenience, with the per-call event extras attached: * * - `stepNumber` — index of the current step * - `messages` — conversation messages visible at tool execution time * - `abortSignal` — signal that aborts if the turn is cancelled * * Pass an explicit `TOOLS` generic for full input typing: * * ```ts * import type { ToolCallContext } from "@cloudflare/think"; * import type { tools } from "./my-tools"; * * beforeToolCall(ctx: ToolCallContext) { * if (ctx.toolName === "search") { * ctx.input.query; // typed * } * } * ``` */ type ToolCallContext = TypedToolCall & { /** Zero-based index of the current step where this tool call occurs. */ readonly stepNumber: | number | undefined /** The conversation messages available at tool execution time. */; readonly messages: ReadonlyArray /** Signal for cancelling the operation. */; readonly abortSignal: AbortSignal | undefined; }; /** * Decision returned by `beforeToolCall` to control tool execution. * Return void/undefined to allow execution with original input. * * Discriminated union — each action has a clear, non-overlapping meaning: * - `allow` — execute the tool (optionally with modified input) * - `block` — don't execute; return `reason` as the tool result so the model can adjust * - `substitute` — don't execute; return `output` as the tool result (afterToolCall still fires) */ type ToolCallDecision = | { action: "allow" /** Modified input — tool executes with this instead of the original. */; input?: Record; } | { action: "block" /** Returned as the tool result so the model can adjust. */; reason?: string; } | { action: "substitute" /** The substitute tool output — model sees this instead of real execution. */; output: unknown /** Optional input attribution for the afterToolCall log. */; input?: Record; }; /** * Context passed to the `afterToolCall` hook after a tool executes. * * Backed by the AI SDK's `OnToolCallFinishEvent` (the parameter of * `experimental_onToolCallFinish`). The full `TypedToolCall` * fields (`toolName`, `toolCallId`, `input`, …) are spread at the top * level, plus the per-call event extras: * * - `stepNumber` — index of the current step * - `messages` — conversation messages visible at tool execution time * - `durationMs` — wall-clock execution time in milliseconds * - `success`/`output`/`error` — discriminated outcome: * - on success: `success: true`, `output: unknown` * - on failure: `success: false`, `error: unknown` * * Pass an explicit `TOOLS` generic for full input typing: * * ```ts * import type { ToolCallResultContext } from "@cloudflare/think"; * import type { tools } from "./my-tools"; * * afterToolCall(ctx: ToolCallResultContext) { * if (ctx.success) { * console.log(`${ctx.toolName} took ${ctx.durationMs}ms`, ctx.output); * } else { * console.error(`${ctx.toolName} failed:`, ctx.error); * } * } * ``` */ type ToolCallResultContext = TypedToolCall & { readonly stepNumber: number | undefined; readonly messages: ReadonlyArray /** Wall-clock execution time in milliseconds. */; readonly durationMs: number; } & ( | { readonly success: true; readonly output: unknown; readonly error?: never; } | { readonly success: false; readonly output?: never; readonly error: unknown; } ); /** * Context passed to the `onStepFinish` hook after each step completes. * * This is the AI SDK's `StepResult` (= `OnStepFinishEvent`) — * the full step record including `text`, `reasoning`, `toolCalls`, * `toolResults`, `files`, `sources`, `usage` (with `cachedInputTokens`, * `reasoningTokens`, `totalTokens`), `finishReason`, `warnings`, `request`, * `response`, and `providerMetadata` (where provider-specific cache * accounting like `cacheCreationInputTokens` lives). * * Pass an explicit `TOOLS` generic for typed `toolCalls`/`toolResults`. */ type StepContext = Parameters< StreamTextOnStepFinishCallback >[0]; /** * Context passed to the `onChunk` hook for each streaming chunk. * * This is the AI SDK's `StreamTextOnChunkCallback` event — `{ chunk }` * where `chunk` is a discriminated union of `TextStreamPart` variants * (text-delta, reasoning-delta, source, tool-call, tool-input-start, * tool-input-delta, tool-result, raw). */ type ChunkContext = Parameters< StreamTextOnChunkCallback >[0]; /** * @internal Re-export of the chunk variant union for consumers that need * to narrow on `chunk.type` without importing `TextStreamPart` directly. */ type ChunkPart = ChunkContext["chunk"]; /** * Configuration for a sandboxed extension, returned by getExtensions(). */ interface ExtensionConfig { /** Extension manifest (name, version, permissions, contributions). */ manifest: ExtensionManifest; /** JavaScript source code defining the extension's tools. */ source: string; } /** * An opinionated chat agent base class. * * @experimental The API surface may change before stabilizing. */ declare class Think< Env extends Cloudflare.Env = Cloudflare.Env, State = unknown, Props extends Record = Record > extends Agent { #private; private _activeChatRecoveryRootRequestId; private static readonly CONFIG_KEYS; /** * Wait for MCP server connections to be ready before the inference * loop. MCP tools are auto-merged into the tool set. * * Set to `true` for a default 10s timeout, or `{ timeout: ms }` * for a custom timeout. Defaults to `false` (no waiting). */ waitForMcpConnections: | boolean | { timeout: number; }; private _skillRegistry; private _loggedSkillWarnings; /** * Controls how overlapping user submit requests behave while another * chat turn is already active or queued. * * @default "queue" */ messageConcurrency: MessageConcurrency; /** * Byte budget for hydrating the persisted transcript into the in-memory * message cache (`this.messages`). * * Hydration runs on every wake (and at safe boundaries during a session). * Without a budget it materializes the ENTIRE stored conversation — for * long-lived, media-heavy sessions that footprint approaches the isolate's * 128MB memory budget and the next SQLite allocation fails with * `SQLITE_NOMEM`, permanently bricking the DO (#1710). * * When the stored path exceeds the budget, only the most recent messages * that fit are hydrated — never fewer than the recent window the model * sees at full fidelity (the `truncateOlderMessages` default of 4), even * when those messages alone exceed the budget — a * `chat:hydration:windowed` observability event is emitted, and * `this.messages` exposes the bounded window. Durable storage is never * truncated by this — `session.getHistory()` still reads the full path. * The model-facing context is unaffected: older content is already * truncated at read time before each turn, and the hydration floor * guarantees the full-fidelity span is always present. * * The default (24MB) leaves headroom for the ~2-3x amplification between * stored JSON and parsed in-memory messages. Set to * `Number.POSITIVE_INFINITY` (or any non-positive value) to disable * windowing and always hydrate the full transcript. * * @default 24 * 1024 * 1024 */ hydrationByteBudget: number; /** * Bound the PERSISTED transcript footprint by evicting oversized inline * media (base64 data-URL attachments, large strings inside tool outputs) * from messages that have aged out of the recent window. * * Read-time truncation already hides aged media from the model, but the * bytes stay in storage forever and are rehydrated on every wake — the * boot footprint grows with every image a session ever produced until * SQLite's allocator fails with `SQLITE_NOMEM` (#1710). Eviction passes * run in the background after the agent starts and as the conversation * grows; each pass processes a bounded number of oversized rows. * * By default evicted values are preserved as workspace files under * `/attachments/evicted/` (same Durable Object storage, but outside the * hydration path) and the in-message marker records the file path. * Pass a {@link MediaEvictionConfig} with `externalizeToWorkspace: false` * to drop the bytes instead of preserving them. Set this field to * `false` to disable eviction entirely. * * `keepRecentMessages` is clamped to at least the recent window the model * replays at full fidelity (4 messages), so eviction can never rewrite * content the model still sees. * * Requires a SessionProvider that implements `getHistoryRowStats` * (the default DO SQLite provider does); otherwise eviction is a no-op * and a warning is logged once. * * @default true */ mediaEviction: MediaEvictionConfig | boolean; /** * When true, chat turns are wrapped in `runFiber` for durable execution. * Enables `onChatRecovery` hook and `this.stash()` during streaming. * * Assign this as a class field or in the constructor — NOT in `onStart()`. * On every wake the SDK evaluates recovery budgets (and may seal an * interrupted turn, firing `onExhausted`) before `onStart()` runs, so a config * set in `onStart()` is applied too late and the built-in defaults are used * for the recovery that matters. See {@link ChatRecoveryConfig}. */ chatRecovery: ChatRecoveryConfig; static readonly CHAT_FIBER_NAME = "__cf_internal_chat_turn"; /** * The conversation session — messages, context, compaction, search. * * Direct message writes are observed and mirrored into Think's live cache. * Prefer the history helpers below when writing UI messages from subclasses; * they sanitize content and enforce row-size limits before delegating here. */ session: Session; /** Cached messages — kept in sync with session storage. */ private _cachedMessages; /** * Internal onStart steps that failed on this wake and were skipped so the * agent could still come up. * * onStart failures are terminal: partyserver resets its init state and * rethrows, so every subsequent wake — including platform alarm retries — * re-runs the failing onStart. A data-driven failure (e.g. SQLITE_NOMEM * hydrating an oversized transcript) would otherwise permanently brick the * DO and drive an unbounded alarm-retry loop (#1710). */ protected _onStartDegradations: OnStartDegradation[]; /** * Internal onStart steps that failed on this wake and were skipped so the * agent could still come up (see {@link OnStartDegradation}). Empty when * boot was clean. Lets hosts and operators surface degraded boots — * e.g. via a health RPC — without subclassing. */ getOnStartDegradations(): ReadonlyArray; private _activeMessengerContext?; private _messengerRuntime?; /** * WorkerLoader binding for sandboxed extensions. * Set this to enable `getExtensions()` and dynamic extension loading. */ extensionLoader?: WorkerLoader; /** * Extension manager — created automatically when `extensionLoader` is set. * Use for dynamic `load()` / `unload()` at runtime. */ extensionManager?: ExtensionManager; /** * Workspace filesystem available in `getTools()` and lifecycle hooks. * Defaults to a full `Workspace` backed by the DO's SQLite storage. * * Typed as `WorkspaceLike` rather than `Workspace` so subclasses can * replace it with anything that satisfies the interface — e.g. a proxy * that forwards to a shared workspace owned by a parent DO. Override as * a class field to skip the default init entirely: * * ```typescript * // Default init with R2 spillover for large files. * override workspace = new Workspace({ * sql: this.ctx.storage.sql, * r2: this.env.R2, * name: () => this.name * }); * * // Or a custom WorkspaceLike — e.g. a parent-owned shared workspace. * override workspace: WorkspaceLike = new SharedWorkspace(this); * ``` */ workspace: WorkspaceLike; /** * The codemode runtime behind the execute tool, when one has been created * via `createExecuteRuntime(this)` / `createExecuteTool(this)` (from * `@cloudflare/think/tools/execute`). Gives callables and lifecycle hooks * access to approvals (`approve`/`reject`/`pending`), the audit trail * (`executions`), `expirePaused`, and snippets. */ codemode?: import("@cloudflare/codemode").CodemodeRuntimeHandle; /** * Include the default workspace Bash tool. Enabled by default so models can * run shell-style multi-file workflows against the workspace. Set to `false` * to omit it from the built-in workspace tools. */ workspaceBash: | boolean | NonNullable[1]>["bash"]; constructor(ctx: DurableObjectState, env: Env); /** * Conversation history as Think's live in-memory view. * * Storage remains the durable source of truth, but runtime logic should read * through this cache so in-flight turns, tool updates, and recovery state all * observe the same message list. Use `_syncMessages()` only at safe * boundaries where a full storage reread cannot drop in-flight state. * * When the stored transcript exceeds `hydrationByteBudget`, this view is a * bounded window of the most recent messages (see `_lastHydration`); the * full history remains readable via `session.getHistory()`. */ get messages(): UIMessage[]; /** * Read the durable message path from session storage. * * Intentionally UNBUDGETED — unlike the cache refresh in `_syncMessages`, * which routes through `session.getRecentHistory(hydrationByteBudget)`, this * returns the full active path. Callers (message reconciliation, tool-update * application) must see every message: reconciliation diffs incoming client * messages against the complete server transcript, and a tool result can * target any message on the path, so a windowed read would drop rows and * corrupt the result. * * These full reads are not the unbounded boot-time hydration that bricked the * DO in #1710: they run during a live turn (never in `onStart`), so an * `SQLITE_NOMEM` here surfaces as a recoverable turn-level error rather than a * partyserver init-reset/alarm-retry loop. They also inherit step 1's * mitigation — `session.getHistory()` now fetches content in bounded chunks * (`messagesByPathIds`) instead of carrying blobs through the recursive CTE * and its `ORDER BY` sorter — and background media eviction shrinks the stored * footprint over time, so the steady-state read size converges down. */ private _readMessagesFromStorage; /** * Normalize a tool part's `input` into something the provider will accept. * * Malformed shapes 400 modern providers and persist forever once written: * a stringified-JSON `input` (e.g. `'{"prompt":"a cat"}'` instead of the * object), and any non-object `input` — `null`, `undefined`, `""`, an array, * or a primitive — on a settled or interrupted tool call (Anthropic rejects * a `tool_use` block whose `input` is not an object). We parse the former and * default the latter to an empty object so the tool-call/tool-result pair * stays valid. Delegates to the shared `normalizeToolInput` so the read-side * repair and the write-side accumulator guard enforce the same invariant. */ private _normalizeToolInput; /** * Whether a tool part already has a settled result the provider accepts, so * it must NOT be re-repaired into an errored result. * * Single source of truth for the terminal tool states, shared by the repair * pass and the backstop detector so they cannot drift. Mirror the AI SDK's * terminal states: `convertToModelMessages` emits a `tool-result` for * `output-available`, `output-error`, AND `output-denied` (a user-denied * approval — its denial reason becomes the tool-result). Omitting any of * these makes repair re-flip the part every turn — clobbering a real * `errorText`/denial with the generic "interrupted" message — and makes the * backstop falsely drop a valid call. */ private _toolPartHasSettledResult; /** * Tool-call ids that still have no recorded result. After repair this should * be empty; a non-empty result means the backstop (`ignoreIncompleteToolCalls`) * will drop those calls — i.e. repair missed a shape and should be extended. * * `approval-responded` is deliberately excluded: an approved server tool has * no result *yet*, but it is not incomplete or abandoned — it is waiting for * its continuation to run `execute()`. `convertToModelMessages` keeps that * call (and the SDK executes it), so flagging it here would log a misleading * "repair gap" warning and emit a spurious `chat:transcript:repaired` event * on every approval continuation. */ private _incompleteToolCallIds; /** * Repair a single interrupted tool call — a tool part with no settled result, * left behind when a stream was cut off mid-flight. Returns the replacement * part that takes its place in the transcript. `input` has already been * normalized to a valid object. * * The default flips it to an errored tool result so the record survives (no * "disappearing" tool call) and `convertToModelMessages` still gets a * tool-result for it (avoiding `AI_MissingToolResultsError`). * * Override to customize the repaired shape for client-resolved tools — e.g. * convert an interrupted `ask_user` (a question with no server `execute`, * normally answered by the user's next message) into a plain text part * carrying the question prose, so the model sees it as ordinary conversation * rather than a tool error and compaction keeps the question verbatim. This * runs DURING transcript repair — before the repaired transcript is persisted * and sent to the model — so the conversion shapes the current turn, not just * the next one. A returned tool part MUST carry a settled result * (`output-available` / `output-error` / `output-denied` or an * `output`/`result` field); returning a non-tool part (e.g. text) is fine. */ protected repairInterruptedToolPart( part: UIMessage["parts"][number] ): UIMessage["parts"][number]; private _repairToolTranscriptParts; private _repairTranscriptForProvider; /** * Run a best-effort internal onStart step, degrading on failure instead of * throwing. * * Throwing out of `onStart` is terminal: partyserver resets its init state * and rethrows, so every wake — including platform alarm retries — re-runs * the failing `onStart` and fails again. A data-driven failure (oversized * transcript, bad declared-task config) would permanently brick the DO and * drive an unbounded alarm-retry loop (#1710). Instead, record the * degradation, emit `chat:onstart:degraded`, and let the agent come up so * it stays reachable for remediation (compaction, clearing, redeploy). * * Returns `true` when the step succeeded. */ private _runBestEffortOnStartStep; private _mediaEvictionRunning; private _mediaEvictionScheduled; private _mediaEvictionObservedOversized; /** * Schedule a background media-eviction pass (see `mediaEviction`). * Coalesces repeated requests; the timer fires after the current * event-loop work (and after `onStart`'s `blockConcurrencyWhile`), so * boot and turn latency are unaffected. */ private _scheduleMediaEvictionPass; private _scheduleMediaEvictionAfterAppend; private _refreshMediaEvictionSignalFromCache; private _messageMayNeedMediaEviction; private _warnedEvictionUnsupported; /** * Evict oversized inline media from aged stored messages (#1710). * * Memory-bounded by design: row sizes come from `getHistoryRowStats()` * (no content loaded), only rows large enough to contain an evictable * part are parsed, and they are processed one at a time via * `session.internal_rewriteMessage` — the maintenance write path that * skips the full-history token-estimate broadcast a public * `updateMessage` performs per row. Evicted values are written to the * workspace BEFORE the row is rewritten, so a failed pass never loses * data. Best-effort: failures are logged and the next pass retries. * * The aged cutoff is `keepRecentMessages` clamped to at least * `MODEL_RECENT_WINDOW`: messages the model still replays at full * fidelity each turn are never rewritten, regardless of configuration. * * When a pass stops at `maxRowsPerPass` with eligible rows remaining, * another pass is scheduled automatically so a large backlog drains * without waiting for new appends. Termination is guaranteed: every * rewritten row drops below `minPartBytes` and is skipped by later * passes, so the eligible set strictly shrinks. * * Returns the pass totals, or `null` when eviction is disabled, already * running, or the provider cannot enumerate row sizes (warned once). */ protected _evictAgedMediaBestEffort(): Promise<{ messages: number; parts: number; bytes: number; externalizedBytes: number; } | null>; /** Replace the live cache with a durable storage snapshot. */ private _replaceCachedMessages; /** * Result of the most recent cache refresh when `hydrationByteBudget` is * active. `truncated` means `this.messages` is a bounded recent window of * a larger stored transcript. */ protected _lastHydration: { truncated: boolean; totalContentBytes: number; hydratedMessages: number; } | null; private _warnedHydrationWindowed; /** * Snapshot of the last `chat:hydration:windowed` emit, used to emit on * CHANGE rather than on every safe-boundary sync — a chronically * oversized session syncs many times per turn and would otherwise spam * identical events. */ private _lastWindowedEmit; /** * Refresh the live cache from durable storage at a safe boundary. * * Bounded by `hydrationByteBudget`: oversized transcripts hydrate as a * recent window instead of exhausting the isolate's memory (#1710). The * window never shrinks below `MODEL_RECENT_WINDOW` messages, so budgeted * hydration cannot starve the model-facing context assembly (which keeps * that many recent messages at full fidelity). */ private _syncMessages; /** Patch or append one message in the live cache after a durable write. */ private _upsertCachedMessage; /** Patch a message that is already present in the live cache. */ private _patchCachedMessage; private _appendMessageToHistory; private _updateMessageInHistory; private _upsertMessageInHistory; private _clearHistory; /** Append a message while keeping Think's live message cache coherent. */ protected appendMessageToHistory( message: UIMessage, parentId?: string | null ): Promise; /** Update a message while keeping Think's live message cache coherent. */ protected updateMessageInHistory(message: UIMessage): Promise; /** Refresh Think's live message cache from the durable session path. */ protected syncMessagesFromStorage(): Promise; private _aborts; private _turnQueue; protected _resumableStream: ResumableStream; private _pendingResumeConnections; private _lastClientTools; private _lastBody; private _continuation; private _continuationTimer; private _continuationBarrierActive; private _insideResponseHook; private _insideInferenceLoop; private _pendingInteractionPromise; private _interactionApplyTail; private _streamingAssistant; private _submitConcurrency; private static MESSAGE_DEBOUNCE_MS; private _agentToolForwarders; private _agentToolClosers; private _agentToolAbortControllers; private _agentToolLastErrors; private _agentToolPreTurnAssistantIds; private _agentToolLiveSequences; /** * Request id → run id for in-flight agent-tool turns (null = resolved as * not an agent-tool turn, cached so unrelated turns don't re-query SQLite * per frame). Drives frame attribution in {@link broadcast}: a frame * belongs to a run iff it carries that run's turn request id, so an error * in an unrelated turn or a concurrent run can never leak into another * run's state (#1575). */ private _agentToolRunsByRequestId; private _submissionTableEnsured; private _workflowNotificationTableEnsured; private _declaredScheduledTasksTableEnsured; private _drainingSubmissions; private _drainingWorkflowNotifications; private _submissionAbortControllers; private _programmaticStreamErrors; protected static submissionRecoveryStaleMs: number; broadcast( msg: string | ArrayBuffer | ArrayBufferView, without?: string[] ): void; /** * Resolve the agent-tool run whose turn owns a request id, or null when the * request is not an agent-tool turn. Falls back to the persisted child-run * row (whose `request_id` is written when the run's turn is bound, see * `startAgentToolRun`) so attribution survives a DO restart mid-run; either * outcome is cached. */ private _agentToolRunForRequest; alarm(): Promise; /** * Persist an arbitrary JSON-serializable configuration object for this * agent instance. Stored in the Think-private `think_config` table — * survives * restarts and hibernation. Pass the config shape as a method generic * for typed call sites: * * ```ts * this.configure({ modelTier: "fast" }); * ``` * * Prefer `state` / `setState` from `Agent` when you want the value * broadcast to connected clients. Use `configure` for private * per-instance config that should stay server-side. */ configure>(config: T): void; /** * Read the persisted configuration, or null if never configured. * Pass the config shape as a method generic for a typed result: * * ```ts * const cfg = this.getConfig(); * ``` */ getConfig>(): T | null; protected _migrateLegacyConfigToThinkTable(): void; private _ensureConfigTable; private _configSet; private _configGet; private _configDelete; /** * Return the language model to use for inference. * Must be overridden by subclasses. */ getModel(): LanguageModel; /** * Return the system prompt for the assistant. * Used as fallback when no context blocks are configured via `configureSession`. */ getSystemPrompt(): string; /** Return the tools available to the assistant. */ getTools(): ToolSet; /** Return messenger integrations that should be routed through this Think agent. */ getMessengers(): ThinkMessengers; getMessengerContext(): MessengerContext | undefined; chatWithMessengerContext( userMessage: string | UIMessage, callback: StreamCallback, context: MessengerContext, options?: ChatOptions ): Promise; private _initializeMessengers; /** Return code-declared scheduled tasks for this agent. */ getScheduledTasks(): ThinkScheduledTasks | Promise; /** * Reconcile code-declared scheduled tasks immediately. * Static declarations are reconciled on startup automatically; call this * after changing app-owned data that `getScheduledTasks()` reads. */ internal_reconcileScheduledTasks(): Promise; /** * Return the default timezone for wall-clock scheduled tasks. * Task-local timezone declarations take precedence. */ getDefaultTimezone(): string | undefined | Promise; private _runChatRecoveryFiber; private _systemPromptForTurn; private _buildThinkCapabilityBlock; /** Maximum number of tool-call steps per turn. Override via property or per-turn via TurnConfig. */ maxSteps: number; /** * Whether reasoning chunks are sent to chat clients by default. Override * per turn by returning `sendReasoning` from `beforeTurn`. */ sendReasoning: boolean; /** * Inactivity watchdog for the streaming read loop, in milliseconds. * * If a turn's model stream produces no chunk for this long, the watchdog * aborts the turn and surfaces a terminal stream error instead of letting the * loop park forever on a hung provider/transport (the "infinite spinner" * failure: the stream never throws, so no error and no `done` ever arrives). * A `chat:stream:stalled` observability event is emitted when it fires. * * This measures the gap *between UI-message-stream chunks*, which includes * time spent executing server-side tools (no chunks flow while a tool runs). * Set it comfortably above your slowest expected model time-to-first-token * and your slowest tool execution, or you will abort healthy long turns. * * Default `0` (disabled) — opt in by setting a value (e.g. `120_000`). * * Can be overridden per-turn via `TurnConfig.chatStreamStallTimeoutMs` * (returned from `beforeTurn`) for turns with known-slow tools. */ chatStreamStallTimeoutMs: number; /** * Per-turn stall-watchdog timeout resolved from `TurnConfig` in * `_runInferenceLoop`, read by the stream loop when arming the watchdog. * `undefined` falls back to the instance-level `chatStreamStallTimeoutMs`. * Turns are serialized, so a single active value is safe; it is reset at the * top of every `_runInferenceLoop`. */ private _activeStallTimeoutMs; /** * Opt-in handling for a turn that overflows the context window mid-flight. * See {@link ContextOverflowConfig}. Unset (the default) leaves the existing * terminal behavior unchanged. * * @example * ```typescript * override contextOverflow = { * reactive: true, * proactive: { maxInputTokens: 200_000 } * }; * ``` */ contextOverflow?: ContextOverflowConfig; /** Whether the reactive compact-and-retry backstop is enabled. */ private get _overflowReactiveEnabled(); /** Reactive compact-and-retry budget. */ private get _overflowMaxRetries(); /** Proactive guard config, when enabled. */ private get _overflowGuard(); /** Per-run cap on proactive compactions (independent of the reactive budget). */ private get _overflowProactiveMaxCompactions(); /** * Count of model messages assembled from history at the start of the current * turn (captured in `_runInferenceLoop`). The proactive guard uses it to * splice this turn's in-flight steps onto a freshly recompacted head. Turns * are serialized, so a single value is safe. */ private _turnModelMessageBaseline; /** * The assembled tool set for the current turn, captured in * `_runInferenceLoop`. The proactive guard reuses it to convert the * recompacted history through the same `convertToModelMessages` tool schemas. * Turns are serialized, so a single value is safe. */ private _activeTurnTools; /** * Number of times the proactive guard has compacted within the current * `_runInferenceLoop` (reset at the top of each run). Capped at * `contextOverflow.proactive.maxCompactions` (default `1`) so a guard that * keeps reading over-budget usage can't compact on every step — once the head * is summarized, further compaction no-ops anyway, and a genuine remaining * overflow falls through to the reactive backstop. */ private _proactiveCompactionsThisRun; /** One-time guard for the "recovery enabled but no classifier" DX warning. */ private _warnedMissingClassifier; /** * Configure the session. Called once during `onStart`. * Override to add context blocks, compaction, search, skills. * * @example * ```typescript * configureSession(session: Session) { * return session * .withContext("memory", { description: "Learned facts", maxTokens: 2000 }) * .withCachedPrompt(); * } * ``` */ configureSession(session: Session): Session | Promise; /** * Return Agent Skills sources for this Think agent. * * Bundled skills are typically imported with the Agents Vite plugin: * * ```typescript * import productSkills from "agents:skills"; // -> ./skills next to this file * ``` * * Sources are applied in order; the first source to register a skill name * wins, and later collisions are skipped with a logged warning. */ getSkills(): SkillSource[] | Promise; private _initializeSkills; /** * Log registry diagnostics (duplicate names, sources that failed to list), * deduped by message so a new collision after a deploy still surfaces while * the same warning is not repeated on every turn. */ private _logSkillWarnings; /** * Return an optional runner that enables the `run_skill_script` tool. * * @experimental Skill script execution is experimental and may change * before stabilizing. */ getSkillScriptRunner(): SkillScriptRunner | null; private _refreshSkillsIfChanged; /** * Return sandboxed extension configurations. Defines load order, * which determines hook execution order. * Requires `extensionLoader` to be set. */ getExtensions(): ExtensionConfig[]; /** * Called before `streamText` — inspect the assembled context and * return overrides. Think assembles tools, system prompt, and messages * internally; this hook sees the result and can override any part. * * Return `void` to accept all defaults. * * @example Switch model for continuations * ```typescript * beforeTurn(ctx: TurnContext) { * if (ctx.continuation) return { model: this.cheapModel }; * } * ``` * * @example Restrict active tools * ```typescript * beforeTurn(ctx: TurnContext) { * return { activeTools: ["read", "write"] }; * } * ``` */ beforeTurn(_ctx: TurnContext): TurnConfig | void | Promise; /** * Called before each AI SDK step in the agentic loop. Backed by * `streamText({ prepareStep })`. * * Return `void` to accept the current step defaults, or return a * `StepConfig` to override the model, tool choice, active tools, * system prompt, messages, experimental context, or provider options * for this step. Use `beforeTurn` for turn-wide assembly and * `beforeStep` when the decision depends on the step number or * previous step results. * * @example Force search on the first step * ```typescript * beforeStep(ctx: PrepareStepContext) { * if (ctx.stepNumber === 0) { * return { * activeTools: ["search"], * toolChoice: { type: "tool", toolName: "search" } * }; * } * } * ``` * * @example Switch to a cheaper model after tool results land * ```typescript * beforeStep(ctx: PrepareStepContext) { * // assumes a `fastSummaryModel` field on your Think subclass * if (ctx.steps.some((s) => s.toolResults.length > 0)) { * return { model: this.fastSummaryModel }; * } * } * ``` */ beforeStep( _ctx: PrepareStepContext ): StepConfig | void | Promise; /** * Called **before** the tool's `execute` function runs. Think wraps * every tool's `execute` so it can consult this hook and act on the * returned `ToolCallDecision`: * * - `void` (or `{ action: "allow" }` with no `input`) — run the * original `execute` with the original input. * - `{ action: "allow", input }` — run the original `execute` with * the substituted input. * - `{ action: "block", reason }` — skip `execute`; the model sees * `reason` as the tool's output. * - `{ action: "substitute", output }` — skip `execute`; the model * sees `output` as the tool's output. * * Only fires for server-side tools (tools with `execute`). Client * tools are handled on the client — Think can't intercept them. * * `afterToolCall` always fires after this hook (or after the original * `execute` when `allow`). For `block`/`substitute`, the substituted * value flows through `afterToolCall` as `success: true, output: ...`. * * @example Log tool calls * ```typescript * beforeToolCall(ctx: ToolCallContext) { * console.log(`Tool called: ${ctx.toolName}`, ctx.input); * } * ``` * * @example Block a tool the model shouldn't be calling here * ```typescript * beforeToolCall(ctx: ToolCallContext): ToolCallDecision | void { * if (ctx.toolName === "delete" && this.isReadOnlyMode) { * return { action: "block", reason: "delete is disabled in read-only mode" }; * } * } * ``` * * @example Substitute a cached result * ```typescript * async beforeToolCall(ctx: ToolCallContext): Promise { * if (ctx.toolName === "weather") { * const cached = await this.cache.get(JSON.stringify(ctx.input)); * if (cached) return { action: "substitute", output: cached }; * } * } * ``` */ beforeToolCall( _ctx: ToolCallContext ): ToolCallDecision | void | Promise; /** * Called **after** a tool's outcome is known — for real executions, for * `block` (carries the `reason` as `output`), and for `substitute` * (carries the substituted `output`). Backed by the AI SDK's * `experimental_onToolCallFinish`, so `durationMs` and the discriminated * `success`/`output`/`error` outcome reflect what the model actually * sees: a thrown error from the original `execute` becomes * `success: false, error: ...`; everything else (including blocked / * substituted calls) is `success: true, output: ...`. * * Override for logging, metrics, or result inspection. * * @example * ```typescript * afterToolCall(ctx: ToolCallResultContext) { * if (ctx.success) { * console.log(`${ctx.toolName} ok in ${ctx.durationMs}ms`); * } else { * console.error(`${ctx.toolName} failed:`, ctx.error); * } * } * ``` */ afterToolCall(_ctx: ToolCallResultContext): void | Promise; /** * Called after each step completes (initial, continue, tool-result). * Override for step-level logging or analytics. */ onStepFinish(_ctx: StepContext): void | Promise; /** * Called for each streaming chunk. High-frequency — fires per token. * Override for streaming analytics, progress indicators, or token counting. * Observational only (void return). */ onChunk(_ctx: ChunkContext): void | Promise; /** * Called after a chat turn completes and the assistant message has been * persisted. The turn lock is released before this hook runs, so it is * safe to call other methods from inside. * * Fires for all turn completion paths: WebSocket chat requests, * sub-agent RPC, and auto-continuation. * * Override for logging, chaining, analytics, usage tracking. */ onChatResponse(_result: ChatResponseResult): void | Promise; /** * Handle an error that occurred during a chat turn. * Override to customize error handling (e.g. logging, metrics). */ onChatError(error: unknown, _ctx?: ChatErrorContext): unknown; /** * Classify a raw chat-turn error into a provider-agnostic category. * * Think deliberately ships **no** provider-specific matching: it cannot know * that Anthropic's `"prompt is too long"` or OpenAI's * `context_length_exceeded` means "context overflow" without baking provider * knowledge into core. The app does know its provider/model, so it owns the * mapping — the same split Think already uses for `tokenCounter`. * * Currently this hook drives **only** context-overflow recovery: it is * consulted when a turn errors **and** `contextOverflow.reactive` is enabled * (if reactive is off, it is not called). Return `"context_overflow"` to run * the compact-and-retry backstop; if recovery cannot save the turn, that * classification is surfaced on the terminal `onChatError` call via * {@link ChatErrorContext.classification}. The other categories are reserved * for future use — returning one today is a no-op (the turn terminalizes as * usual) and it is **not** forwarded to `onChatError`. Returning * `void`/`"unknown"` keeps the existing terminal behavior. * * The argument may be an `Error`, an AI SDK `APICallError` (with * `statusCode`/`responseBody`), or — for in-stream provider errors that * surface as a stream error part rather than a throw — the error message * string. Narrow accordingly. * * The second argument carries a {@link ChatErrorContext}: when consulted for * overflow recovery it is `{ stage: "stream", requestId }`, so a classifier * can correlate the error with the in-flight turn (e.g. to call * {@link cancelChat}). * * @example Anthropic + OpenAI context-overflow * ```typescript * classifyChatError(error: unknown): ChatErrorClassification | void { * const text = error instanceof Error ? error.message : String(error); * if (/prompt is too long|context length|context_length_exceeded|maximum context/i.test(text)) { * return "context_overflow"; * } * } * ``` */ classifyChatError( _error: unknown, _ctx?: ChatErrorContext ): ChatErrorClassification | void; /** * Whether an error (thrown or surfaced as an in-stream error string) should * trigger the opt-in compact-and-retry backstop. Consults the app's * `classifyChatError` and the `contextOverflow.reactive` flag. Centralized * so both stream consumers (WebSocket + RPC) classify identically. */ private _isRecoverableContextOverflow; /** * Compact the session in response to a context overflow (reactive backstop or * proactive guard). Returns whether history was actually shortened — a no-op * compaction (returns `null`) means a retry would just overflow again, so the * caller should fall through to the terminal error rather than loop. * * This is the single emit point for `chat:context:compacted`, so callers must * NOT emit it again. */ private _compactForContextOverflow; /** * Finalize a context overflow that recovery could not fix (compaction was a * no-op, or the retry budget is spent). Routes the error through * `onChatError` with `classification: "context_overflow"` and emits * `chat:request:failed`, so every overflow terminal — whichever path it took * — is reported identically. Returns the (possibly app-reshaped) message for * the caller to deliver via its own transport (RPC callback / WS broadcast). */ private _finalizeContextOverflowError; private _initializeExtensions; /** * Assemble provider-ready model messages from the current session history: * repair the transcript, truncate older messages, drop any still-incomplete * tool calls, and convert to `ModelMessage[]`. Shared by the turn entry point * and the proactive context guard so a mid-turn recompaction rebuilds the * head through the exact same pipeline. */ private _assembleModelMessages; /** * Proactive context guard (Layer 1). Runs before each step from the * `prepareStep` wrapper. If `contextOverflow.proactive` is set and the *previous* * step's model-reported input tokens cross the budget, compact the session in * place and return recompacted messages for the upcoming step — heading off a * provider context-overflow 400 before it happens. * * Keys off `usage.inputTokens` (provider-agnostic; every provider reports it) * rather than any provider error string, and reuses `_assembleModelMessages` * so the recompacted head goes through the same repair/convert pipeline. The * current turn's in-flight steps (everything after `_turnModelMessageBaseline`) * are spliced back on so no completed work is lost. * * Best-effort: any failure (no-op compaction, reconciliation that would leave * an incomplete tool pair) returns `undefined` so the step proceeds unchanged * and the reactive backstop (`contextOverflow.reactive`) can still * catch a genuine overflow. */ private _maybeProactiveContextCompact; /** * The single convergence point for all chat turn entry paths. * Merges tools, assembles context, fires lifecycle hooks, wraps tools * for interception, and calls streamText. */ private _runInferenceLoop; /** @internal Test seam — override in test agents to wrap the stream (e.g. error injection). */ protected _transformInferenceResult( result: StreamableResult ): StreamableResult; /** Default hook timeout in milliseconds. */ hookTimeout: number; /** * Pipeline beforeTurn through sandboxed extensions in load order. * Each extension sees the accumulated state from prior extensions * (snapshot is rebuilt after each extension's modifications). * Results are merged with last-write-wins for scalar fields. * Extensions that don't subscribe to beforeTurn are skipped. */ private _pipelineExtensionBeforeTurn; /** * Dispatch an observation hook to all extensions that subscribe to it. * * Used by `_pipelineExtensionToolCallStart`, `_pipelineExtensionToolCallFinish`, * `_pipelineExtensionStepFinish`, and `_pipelineExtensionChunk`. Unlike * `beforeTurn`, these hooks are observation-only — extensions can't * influence the turn — so we ignore return values, log errors, and * apply a per-extension timeout. * * `onChunk` is high-frequency (per token) — extensions that subscribe * to it pay an RPC cost per chunk and should be used sparingly. */ private _dispatchExtensionObservation; /** * Wrap each tool's `execute` function so the agent's `beforeToolCall` * hook is consulted before the tool runs. The hook can return a * `ToolCallDecision` to: * * - `allow` (default if `void` is returned) — run the original * `execute`, optionally with a substituted `input`. * - `block` — skip `execute` and return `reason` (or a default string) * as the tool result. The model sees this as the tool's output. * - `substitute` — skip `execute` and return `output` directly. The * model sees this as the tool's output. * * The wrapped `execute` also dispatches the `beforeToolCall` * observation snapshot to subscribed extensions. `afterToolCall` is * still wired through the AI SDK's `experimental_onToolCallFinish` * callback so we get accurate `durationMs` and proper success/error * discrimination — `block` and `substitute` outcomes show up as * `success: true` with the substituted output; uncaught throws from * the original `execute` show up as `success: false` with the error. * * Tools without an `execute` (output-schema-only tools, client tools * routed via `needsApproval`) are left untouched. * * **Streaming tools (AsyncIterable):** the AI SDK supports tools whose * `execute` returns `AsyncIterable` to emit preliminary * results before a final value. This works whether the iterator is * returned directly (sync function, `async function*`) or wrapped in * a Promise (`async function execute(...) { return makeIter(); }`). * Because the wrapper must `await beforeToolCall` first, preliminary * chunks are collapsed — only the *final* yielded value reaches the * model. If you need true preliminary streaming, override * `getTools()` to provide such tools and avoid using `beforeToolCall` * for them (or accept the collapse). */ private _wrapToolsWithDecision; private _pipelineExtensionToolCallStart; private _pipelineExtensionToolCallFinish; private _pipelineExtensionStepFinish; private _pipelineExtensionChunk; _hostReadFile(path: string): Promise; _hostWriteFile(path: string, content: string): Promise; _hostDeleteFile(path: string): Promise; _hostListFiles(dir: string): Promise< Array<{ name: string; type: string; size: number; path: string; }> >; _hostGetContext(label: string): Promise; _hostSetContext(label: string, content: string): Promise; _hostGetMessages(limit?: number): Promise< Array<{ id: string; role: string; content: string; }> >; _hostSendMessage(content: string): Promise; _hostGetSessionInfo(): Promise<{ messageCount: number; }>; /** * Run a chat turn: persist the user message, run the agentic loop, * stream UIMessageChunk events via callback, and persist the * assistant's response. * * @param userMessage The user's message (string or UIMessage) * @param callback Streaming callback (typically an RpcTarget from the parent) * @param options Optional chat options (e.g. AbortSignal) */ chat( userMessage: string | UIMessage, callback: StreamCallback, options?: ChatOptions ): Promise; /** Get the conversation history as UIMessage[]. */ getMessages(): Promise; /** Clear all messages from storage. */ clearMessages(): Promise; private _ensureAgentToolChildRunTable; private _addAgentToolChildRunColumnIfMissing; private _readAgentToolChildRun; private _inspectionFromChildRow; protected formatAgentToolInput(input: unknown): UIMessage; protected getAgentToolOutput(_runId: string): unknown; protected getAgentToolSummary(runId: string, output: unknown): string; startAgentToolRun( input: unknown, options: { runId: string; } ): Promise; cancelAgentToolRun(runId: string, reason?: unknown): Promise; /** * Classify any in-flight chat-recovery on this child facet (#1630). A child * facet is dedicated to a single agent-tool run, so any recovery incident is * that run's. `detected`/`scheduled`/`attempting` mean recovery is still * resolving the interrupted turn; `exhausted`/`failed` mean it gave up; a * completed recovery deletes its incident. */ private _classifyAgentToolChildRecovery; inspectAgentToolRun(runId: string): Promise; private _isStaleAgentToolChildRun; /** * Reconcile a stale (post-eviction) child run row from the child's own * durable recovery (#1630). The child facet self-heals its interrupted turn * via `chatRecovery`, but that path never writes the run row, so without this * the row strands `running` and the parent can only collect `interrupted`. * * Persisting the terminal here (rather than only computing it) is intentional: * it's a lazy materialization of the run's true terminal that also lets a * tailing parent's stream close promptly and makes subsequent inspects cheap. * While recovery is still resolving (active stream or in-progress incident) * the row is left `running` so the parent's bounded re-attach keeps waiting. */ private _reconcileStaleAgentToolChildRun; /** Release a re-attached run's live tail + per-run streaming bookkeeping. */ private _finalizeAgentToolChildRunTailers; /** * Eagerly terminalize this child facet's OWN agent-tool run row(s) once a * recovered turn has settled. A recovered turn re-runs via either * `_chatRecoveryContinue` → `continueLastTurn` or, for a pre-stream eviction, * `_chatRecoveryRetry` (a fresh user turn) — neither flows through * `startAgentToolRun`'s finalizer, so without this the run row strands * `running` and its tailers stay open until a parent inspect lazily * reconciles it — forcing a re-attached parent to wait out a full no-progress * window before collecting an already-finished result (#1630 follow-up). * Reconciling here closes the tail promptly so the parent collects the * terminal immediately. No-op on non-child facets (their * `cf_agent_tool_child_runs` table is empty) and on rows whose in-memory run * is still live (those are finalized by `startAgentToolRun`); the underlying * reconcile leaves a row `running` while its recovery is still in progress. */ private _reconcileOwnStaleAgentToolChildRuns; getAgentToolChunks( runId: string, options?: { afterSequence?: number; } ): Promise; tailAgentToolRun( runId: string, options?: { afterSequence?: number; signal?: AbortSignal; } ): Promise>; private static _stringifyAgentToolOutput; private static _parseAgentToolOutput; /** * Whether the run produced an assistant turn (text or tool-only). Used by the * post-eviction reconcile to mark a settled run `completed` even when it ended * without final text. A dedicated child facet starts with no assistant * messages, so a missing in-memory pre-turn snapshot is treated as empty. */ private _hasRecoveredAgentToolAssistantTurn; private _getAgentToolFinalText; private _ensureDeclaredScheduledTasksTable; private _readDeclaredScheduledTaskRow; private _listDeclaredScheduledTaskRows; private _updateDeclaredScheduledTaskSchedule; private _normalizeDeclaredScheduledTasks; private _declaredScheduledTasksForNow; private _declaredScheduleOwnerKey; private _declaredScheduleValidationError; private _nextDeclaredScheduleTimeForConfig; private _reconcileDeclaredScheduledTasks; private _scheduleDeclaredTaskOccurrence; private _scheduleDeclaredTaskOccurrenceAt; private _advanceDeclaredScheduledTask; private _declaredScheduledTaskContext; _runDeclaredScheduledTask( payload: DeclaredScheduledTaskPayload ): Promise; private _ensureSubmissionTable; private _ensureWorkflowNotificationTable; private _readSubmission; private _readSubmissionByIdempotencyKey; private _normalizeStatusFilter; private _listSubmissionRows; private _listSubmissionRowsByStatus; private _inspectionFromSubmissionRow; private _parseJsonObject; private _parseSubmissionMessages; private _serializeSubmissionMessages; private _serializeMetadata; private _readWorkflowPromptContext; private _emitSubmissionStatus; protected onSubmissionStatus( _submission: ThinkSubmissionInspection ): void | Promise; private _enqueueWorkflowNotification; private _insertWorkflowNotification; private _recoverWorkflowNotifications; private _startWorkflowNotificationDrain; private _hasPendingWorkflowNotifications; private _drainWorkflowNotifications; private _scheduleWorkflowNotificationAlarm; inspectSubmission( submissionId: string ): Promise; listSubmissions( options?: ListSubmissionsOptions ): Promise; deleteSubmission(submissionId: string): Promise; deleteSubmissions(options?: DeleteSubmissionsOptions): Promise; private _listTerminalSubmissionRowsForDelete; private _isTerminalSubmissionStatus; cancelSubmission(submissionId: string, reason?: unknown): Promise; submitMessages( messages: UIMessage[], options?: SubmitMessagesOptions ): Promise; private _scheduleSubmissionDrain; private _startSubmissionDrain; private _hasPendingSubmissions; _drainThinkSubmissions(): Promise; private _drainSubmissions; private _runSubmission; private _getSubmissionFinalStatus; private _markPendingSubmissionsSkipped; private _emitSkippedSubmissions; private _recoverSubmissionsOnStart; private _getSubmissionMessagesAppliedState; private _hasRecoverableChatTurn; private _hasFreshRecoverableSubmissionEvidence; private _hasScheduledRecoveredContinuation; /** * Inject messages and trigger a model turn — without a WebSocket request. * * Use for scheduled responses, webhook-triggered turns, proactive agents, * or chaining from `onChatResponse`. * * Accepts static messages or a callback that derives messages from the * current state (useful when multiple calls queue up — the callback runs * with the latest messages when the turn actually starts). * * Pass `options.signal` to cancel the turn from outside without knowing * the internally-generated request id. The signal is linked to the * registry's controller for this turn — when it aborts, the inference * loop's signal aborts and the result reports `status: "aborted"`. * Pre-aborted signals short-circuit before any model work runs. See * {@link SaveMessagesOptions} for the integration point. * * @example Scheduled follow-up * ```typescript * async onScheduled() { * await this.saveMessages([{ * id: crypto.randomUUID(), * role: "user", * parts: [{ type: "text", text: "Time for your daily summary." }] * }]); * } * ``` * * @example Function form * ```typescript * await this.saveMessages((current) => [ * ...current, * { id: crypto.randomUUID(), role: "user", parts: [{ type: "text", text: "Continue." }] } * ]); * ``` * * @example External cancellation (helper-as-sub-agent) * ```typescript * // Inside a parent agent's tool execute — forward the AI SDK's * // abortSignal so a parent stop / tab close cancels the helper. * await helper.saveMessages([userMsg], { signal: abortSignal }); * ``` */ saveMessages( messages: | UIMessage[] | ((currentMessages: UIMessage[]) => UIMessage[] | Promise), options?: SaveMessagesOptions ): Promise; /** * Add messages to history WITHOUT starting a model turn. * * Distinct from {@link Think.saveMessages} (which runs a turn) and from * AIChatAgent's `persistMessages()` (which replaces/reconciles a flat array): * `addMessages` appends or upserts into the Session tree and never enqueues a * turn. Because it bypasses the turn queue, it never deadlocks — including * when called from inside a tool `execute` during an active turn. * * Array entries are appended **linearly**: the first attaches under the * resolved parent (the latest committed leaf by default, or `parentId`), and * each subsequent message attaches under the previous one, so imported history * stays a single path rather than a fan-out of siblings. Appends are * idempotent by message id; pass `{ mode: "upsert" }` to update an existing * message in place instead (upsert never re-parents). Any role may be written; * an `assistant` message added this way is inert transcript data (it does not * mark a completed turn or trigger auto-continuation). * * The live message cache stays coherent automatically (the Session keeps it * in sync on every write, branches included). Broadcast behaviour depends on * whether a turn is running: * * - **Out of a turn** (the supported pattern — "add context, then run a * turn"): the new messages are broadcast to connected clients immediately * (unless `broadcast: false`). * - **Inside a turn** (e.g. from a tool `execute`): no broadcast is sent, so a * full snapshot can't clobber the in-progress streamed message; the injected * messages ride along on the turn's next broadcast. The write is still * durable and visible to the running turn's next sync. */ addMessages( messages: | UIMessage[] | ((currentMessages: UIMessage[]) => UIMessage[] | Promise), options?: AddMessagesOptions ): Promise; private _runProgrammaticMessagesTurn; /** * Run a new LLM call following the last assistant message. * * The model sees the full conversation (including the last assistant * response) and generates a new response. The new response is persisted * as a separate assistant message. Building block for chat recovery * (Phase 4), "generate more" buttons, and self-correction. * * Note: this creates a new message, not an append to the existing one. * True continuation-as-append (chunk rewriting) is planned for Phase 4. * * Returns early with `status: "skipped"` if there is no assistant message * to continue from. * * Pass `options.signal` to cancel the continuation from outside — * matches the {@link saveMessages} contract. */ protected continueLastTurn( body?: Record, options?: SaveMessagesOptions ): Promise; private _retryLastUserTurn; private _setupProtocolHandlers; private _handleProtocolEvent; private _handleStreamResumeRequest; private _handleStreamResumeAck; private _handleChatRequest; /** * Abort the active turn, invalidate queued turns, and reset * concurrency/continuation state. Call this when intercepting * clear events or implementing custom reset logic. * * Does NOT clear messages, streams, or persisted state — * only turn execution state. */ protected resetTurnState(): void; /** * Abort a single in-flight chat turn by request id. * * Equivalent to the cancel path that fires when a client sends a * `chat-request-cancel` WebSocket message — the inference loop's * signal aborts, partial chunks already streamed are still * persisted, and the turn's `ChatResponseResult` reports * `status: "aborted"`. * * No-op if no controller exists for `requestId` (the turn already * completed, was never started, or used a different id). * * `chat()` callers can read the request id from * {@link StreamCallback.onStart} and later pass it here from another * RPC call. * * Prefer {@link SaveMessagesOptions.signal} when driving a turn * programmatically — it threads the abort intent in from the start * without requiring the caller to know the id. */ cancelChat(requestId: string, reason?: string): void; /** Abort every in-flight chat turn on this agent. */ cancelAllChats(): void; protected abortRequest(requestId: string, reason?: unknown): void; /** * Abort every in-flight chat turn on this agent. * * Aborts all controllers in the registry and clears it. Used by * subclasses that drive single-purpose turns (e.g. a sub-agent * helper that runs one turn at a time over RPC) and want a coarse * "cancel whatever is running" handle without tracking request ids. * * Does NOT reset queued turns, continuation timers, or submit * concurrency state — use {@link resetTurnState} for the full * teardown that runs on `chat-clear`. */ protected abortAllRequests(): void; private _handleClear; /** * Stamp the allocated assistant id onto a new turn's `start` chunk so a chat * client builds the live-streamed message under the SAME id this agent * persists under. Providers that emit no `start.messageId` (e.g. Workers AI) * otherwise leave the client to generate its own id; the live stream and the * persisted message broadcast then can't reconcile by id, and the originating * tab briefly renders the turn twice before collapsing. Mirrors the fix in * `@cloudflare/ai-chat`. Continuations are skipped — they reuse the existing * assistant message via the `continuation` frame flag, so the id must not * change mid-message. The orphan-recovery path inherits the id from the * stored chunk, so it needs no separate stamping. */ private _alignStreamStartId; private _streamResultToRpcCallback; /** * Whether storing this chunk should immediately flush the resumable-stream * buffer to SQLite. * * A settled tool result (`tool-output-available` / `tool-output-error` / * `tool-output-denied`) captures a completed, often non-idempotent side * effect — or, for a denial, a user decision — so it is flushed * **immediately**. An isolate eviction (deploy) before the next batch flush * would otherwise lose it, and recovery would re-anchor without it and re-run * the already-completed tool call (or drop the denial). Frequent recoverable * content (text / reasoning / tool-input streaming) is throttled to avoid * write amplification. */ private _shouldFlushRecoverableChunk; /** * Store a stream chunk, flushing settled tool results durably and promptly. * Shared by the WebSocket and sub-agent RPC streaming paths so both get * tool-call-level recovery durability (recovery loses at most the in-flight * step, never an already-completed tool call). */ private _storeChunkDurably; /** * Wrap a UI-message stream with an inactivity watchdog. If no chunk arrives * within `timeoutMs`, `onStall` runs (aborting the upstream model stream) and * the iterator throws, so the consumer loop exits with a terminal error * instead of parking forever on a hung provider/transport. `timeoutMs <= 0` * passes the source through untouched. */ private _iterateWithStallWatchdog; private _streamResult; private _persistAssistantMessage; /** * Remove parts belonging to Think's internal structured-output final-answer * tool (`think_final_answer`, or a collision-suffixed variant) from a UI * message so the internal call/result never enters the persisted conversation * (and is never re-fed to the model on later turns). Stateless and matched by * the reserved name so it also covers recovery re-persist paths. Handles both * the static (`tool-`) and dynamic (`dynamic-tool`) part shapes the AI * SDK can emit. */ private _stripInternalFinalAnswerParts; /** * Persist an incoming message after reconciliation. For assistant * messages, also resolve their ID against any server-side row that * already owns the same `toolCallId` so we update the existing row * instead of inserting an orphan duplicate. */ private _persistIncomingMessage; private _persistClientTools; private _restoreClientTools; private _persistBody; private _restoreBody; /** * Serialize a client-tool result/approval apply behind any in-flight apply * (#1649). Parallel tool results arrive as independent WebSocket messages, * and each apply is a read-modify-write of the full message in durable * storage. Running them concurrently means every apply reads the same * snapshot (all siblings still `input-available`), patches only its own part, * and writes the whole message back — so the last write clobbers the others * back to `input-available`, and the auto-continuation barrier later times * out and the transcript-repair backstop errors the lost siblings. * * Chaining each apply off `_interactionApplyTail` makes the read-modify-write * atomic per result and in arrival order. `_pendingInteractionPromise` is set * to the newest link so the barrier's single-slot wake-up still observes the * latest apply; because the chain is serial, awaiting it transitively waits * for every predecessor. * * @internal */ protected _enqueueInteractionApply( apply: () => Promise ): Promise; private _applyToolResult; private _applyToolApproval; /** * The codemode runtime handle behind the execute tool. `this.codemode` is * assigned when `createExecuteRuntime(this)` / `createExecuteTool(this)` * runs (normally at turn start, via `getTools()`); after a DO restart no * turn may have run yet, so fall back to building the tools once. */ private _codemodeRuntime; /** * Pending (awaiting-approval) actions across paused executions of the * execute tool's codemode runtime — `{ executionId, seq, connector, * method, args }` each, with FULL args (the transcript copy is truncated). * Clients reconcile approval cards against this on load. * * Client-callable (registered below — see the `callable()` calls after the * class body). */ pendingExecutions( executionId?: string ): Promise; /** * Approve a paused execution and resume it. The run continues from where * it stopped (replaying logged work, executing the approved call); the * outcome — completed, errored, or paused again on the NEXT gated call — * replaces the paused tool output in the transcript and the chat * auto-continues so the model can act on it. * * Approving an execution that is no longer pending (already settled, * expired, or unknown) returns `{ status: "error" }` with an explanatory * message — it never throws. * * Client-callable. */ approveExecution(executionId: string): Promise; /** * Reject a paused execution's pending action, ending the run. The * transcript's paused output is replaced with * `{ status: "rejected", executionId, reason }` and the chat * auto-continues so the model can adapt (or explain) instead of erroring. * * Client-callable. */ rejectExecution(executionId: string, reason?: string): Promise; /** * Replace a paused execute-tool output in the transcript with the * execution's new outcome and kick the auto-continuation so the model sees * it. * * When no paused part carries `executionId` — the output was already * replaced from another tab, or compaction summarized the part away — the * runtime has still durably applied the approval/rejection, so the outcome * must not be dropped: it is appended as a system note instead, and the * continuation still fires so the model can act on it. */ private _applyExecutionOutcome; /** * Find the tool part holding the paused output of `executionId` — in the * in-flight streaming accumulator first (an approval can land while a new * turn streams), then the persisted transcript, newest message first. */ private _findPausedExecutionToolCall; /** * Find the tool part carrying `executionId` in its output. With * `pausedOnly`, only a still-paused output matches — used to locate the * part an approval outcome should replace. Without it, any settled output * matches — used to distinguish "already resolved elsewhere" from "the * part is gone from the transcript" (e.g. compacted away). */ private _findExecutionToolCall; private _applyToolUpdateToMessages; protected hasPendingInteraction(): boolean; protected waitUntilStable(options?: { timeout?: number }): Promise; private _awaitWithDeadline; private _messageHasPendingInteraction; /** * Names of the tools whose interrupted `input-available` part can still be * resolved by the CLIENT after a restart — i.e. the client tools (no server * `execute`) from the last request, which the SPA answers by replaying a * `tool-result` over the WebSocket. A server tool is intentionally absent: * its `execute()` promise died with the evicted isolate, so nothing will * ever post its result. */ private _clientResolvableToolNames; /** Extract a tool part's name from its `tool-` / `dynamic-tool` shape. */ private _toolPartName; /** * Whether a part is still awaiting a CLIENT interaction that can genuinely * arrive after a restart, so `waitUntilStable` must keep waiting for it: * - `approval-requested`: a reconnecting client can replay the approval. * - `input-available` for a CLIENT tool: the SPA can replay the * `tool-result` (this is why client-tool recovery works — see the * `tool-result` handler, which sets `_pendingInteractionPromise`). * * A SERVER tool's `input-available` is deliberately NOT pending. After an * eviction its `execute()` promise is gone and no interaction will ever * resolve it, so treating it as pending wedges `waitUntilStable` forever: * the recovery continuation times out every attempt, burns the attempt * budget on a wait that can never converge, and — if any transient * storage/schedule error throws on the way — the one-shot recovery alarm row * is swallowed and deleted with no terminal `onExhausted` (the half-finished * message wedges silently). Excluding it lets `waitUntilStable` converge so * `continueLastTurn` runs, where the existing transcript-repair pass * (`_repairTranscriptForProvider`) flips the orphan to an errored result and * the model proceeds. */ private _partAwaitsClientInteraction; private _resolveChatRecoveryConfig; private _chatRecoveryIncidentId; private _chatRecoveryIncidentKey; /** * Monotonic forward-progress signal for recovery budget resets. * * This used to count assistant messages in `this.messages`, but that is * recomputed from the live, mutable transcript. Compaction collapses older * assistant messages into a summary, lowering the count — so a turn that had * genuinely advanced could read as "no progress" between attempts and exhaust * its budget prematurely (#1628). Instead we read a durably-persisted counter * that only ever increments — bumped at production time when new content is * durably flushed (see `_storeChunkDurably`), which is genuine forward * progress and is immune to client reconnects / recovery re-persists — so * compaction can never lower it and a reconnect can't fake it (#1637). */ private _chatRecoveryProgressMarker; /** Advance the durable recovery-progress counter. Called from * `_storeChunkDurably` when new content is durably flushed (real, reconnect- * immune forward progress). */ private _bumpChatRecoveryProgress; /** In-memory wall-clock of the last N9 child-stream progress bump (reset per * isolate so the first forwarded chunk after a restart always credits). */ private _lastAgentToolStreamProgressAt; /** * N9: forwarding a sub-agent's chunks IS forward progress for this parent * turn, so credit the parent's recovery progress marker — otherwise a parent * whose turn merely `await`s a child banks no progress of its own and its * no-progress window exhausts while the child is healthily streaming. Only * invoked after a child actually produced output (see * `_forwardAgentToolStream`), so a silent child still lets the parent exhaust. * Throttled (and reset per isolate) so we never write storage per token. */ protected _onAgentToolStreamProgress(): Promise; /** Sweep recovery incidents that have been inactive past the TTL. */ private _sweepStaleChatRecoveryIncidents; private _beginChatRecoveryIncident; private _updateChatRecoveryIncident; private _exhaustChatRecovery; /** * Route a stream-stall watchdog abort into bounded recovery instead of a * terminal error (#1626). A stall happens inside a LIVE isolate (no DO * restart), so the normal restart-detected recovery path never runs — we * open/advance a recovery incident here and schedule a continuation, reusing * the SAME budget (`maxAttempts` + wall-clock window + progress-aware reset) * as deploy/eviction recovery. A transient hang recovers; a persistently * hanging provider exhausts the budget. Idempotency matches deploy recovery: * settled tool results are durable and won't re-run, but a tool that was * mid-execution when the stall fired re-runs on the continuation. * * Returns: * - `"scheduled"` — a continuation was scheduled; the caller suppresses the * terminal error and closes the stream cleanly. * - `"exhausted"` — the budget is spent; this routes through the SAME * `_exhaustChatRecovery` path as deploy recovery (fires `onExhausted`, * emits `chat:recovery:exhausted`, marks the submission interrupted, and * delivers the configured `terminalMessage`). The caller must NOT run the * generic terminal path — the terminal UX is already delivered. * - `"disabled"` — chat recovery is off; the caller falls through to the * generic terminal error (the watchdog's original "kill the spinner" * behavior, unchanged). */ private _routeStallToBoundedRecovery; protected _handleInternalFiberRecovery( ctx: FiberRecoveryContext ): Promise; private _recoverablePreStreamUserId; private _hasPersistedRecoveredAssistant; /** * Whether the orphaned stream's partial should be materialized into an * assistant message: there is a stream, and it is either still active or * terminal-but-not-yet-persisted. Shared by the normal recovery path AND the * exhaustion path so neither discards settled work nor duplicates a partial * an earlier attempt already saved. */ private _shouldPersistOrphanedPartial; /** Whether a reconstructed partial carries any settled (provider-accepted) * tool result — the completed, often non-idempotent work that a * `{ persist: false }` recovery return would silently discard. */ private _partialHasSettledToolResults; /** * Reschedule a recovery callback that timed out waiting for stable state, * consuming one attempt. Returns `true` if rescheduled, `false` if the * attempt budget is exhausted (the caller then fails the turn terminally). * * Shared by `_chatRecoveryRetry` and `_chatRecoveryContinue` so the * non-idempotent scheduling invariant lives in exactly one place — a fix to * one path can't silently diverge from the other. Mirrors the same helper in * `@cloudflare/ai-chat`. */ private _rescheduleRecoveryAfterStableTimeout; /** * Park a recovery continuation that timed out waiting for stable state * because the turn is holding a pending CLIENT interaction (an * `input-available` client-tool part or an `approval-requested` part — see * `hasPendingInteraction`). Such a turn is WAITING ON THE HUMAN, not stuck: * the SPA replays the interrupted tool-result / approval after reconnect, * which drives a fresh continuation via the auto-continuation barrier * independently of the recovery retry loop. Burning the attempt budget on * that wait (each `waitUntilStable` times out because the human hasn't * answered) would seal a perfectly healthy turn on `stable_timeout` — the * exact symptom behind HITL "session recovery errors" under deploy churn. * * So instead of rescheduling or exhausting, we stop the loop and mark the * incident `skipped` (reason `awaiting_client_interaction`). That retains the * incident record (a later genuine interruption re-evaluates it) while * resolving the live "recovering…" indicator via `_updateChatRecoveryIncident` * so the client sees the parked tool-call UI rather than an eternal spinner. * A client that never returns is reclaimed by the incident TTL sweep and DO * idle-eviction. SERVER-tool orphans are excluded by `hasPendingInteraction` * (their `execute` died with the isolate), so they still recover normally. * * For a SUBMISSION-backed turn (`recoveredRequestId` present) the recovery * loop is the submission row's SOLE completion driver after a restart, and the * client's replay resumes the conversation as an independent auto-continuation * that never touches the submission. Parking would therefore leave the row * `running` until `_recoverSubmissionsOnStart` swept it to `error` on the next * restart. We instead complete it `completed` here: the park condition is a * fully-materialized client tool call in the leaf, which is exactly the * terminal state a non-interrupted submission reaches when its step emits a * client tool call (the model does not block on client tools — see * `_runProgrammaticMessagesTurn`, which marks such a step `completed`). The * human round-trip then proceeds via the normal auto-continuation, identical * to the non-crash flow. * * Returns `true` when the recovery was parked (caller must return), `false` * when there is no pending client interaction (caller proceeds to the normal * reschedule / exhaustion path). */ private _parkRecoveryForPendingInteraction; /** * Resolve the stream id for a recovery turn so the give-up terminalization * can surface whatever partial the turn produced. Prefers the durable stream * row keyed by the recovery-root request id; falls back to the live active * stream. Returns `""` when neither is available (the terminal banner still * fires — `_exhaustChatRecovery` does not require a stream). */ private _resolveRecoveryStreamId; /** * Terminalize a recovery turn that is giving up — whether because the * stable-state-timeout retry budget drained, or because the recovery * continuation threw a non-recoverable error — by routing through the SAME * `_exhaustChatRecovery` path as deploy-recovery and stall exhaustion * (#1626/#1631). It fires `onExhausted`, emits `chat:recovery:exhausted`, * marks the durable submission interrupted, records the terminal chat status, * and delivers the configured `terminalMessage`. `reason` carries the cause * (`stable_timeout` for a budget give-up, `recovery_error` for a thrown * error) through to `onExhausted` / `chat:recovery:exhausted`. * * This replaces the older give-up that only set the incident to `failed` and * completed the recovered submission as `error`, which bypassed * `_exhaustChatRecovery` entirely — so an app relying on `onExhausted` for the * terminal banner regressed to an eternal spinner when recovery gave up under * extreme churn. The error path matters just as much: a non-transient throw * in a recovery callback is SWALLOWED by `Agent._executeScheduleCallback` * (only a platform transient is re-thrown to preserve the one-shot row), so * without routing it here the alarm row is deleted with no terminal UX at * all — the half-finished message wedges silently. Shared by * `_chatRecoveryRetry` and `_chatRecoveryContinue`. * * Exactly-once terminalization is defended by two independent guards: * 1. The `stored?.status === "exhausted"` re-entry guard below — once an * incident is sealed, a duplicate stale alarm (or retried callback) * returns before re-firing. The seal is persisted only AFTER the * terminal writes in `_exhaustChatRecovery` succeed (see the ordering * note at the call below), so a give-up interrupted by a platform * transient re-runs in full instead of being half-sealed. * 2. The durable-submission paths additionally short-circuit earlier at the * `submission_not_running` check (the submission is already `error` after * the first give-up). This is the ONLY guard `@cloudflare/ai-chat` lacks * (no submission layer), so guard #1 carries it there. * * Residual at-least-once edges, all deliberately accepted as "deliver a * second banner" ≫ "silently drop the turn": * • No `incidentId` at all in the payload (only reachable via a direct/test * invocation — every production scheduler carries one): the synthesized * incident can't be persisted (no key), so guard #1 can't arm. * • The record is swept AGAIN between two alarms (guard #1 re-persists on the * first, so this needs a second independent sweep) — vanishingly unlikely. * • A platform transient interrupts `_exhaustChatRecovery` after the banner * broadcast — the deferred re-run re-fires `onExhausted` + the banner * (the terminal writes themselves are idempotent). */ private _exhaustRecoveryGiveUp; /** * Give-up after the stable-state-timeout retry budget drained. Thin wrapper * over `_exhaustRecoveryGiveUp` so the give-up cause is recorded as * `stable_timeout`. */ private _exhaustRecoveryAfterStableTimeout; /** * Handle an error thrown by `_chatRecoveryContinue` / `_chatRecoveryRetry` * after the incident was opened. * * - A platform transient (`isPlatformTransientError` from `agents` — a * deploy code-update reset / script supersede, a `retryable`-flagged * platform error, or "Network connection lost.", looking through wrappers * like `SqlError` via the `cause` chain) is re-thrown (after best-effort * marking the incident `failed` for observability) so * `Agent._executeScheduleCallback` preserves the one-shot alarm row and * the platform re-runs recovery once it is healthy again — the turn can * still recover, so it must NOT terminalize. Terminalizing here was the * #1730 freeze: the give-up's own seal needs the very storage that is * down, so it throws too, burns the in-process retry budget inside the * same reset window, and the row is consumed milliseconds before storage * recovers. The submission is deliberately left `running` — the deferred * re-run reads it via `_readRunningSubmissionByRequestId`, so marking it * terminal here would turn the preserved row into a guaranteed * `submission_not_running` no-op skip (a self-defeating defer). * - Any OTHER (application) error is terminalized through the give-up path * (`onExhausted` + the `terminalMessage` banner) and NOT re-thrown. This is * the fix for the silent-seal failure mode: `_executeScheduleCallback` * swallows a non-transient throw and then `alarm()` deletes the one-shot * row, so without terminalizing here the half-finished turn is dropped * with no terminal event and no banner (the user stares at a frozen * message until they send something new). */ private _handleRecoveryCallbackError; _chatRecoveryRetry(data?: ChatRecoveryRetryData): Promise; private _hasRunningSubmission; private _readRunningSubmissionByRequestId; private _markRecoveredSubmissionInterrupted; private _completeRecoveredSubmission; protected onChatRecovery( _ctx: ChatRecoveryContext ): Promise; _chatRecoveryContinue(data?: ChatRecoveryContinueData): Promise; private _applyRecoveredRequestContext; private _getPartialStreamText; private _getSubmitConcurrencyDecision; private _completeSkippedRequest; private _rollbackDroppedSubmit; private _scheduleAutoContinuation; /** * Re-arm the barrier for a tool result/approval that arrived WITHOUT * `autoContinue` (#1650). The client sends `autoContinue: false` for an * errored tool result (it declines to auto-continue a standalone error), but * in a parallel batch a SIBLING may already have requested continuation — and * this result can be the one that completes the batch. In that case we must * re-run the barrier check so the continuation the sibling requested still * fires once the batch is whole. * * Unlike `_scheduleAutoContinuation` this never CREATES a pending * continuation: a standalone errored tool (no opted-in sibling, so no pending) * must not auto-continue. It also no-ops once the continuation is running * (`pastCoalesce`) — a late result then defers/applies through the normal * path rather than re-arming. */ private _rearmPendingAutoContinuationForBatch; /** * Called when a streaming assistant turn finalizes (its message, with ALL * tool parts, is now persisted). Clears the in-flight accumulator and re-runs * the auto-continuation barrier for a continuation the stream-active gate held * (#1650). This is essential for an all-fast parallel batch whose every result * landed mid-stream: once the stream ends there is no further tool-result * event to re-arm the barrier, so without this re-check the held continuation * would never fire. A slow batch is also re-checked here and simply continues * to hold (event-driven) until its remaining siblings answer. */ private _onStreamingTurnFinalized; private _resetAutoContinuationTimer; /** * Fire an auto-continuation, but only once the model's parallel tool-call * batch is fully answered (#1649). When the model emits several tool calls in * one step the client answers each independently, so the first `autoContinue` * arrives while slower siblings are still `input-available`. Continuing then * would feed the provider an incomplete tool-result set * (`MissingToolResultsError`) or, via the transcript-repair backstop, silently * error the in-flight sibling and run a spurious extra continuation. * * The barrier is event-driven (#1650). Auto-continuation is only ever * triggered by a tool-result/approval event, so rather than wait on a fixed * timer we drain the in-flight applies, re-check, and — if the batch is still * incomplete — return WITHOUT firing and WITHOUT holding the isolate, leaving * `_continuation.pending` in place. The next sibling's result re-arms the * coalesce timer (`_scheduleAutoContinuation`) and re-runs this check; the * continuation fires once the final sibling lands. If the in-memory pending * state is lost to eviction between siblings, the final result re-creates it * from the persisted transcript and fires with a complete batch — self-healing. * A true orphan (a sibling that never arrives) simply never auto-continues, * which is correct: there is nothing valid to continue, and a later user turn * / chat recovery repairs the transcript. * * The barrier also holds while the assistant turn is still streaming (the * stream-active gate below): mid-stream the batch can still grow with tool * calls the model hasn't emitted yet, so no completeness check is meaningful. * `_onStreamingTurnFinalized` re-runs the check once the stream ends. */ private _fireAutoContinuationWhenStable; /** * Drain every in-flight tool-result/approval apply, including any enqueued * while we wait, so the subsequent `_hasIncompleteToolBatch()` re-check sees * every result that has ALREADY arrived. Bounded by real apply activity (a * storage write each), never by a fixed timer: a batch with no further * results drains in the time its pending applies take and then returns. The * loop re-reads `_interactionApplyTail` after each await because a sibling can * extend the tail mid-drain; we stop once the tail stops advancing. */ private _drainInteractionApplies; /** * `true` when the latest assistant message is mid-batch: it carries at least * one settled tool result AND at least one tool call/approval still awaiting a * client result. That is the #1649 signature — the model fanned out parallel * tool calls and only some have been answered. Scoped to the leaf (the step * the continuation answers) so an unrelated dangling tool in an earlier * message doesn't block a legitimate follow-up continuation. */ private _hasIncompleteToolBatch; private _fireAutoContinuation; private _activateDeferredContinuation; private _fireResponseHook; /** * Persist (on `error`/`interrupted`) or clear (on `completed`/`aborted`) the * durable terminal record so it can be replayed to clients on reconnect, and * resolve any in-progress "recovering…" indicator. A `completed`/`aborted` * turn is conveyed by the persisted messages, so the record is cleared; an * `error`/`interrupted` turn has no durable trace otherwise, so it is kept * until a later turn supersedes it. * * The storage primitives are shared with `@cloudflare/ai-chat` * (`_recordChatTerminal` / `_clearChatTerminal` / `_pendingChatTerminal`). */ private _recordTerminalChatStatus; /** * Persist a durable record of the last terminal turn so a client that * (re)connects after the turn ended still learns its outcome (#1645). Kept * until a later turn supersedes it (`_clearChatTerminal`); a single record is * sufficient because only the most recent terminal is relevant. */ private _recordChatTerminal; /** Clear the durable terminal record once a later turn supersedes it (#1645). */ private _clearChatTerminal; private _pendingChatTerminal; /** * Replay a pending terminal outcome (#1645) over the resume handshake so a * reconnecting client surfaces it exactly like a live exhaustion. A bare * terminal frame sent on connect is dropped by the `useAgentChat` client * because it never reaches a transport stream reader; only a frame delivered * on a resumed stream becomes `useChat.error` (this is why the terminal is * NOT included in `_buildIdleConnectMessages`). So we drive `STREAM_RESUMING` * here and send the error frame once the client ACKs (see * `_replayTerminalOnAck`). Returns true if a terminal was pending. */ private _replayTerminalOnResume; /** * Deliver the pending terminal error frame on the resumed stream the client * ACKed (#1645). The record is retained (cleared only when a later turn * supersedes it) so concurrent reconnects each learn the outcome. */ private _replayTerminalOnAck; /** * Set or clear the live "recovering…" status for a durable chat turn (#1620). * Persists a durable record (replayed on connect via `_buildIdleConnectMessages`) * and broadcasts a `MSG_CHAT_RECOVERING` frame — but only on a genuine * transition, so a deploy/reconnect storm (which re-detects recovery many * times) doesn't spam the wire. Cleared on every terminal outcome so the * indicator can't spin forever. */ private _setChatRecovering; /** * Messages sent to a client on connect when no stream is active: the current * transcript, plus a replay of an in-progress "recovering…" status (if any). * * A terminal error is deliberately NOT replayed here. A bare * `MSG_CHAT_RESPONSE` frame on connect is dropped by the `useAgentChat` * client because it never reaches a transport stream reader, so it cannot * become `useChat.error` — a failed turn would still look frozen (#1645). * The terminal outcome is instead surfaced over the resume handshake * (`_replayTerminalOnResume` → ACK → `_replayTerminalOnAck`), the only path * that lands on the stream reader. */ private _buildIdleConnectMessages; private _notifyStreamResuming; /** * Start a resumable stream and arm buffer cleanup. Wrapper around * `ResumableStream.start`: arming on START as well as finish guarantees a * stream whose DO is evicted mid-flight and never reaches a finish still gets * a future sweep instead of leaking its buffer. * * When a turn runs inside `runFiber` (durable recovery), the DO already * self-heals: `runFiber` holds `keepAlive`, which leaves a durable alarm in * storage that survives eviction, fires within ~keepAliveIntervalMs, and runs * the fiber-recovery scan — finalizing the stream (which arms cleanup) without * any client reconnect. Arming here is the safety net for any non-fiber stream * path, where no such alarm exists. The last-activity sweep threshold prevents * an actively streaming run from being reclaimed before it goes quiet (#1706). */ protected _startResumableStream( requestId: string, options?: { messageId?: string; } ): string; /** * Mark a resumable stream completed and arm buffer cleanup. Wrapper around * `ResumableStream.complete` so every stream-finish path also schedules the * cleanup alarm (#1706). */ protected _completeResumableStream(streamId: string): void; /** * Mark a resumable stream errored and arm buffer cleanup. Wrapper around * `ResumableStream.markError` — see {@link _completeResumableStream}. */ protected _errorResumableStream(streamId: string): void; /** * Ensure a single cleanup alarm is pending for this DO's resumable-stream * buffers. Armed whenever a stream finishes so that idle/one-off chat DOs * still reclaim their buffers — the lazy sweep in {@link ResumableStream} * only fires when a *subsequent* stream completes, which never happens for a * chat that receives a single turn (#1706). * * `idempotent` dedupes on (callback, payload, owner) so repeated finishes * collapse onto one pending alarm rather than stacking. */ protected _ensureStreamCleanupScheduled({ idempotent }?: { idempotent?: boolean; }): Promise; /** * Alarm callback: sweep aged stream buffers, then re-arm only while rows * remain so a fully-swept DO stops waking itself. */ _cleanupStreamBuffers(): Promise; private _persistOrphanedStream; private _broadcastChat; private _broadcast; private _broadcastMessages; } //#endregion export { TurnConfig as $, MessengerContext as $t, SkillSource$1 as A, toMessengerThread as At, Think as B, MessengerReplyStage as Bt, PrepareStepFunction$1 as C, defaultConversationName as Ct, SaveMessagesResult$1 as D, toMessengerAttachment as Dt, SaveMessagesOptions$1 as E, normalizeMessengers as Et, StreamCallback as F, MESSENGER_REPLY_FIBER_NAME as Ft, ThinkScheduledTasks as G, messengerReplyRecoveryMode as Gt, ThinkScheduledTask as H, TextStreamCallbackOptions as Ht, StreamableResult as I, MessengerDeliveryPolicy as It, ThinkTime as J, textDeltaFromStreamChunk as Jt, ThinkSubmissionInspection as K, messengerReplySnapshot as Kt, SubmitMessagesOptions as L, MessengerDeliverySurface as Lt, StepContext as M, EMPTY_MESSENGER_RESPONSE as Mt, StepResult as N, ERROR_MESSENGER_RESPONSE as Nt, Session$1 as O, toMessengerAuthor as Ot, StopCondition$1 as P, INTERRUPTED_MESSENGER_RESPONSE as Pt, ToolCallResultContext as Q, MessengerCapabilities as Qt, SubmitMessagesResult as R, MessengerDeliveryTarget as Rt, PrepareStepContext as S, defaultChatSdkEvent as St, ResolvedChatRecoveryConfig as T, idempotencyKeyForEvent as Tt, ThinkScheduledTaskContext as U, deliverMessengerReply as Ut, ThinkIntervalSchedule as V, TextStreamCallback as Vt, ThinkScheduledTaskSchedule as W, messengerReplyFailureMode as Wt, ToolCallContext as X, MessengerAttachment as Xt, ThinkWallClockSchedule as Y, MessengerAction as Yt, ToolCallDecision as Z, MessengerAuthor as Zt, FiberContext$1 as _, NormalizedMessengerDefinition as _t, ChatRecoveryConfig$1 as a, serializableMessengerEvent as an, defaultContextOverflowClassifier as at, MessageConcurrency$1 as b, ThinkMessengers as bt, ChatRecoveryOptions$1 as c, ChatSdkMessengerEventInput as ct, ChatStartEvent as d, MessengerConversationResolver as dt, MessengerEvent as en, TurnContext as et, ChunkContext as f, MessengerConversationTarget as ft, ExtensionConfig as g, MessengerThinkTarget as gt, DeleteSubmissionsOptions as h, MessengerThinkHost as ht, ChatOptions as i, messengerContextFromEvent as in, Workspace$1 as it, StepConfig as j, DeliverMessengerReplyOptions as jt, SkillRunContext as k, toMessengerMessage as kt, ChatRecoveryProgressContext as l, ChatSdkMessengerOptions as lt, ContextOverflowConfig as m, MessengerRespondTo as mt, ChatErrorClassification as n, MessengerMessage as nn, TypedToolCall$1 as nt, ChatRecoveryContext$1 as o, toMessengerUserMessage as on, defineScheduledTasks as ot, ChunkPart as p, MessengerDefinition as pt, ThinkSubmissionStatus as q, parseMessengerReplySnapshot as qt, ChatErrorContext as r, MessengerThread as rn, TypedToolResult as rt, ChatRecoveryExhaustedContext as s, MediaEvictionConfig as sn, skills as st, AddMessagesOptions as t, MessengerEventKind as tn, TurnInput as tt, ChatResponseResult$1 as u, MessengerConversationMode as ut, FiberRecoveryContext$1 as v, ThinkMessengerRuntime as vt, PrepareStepResult$1 as w, defineMessengers as wt, OnStartDegradation as x, chatSdkMessenger as xt, ListSubmissionsOptions as y, ThinkMessengerStateAgent as yt, TextStreamPart as z, MessengerReplySnapshot as zt }; //# sourceMappingURL=think-i1Pkgb1R.d.ts.map