API reference
Every public symbol exported from toolroute. Source of truth: src/index.ts.
| Export | Kind | What it does |
|---|---|---|
defineTool | function | Wrap a tool definition with nextAllowed. |
createRouterFromTools | function | Build a router from a const tool array. |
nextTools | function | Narrow the legal next subset for a previous tool. |
printRouterGraph | function | Plain-text adjacency dump. |
checkTransition | function | Lower-level guard primitive. |
legalNextFor | function | Compute the legal-next set for a previous tool. |
detectEdgeRuntime | function | Edge-runtime probe. |
buildRouterVersion | function | Build the routerVersion diagnostic string. |
ToolRouteViolation | class | Thrown / warned on illegal transitions. |
Router | type | Router shape. |
RouterOptions | type | Constructor options. |
ToolRouteDef | type | Tool-definition shape returned by defineTool. |
NextTools | type | Per-step narrowed tools record. |
SDKToolSet | type | The tools record passed to streamText. |
SDKToolFor | type | Single SDK tool inferred from a ToolRouteDef. |
Adjacency | type | Record<string, readonly string[]>. |
TOOLROUTE_VERSION | const | Current package version string. |
EDGE_INIT_WARNING | const | One-time init warning text. |
defineToolfunction defineTool<
const Name extends string,
Schema extends FlexibleSchema,
const Next extends readonly string[],
Output = unknown
>(def: ToolRouteDef<Name, Schema, Next, Output>): ToolRouteDef<Name, Schema, Next, Output> Pass-through helper that preserves literal types on name and tuple types
on nextAllowed. Validates at runtime that name is non-empty, nextAllowed is an array, and execute is a function.
const search = defineTool({
name: 'search',
description: 'find files',
inputSchema: z.object({ query: z.string() }),
nextAllowed: ['review'] as const,
execute: async ({ query }) => ({ hits: [] }),
});
// typeof search.name // 'search'
// typeof search.nextAllowed // readonly ['review'] createRouterFromToolsfunction createRouterFromTools<
const Tools extends readonly ToolRouteDef[]
>(tools: Tools, options?: RouterOptions): Router<Tools> Build a router from a const tuple of defineTool outputs. Validates that
every nextAllowed reference exists, derives the name set, builds the
adjacency map, and returns the wrapped tools record.
Throws if a nextAllowed entry references an unknown tool, if the
array is empty, or if there are duplicate names.
const router = createRouterFromTools([search, review, commit] as const, {
strictMode: true,
});
router.routerVersion // 'toolroute@0.1.0+ai-sdk@6.0.174'
router.adjacency // { search: ['review'], review: ['commit'], commit: [] } nextToolsfunction nextTools<R, Prev extends string | null>(
router: R, prev: Prev
): NextTools<R, Prev> Returns a freshly built record containing only the legal next tools for
the given prev. Use it when driving the agent yourself, one streamText step at a time. With prev = null the result is the entry
set (every tool whose nextAllowed is non-empty).
await streamText({ model, tools: nextTools(router, 'search') });
// type: { review: SDKToolFor<typeof review> } printRouterGraphfunction printRouterGraph(router: { adjacency: Adjacency }): string Returns a deterministic plain-text adjacency dump. Tools are sorted
alphabetically; nextAllowed is sorted within each line; terminal tools
print as name -> (terminal). Zero runtime dependencies. Caller decides
whether to write to stdout, a log sink, or a string snapshot.
commit -> (terminal)
review -> commit
search -> review checkTransitionfunction checkTransition(input: CheckTransitionInput): void The lower-level guard primitive that the router calls inside each
wrapped execute. Throws ToolRouteViolation when strictMode: true,
otherwise emits a single warn line. Useful if you want to layer the
guard onto a non-router code path.
checkTransition({
prev: 'search',
next: 'commit',
adjacency: router.adjacency,
strictMode: true,
routerVersion: router.routerVersion,
});
// throws ToolRouteViolation legalNextForfunction legalNextFor(adjacency: Adjacency, prev: string | null): readonly string[] Pure helper. With prev = null returns the entry tools (those with a
non-empty nextAllowed), sorted. With prev = name returns adjacency[name] or [] if the tool is terminal/unknown.
detectEdgeRuntimefunction detectEdgeRuntime(): boolean Probes for globalThis.EdgeRuntime, process.env.NEXT_RUNTIME === 'edge',
and Cloudflare worker globals (WebSocketPair). Wrapped in try/catch and
fails open (returns false) rather than throwing.
buildRouterVersionfunction buildRouterVersion(opts: { packageVersion?: string; sdkVersion?: string }): string Returns toolroute@<pkg>+ai-sdk@<peer>. Defaults read from TOOLROUTE_VERSION and ai/package.json. Override sdkVersion to keep
recording snapshots stable across SDK bumps in tests.
ToolRouteViolationclass ToolRouteViolation extends Error {
readonly prev: string | null;
readonly next: string;
readonly legalNext: readonly string[];
readonly routerVersion: string;
} Thrown by the router (or checkTransition) when an illegal hop is
attempted in strict mode; passed to warn as a formatted message
otherwise. The message is single-line so a copy-paste lands cleanly in
a Sentry issue title.
try {
await tool.execute(input, ctx);
} catch (err) {
if (err instanceof ToolRouteViolation) {
console.error(err.prev, err.next, err.legalNext, err.routerVersion);
}
throw err;
} Object.setPrototypeOf, so err instanceof ToolRouteViolation works across module
boundaries even with downlevel transpilation.Routerinterface Router<Tools extends readonly ToolRouteDef[]> {
tools: SDKToolSet<Tools>;
adjacency: Adjacency;
routerVersion: string;
strictMode: boolean;
reset(): void;
} The shape returned by createRouterFromTools. tools is the SDK-shaped
record you pass to streamText. adjacency is read-only. reset() clears the internal prev pointer — call it between agent runs that
share the same router.
RouterOptionsinterface RouterOptions {
strictMode?: boolean; // default: false (warn)
warn?: (msg: string) => void; // default: console.warn
sdkVersion?: string; // default: read from ai/package.json
detectEdgeRuntime?: () => boolean; // default: built-in probe
} ToolRouteDefinterface ToolRouteDef<
Name extends string = string,
Schema extends FlexibleSchema = FlexibleSchema,
Next extends readonly string[] = readonly string[],
Output = unknown
> {
name: Name;
description?: string;
inputSchema: Schema;
nextAllowed: Next;
execute: (input: InferSchema<Schema>, options: { toolCallId: string }) => Output | Promise<Output>;
} NextToolstype NextTools<R, Prev extends string | null> =
R extends Router<infer Tools>
? { [N in AllowedNextNames<Tools, Prev>]: SDKToolFor<ToolByName<Tools, N>> }
: never; The legal next tools record for a given previous tool, at the type
level. With Prev = null, the legal set is every tool whose nextAllowed is non-empty (i.e., every entry tool).
SDKToolSettype SDKToolSet<Tools extends readonly ToolRouteDef[]> = {
[K in Tools[number] as K['name']]: SDKToolFor<K>;
}; SDKToolFortype SDKToolFor<T extends ToolRouteDef> = Tool<Infer<T['inputSchema']>, /* output */ unknown>; Adjacencytype Adjacency = Readonly<Record<string, readonly string[]>>; TOOLROUTE_VERSIONconst TOOLROUTE_VERSION: '0.1.0'; EDGE_INIT_WARNINGconst EDGE_INIT_WARNING: '[ToolRoute] Edge runtime detected. console.warn may be suppressed; pipe runtime logs to capture violations.';