SubagentTool

Render a task with nested tool calls. Shows elapsed time and the last nested tool while running.

Getting Started
$pnpm add @sinups/ai-kit @mantine/core @mantine/hooks @tabler/icons-react
Examples
Completed
Pending
Interrupted
Source

The component source as it ships in the package. Useful as a reference, or as a starting point if you want to fork a piece.

tools/SubagentTool.tsx
import React, { memo } from 'react';
import { Box } from '@mantine/core';
import { ToolRowBase } from '../ToolRowBase/ToolRowBase';
import type { ToolPart } from '../types';
import { cx } from '../utils/cx';
import { getPartInput, getToolStatus } from '../utils/format-tool';
import { GenericTool } from './GenericTool';
import { toolRegistry } from './tool-registry';
import { useElapsed } from './use-elapsed';
import classes from './SubagentTool.module.css';

export interface SubagentToolProps {
  /** Tool part in AI SDK v5 shape: `{ type, toolCallId, state, input, output }` */
  part: ToolPart;
  /** Tool parts executed by the subagent, rendered as nested rows */
  nestedTools?: ToolPart[];
  /** Chat status from `useChat()`, used to tell a pending tool from an interrupted one */
  chatStatus?: string;
  /** Class name added to the root element */
  className?: string;
  /** Inline styles added to the root element */
  style?: React.CSSProperties;
}

const MAX_VISIBLE_TOOLS = 5;

/** "Running Subagent" row that expands into the list of nested tool calls */
export const SubagentTool = memo(function SubagentTool({
  part,
  nestedTools = [],
  chatStatus,
  className,
  style,
}: SubagentToolProps) {
  const { isPending, isInterrupted } = getToolStatus(part, chatStatus);
  const input = getPartInput(part);
  const description: string = input.description || '';
  const hasNestedTools = nestedTools.length > 0;
  const elapsedTimeDisplay = useElapsed(part, isPending);

  const subtitle = (() => {
    if (isPending && hasNestedTools) {
      const lastTool = nestedTools[nestedTools.length - 1];
      const meta = lastTool ? toolRegistry[lastTool.type] : null;
      if (meta && lastTool) {
        const title = meta.title(lastTool);
        const nestedSubtitle = meta.subtitle?.(lastTool);
        return nestedSubtitle ? `${title} ${nestedSubtitle}` : title;
      }
    }
    if (!description) {
      return '';
    }
    return description.length > 60 ? `${description.slice(0, 57)}...` : description;
  })();

  if (isInterrupted && !part.output) {
    return (
      <ToolRowBase
        completeLabel="Subagent interrupted"
        isAnimating={false}
        className={className}
        style={style}
      />
    );
  }

  const isStreamingList = isPending && nestedTools.length > MAX_VISIBLE_TOOLS;

  return (
    <Box className={cx(classes.root, className)} style={style}>
      <ToolRowBase
        completeLabel="Completed Subagent"
        shimmerLabel="Running Subagent"
        isAnimating={isPending}
        detail={subtitle}
        expandable={hasNestedTools}
        trailingContent={
          elapsedTimeDisplay ? (
            <span className={classes.elapsed}>{elapsedTimeDisplay}</span>
          ) : undefined
        }
      >
        <div className={classes.body}>
          {isStreamingList && <div className={classes.mask} />}
          <div
            className={classes.list}
            data-multi={nestedTools.length > 1 || undefined}
            data-stream={isStreamingList || undefined}
          >
            {nestedTools.map((nestedPart, idx) => {
              const nestedMeta = toolRegistry[nestedPart.type];
              if (!nestedMeta) {
                return (
                  <ToolRowBase
                    key={idx}
                    completeLabel={nestedPart.type?.replace('tool-', '') ?? 'Tool'}
                    isAnimating={false}
                  />
                );
              }
              const { isPending: nestedIsPending, isError: nestedIsError } = getToolStatus(
                nestedPart,
                chatStatus
              );
              return (
                <GenericTool
                  key={idx}
                  icon={nestedMeta.icon}
                  title={nestedMeta.title(nestedPart)}
                  subtitle={nestedMeta.subtitle?.(nestedPart)}
                  isPending={nestedIsPending}
                  isError={nestedIsError}
                />
              );
            })}
          </div>
        </div>
      </ToolRowBase>
    </Box>
  );
});

SubagentTool.displayName = 'SubagentTool';
API reference
Prop
Type
Required
part
ToolPart
Yes
Tool part in AI SDK v5 shape: `{ type, toolCallId, state, input, output }`
nestedTools
ToolPart[]
No
Tool parts executed by the subagent, rendered as nested rows
chatStatus
string
No
Chat status from `useChat()`, used to tell a pending tool from an interrupted one
className
string
No
Class name added to the root element
style
React.CSSProperties
No
Inline styles added to the root element