Skip to content

Components as Tools

Wrap any component as a tool using createComponentTool, enabling model-driven delegation to sub-agents.

The Key Insight

In AIDK, tools and components have a bidirectional relationship:

DirectionWhat It MeansUse Case
Tools → ComponentsTools ARE components with lifecycle, state, renderingCreating Tools
Components → ToolsComponents CAN BE tools via createComponentToolThis page

This page covers the second direction: turning components into tools that the model can call.

Why Component Tools?

ApproachWho DecidesBest For
Fork/SpawnYour codeKnown parallel workflows, pipelines
Component ToolsThe modelDynamic delegation, unknown task routing

With component tools, you give the model a "specialist" it can call when needed. The model decides if and when to use it.

Basic Usage

tsx
import { createComponentTool, Model, System, Section, H2, H3, Paragraph } from 'aidk';
import { aisdk } from 'aidk-ai-sdk';
import { openai } from '@ai-sdk/openai';

// Define a specialist agent
const ResearchAgent = () => (
  <>
    <Model model={aisdk({ model: openai('gpt-5.2') })} />
    <System>
      You are a research specialist. Given a topic, provide comprehensive
      research findings with sources and confidence levels.
    </System>
    <WebSearchTool />
    <ArxivSearchTool />
  </>
);

// Wrap it as a tool - render() provides context about this tool
const ResearchTool = createComponentTool({
  name: 'research',
  description: 'Delegate research tasks to a specialist agent',
  component: ResearchAgent,
  render() {
    return (
      <>
        <H3>Research Agent</H3>
        <Paragraph>
          Research specialist with web and academic search.
        </Paragraph>
      </>
    );
  }
});

// Use in an orchestrator agent
const OrchestratorAgent = () => (
  <>
    <Model model={aisdk({ model: openai('gpt-5.2') })} />
    <System>You are a helpful assistant.</System>

    {/* Parent controls the organization - tools render inside */}
    <Section id="sub-agents" title="Specialized Sub-Agents">
      <H2>You have access to the following sub-agents. Delegate appropriately.</H2>
      <ResearchTool />
      {/* Add more tools here - their render() output flows into this Section */}
    </Section>
  </>
);

What's happening here:

  1. ResearchTool has a render() method that outputs content (H3, Paragraph)
  2. The tool is placed inside the <Section> in the orchestrator
  3. The compiler collects all children of the Section—including the tool's rendered content
  4. The model sees one organized "Specialized Sub-Agents" section with all tool descriptions
  5. The tool is automatically registered with the COM onMount and removed onUnmount.

This shows the full circle: component tools are themselves components with lifecycle and rendering. You get sub-agent delegation and clean context organization. The parent controls the structure, and tools just contribute their content.

How It Works

  1. Model calls the tool with a prompt (or custom input)
  2. New engine instance is created for the sub-agent
  3. Sub-agent executes independently with its own tick loop
  4. Result extracted from the last assistant message
  5. Tool result returned to the orchestrating model
┌─────────────────────────────────────────────────────────────┐
│ Orchestrator Agent                                         │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Model calls research tool with: "quantum computing"     │ │
│ └────────────────────────┬────────────────────────────────┘ │
│                          │                                  │
│                          ▼                                  │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Component Tool: Creates isolated engine                 │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ Research Agent (sub-engine)                         │ │ │
│ │ │ - Receives "quantum computing" as user message      │ │ │
│ │ │ - Executes ticks with its own model                 │ │ │
│ │ │ - Calls WebSearchTool, ArxivSearchTool              │ │ │
│ │ │ - Returns research findings                         │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ └────────────────────────┬────────────────────────────────┘ │
│                          │                                  │
│                          ▼                                  │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Last assistant message content → tool result            │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

Default Input Schema

By default, component tools accept:

typescript
{
  prompt: string;           // Required: The task or question
  attachments?: Array<{     // Optional: Images, documents, audio, video
    type: 'text' | 'image' | 'document' | 'audio' | 'video';
    // For text: { type: 'text', text: string }
    // For media: { type: 'image', source: { type: 'base64' | 'url', ... } }
  }>;
}

This allows the model to pass images or documents for visual analysis:

