Models and providers
Reuse models and executors from other AI frameworks via AI SDK LanguageModel objects, raw ai functions, and OpenAI-compatible endpoints.
Alpha:
@statelyai/agent2.0 is in alpha. APIs can change between releases; pin an exact version. Feedback: github.com/statelyai/agent.
Reusing models from other frameworks
Where a host's executors come from is the only integration point. The shared type across frameworks is the AI SDK LanguageModel object: whatever framework hands you one, drop it into createAiSdkExecutors({ models }) for a full { generateText, streamText, decide } set:
import { createAiSdkExecutors } from "@statelyai/agent/ai-sdk";
const executors = createAiSdkExecutors({
models: { quick: someLanguageModel, careful: anotherLanguageModel },
});
await runAgent(machine, { input, executors });Three ways in, from most to least capable:
- AI SDK adapter. Any
LanguageModel(Mastra, Cloudflare Workers AI viaworkers-ai-provider, TanStack AI, OpenRouter's AI SDK provider, any@ai-sdk/*package). Full support, includingdecide. - OpenAI-compatible endpoints. Point
createOpenAI({ baseURL })from@ai-sdk/openaiat any OpenAI-shaped endpoint (Groq, Ollama, vLLM, Together, LM Studio) and feed the result to the same adapter. Full support, includingdecide. - Raw
aifunctions. Passai'sgenerateText/streamTextas yourexecutorsset. Text only:decideneeds an adapter, and structured output is best-effort.
Mastra models
Mastra is a TypeScript agent framework whose agents are configured with an AI SDK LanguageModel. Reuse that same model object as an executor, no re-config and no second provider setup:
import { openai } from "@ai-sdk/openai";
import { createAiSdkExecutors } from "@statelyai/agent/ai-sdk";
// The model you already pass to `new Agent({ model })` in Mastra.
// Model IDs here are illustrative; substitute your provider's current models.
const model = openai("gpt-5.4-mini");
await runAgent(machine, {
input,
executors: createAiSdkExecutors({ models: { quick: model } }),
});Anything exposing a LanguageModel works the same way, so machine and Mastra share one model definition.
Cloudflare Workers AI
Workers AI runs models on Cloudflare's edge, reached through a binding on the Worker's env. The workers-ai-provider package turns that binding into an AI SDK provider, so its models are ordinary LanguageModel objects:
import { createWorkersAI } from "workers-ai-provider";
import { createAiSdkExecutors } from "@statelyai/agent/ai-sdk";
export default {
async fetch(request, env) {
const workersai = createWorkersAI({ binding: env.AI });
const result = await runAgent(machine, {
input: await request.json(),
executors: createAiSdkExecutors({
models: { quick: workersai("@cf/meta/llama-3.1-8b-instruct") },
}),
});
return Response.json(result);
},
};Pass Cloudflare-specific per-call options through request metadata: the host owns it, the machine just carries it.
Ollama and OpenAI-compatible endpoints
Ollama runs models locally and serves them over an OpenAI-compatible HTTP API, so the AI SDK's OpenAI provider pointed at the local endpoint is enough. apiKey is optional: omit it for keyless local servers.
import { createOpenAI } from "@ai-sdk/openai";
import { createAiSdkExecutors } from "@statelyai/agent/ai-sdk";
const ollama = createOpenAI({ baseURL: "http://localhost:11434/v1" });
await runAgent(machine, {
input,
executors: createAiSdkExecutors({
models: { quick: ollama("llama3.1") },
}),
});Swap baseURL (and add apiKey where the endpoint requires one) for Groq, vLLM, Together, OpenRouter, or LM Studio; nothing else changes.
If you would rather not depend on ai at all, write the three executors over raw fetch against the same Chat Completions endpoint: build the request body from the plain AgentTextRequest fields, and use buildEnvelopeSchema, getJsonSchema, and parseOutput from @statelyai/agent for structured output, plus renderDecisionAttempts for decision retries. See Hosts.
Raw AI SDK functions
The generateText/streamText executors accept the raw Vercel AI SDK functions directly, no adapter needed:
import { generateText, streamText } from "ai";
await runAgent(machine, { input, executors: { generateText, streamText } });An AgentTextRequest is spread-compatible with the AI SDK's call options, and result shapes unwrap natively ({ text }; { textStream }, final text via await result.text). Two caveats:
- Structured output is best-effort. A request with an
outputSchemahas its raw textJSON.parsed and validated; a parse failure throws. For reliable structured output, usecreateAiSdkExecutors. decideneeds an adapter. The tool-per-event mapping lives in the adapter; there is no raw AI SDK function for it.
Support by path
| Path | generateText | streamText | decide | Structured output |
|---|---|---|---|---|
createAiSdkExecutors | yes | yes | yes | yes |
Raw fetch executors | yes | yes | yes | yes (you map it) |
Raw ai functions | yes | yes | no | best-effort |
The decide executor maps each machine event to a forced tool call, and that mapping lives in the adapter layer, so raw ai functions cannot back a decision. For reliable structured output, use createAiSdkExecutors or map the envelope yourself. See Text requests and Decisions.
Reference hosts by provider
Runnable hosts, one per provider stack:
| Example | Backing |
|---|---|
| ai-sdk-host | Vercel AI SDK, through the shipped adapter |
| openai-sdk-host | raw openai (Chat Completions); structured via response_format, decisions via tool_choice: 'required' |
| anthropic-sdk-host | raw @anthropic-ai/sdk (Messages); structured via forced tool call, decisions via tool_choice: { type: 'any' } |
| cloudflare-agent-host | Durable Object |
| cloudflare-workers-ai-host | Workers AI binding |
Package entry points are @statelyai/agent (root), @statelyai/agent/ai-sdk, @statelyai/agent/machines, @statelyai/agent/otel, @statelyai/agent/sqlite, and @statelyai/agent/agent-workflow.json. Everything a hand-written host needs (buildEnvelopeSchema, getJsonSchema, parseOutput, parseStructuredEnvelope, getAgentOutputMode, resolveDecision, renderDecisionAttempts) comes from the root.