API reference

Every public symbol exported from toolroute. Source of truth: src/index.ts.

ExportKindWhat it does
defineToolfunctionWrap a tool definition with nextAllowed.
createRouterFromToolsfunctionBuild a router from a const tool array.
nextToolsfunctionNarrow the legal next subset for a previous tool.
printRouterGraphfunctionPlain-text adjacency dump.
checkTransitionfunctionLower-level guard primitive.
legalNextForfunctionCompute the legal-next set for a previous tool.
detectEdgeRuntimefunctionEdge-runtime probe.
buildRouterVersionfunctionBuild the routerVersion diagnostic string.
ToolRouteViolationclassThrown / warned on illegal transitions.
RoutertypeRouter shape.
RouterOptionstypeConstructor options.
ToolRouteDeftypeTool-definition shape returned by defineTool.
NextToolstypePer-step narrowed tools record.
SDKToolSettypeThe tools record passed to streamText.
SDKToolFortypeSingle SDK tool inferred from a ToolRouteDef.
AdjacencytypeRecord<string, readonly string[]>.
TOOLROUTE_VERSIONconstCurrent package version string.
EDGE_INIT_WARNINGconstOne-time init warning text.

# function defineTool
function 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']
# function createRouterFromTools
function 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: [] }
# function nextTools
function 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> }
# function printRouterGraph
function 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
# function checkTransition
function 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
# function legalNextFor
function 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.

# function detectEdgeRuntime
function 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.

# function buildRouterVersion
function 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.

# class ToolRouteViolation
class 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;
}
instanceof works in tests too
The constructor calls Object.setPrototypeOf, so err instanceof ToolRouteViolation works across module boundaries even with downlevel transpilation.
# type Router
interface 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.

# type RouterOptions
interface 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
}
# type ToolRouteDef
interface 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>;
}
# type NextTools
type 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).

# type SDKToolSet
type SDKToolSet<Tools extends readonly ToolRouteDef[]> = {
  [K in Tools[number] as K['name']]: SDKToolFor<K>;
};
# type SDKToolFor
type SDKToolFor<T extends ToolRouteDef> = Tool<Infer<T['inputSchema']>, /* output */ unknown>;
# type Adjacency
type Adjacency = Readonly<Record<string, readonly string[]>>;
# const TOOLROUTE_VERSION
const TOOLROUTE_VERSION: '0.1.0';
# const EDGE_INIT_WARNING
const EDGE_INIT_WARNING: '[ToolRoute] Edge runtime detected. console.warn may be suppressed; pipe runtime logs to capture violations.';