@tanstack/ai
Version:
Type-safe TypeScript AI SDK for streaming chat, tool calling, agents, structured outputs, and multimodal generation.
90 lines (89 loc) • 3.76 kB
TypeScript
import { boolean, choice } from '../../evaluate/index.js';
import { InferSchemaType, SchemaInput } from '../../../types.js';
import { DefinedAgent } from './define-agent.js';
import { SubagentOrder, SubagentRouterPick } from './spawn.js';
export interface SubagentRouteOptions<TAgents extends ReadonlyArray<DefinedAgent>> {
/**
* Question text for every agent. The key is the agent name.
* Omit this and each question uses that agent's description.
*/
when?: {
[K in TAgents[number]['name']]: string;
};
/**
* Agents that run after the other selected agents. The lead group starts
* together. The `then` agents then run one after another in list order, and
* each reads the text so far. Used only when the router picks at least one
* agent from each group. Otherwise `pick` returns `{ names, order }`.
*/
then?: ReadonlyArray<TAgents[number]['name']>;
}
type RouteQuestions<TAgents extends ReadonlyArray<DefinedAgent>> = {
[K in TAgents[number]['name']]: ReturnType<typeof boolean>;
} & {
order: ReturnType<typeof choice<{
parallel: string;
sequence: string;
}>>;
};
type RouteAnswers<TAgents extends ReadonlyArray<DefinedAgent>> = {
[K in TAgents[number]['name']]: {
value: boolean;
};
} & {
order: {
value: SubagentOrder;
};
};
/** The name and the `inputSchema` of an agent that has one. Else `never`. */
type InputNeed<TAgent> = TAgent extends {
name: infer TName;
inputSchema?: infer TSchema;
} ? TSchema extends SchemaInput ? {
name: TName;
inputSchema: TSchema;
} : never : never;
/** One input for each agent with an `inputSchema`, typed from that schema. */
type RouteInputs<TAgents extends ReadonlyArray<DefinedAgent>> = {
[TNeed in InputNeed<TAgents[number]> as TNeed['name']]?: InferSchemaType<TNeed['inputSchema']>;
};
interface RoutePickOptions<TAgents extends ReadonlyArray<DefinedAgent>> {
/**
* The input of each picked agent that has an `inputSchema`. The key is the
* agent name. `needsInput` lists the agents that need one. An input for an
* agent that is not picked is not used.
*/
inputs?: RouteInputs<TAgents>;
}
/**
* Build `decide()` questions for a subagent router.
*
* One yes/no question per agent, plus an `order` choice.
* `pick` returns `main`, one name, `{ names, order }`, or `{ steps }`.
* Names follow the `agents` array order.
* `{ names, order }` overrides `subagents.order` for that turn.
* `then`: agents that run after the other selected agents. The lead group
* starts together. The `then` agents then run one after another in list
* order, and each reads the text so far. Used only when the router picks at
* least one agent from each group. Otherwise `pick` returns `{ names, order }`.
*
* An agent with `inputSchema` needs input. `needsInput(result)` lists the
* picked agents that need it, with their schemas. Make each input, then pass
* them as `pick(result, { inputs })`. Each name with an input becomes
* `{ name, input }`. `pick` throws when a picked agent with a schema has no
* input.
*
* @example
* ```ts
* const route = subagentRoute(agents)
* const result = await decide({ adapter, state, questions: route.questions })
* const inputs = { pricer: { sku: 'A-1' } }
* return route.pick(result, { inputs })
* ```
*/
export declare function subagentRoute<const TAgents extends ReadonlyArray<DefinedAgent>>(agents: TAgents, options?: SubagentRouteOptions<TAgents>): {
questions: RouteQuestions<TAgents>;
pick: (result: RouteAnswers<TAgents>, pickOptions?: RoutePickOptions<TAgents>) => SubagentRouterPick;
needsInput: (result: RouteAnswers<TAgents>) => Array<InputNeed<TAgents[number]>>;
};
export {};