Skip to content

Latest commit

 

History

History
290 lines (219 loc) · 10.3 KB

File metadata and controls

290 lines (219 loc) · 10.3 KB

AGENTS.md — examples/

Self-contained demo apps that show how to use the Agents SDK. These are user-facing learning material — keep them simple, clear, and consistent.

Each example should focus on one feature or concept (e.g., MCP servers, email routing, workflows). The exception is playground/, which is the kitchen-sink showcase covering the full spread of SDK features in a single app.

Example shape

Most examples have both a frontend and a backend. This makes them immediately runnable and visually demonstrable — users can pnpm run start and see the feature in action, not just read server logs.

Focused protocol examples can be server-only when a frontend would obscure the concept. Keep MCP server examples minimal when they demonstrate Cloudflare Workers setup, raw transports, or elicitation behavior that must be exercised from an MCP client.

Full-stack examples use the Cloudflare Vite plugin for development and builds. Server-only examples use Wrangler directly.

Required structure

Full-stack examples must have:

example-name/
  package.json          # name, dependencies, scripts
  vite.config.ts        # must use @cloudflare/vite-plugin
  wrangler.jsonc        # Workers config (not .toml)
  tsconfig.json         # must extend agents/tsconfig
  index.html            # Vite entry point
  README.md             # What this example demonstrates, how to run it
  public/
    favicon.ico         # Cloudflare favicon
  src/
    server.ts           # Worker entry point
    client.tsx          # React client entry
    styles.css          # Kumo + Tailwind imports

Server-only examples must have:

example-name/
  package.json          # name, dependencies, scripts
  wrangler.jsonc        # Workers config (not .toml)
  tsconfig.json
  README.md             # What this example demonstrates, how to run it
  src/
    index.ts            # Worker entry point

Conventions

Scripts

Full-stack examples use start (not dev) for the development server:

{
  "scripts": {
    "start": "vite dev",
    "deploy": "vite build && wrangler deploy",
    "types": "wrangler types env.d.ts --include-runtime false"
  }
}

Server-only examples can use Wrangler directly:

{
  "scripts": {
    "dev": "wrangler dev",
    "deploy": "wrangler deploy",
    "types": "wrangler types env.d.ts --include-runtime false"
  }
}

wrangler.jsonc

  • Use wrangler.jsonc (not .toml)
  • Include "$schema": "../../node_modules/wrangler/config-schema.json"
  • compatibility_date: "2026-06-11", compatibility_flags: ["nodejs_compat"]
  • Full-stack apps with client routing: add "assets": { "not_found_handling": "single-page-application" }
  • Use "run_worker_first" to route API/agent paths to the Worker
  • Do not set "directory" in assets — the Vite plugin handles this

vite.config.ts

Full-stack examples must use the React, Cloudflare, and Tailwind Vite plugins. Examples that use @callable() or other decorators must also include the agents() plugin from agents/vite:

import { cloudflare } from "@cloudflare/vite-plugin";
import tailwindcss from "@tailwindcss/vite";
import react from "@vitejs/plugin-react";
import agents from "agents/vite";
import { defineConfig } from "vite";

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