tsx
const AnalysisTool = createComponentTool({
  name: 'analyze',
  description: 'Analyze text or images. Pass attachments for visual analysis.',
  component: AnalysisAgent,
});

// Model can call with:
// { prompt: "Research AI safety" }
// { prompt: "What's in this image?", attachments: [{ type: "image", source: { type: "url", url: "..." } }] }

Typed Options

Use the options field to define a typed schema for component configuration. The model sees the exact options available, and they're passed as props to the component:

tsx
// Component receives options as props
const ConfigurableAgent = ({ maxIterations = 3, depth = 'standard' }) => (
  <>
    <System>
      Run up to {maxIterations} iterations with {depth} analysis.
    </System>
  </>
);

const ConfigurableTool = createComponentTool({
  name: 'configurable_task',
  description: 'A configurable task with iteration and depth options.',
  component: ConfigurableAgent,
  options: z.object({
    maxIterations: z.number().describe('Maximum iterations to run'),
    depth: z.enum(['shallow', 'standard', 'deep']).describe('Analysis depth'),
  }),
});

// Model sees schema: { prompt: string, attachments?: ..., options?: { maxIterations?: number, depth?: ... } }
// Model calls: { prompt: "Do the task", options: { maxIterations: 5, depth: "deep" } }

Benefits of typed options:

  • Model sees exactly what configuration is available
  • Validation before passing to component
  • Better IDE autocomplete for developers

Note: When no options schema is provided, the options field is omitted from the input schema entirely.

Transforming Input with transformInput

When your component expects props not in the schema, use transformInput to modify the input before processing:

tsx
// Component expects: { task: string, k?: number, numVoters?: number }
// Default schema provides: { prompt: string, options?: { k?, numVoters? } }
// Need to add 'task' to options so it becomes a prop

const VotingTool = createComponentTool({
  name: 'voting_agent',
  description: 'Run a voting consensus on a question',
  component: VotingAgent,
  options: z.object({
    k: z.number().describe('Lead required for consensus').optional(),
    numVoters: z.number().describe('Number of voters').optional(),
  }),
  transformInput: (input) => ({
    ...input,
    options: { ...input.options, task: input.prompt },  // Add task to options
  }),
});

// Model calls: { prompt: "What is 2+2?", options: { k: 2, numVoters: 5 } }
// After transform: { prompt: "...", options: { k: 2, numVoters: 5, task: "What is 2+2?" } }
// Component receives props from options: { task: "What is 2+2?", k: 2, numVoters: 5 }

The transformed input is used for:

  1. Creating the user message (from prompt and attachments)
  2. Extracting component props (from options)

Custom Input Schema

For structured tasks, you can define a completely custom input schema:

tsx
import { z } from 'zod';

const CodeReviewTool = createComponentTool({
  name: 'review_code',
  description: 'Review code for issues. Expects code and language.',
  input: z.object({
    code: z.string().describe('The code to review'),
    language: z.string().describe('Programming language'),
    focus: z.enum(['security', 'performance', 'style']).optional(),
  }),
  component: CodeReviewAgent,
});

Custom input is JSON-serialized into the user message for the sub-agent.

Custom Result Transformation

Control how sub-agent output becomes the tool result:

tsx
const SummarizerTool = createComponentTool({
  name: 'summarize',
  description: 'Summarize content',
  component: SummarizerAgent,
  transformResult: (output) => {
    // Extract from timeline
    const assistantMessages = output.timeline
      .filter(e => e.message?.role === 'assistant')
      .map(e => e.message?.content.map(c => c.text).join(''))
      .join('\n');

    return [{
      type: 'text',
      text: `Summary:\n${assistantMessages}`,
    }];
  },
});

Nested Component Tools

Component tools can use other component tools, enabling hierarchical delegation:

tsx
// Level 1: Specialist agents
const WebSearchAgent = () => <>{/* ... */}</>;
const ArxivAgent = () => <>{/* ... */}</>;

const WebSearchTool = createComponentTool({
  name: 'web_search',
  description: 'Search the web',
  component: WebSearchAgent,
});

const ArxivTool = createComponentTool({
  name: 'arxiv_search',
  description: 'Search academic papers',
  component: ArxivAgent,
});

