ai-sdk-cloudflare-workflow-agent
v0.1.2
Published
Run an AI SDK agent loop as a durable workflow on Cloudflare Workflows.
Downloads
436
Maintainers
Readme
AI SDK - Cloudflare Workflow Agent
This package is experimental.
WorkflowAgent runs an AI SDK agent as a durable workflow on Cloudflare Workflows. Each model turn and each tool call is its own step.do, so a Worker eviction or a flaky upstream retries that step alone instead of restarting the loop.
It is shaped after Vercel's @ai-sdk/workflow WorkflowAgent: the same model, system, tools (inputSchema + execute), and stopWhen. The difference is the substrate. Vercel's durability comes from the Workflow DevKit compiling each tool execute into a 'use step'; here it comes from Cloudflare Workflows, so you hand the agent the WorkflowStep and it wraps each turn and tool call in step.do. No DevKit involved.
Setup
npm i ai-sdk-cloudflare-workflow-agent ai zodRequires a Workers project with Workflows enabled.
Usage
agent-workflow.ts:
import {
WorkflowEntrypoint,
type WorkflowEvent,
type WorkflowStep,
} from 'cloudflare:workers';
import { createAnthropic } from '@ai-sdk/anthropic';
import { z } from 'zod';
import { WorkflowAgent, tool, isStepCount } from 'ai-sdk-cloudflare-workflow-agent';
interface Params {
prompt: string;
}
export class AgentWorkflow extends WorkflowEntrypoint<Env, Params> {
async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
const agent = new WorkflowAgent({
model: createAnthropic({ apiKey: this.env.ANTHROPIC_API_KEY })('claude-haiku-4-5'),
system: 'You are terse.',
tools: {
getWeather: tool({
description: 'Get weather for a location',
inputSchema: z.object({ location: z.string() }),
execute: async ({ location }) => ({ temperature: 72, condition: 'sunny' }),
}),
},
stopWhen: isStepCount(6),
});
// Same as @ai-sdk/workflow's agent.stream(), with one Cloudflare
// difference: hand it the WorkflowStep. Pass a `writable` to stream.
const { text } = await agent.stream({ step, prompt: event.payload.prompt });
return text;
}
}wrangler.toml:
[[workflows]]
name = "agent"
binding = "AGENT_WORKFLOW"
class_name = "AgentWorkflow"Trigger a run from a fetch handler with env.AGENT_WORKFLOW.create({ params }).
How it works
- One model call per step. One generation per turn, tools declared without
execute, so the SDK surfaces tool calls without running them. - Each tool's
executeis its own step. The call runs in its ownstep.do. Throw for a transient failure and the step retries undertoolPolicy; a zod validation error is treated as semantic and fed back to the model on the next turn. - Malformed tool calls are repaired once. A bad call is regenerated with
generateObjectagainst the same schema. Setrepair: falseto disable. - Progress streams to
writable. UI-message chunks are written inside each work step, so a cached step on replay never re-emits. Lifecycle callbacks (experimental_onStart,experimental_onStepStart,onStepEnd,onEnd) run outside the steps and are observational: they may re-fire on replay. - Result drift fails loud. The SDK result is validated: load-bearing fields are strict, telemetry fields are defaulted.
- Structured
outputcrosses the JSON boundary. Each turn is persisted as JSON in itsstep.do, sooutputcomes back as its JSON form. Non-JSON types do not round-trip: az.date()field, for example, resolves to the ISO string it serialized to, not aDate. Modeloutputwith JSON-native types (parse or coerce on the far side). - Tool steps run at least once. A tool
executethat throws a transient error is retried by itsstep.do. If the first attempt already ran a side effect before throwing, that side effect happens again on retry. Making a tool idempotent (a natural key, an idempotency token) is the tool author's job.
Options
Constructor: model is required; the rest are optional.
systemthe system prompt.toolsa map oftool({ description, inputSchema, execute }).stopWhenaistop conditions (isStepCount(n),hasToolCall(name), …); the loop also always ends when the model stops calling tools.outputstructured output, e.g.Output.object({ schema }); the parsed value is returned asresult.output.toolChoicepassed to the model. Defaults toauto.repairone-shot malformed tool-call repair. Defaults totrue.llmPolicy/toolPolicystep.doretry and timeout for the model turn and each tool call.isToolErrorRetryabletransient (retry the step) versus semantic (feed the message back to the model). Defaults to treating a zod validation error as semantic and everything else as transient.
stream({ step, prompt, writable?, approve? }) returns { text, messages, steps, finishReason, output? }, where steps is one entry per turn (like @ai-sdk/workflow). When writable is given it streams the turn's UI-message chunks as it runs. prompt is a string or model messages.
Low-level: runAgentLoop
WorkflowAgent is built on runAgentLoop, a plain function with the same behavior and no class. Reach for it when you want a single runTool(call) dispatcher instead of per-tool execute. It takes JSON Schema tool declarations and is exported alongside the class and the default step.do policies (LLM_TURN, TOOL_CALL).
License
Apache-2.0