The agents() plugin handles TC39 decorator transforms (Oxc doesn't support them yet). It's safe to include even if the example doesn't use decorators.

index.html

Include the theme flash prevention script and favicon:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="icon" href="/favicon.ico" />
    <title>Example Title</title>
    <script>
      (() => {
        const stored = localStorage.getItem("theme");
        const mode = stored || "light";
        document.documentElement.setAttribute("data-mode", mode);
        document.documentElement.style.colorScheme = mode;
      })();
    </script>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/client.tsx"></script>
  </body>
</html>

tsconfig.json

Extend the base config:

{
  "extends": "agents/tsconfig"
}

env.d.ts

Generated by pnpm exec wrangler types. Do not hand-edit. Regenerate when bindings change.

.env.example

If the example needs secrets (API keys, etc.), include a .env.example showing what keys are needed:

OPENAI_API_KEY=your-key-here

Never commit actual secrets. Use .env / .env.example.

UI — Kumo

Full-stack examples use Kumo (@cloudflare/kumo) for components, with Kumo's default theme (no custom overrides).

Kumo basics

  • Use Kumo components (Button, Surface, Text, Badge, Empty, etc.) instead of hand-rolled HTML
  • Use @phosphor-icons/react for icons (Kumo's icon library)
  • Use Kumo semantic color tokens (text-kumo-default, bg-kumo-base, border-kumo-line, etc.) instead of raw Tailwind colors
  • Text does not accept a className prop — wrap in a <span> if you need custom classes
  • Use the data-mode attribute for dark mode — no dark: Tailwind variants

CSS imports (in src/styles.css)

@import "tailwindcss";
@import "@cloudflare/kumo/styles/tailwind";
@source "../../../node_modules/@cloudflare/kumo/dist/**/*.{js,jsx,ts,tsx}";

Required UI patterns

Every full-stack example should include:

  • PoweredByCloudflare (from @cloudflare/kumo) — footer attribution badge (required in every example)
  • A dark mode toggle — inline a simple ModeToggle component using useState/useEffect + localStorage + Kumo Button with sun/moon icons
  • Connection status (for WebSocket-based examples) — inline a simple ConnectionIndicator showing a colored dot + label

See /design/visuals.md for detailed Kumo usage patterns and known gaps.

Explainer section

Every example should include an info card at the top of the page explaining what the demo shows. Use the consistent pattern:

<Surface className="p-4 rounded-xl ring ring-kumo-line">
  <div className="flex gap-3">
    <InfoIcon
      size={20}
      weight="bold"
      className="text-kumo-accent shrink-0 mt-0.5"
    />
    <div>
      <Text size="sm" bold>
        Title
      </Text>
      <span className="mt-1 block">
        <Text size="xs" variant="secondary">
          Description of what this demo shows and how to use it.
        </Text>
      </span>
    </div>
  </div>
</Surface>

Chat message rendering

Any example with a chat UI (useAgentChat from @cloudflare/ai-chat/react or @cloudflare/think/react, or useChat) must render the full shape of an assistant turn — not just text. Reference implementations: examples/ai-chat (streamdown + complete tool states) and examples/assistant (adds reasoning + approvals + branches).

Rules:

  1. Render message.parts in array order (chronological). Walk the parts with a single message.parts.map(...) and branch on part.type inside it. Never split parts into separate buckets (e.g. all text, then all tools) — that reorders the turn and hides which tool ran between which sentences.
  2. Assistant text → streamdown. Render assistant text parts with <Streamdown> (markdown, code highlighting). User messages stay plain text (getMessageText(message)) — user input is rarely markdown.
  3. Reasoning traces. Render part.type === "reasoning" parts as a distinct, muted "Reasoning"/"Thinking" block (reasoning is on by default for reasoning-capable models).
  4. Tool parts — render input, output, AND errors:
    • Input for every tool (not just a code field) — render it while the call is still streaming (input-streaming / input-available) with a running indicator, and after it resolves.
    • Output on output-available.
    • Errors on output-error — show part.errorText in a clearly-distinct (red) block. Don't let failed tools render as nothing.
    • Handle approval-requested / output-denied where the example uses tool approval.
  5. Avoid empty bubbles while streaming. Text and reasoning parts arrive as { state: "streaming", text: "" } before the first delta — gate on part.text.length > 0 || part.state === "streaming".

Streamdown setup

streamdown (+ @streamdown/code) are already repo dependencies. To add markdown rendering to an example:

// package.json dependencies
"streamdown": "^2.5.0",
"@streamdown/code": "^1.1.1"
import { Streamdown } from "streamdown";
import { code } from "@streamdown/code";

<Streamdown
  className="sd-theme"
  plugins={{ code }}
  controls={false}
  isAnimating={isLastAssistant && isLastTextPart && isStreaming}
>
  {part.text}
</Streamdown>;

Tailwind ignores node_modules, so src/styles.css needs @source lines plus the .sd-theme bridge that maps Streamdown's tokens to Kumo. Copy the @source ".../streamdown/..." / @source ".../@streamdown/code/..." lines and the .sd-theme { ... } block from examples/assistant/src/styles.css verbatim.

Agent communication

Prefer @callable methods + useAgent/agent.call() over manual onRequest/agentFetch or raw WebSocket messages:

// server.ts
export class MyAgent extends Agent {
  @callable()
  async doSomething(arg: string) {
    return { result: arg };
  }
}

// client.tsx
const agent = useAgent({ agent: "my-agent", name: sessionId });
const result = await agent.call("doSomething", ["hello"]);

Dependencies

  • Keep example-specific dependencies minimal — these ship as learning material
  • Shared dependencies (react, vite, wrangler, @cloudflare/vite-plugin, etc.) live in the root package.json
  • Only add dependencies in the example's package.json if they're specific to what the example demonstrates

README.md

Every example needs one. Keep it short:

  1. One sentence: what this example demonstrates
  2. How to run it (pnpm install && pnpm run start for full-stack examples, pnpm install && pnpm run dev for server-only examples)
  3. Any required env vars
  4. Code snippets showing the key pattern
  5. Links to related examples

Known issues to clean up

See TODO.md in this folder for the full checklist.

  • cross-domain/ has a vite.config.ts but does not use @cloudflare/vite-plugin