Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions src/content/docs/agents/api-reference/callable-methods.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -710,16 +710,28 @@ for (const [name, meta] of methods) {

### `SyntaxError: Invalid or unexpected token`

If your dev server fails with `SyntaxError: Invalid or unexpected token` when using `@callable()`, set `"target": "ES2021"` in your `tsconfig.json`. This ensures that Vite's esbuild transpiler downlevels TC39 decorators instead of passing them through as native syntax.
If your dev server fails with `SyntaxError: Invalid or unexpected token` when using `@callable()`, you need two things:

```json
**1. Add the `agents/vite` plugin** — Vite 8 uses Oxc for transpilation, which does not yet support TC39 decorators. The plugin adds the required transform:

```ts title="vite.config.ts"
import agents from "agents/vite";

export default defineConfig({
plugins: [agents(), react(), cloudflare()],
});
```

**2. Extend `agents/tsconfig`** — this sets `"target": "ES2021"` and all other recommended compiler options:

```json title="tsconfig.json"
{
"compilerOptions": {
"target": "ES2021"
}
"extends": "agents/tsconfig"
}
```

If you cannot extend the shared config, set `"target": "ES2021"` manually in your `tsconfig.json`.

:::caution
Do not set `"experimentalDecorators": true` in your `tsconfig.json`. The Agents SDK uses [TC39 standard decorators](https://github.com/tc39/proposal-decorators), not TypeScript legacy decorators. Enabling `experimentalDecorators` applies an incompatible transform that silently breaks `@callable()` at runtime.
:::
Expand Down
176 changes: 168 additions & 8 deletions src/content/docs/agents/api-reference/chat-agents.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -277,12 +277,12 @@ export class ChatAgent extends AIChatAgent {

Controls whether `AIChatAgent` waits for MCP server connections to settle before calling `onChatMessage`. This ensures `this.mcp.getAITools()` returns the full set of tools, especially after Durable Object hibernation when connections are being restored in the background.

| Value | Behavior |
| ---------------------- | --------------------------------------------- |
| Value | Behavior |
| --------------------- | --------------------------------------------- |
| `{ timeout: 10_000 }` | Wait up to 10 seconds (default) |
| `{ timeout: N }` | Wait up to `N` milliseconds |
| `true` | Wait indefinitely until all connections ready |
| `false` | Do not wait (old behavior before 0.2.0) |
| `true` | Wait indefinitely until all connections ready |
| `false` | Do not wait (old behavior before 0.2.0) |

<TypeScriptExample>

Expand All @@ -303,22 +303,182 @@ export class ChatAgent extends AIChatAgent {

For lower-level control, call `this.mcp.waitForConnections()` directly inside your `onChatMessage` instead.

### `messageConcurrency`

Controls how overlapping user submissions behave when a chat turn is already active or queued.

<TypeScriptExample>

```ts
export class ChatAgent extends AIChatAgent {
messageConcurrency = "queue";
}
```

</TypeScriptExample>

| Strategy | Behavior |
| ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `"queue"` (default) | Queue every submission and process in order |
| `"latest"` | Keep only the latest overlapping submission; superseded submissions still persist their user messages but do not start a model turn |
| `"merge"` | Queue overlapping submissions, then collapse their trailing user messages into one combined turn before the latest queued turn runs |
| `"drop"` | Ignore overlapping submissions entirely |
| `{ strategy: "debounce", debounceMs?: number }` | Trailing-edge latest with a quiet window (default 750ms) |

This setting only applies to `sendMessage()` submissions. Regenerations, tool continuations, approvals, clears, and programmatic `saveMessages()` calls keep their existing serialized behavior.

### `persistMessages` and `saveMessages`

For advanced cases, you can manually persist messages:
`persistMessages` stores messages in SQLite and broadcasts the update to all connected clients, but does **not** trigger a model turn. Use it when you want to inject messages into the conversation without starting a new response.

`saveMessages` persists messages **and** triggers `onChatMessage()` for a new response. It waits for any active chat turn to finish before starting, so scheduled or programmatic messages never overlap an in-flight stream.

<TypeScriptExample>

```ts
// Persist messages without triggering a new response
// Store messages without triggering a response
await this.persistMessages(messages);

// Persist messages AND trigger onChatMessage (e.g., programmatic messages)
await this.saveMessages(messages);
// Store messages AND trigger onChatMessage
const { requestId, status } = await this.saveMessages(messages);
```

</TypeScriptExample>

`saveMessages` accepts either an array of messages or a function that derives the next message list from the latest persisted `this.messages`. Use the function form to avoid stale baselines when multiple calls queue up:

<TypeScriptExample>

```ts
await this.saveMessages((messages) => [
...messages,
{
id: crypto.randomUUID(),
role: "user",
parts: [{ type: "text", text: "Summarize the latest data" }],
createdAt: new Date(),
},
]);
```

</TypeScriptExample>

`saveMessages` returns `{ requestId, status }` where `status` is `"completed"` if the turn ran, or `"skipped"` if the chat was cleared before it started.

### `onChatResponse`

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 `saveMessages` from inside. Fires for all turn completion paths: WebSocket chat requests, `saveMessages`, and auto-continuation.

<TypeScriptExample>

```ts
import type { ChatResponseResult } from "@cloudflare/ai-chat";

export class ChatAgent extends AIChatAgent {
protected async onChatResponse(result: ChatResponseResult) {
if (result.status === "completed") {
console.log("Turn completed:", result.requestId);
}
if (result.status === "error") {
console.error("Turn failed:", result.error);
}
}
}
```

</TypeScriptExample>

The `ChatResponseResult` contains:

| Field | Type | Description |
| -------------- | ------------------------------------- | ----------------------------------------------------------------- |
| `message` | `UIMessage` | The finalized assistant message from this turn |
| `requestId` | `string` | The request ID associated with this turn |
| `continuation` | `boolean` | Whether this turn was a continuation of a previous assistant turn |
| `status` | `"completed" \| "error" \| "aborted"` | How the turn ended |
| `error` | `string \| undefined` | Error message when `status` is `"error"` |

:::note
Responses triggered from inside `onChatResponse` (for example, via `saveMessages`) do not fire `onChatResponse` recursively.
:::

### `sanitizeMessageForPersistence`

Override this method to apply custom transformations to messages before they are persisted to storage. This hook runs **after** the built-in sanitization (OpenAI metadata stripping, Anthropic provider-executed tool payload truncation, empty reasoning part filtering).

<TypeScriptExample>

```ts
export class ChatAgent extends AIChatAgent {
protected sanitizeMessageForPersistence(message: UIMessage): UIMessage {
return {
...message,
parts: message.parts.map((part) => {
if (
"output" in part &&
typeof part.output === "string" &&
part.output.length > 1000
) {
return { ...part, output: "[redacted]" };
}
return part;
}),
};
}
}
```

</TypeScriptExample>

### Turn lifecycle helpers

These methods help you coordinate programmatic turns and wait for pending interactions.

#### `hasPendingInteraction()`

Returns `true` when an assistant message is waiting on a client tool result or approval.

<TypeScriptExample>

```ts
if (this.hasPendingInteraction()) {
console.log("Waiting for user to approve or provide tool output");
}
```

</TypeScriptExample>

#### `waitUntilStable()`

Waits until the conversation is fully stable — no active stream, no pending client-tool interactions, and no queued continuation turns. Returns `true` when stable, or `false` if the timeout expires before a pending interaction resolves.

<TypeScriptExample>

```ts
const stable = await this.waitUntilStable({ timeout: 30_000 });
if (stable) {
console.log("All turns complete, safe to proceed");
}
```

</TypeScriptExample>

This is especially useful with `saveMessages` for server-driven flows:

<TypeScriptExample>

```ts
await this.saveMessages((messages) => [...messages, syntheticUserMessage]);
await this.waitUntilStable({ timeout: 60_000 });
// The assistant has finished responding
```

</TypeScriptExample>

#### `resetTurnState()`

Aborts the active turn and invalidates queued continuations. The built-in `CF_AGENT_CHAT_CLEAR` handler calls this automatically, but you can call it manually if needed.

### Lifecycle hooks

Override `onConnect` and `onClose` to add custom logic. Stream resumption and message sync are handled for you:
Expand Down
17 changes: 17 additions & 0 deletions src/content/docs/agents/api-reference/client-sdk.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,23 @@ When the WebSocket connection closes — whether due to network issues, server r

Agents can maintain state that syncs bidirectionally with all connected clients.

### Reading current state

Both `useAgent` and `AgentClient` expose a `state` property that reflects the current agent state. It starts as `undefined` until the first state message is received from the server.

<TypeScriptExample>

```ts
const agent = useAgent({ agent: "GameAgent", name: "game-123" });

// Read the current state at any time
console.log("Current score:", agent.state?.score);
```

</TypeScriptExample>

With `useAgent`, state updates trigger a React re-render, so `agent.state` always reflects the latest value in your JSX. With `AgentClient`, the `state` field is updated synchronously on each incoming server broadcast or `setState` call.

### Receiving state updates

<TypeScriptExample>
Expand Down
75 changes: 75 additions & 0 deletions src/content/docs/agents/api-reference/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,81 @@ const response = await this.env.AI.run("@cf/meta/llama-3-8b-instruct", {

</TypeScriptExample>

## TypeScript configuration

The Agents SDK ships a shared `tsconfig.json` that sets all the compiler options needed for agents projects — including the `ES2021` target required for `@callable()` decorators, strict mode, bundler module resolution, and Workers types.

Extend it in your `tsconfig.json`:

```json
{
"extends": "agents/tsconfig"
}
```

This is equivalent to:

```json
{
"compilerOptions": {
"target": "ES2021",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"jsx": "react-jsx",
"module": "ES2022",
"moduleResolution": "bundler",
"types": ["node", "@cloudflare/workers-types", "vite/client"],
"allowImportingTsExtensions": true,
"noEmit": true,
"isolatedModules": true,
"verbatimModuleSyntax": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
}
}
```

You can override individual options as needed:

```json
{
"extends": "agents/tsconfig",
"compilerOptions": {
"jsx": "preserve"
}
}
```

:::caution
Do not set `"experimentalDecorators": true`. The Agents SDK uses [TC39 standard decorators](https://github.com/tc39/proposal-decorators), not TypeScript legacy decorators. Enabling `experimentalDecorators` applies an incompatible transform that silently breaks `@callable()` at runtime.
:::

## Vite configuration

The Agents SDK provides a Vite plugin that handles TC39 decorator transforms. Vite 8 uses Oxc for transpilation, which does not yet support TC39 decorators — without this plugin, `@callable()` and other decorators will fail at runtime.

Add the plugin to your `vite.config.ts`:

<TypeScriptExample>

```ts title="vite.config.ts"
import { cloudflare } from "@cloudflare/vite-plugin";
import react from "@vitejs/plugin-react";
import agents from "agents/vite";
import { defineConfig } from "vite";

export default defineConfig({
plugins: [agents(), react(), cloudflare()],
});
```

</TypeScriptExample>

The `agents()` plugin is safe to include even if your project does not use decorators. It only runs the transform on files that contain `@` syntax.

The starter template and all examples include this plugin by default. If you encounter `SyntaxError: Invalid or unexpected token` with decorators, refer to [Callable methods — Troubleshooting](/agents/api-reference/callable-methods/#troubleshooting).

## Generating types

Wrangler can generate TypeScript types for your bindings.
Expand Down
Loading
Loading