Skip to content

Releases: cloudflare/agents

@cloudflare/shell@0.3.5

28 Apr 22:56
cbf3e18

Choose a tag to compare

Patch Changes

  • 19a4c08 Thanks @threepointone! - Bump dependencies: isomorphic-git from ^1.37.5 to ^1.37.6 (runtime) and @cloudflare/vitest-pool-workers from ^0.15.0 to ^0.15.1 (devDependency, test-only — does not affect the published artifact).

    No API or runtime behavior change in @cloudflare/shell itself.

@cloudflare/ai-chat@0.5.3

28 Apr 22:56
cbf3e18

Choose a tag to compare

Patch Changes

  • ca510d4 Thanks @threepointone! - Tighten the agents peer dependency floor from >=0.8.7 to >=0.11.7 to reflect the current monorepo set we actually test against. Upper bound (<1.0.0) is unchanged.

    No runtime change in @cloudflare/ai-chat itself. The visible effect for consumers: pairing the latest @cloudflare/ai-chat with a stale agents (<0.11.7) now produces a peer warning where it previously did not. That's the intended signal — agents versions older than 0.11.7 are no longer tested against this @cloudflare/ai-chat.

  • #1411 2fa68be Thanks @threepointone! - Add options.signal to AIChatAgent.saveMessages and continueLastTurn for external cancellation of programmatic turns, plus protected abortRequest(id) / abortAllRequests() methods (#1406).

    saveMessages and continueLastTurn accept a second SaveMessagesOptions argument:

    const result = await this.saveMessages(messages, {
      signal: controller.signal,
    });
    if (result.status === "aborted") {
      // Inference loop terminated mid-stream; partial chunks persisted.
    }

    The signal is linked to AIChatAgent's per-turn AbortController and produces the same end state as a chat-request-cancel WebSocket message: the inference loop's signal aborts, partial chunks persist, the result reports status: "aborted", and onChatResponse fires with the same status. Pre-aborted signals short-circuit before any model work runs. Listeners are detached cleanly when the turn finishes, so the same long-lived signal can be passed to many turns without leaking.

    abortRequest(id, reason?) and abortAllRequests() are protected entry points for subclasses that want to cancel turns without tracking ids.

    SaveMessagesResult.status now includes "aborted" alongside "completed" and "skipped". Existing callers that only switch on "completed" are unaffected.

    Limitations.

    • AbortSignal cannot cross Durable Object RPC. Construct the controller inside the DO that calls saveMessages.
    • The signal lives in memory only. If the DO hibernates mid-turn and chatRecovery is enabled, the recovered turn runs without the original signal.

    See cloudflare/agents#1406 for the motivating use case.

agents@0.11.6

27 Apr 08:27
1ad4cde

Choose a tag to compare

