Skip to content

minpeter/ai-sdk-tool-call-middleware

Repository files navigation

AI SDK Tool monorepo banner

npm - parser npm downloads - parser codecov

AI SDK middleware for parsing tool calls from models that do not natively support tools.

Install

pnpm add @ai-sdk-tool/parser

AI SDK compatibility

Fact-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.

Package map

Import Purpose
@ai-sdk-tool/parser Main middleware factory, preconfigured middleware, protocol exports
@ai-sdk-tool/parser/community Community middleware (Sijawara, UI-TARS)

Quick start

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
}

Choose middleware

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

Build custom middleware

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)}`,
});

Streaming semantics

  • Stream parsers emit tool-input-start, tool-input-delta, and tool-input-end when a tool input can be incrementally reconstructed.
  • tool-input-start.id, tool-input-end.id, and final tool-call.toolCallId are reconciled to the same ID.
  • emitRawToolCallTextOnError defaults to false; malformed tool-call markup is suppressed from text-delta unless explicitly enabled.

Configure parser error behavior through providerOptions.toolCallMiddleware:

const result = streamText({
  // ...
  providerOptions: {
    toolCallMiddleware: {
      onError: (message, metadata) => {
        console.warn(message, metadata);
      },
      emitRawToolCallTextOnError: false,
    },
  },
});

Local development

pnpm build
pnpm test
pnpm check:biome
pnpm check:types
pnpm check

Examples in this repo

  • 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

About

Vercel AI SDK and middleware for models that do not support function calls (Part of Project Wrench)

Resources

License

Stars

Watchers

Forks

Contributors