// Level 2: Research agent that uses specialists
const ResearchAgent = () => (
  <>
    <Model model={aisdk({ model: openai('gpt-5.2') })} />
    <System>You coordinate research using web and academic search.</System>
    <WebSearchTool />
    <ArxivTool />
  </>
);

const ResearchTool = createComponentTool({
  name: 'research',
  description: 'Deep research on a topic',
  component: ResearchAgent,
});

// Level 3: Top-level orchestrator
const OrchestratorAgent = () => (
  <>
    <Model model={aisdk({ model: openai('gpt-5.2') })} />
    <System>You help users. Delegate research when needed.</System>
    <ResearchTool />
  </>
);

Confirmation Support

Require user approval before delegating:

tsx
const DangerousTool = createComponentTool({
  name: 'execute_code',
  description: 'Execute arbitrary code',
  component: CodeExecutorAgent,
  requiresConfirmation: true,
  confirmationMessage: 'This will execute code. Continue?',
});

// Dynamic confirmation
const ConditionalTool = createComponentTool({
  name: 'file_operation',
  description: 'Perform file operations',
  component: FileAgent,
  requiresConfirmation: (input) => input.prompt.includes('delete'),
});

Component Tools vs Fork/Spawn

FeatureComponent ToolsFork/Spawn
Decision makerModelYour code
IsolationSeparate engineSame engine, separate fibers
CommunicationTool input/outputShared state, callbacks
ParallelismSequential (model calls one at a time)True parallel execution
Best forDynamic routing, specialist delegationKnown workflows, fan-out/fan-in

Use component tools when:

  • The model should decide what work to delegate
  • You want clean isolation between agents
  • Sub-agents have their own tool sets
  • You're building a "team of specialists"

Use Fork/Spawn when:

  • You know the parallel structure upfront
  • You need true concurrent execution
  • Agents need to share state or communicate
  • You're building pipelines

Example: Multi-Specialist Orchestrator

tsx
// Specialist agents
const WriterAgent = () => (
  <>
    <Model model={aisdk({ model: openai('gpt-5.2') })} />
    <System>You are a skilled writer. Create polished content.</System>
  </>
);

const EditorAgent = () => (
  <>
    <Model model={aisdk({ model: openai('gpt-5.2') })} />
    <System>You are an editor. Review and improve content.</System>
  </>
);

const FactCheckerAgent = () => (
  <>
    <Model model={aisdk({ model: openai('gpt-5.2') })} />
    <System>You verify facts. Check claims against sources.</System>
    <WebSearchTool />
  </>
);

// Wrap as tools - each provides its own description via render()
const WriterTool = createComponentTool({
  name: 'writer',
  description: 'Create written content on any topic',
  component: WriterAgent,
  render: () => <>
    <H3>Writer</H3>
    <Paragraph>Creates initial drafts and written content.</Paragraph>
  </>
});

const EditorTool = createComponentTool({
  name: 'editor',
  description: 'Review and improve written content',
  component: EditorAgent,
  render: () => <>
    <H3>Editor</H3>
    <Paragraph>Reviews, polishes, and improves content quality.</Paragraph>
  </>
});

const FactCheckerTool = createComponentTool({
  name: 'fact_checker',
  description: 'Verify factual claims in content',
  component: FactCheckerAgent,
  render: () => <>
    <H3>Fact Checker</H3>
    <Paragraph>Verifies claims against sources. Has web search.</Paragraph>
  </>
});

// Orchestrator - tools inside Section, content flows together
const ContentOrchestratorAgent = () => (
  <>
    <Model model={aisdk({ model: openai('gpt-5.2') })} />
    <System>You are a content production manager.</System>

    <Section id="team" title="Your Content Team">
      <Paragraph>
        Coordinate these specialists to produce high-quality content:
      </Paragraph>
      <WriterTool />
      <EditorTool />
      <FactCheckerTool />
    </Section>
  </>
);

The model sees a single "Your Content Team" section with all three specialists described. It decides the workflow dynamically: maybe writer → fact_checker → editor, or skip fact_checker for opinion pieces.


Next: Reactive State

Released under the MIT License.