Patch Changes

  • #1393 5aaf7c4 Thanks @threepointone! - Migrate facet (sub-agent) bootstrap to the documented Cloudflare facet API: pass id: parentNs.idFromName(name) to ctx.facets.get() so the facet has its own ctx.id.name. Drops the __ps_name storage write and setName() bootstrap from _cf_initAsFacet.

    Why this matters. Facets spawned without an explicit id inherit the parent DO's ctx.id, so on a facet ctx.id.name was the parent's name and this.name silently misreported as the parent's name. Anything that read this.name from inside a sub-agent (including selfPath, parentPath, and any user code) was getting the wrong value. With the explicit id passed at facet creation time, the runtime gives the facet a real ctx.id.name === name and PartyServer's existing 0.5.x name getter resolves this.name correctly without any override mechanism, storage write, or cold-wake hydrate cost. Cold-wake recovery happens for free because idFromName is deterministic and the factory re-runs on resume.

    This requires partyserver ≥ 0.5.3 (bumped in this release); 0.5.3 is byte-identical to 0.5.2 at runtime, only adds documentation and test coverage of the explicit-id facet pattern.

    Other changes:

    • New error path. If subAgent() is called from a parent class that isn't bound as a Durable Object namespace, the framework now throws a descriptive error pointing at wrangler.jsonc. If this.constructor.name looks minified (e.g. _a), the message includes a bundler-config hint about preserving class names.
    • Defensive runtime check. _cf_initAsFacet now asserts this.name === name so any future bug in the parent's id construction surfaces immediately instead of silently mis-identifying the facet.
    • alarm() docstring clarified to reflect the new resolution path (this.name from ctx.id.name, not from a storage hydrate).
    • MCP test cleanup. Vestigial setName("default") + explicit onStart() call pairs in oauth2-mcp-client, wait-connections-e2e, and create-oauth-provider test files have been removed; they were originally needed for partyserver 0.4.x bootstrap but became actual ctx.id.name mismatches under partyserver 0.5.x.

    Backward-compatible for all public APIs: subAgent(), parentAgent(), hasSubAgent(), listSubAgents(), deleteSubAgent(), and abortSubAgent() keep their signatures and semantics. The change is purely in the facet bootstrap internals; the user-facing effect is that this.name inside a sub-agent now correctly reports the sub-agent's own name (was previously the parent's name when run against partyserver 0.5.x).

    See cloudflare/partykit#386 for the partyserver-side documentation companion.

  • #1395 63cfae6 Thanks @threepointone! - Share submit concurrency bookkeeping through agents/chat and use it from both chat agents.

    This extracts the latest/merge/drop/debounce admission state machine into a SubmitConcurrencyController exported from agents/chat. AIChatAgent semantics (including merge persistence) are preserved. Think now picks up the same pending-enqueue protection, so an overlapping submit is still detected while an accepted request is between admission and turn queue registration.

    Additional fixes:

    • Think now captures the turn generation immediately after admission and threads it into _turnQueue.enqueue, so a clear that lands between admission and queue registration cannot run a stale turn.
    • Pending-enqueue tracking is now bound to a release function tied to the controller's reset epoch, so a release from a pre-reset submit can no longer erase a post-reset submit's marker and let a third submit slip through as non-overlapping.
    • Debounce cancellation correctly resolves all in-flight waiters instead of overwriting a single timer slot.
  • #1396 fdf5a8a Thanks @threepointone! - Fix Think persisting a duplicate orphan assistant row when a user submits during a streaming tool turn (#1381).

    When useAgentChat posts an in-flight assistant snapshot it minted optimistically (client-generated ID, state: "input-available"), Session's INSERT-OR-IGNORE-by-ID would store it as a separate row alongside the eventual server-owned assistant for the same toolCallId. The next turn's convertToModelMessages then produced a malformed Anthropic prompt and the provider rejected it.

    reconcileMessages and resolveToolMergeId now live in agents/chat and Think runs them in _handleChatRequest before persistence. Stale input-available snapshots pick up the server's tool output via mergeServerToolOutputs, and any incoming assistant whose toolCallId already exists on a server row adopts the server's ID so persistence updates the existing row instead of inserting an orphan.

    @cloudflare/ai-chat keeps its existing reconciler behavior; the only change is that it now imports reconcileMessages / resolveToolMergeId from agents/chat instead of a local file.

@cloudflare/think@0.4.1

27 Apr 08:27
1ad4cde

Choose a tag to compare

