Out-of-order tool calls
cannot reach your model.
ToolRoute wraps Vercel AI SDK tool definitions so each tool declares
its legal nextAllowed successors. Compile-time narrowing plus a 50-line
runtime guard reject the same illegal call from both sides.
$ pnpm add toolroute ai zodOne source of truth
nextAllowed lives on the tool itself. The type narrowing and the runtime
guard read the same array — they cannot drift.
Runtime before types
The 50-line guard shipped first, with a recorded terminal session of a real rejection. The narrowing matches observable behaviour, not a compile-time lie.
Names derived
createRouterFromTools infers the name set from the array. Rename review once and every nextAllowed referencing it is a tsc error at its site, not yours.
Loud diagnostics
ToolRouteViolation carries prev, next, legalNext, and routerVersion — paste the message into a
Sentry title and you know which SDK was on the box.
Edge-aware
console.warn is suppressed in Vercel Edge Functions. ToolRoute detects
the runtime and emits a one-time init warning so violations do not vanish.
Calling card, not platform
No paid tier, no hosted dashboard, no playground in v1. ~340 LOC, 37 tests, weekly CI
cron pinned to ai@latest. Zero distraction surface.
Inspect the graph in two minutes
printRouterGraph(router) emits a deterministic plain-text adjacency
dump. Sorted alphabetically, terminals tagged, zero runtime dependencies. At 7+
tools your routing graph lives across files; this is your single-screen view.
// printRouterGraph(router) — diff-stable, zero deps.
commit -> (terminal)
review -> commit
search -> review
// At runtime: every ToolRouteViolation carries the version
// you compiled against — paste into Sentry as-is.
routerVersion: "toolroute@0.1.0+ai-sdk@6.0.174"Wrap an existing Vercel AI SDK agent in five minutes.
The 5-line quickstart in the docs is the same code as examples/code-review-agent/ in the repo — the same code that produced the
recorded rejection.