AI SDK middleware for parsing tool calls from models that do not natively support tools.
pnpm add @ai-sdk-tool/parserFact-checked from this repo CHANGELOG.md and npm release metadata (as of 2026-02-18).
@ai-sdk-tool/parser major |
AI SDK major | Maintenance status |
|---|---|---|
v1.x |
v4.x |
Legacy (not actively maintained) |
v2.x |
v5.x |
Legacy (not actively maintained) |
v3.x |
v6.x |
Legacy (not actively maintained) |
v4.x |
v6.x |
Active (current latest line) |
Note: there is no separate formal EOL announcement in releases/changelog for v1-v3; "legacy" here means non-current release lines.
| Import | Purpose |
|---|---|
@ai-sdk-tool/parser |
Main middleware factory, preconfigured middleware, protocol exports |
@ai-sdk-tool/parser/community |
Community middleware (Sijawara, UI-TARS) |
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
import { morphXmlToolMiddleware } from "@ai-sdk-tool/parser";
import { stepCountIs, streamText, wrapLanguageModel } from "ai";
import { z } from "zod";
const model = createOpenAICompatible({
name: "openrouter",
apiKey: process.env.OPENROUTER_API_KEY,
baseURL: "https://openrouter.ai/api/v1",
})("arcee-ai/trinity-large-preview:free");
const result = streamText({
model: wrapLanguageModel({
model,
middleware: morphXmlToolMiddleware,
}),
stopWhen: stepCountIs(4),
prompt: "What is the weather in Seoul?",
tools: {
get_weather: {
description: "Get weather by city name",
inputSchema: z.object({ city: z.string() }),
execute: async ({ city }) => ({ city, condition: "sunny", celsius: 23 }),
},
},
});
for await (const part of result.fullStream) {
// text-delta / tool-input-start / tool-input-delta / tool-input-end / tool-call / tool-result
}Use the preconfigured middleware exports from src/preconfigured-middleware.ts:
| Middleware | Best for |
|---|---|
hermesToolMiddleware |
JSON-style tool payloads |
morphXmlToolMiddleware |
XML-style payloads with schema-aware coercion |
yamlXmlToolMiddleware |
XML tool tags + YAML bodies |
qwen3CoderToolMiddleware |
Qwen/UI-TARS style <tool_call> markup |
import { createToolMiddleware, qwen3CoderProtocol } from "@ai-sdk-tool/parser";
export const myToolMiddleware = createToolMiddleware({
protocol: qwen3CoderProtocol,
toolSystemPromptTemplate: (tools) =>
`Use these tools and emit <tool_call> blocks only: ${JSON.stringify(tools)}`,
});- Stream parsers emit
tool-input-start,tool-input-delta, andtool-input-endwhen a tool input can be incrementally reconstructed. tool-input-start.id,tool-input-end.id, and finaltool-call.toolCallIdare reconciled to the same ID.emitRawToolCallTextOnErrordefaults tofalse; malformed tool-call markup is suppressed fromtext-deltaunless explicitly enabled.
Configure parser error behavior through providerOptions.toolCallMiddleware:
const result = streamText({
// ...
providerOptions: {
toolCallMiddleware: {
onError: (message, metadata) => {
console.warn(message, metadata);
},
emitRawToolCallTextOnError: false,
},
},
});pnpm build
pnpm test
pnpm check:biome
pnpm check:types
pnpm check- Parser middleware examples:
examples/parser-core/README.md - RXML examples:
examples/rxml-core/README.md
Run one example from repo root:
pnpm dlx tsx examples/parser-core/src/01-stream-tool-call.ts