Patch Changes

  • #1395 63cfae6 Thanks @threepointone! - Share submit concurrency bookkeeping through agents/chat and use it from both chat agents.

    This extracts the latest/merge/drop/debounce admission state machine into a SubmitConcurrencyController exported from agents/chat. AIChatAgent semantics (including merge persistence) are preserved. Think now picks up the same pending-enqueue protection, so an overlapping submit is still detected while an accepted request is between admission and turn queue registration.

    Additional fixes:

    • Think now captures the turn generation immediately after admission and threads it into _turnQueue.enqueue, so a clear that lands between admission and queue registration cannot run a stale turn.
    • Pending-enqueue tracking is now bound to a release function tied to the controller's reset epoch, so a release from a pre-reset submit can no longer erase a post-reset submit's marker and let a third submit slip through as non-overlapping.
    • Debounce cancellation correctly resolves all in-flight waiters instead of overwriting a single timer slot.
  • #1394 a0a0d17 Thanks @threepointone! - think: add beforeStep lifecycle hook and output passthrough on TurnConfig.

    • beforeStep(ctx) — new lifecycle hook called before each AI SDK step in the agentic loop, wired to streamText({ prepareStep }). Receives a PrepareStepContext (the AI SDK's PrepareStepFunction parameter — steps, stepNumber, model, messages, experimental_context) and may return a StepConfig (PrepareStepResult) to override model, toolChoice, activeTools, system, messages, experimental_context, or providerOptions for the current step. Use beforeTurn for turn-wide assembly and beforeStep when the decision depends on the step number or previous step results. Resolves #1363.
    • TurnConfig.output — new optional field on TurnConfig forwarded to streamText. Accepts the AI SDK's structured-output spec (e.g. Output.object({ schema }), Output.text()) so a single agent can keep tools enabled on intermediate turns and return schema-validated structured output on a designated turn — without losing tools at model construction. Combine with activeTools: [] for providers that strip tools when responseFormat: "json" is active (e.g. workers-ai-provider). Resolves #1383.
    • New re-exports from @cloudflare/think: PrepareStepFunction, PrepareStepResult, PrepareStepContext, StepConfig.

    beforeStep is available to subclasses; it is not dispatched to extensions (the AI SDK prepareStep boundary surfaces non-serializable inputs like LanguageModel instances). The AI SDK does not expose output or maxSteps per step — set those at the turn level via TurnConfig. All other extension hook subscriptions are unchanged.

  • #1372 040da0f Thanks @threepointone! - Remove Think's unused internal session_id config scaffolding and move Think's private config into a dedicated think_config table.

    Older builds wrote Think-owned config into Session's shared assistant_config(session_id, key, value) table even though Think never actually had top-level multi-session support and _sessionId() always returned the empty string. Think now stores its private config rows in think_config(key, value), which better matches the shipped model of one Think Durable Object per conversation and avoids overloading Session's shared metadata table.

    Existing Durable Objects are migrated automatically on startup: legacy Think-owned keys stored in assistant_config with session_id = '' are copied into think_config before config reads and writes continue.

  • #1396 fdf5a8a Thanks @threepointone! - Fix Think persisting a duplicate orphan assistant row when a user submits during a streaming tool turn (#1381).

    When useAgentChat posts an in-flight assistant snapshot it minted optimistically (client-generated ID, state: "input-available"), Session's INSERT-OR-IGNORE-by-ID would store it as a separate row alongside the eventual server-owned assistant for the same toolCallId. The next turn's convertToModelMessages then produced a malformed Anthropic prompt and the provider rejected it.

    reconcileMessages and resolveToolMergeId now live in agents/chat and Think runs them in _handleChatRequest before persistence. Stale input-available snapshots pick up the server's tool output via mergeServerToolOutputs, and any incoming assistant whose toolCallId already exists on a server row adopts the server's ID so persistence updates the existing row instead of inserting an orphan.

    @cloudflare/ai-chat keeps its existing reconciler behavior; the only change is that it now imports reconcileMessages / resolveToolMergeId from agents/chat instead of a local file.

  • #1374 a6e22c3 Thanks @threepointone! - Fix stream resumption on page refresh: do not broadcast cf_agent_chat_messages from Think's onConnect while a resumable stream is in flight.

    Previously, Think unconditionally sent a cf_agent_chat_messages frame on every new WebSocket connection. When a client refreshed during an active chat turn, that broadcast arrived in the same connect sequence as cf_agent_stream_resuming and overwrote the in-progress assistant message the client was about to rebuild from the resumed stream. The assistant reply would stay hidden until the server finished the turn and re-broadcast the persisted history.

    Now Think only broadcasts cf_agent_chat_messages on connect when there is no active resumable stream. During an active stream the resume flow is the authoritative source of state: STREAM_RESUMING triggers replay of buffered chunks, and the final state broadcast happens when the turn completes. This matches the behavior that AIChatAgent already had.

    Marked the internal _resumableStream field as protected (previously private) so framework subclasses and focused tests can coordinate around the resume lifecycle.

  • #1384 a7059d4 Thanks @threepointone! - Introduce WorkspaceLike — type the this.workspace field as the minimum surface Think actually uses instead of the concrete Workspace class.

    Think's workspace is now typed as WorkspaceLike (Pick<Workspace, "readFile" | "writeFile" | "readDir" | "rm" | "glob" | "mkdir" | "stat">) rather than Workspace. createWorkspaceTools() likewise accepts any WorkspaceLike. The default runtime value is unchanged — a full Workspace backed by the DO's SQLite — so the vast majority of consumers need no changes.

    This unlocks patterns like a shared workspace across multiple agents: a child agent can override workspace with a proxy that forwards each call to a parent DO via RPC, and the rest of Think's workspace-aware code (the builtin tools, lifecycle hooks) keeps working without cast gymnastics. See examples/assistant for the cross-chat shared workspace built on this.

    Consumers who use createWorkspaceStateBackend(workspace) from @cloudflare/shell (codemode's state.* API) still need a concrete Workspace — that helper reaches for more of the filesystem surface than WorkspaceLike covers.

@cloudflare/shell@0.3.4

27 Apr 08:27
1ad4cde

Choose a tag to compare

Patch Changes

  • #1384 a7059d4 Thanks @threepointone! - Introduce WorkspaceFsLike — the minimum Workspace surface required by WorkspaceFileSystem and createWorkspaceStateBackend.

    WorkspaceFileSystem's constructor and createWorkspaceStateBackend's parameter both now accept any WorkspaceFsLike (a Pick<Workspace, …> of the 16 filesystem methods the adapter reaches for) rather than a concrete Workspace. Non-breaking — Workspace still satisfies WorkspaceFsLike so every existing call site keeps working without changes.

    This unlocks wrapping a real Workspace behind your own layer — most commonly a cross-DO proxy that forwards each call to a parent agent's workspace over RPC — and still using it as the storage for codemode's state.* sandbox API via createWorkspaceStateBackend. See examples/assistant for the end-to-end pattern with SharedWorkspace.

@cloudflare/ai-chat@0.5.2

27 Apr 08:27
1ad4cde

Choose a tag to compare

Patch Changes

  • #1374 a6e22c3 Thanks @threepointone! - Fix useAgentChat recreating the AI SDK Chat instance — and orphaning any in-flight resumeStream — whenever agent.name transitions in place.

    The useAgent({ basePath }) + static options = { sendIdentityOnConnect: true } pattern lets the server own the Durable Object instance name. The browser starts with a placeholder ("default"), then useAgent mutates the agent object's .name to the server-assigned value when the identity frame arrives. useAgentChat previously included agent.name in the stable chat id it passed to useChat({ id }), so the transition changed the id and the AI SDK recreated the underlying Chat instance. The useEffect that fires chatRef.current.resumeStream() is keyed on the ref object, not the Chat instance, so it does not re-fire on recreation — the resumed stream kept feeding chunks into the orphaned Chat's state while React subscribed to the new Chat's state, so the user saw an empty assistant reply after a mid-stream refresh until the server's final CF_AGENT_CHAT_MESSAGES broadcast landed.

    useAgentChat now distinguishes an in-place agent.name mutation from a genuine "consumer switched chats" event by checking the agent object's reference identity:

    • same agent reference, name mutation → not a chat switch; keep the Chat instance stable.
    • new agent reference → chat switch; recompute the stable chat id so the AI SDK recreates the Chat against the new conversation.

    The stable id is also still upgraded once from the identity-only fallback to the URL-resolved key when the WebSocket handshake completes.

    Consumers who want to switch chats without remounting should pass a different agent object (e.g. a new useAgent({...}) call with a different name). To get a completely fresh Chat (e.g. when mounting a different chat tab), the conventional React pattern — key={chatId} on the parent or swapping the subtree — continues to work.

  • #1395 63cfae6 Thanks @threepointone! - Share submit concurrency bookkeeping through agents/chat and use it from both chat agents.

    This extracts the latest/merge/drop/debounce admission state machine into a SubmitConcurrencyController exported from agents/chat. AIChatAgent semantics (including merge persistence) are preserved. Think now picks up the same pending-enqueue protection, so an overlapping submit is still detected while an accepted request is between admission and turn queue registration.

    Additional fixes:

    • Think now captures the turn generation immediately after admission and threads it into _turnQueue.enqueue, so a clear that lands between admission and queue registration cannot run a stale turn.
    • Pending-enqueue tracking is now bound to a release function tied to the controller's reset epoch, so a release from a pre-reset submit can no longer erase a post-reset submit's marker and let a third submit slip through as non-overlapping.
    • Debounce cancellation correctly resolves all in-flight waiters instead of overwriting a single timer slot.
  • #1396 fdf5a8a Thanks @threepointone! - Fix Think persisting a duplicate orphan assistant row when a user submits during a streaming tool turn (#1381).

    When useAgentChat posts an in-flight assistant snapshot it minted optimistically (client-generated ID, state: "input-available"), Session's INSERT-OR-IGNORE-by-ID would store it as a separate row alongside the eventual server-owned assistant for the same toolCallId. The next turn's convertToModelMessages then produced a malformed Anthropic prompt and the provider rejected it.

    reconcileMessages and resolveToolMergeId now live in agents/chat and Think runs them in _handleChatRequest before persistence. Stale input-available snapshots pick up the server's tool output via mergeServerToolOutputs, and any incoming assistant whose toolCallId already exists on a server row adopts the server's ID so persistence updates the existing row instead of inserting an orphan.

    @cloudflare/ai-chat keeps its existing reconciler behavior; the only change is that it now imports reconcileMessages / resolveToolMergeId from agents/chat instead of a local file.

agents@0.11.5

23 Apr 03:31
f07411b

Choose a tag to compare

Patch Changes

  • #1353 f834c81 Thanks @threepointone! - Align AIChatAgent generics and types with @cloudflare/think, plus a reference example for multi-session chat built on the sub-agent routing primitive.

    • New Props generic: AIChatAgent<Env, State, Props> extending Agent<Env, State, Props>. Subclasses now get properly typed this.ctx.props.
    • Shared lifecycle types: ChatResponseResult, ChatRecoveryContext, ChatRecoveryOptions, SaveMessagesResult, and MessageConcurrency now live in agents/chat and are re-exported by both @cloudflare/ai-chat and @cloudflare/think. No behavior change; one place to edit when shapes evolve.
    • ChatMessage stays the public message type: the package continues to export ChatMessage, and the public API/docs keep using that name.
    • messages stays a public field: messages: ChatMessage[].

    The full stance (AIChatAgent is first-class, production-ready, and continuing to get features; shared infrastructure should land in agents/chat where both classes benefit) is captured in design/rfc-ai-chat-maintenance.md.

    A new example, examples/multi-ai-chat, demonstrates the multi-session pattern end-to-end on top of the sub-agent routing primitive: an Inbox Agent owns the chat list + shared memory; each chat is an AIChatAgent facet (this.subAgent(Chat, id)). The client addresses the active chat via useAgent({ sub: [{ agent: "Chat", name: chatId }] }) — no separate DO binding, no custom routing on the server. Inbox.onBeforeSubAgent gates with hasSubAgent as a strict registry, and Chat reaches its parent via this.parentAgent(Inbox).

  • #1348 0693a5f Thanks @threepointone! - Bump dependencies.

  • #1362 d901804 Thanks @threepointone! - fix(mcp): capture tool title in MCP client

  • #1355 df2023f Thanks @threepointone! - External addressability for sub-agents.

    Clients can now reach a facet (a child DO created by Agent#subAgent()) directly via a nested URL:

    /agents/{parent-class}/{parent-name}/sub/{child-class}/{child-name}[/...]
    

    New public APIs (all @experimental):

    • routeSubAgentRequest(req, parent, options?) — sub-agent analog of routeAgentRequest. For custom-routing setups where the outer URL doesn't match the default /agents/... shape.
    • getSubAgentByName(parent, Cls, name) — sub-agent analog of getAgentByName. Returns a typed Proxy that round-trips typed RPC calls through the parent. RPC-only (no .fetch()); use routeSubAgentRequest for external HTTP/WS.
    • parseSubAgentPath(url, options?) — public URL parser used internally by the routers.
    • SUB_PREFIX — the "sub" separator constant (not configurable; exposed for symbolic URL building).

    New public on Agent:

    • onBeforeSubAgent(req, { className, name }) — overridable middleware hook, mirrors onBeforeConnect / onBeforeRequest. Returns Request | Response | void for short-circuit responses, request mutation, or passthrough. Default: void.
    • parentPath / selfPath — root-first { className, name } ancestor chains, populated at facet init time. Inductive across recursive nesting.
    • hasSubAgent(ClsOrName, name) / listSubAgents(ClsOrName?) — parent-side introspection backed by an auto-maintained SQLite registry written by subAgent() / deleteSubAgent(). Both accept either the class constructor or a CamelCase class name string.

    New public on useAgent (React):

    • sub?: Array<{ agent, name }> — flat root-first chain addressing a descendant facet. The hook's .agent / .name report the leaf identity; .path exposes the full chain.

    Breaking changes: none. routeAgentRequest behavior is unchanged when URLs don't contain /sub/. onBeforeSubAgent defaults to permissive (forward unchanged). useAgent without sub is unchanged. subAgent() / deleteSubAgent() gain registry side effects but preserve return types and failure modes. The _cf_initAsFacet signature gained an optional parentPath parameter. deleteSubAgent() is now idempotent — calling it for a never-spawned or already-deleted child no longer throws. Sub-agent class names equal to "Sub" are rejected (the /sub/ URL separator is reserved).

    See design/rfc-sub-agent-routing.md for the full rationale, design decisions, and edge cases. The spike at packages/agents/src/tests/spike-sub-agent-routing.test.ts documents the three candidate approaches considered for cross-DO stub passthrough and why the per-call bridge won.

  • #1346 a78bb2a Thanks @threepointone! - Remove unused dependencies, devDependencies, and peerDependencies from the agents package.

    • dependencies: drop json-schema, json-schema-to-typescript, and picomatch. None are imported by the package; picomatch was already pulled in transitively via @rolldown/plugin-babel.
    • devDependencies: drop @ai-sdk/openai (only referenced in a commented-out line) and @cloudflare/workers-oauth-provider (not referenced anywhere).
    • peerDependencies / peerDependenciesMeta: drop @ai-sdk/react and viem. @ai-sdk/react is already a peer of @cloudflare/ai-chat (itself an optional peer here), and viem is a regular dependency of @x402/evm, so both are supplied transitively when the relevant optional features are used.

@cloudflare/think@0.4.0

23 Apr 03:31
f07411b

Choose a tag to compare

Minor Changes

  • #1350 3a1140f Thanks @threepointone! - Align Think generics with Agent / AIChatAgent.

    Think is now Think<Env, State, Props> and extends Agent<Env, State, Props>, so subclasses get properly typed this.state, this.setState(), initialState, and this.ctx.props. The previous Config class generic is removed.

    configure() and getConfig() remain, but the config type is now specified at the call site via a method-level generic:

    // Before
    export class MyAgent extends Think<Env, MyConfig> {
      getModel() {
        const tier = this.getConfig()?.modelTier ?? "fast";
        // ...
      }
    }
    
    // After
    export class MyAgent extends Think<Env> {
      getModel() {
        const tier = this.getConfig<MyConfig>()?.modelTier ?? "fast";
        // ...
      }
    }

    This is a breaking change for anyone using the second type parameter of Think. Update the class declaration and any direct configure(...) / getConfig() call sites that relied on the class-level Config type.

@cloudflare/ai-chat@0.5.1

23 Apr 16:19
140cde3

Choose a tag to compare

Patch Changes

  • #1368 2fe85cb Thanks @threepointone! - Add isToolContinuation: boolean to useAgentChat() so consumers can disambiguate a fresh user-initiated status === "submitted" from one driven by a server-pushed tool continuation. See #1365.

    status already tracks the whole tool round-trip (submittedstreamingready) after addToolOutput / addToolApprovalResponse, on purpose — that's what #1157 asked for and what many loading-spinner UIs now rely on. But some consumers want a typing indicator only for new user messages, not for mid-turn continuations, and previously had to inspect message history to tell them apart.

    isToolContinuation is true from the moment addToolOutput / addToolApprovalResponse kicks off an auto-continuation until the continuation stream closes (or is aborted by stop()). It is false otherwise — including during cross-tab server broadcasts, which surface via isServerStreaming only.

    const { status, isStreaming, isToolContinuation } = useAgentChat({ ... });
    
    const isLoading = isStreaming || status === "submitted";
    const showTypingIndicator = status === "submitted" && !isToolContinuation;

    Purely additive — no change to status, isServerStreaming, or isStreaming semantics.

  • #1366 53600d0 Thanks @threepointone! - Fix useAgentChat() going silent while an onToolCall handler is running. The server's streamText ends the stream as soon as the model emits a client-tool call, which dropped status back to ready and isStreaming/isServerStreaming to false for the full duration of the client-side tool.execute() — often a fetch taking several seconds. Consumers had no single flag that covered the whole "turn in progress" window. See #1365.

    useAgentChat() now treats any unresolved client-side tool call on the latest assistant message as an active server-driven phase:

    • isServerStreaming is true from the moment the tool part appears in input-available (with an active handler — onToolCall or a deprecated tools entry with execute) until it transitions out via addToolOutput / addToolResult.
    • isStreaming (status === "streaming" || isServerStreaming) stays true across the whole tool round-trip, including the gap between the model emitting the call and the server pushing its continuation.
    • status is untouched — it still means "user-initiated submission awaiting a response." Tools waiting for explicit user confirmation are excluded from the busy signal (nothing is happening until the user acts).

    Consumer code simplifies to:

    const { isStreaming, status } = useAgentChat({ ... });
    const isLoading = isStreaming || status === "submitted";
    const showTypingIndicator = status === "submitted";

    No API changes. Existing code that only looked at status behaves the same.

@cloudflare/ai-chat@0.5.0

23 Apr 03:31
f07411b

Choose a tag to compare

Minor Changes

  • #1353 f834c81 Thanks @threepointone! - Align AIChatAgent generics and types with @cloudflare/think, plus a reference example for multi-session chat built on the sub-agent routing primitive.

    • New Props generic: AIChatAgent<Env, State, Props> extending Agent<Env, State, Props>. Subclasses now get properly typed this.ctx.props.
    • Shared lifecycle types: ChatResponseResult, ChatRecoveryContext, ChatRecoveryOptions, SaveMessagesResult, and MessageConcurrency now live in agents/chat and are re-exported by both @cloudflare/ai-chat and @cloudflare/think. No behavior change; one place to edit when shapes evolve.
    • ChatMessage stays the public message type: the package continues to export ChatMessage, and the public API/docs keep using that name.
    • messages stays a public field: messages: ChatMessage[].

    The full stance (AIChatAgent is first-class, production-ready, and continuing to get features; shared infrastructure should land in agents/chat where both classes benefit) is captured in design/rfc-ai-chat-maintenance.md.

    A new example, examples/multi-ai-chat, demonstrates the multi-session pattern end-to-end on top of the sub-agent routing primitive: an Inbox Agent owns the chat list + shared memory; each chat is an AIChatAgent facet (this.subAgent(Chat, id)). The client addresses the active chat via useAgent({ sub: [{ agent: "Chat", name: chatId }] }) — no separate DO binding, no custom routing on the server. Inbox.onBeforeSubAgent gates with hasSubAgent as a strict registry, and Chat reaches its parent via this.parentAgent(Inbox).

Patch Changes

  • #1358 ea229b1 Thanks @threepointone! - Fix useAgentChat() crashing on first render when agent.getHttpUrl() returns an empty string. This happened in setups where the WebSocket handshake hadn't completed by the time React rendered — most commonly when the agent is reached through a proxy or custom-routed worker — because @cloudflare/ai-chat unconditionally called new URL(agent.getHttpUrl()). See #1356.

    useAgentChat() now treats a missing HTTP URL as "not ready yet":

    • The built-in /get-messages fetch is deferred until the URL is known, and applied exactly once when it resolves (empty chats only — existing messages are never overwritten).
    • Custom getInitialMessages callbacks continue to run and are passed url: undefined so they can load from other sources if they don't need the socket URL. GetInitialMessagesOptions.url is now string | undefined; callers that previously typed url: string should widen to url?: string.
    • Initial messages are cached by agent identity (class + name) rather than by URL + identity, so the URL-arrival transition no longer invalidates the cache, re-invokes the loader, or re-triggers Suspense once the chat has already been populated.
    • The underlying useChat instance keeps a stable id across the URL-arrival transition, so in-flight stream resume and chat state are preserved.

    No API or behavior changes for apps where the URL was already available synchronously on first render.