UNPKG

@tanstack/ai

Version:

Type-safe TypeScript AI SDK for streaming chat, tool calling, agents, structured outputs, and multimodal generation.

122 lines (116 loc) • 3.88 kB
import type { SubagentInfo as AGUISubagentInfo } from '@ag-ui/core' import type { InterruptDefinition } from '../../../interrupt-definition' import type { AnyTool, ModelMessage, RunAgentResumeItem, SchemaInput, StreamChunk, UIMessage, } from '../../../types' import type { AnyClientTool } from '../tools/tool-definition' /** * Context the library passes into {@link defineAgent} `run`. */ export interface SubagentRunContext { messages: Array<UIMessage | ModelMessage> abortSignal?: AbortSignal threadId: string /** Run id for the child `chat()`. */ runId: string /** * The run this child run continues. It is the parent chat run on the first * run, and the interrupted parent run on a resume. Pass it to the child * `chat()`. */ parentRunId: string /** Answers to this child's interrupts. Pass it to the child `chat()`. */ resume?: Array<RunAgentResumeItem> /** * The child's AG-UI run id. Stays the same when an interrupted child * continues. Pass it to the child `chat()` so its middleware sees * `ctx.subagentRunId`. */ subagentRunId: string parentSubagentRunId?: string } /** * A tool a child agent can carry into client part types. * Server tools and client tools both qualify. */ export type SubagentTool = AnyTool | AnyClientTool /** * A named child agent. `run` is a `chat()` call (or any stream of AG-UI chunks). * `TTools` and `TSchema` stay on the object so `useChat({ subagents })` can * type that child's parts. */ export interface DefinedAgent< TName extends string = string, TTools extends ReadonlyArray<SubagentTool> = ReadonlyArray<SubagentTool>, TSchema extends SchemaInput | undefined = SchemaInput | undefined, TInterrupts extends ReadonlyArray<InterruptDefinition<any, any, any, any>> = ReadonlyArray<InterruptDefinition<any, any, any, any>>, > extends AGUISubagentInfo { name: TName /** Required here: the router and the synthetic tool both read it. */ description: string run: ( ctx: SubagentRunContext, ) => AsyncIterable<StreamChunk> | Promise<AsyncIterable<StreamChunk>> tools?: TTools interrupts?: TInterrupts outputSchema?: TSchema subagents?: unknown } /** * Choice options for a `decide()` router. `main` is required plus every agent name. */ export type SubagentChoiceOptions<TAgents extends ReadonlyArray<DefinedAgent>> = { main: string } & { [K in TAgents[number]['name']]: string } /** * Define a named child agent. Pass the same object to `chat({ subagents })`. * Pass the agents array to `useChat({ subagents })` when you render parts * yourself. The hook uses it for types only. It does not call `run`. * * @example * ```ts * const researcher = defineAgent({ * name: 'researcher', * description: 'Looks up facts', * run: (ctx) => * chat({ * adapter: openaiText('gpt-5.6'), * messages: ctx.messages, * threadId: ctx.threadId, * runId: ctx.runId, * parentRunId: ctx.parentRunId, * subagentRunId: ctx.subagentRunId, * resume: ctx.resume, * }), * }) * ``` */ export function defineAgent< const TName extends string, const TTools extends ReadonlyArray<SubagentTool> = readonly [], TSchema extends SchemaInput | undefined = undefined, const TInterrupts extends ReadonlyArray< InterruptDefinition<any, any, any, any> > = readonly [], >(agent: DefinedAgent<TName, TTools, TSchema, TInterrupts>) { if (agent.name.trim() === '') { throw new Error('defineAgent requires a non-empty name') } // A router returns 'main' to keep the turn on the parent. if (agent.name.trim() === 'main') { throw new Error( "defineAgent cannot use the name 'main'. A router uses it for the parent.", ) } if (agent.description.trim() === '') { throw new Error('defineAgent requires a non-empty description') } return agent }