feat(vscode-ide-companion): add agent execution tool display (#2590)

Preserve structured agent rawOutput through the VSCode session pipeline.

Render dedicated agent execution cards from shared webui components.
This commit is contained in:
易良 2026-04-18 23:39:26 +08:00 committed by GitHub
parent 4ee9ca912c
commit cd1be1c524
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 667 additions and 7 deletions

View file

@ -0,0 +1,106 @@
# Agent Tool Display Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Add a dedicated VSCode/web UI display for Agent tool executions so subagent progress, summaries, and failures render from structured `rawOutput` instead of falling back to the generic tool card.
**Architecture:** Preserve ACP `rawOutput` through the VSCode session/update pipeline into `ToolCallData`, then let the shared web UI router detect `task_execution` payloads and render a dedicated `AgentToolCall` component. Keep the change shared in `packages/webui` so VSCode and `ChatViewer` stay aligned.
**Tech Stack:** TypeScript, React, Vitest, shared `@qwen-code/webui` tool-call components.
### Task 1: Lock in the failing data-flow behavior
**Files:**
- Modify: `packages/vscode-ide-companion/src/services/qwenSessionUpdateHandler.test.ts`
- Create: `packages/vscode-ide-companion/src/webview/hooks/useToolCalls.test.tsx`
**Step 1: Write the failing tests**
- Add a session handler test asserting `tool_call_update` forwards `rawOutput` when ACP sends a `task_execution` payload.
- Add a hook test asserting `useToolCalls` stores and updates `rawOutput` for an agent tool call.
**Step 2: Run test to verify it fails**
Run: `npm test --workspace=packages/vscode-ide-companion -- --run qwenSessionUpdateHandler.test.ts useToolCalls.test.tsx`
Expected: failures because `rawOutput` is not preserved in the current handler/hook pipeline.
### Task 2: Lock in the failing renderer behavior
**Files:**
- Create: `packages/vscode-ide-companion/src/webview/components/messages/toolcalls/index.test.tsx`
**Step 1: Write the failing test**
- Render the routed tool call with `kind: 'other'` plus `rawOutput.type === 'task_execution'`.
- Assert the task description, active child tool, summary, and failure reason render from a dedicated agent display instead of generic text output.
**Step 2: Run test to verify it fails**
Run: `npm test --workspace=packages/vscode-ide-companion -- --run packages/vscode-ide-companion/src/webview/components/messages/toolcalls/index.test.tsx`
Expected: failure because the router only keys off `kind` and no dedicated agent component exists.
### Task 3: Preserve structured agent output end-to-end
**Files:**
- Modify: `packages/vscode-ide-companion/src/types/chatTypes.ts`
- Modify: `packages/vscode-ide-companion/src/services/qwenSessionUpdateHandler.ts`
- Modify: `packages/vscode-ide-companion/src/webview/hooks/useToolCalls.ts`
- Modify: `packages/webui/src/components/toolcalls/shared/types.ts`
**Step 1: Implement the minimal data model changes**
- Add optional `rawOutput` to the VSCode session/webview tool-call types.
- Forward `rawOutput` in `QwenSessionUpdateHandler`.
- Store/merge `rawOutput` in `useToolCalls`.
- Expose `rawOutput` in shared web UI tool-call data types.
**Step 2: Run the focused tests**
Run: `npm test --workspace=packages/vscode-ide-companion -- --run qwenSessionUpdateHandler.test.ts useToolCalls.test.tsx`
Expected: pass.
### Task 4: Add the shared agent tool-call UI
**Files:**
- Create: `packages/webui/src/components/toolcalls/AgentToolCall.tsx`
- Modify: `packages/webui/src/components/toolcalls/index.ts`
- Modify: `packages/vscode-ide-companion/src/webview/components/messages/toolcalls/index.tsx`
- Modify: `packages/webui/src/components/ChatViewer/ChatViewer.tsx`
**Step 1: Implement the minimal renderer**
- Add a guard for `rawOutput.type === 'task_execution'`.
- Render task description as the header.
- Show agent name + status, currently running child tools, completion summary, and failure/cancel reason.
- Keep the layout compatible with multiple parallel agent cards by rendering each tool call independently.
**Step 2: Run the focused renderer test**
Run: `npm test --workspace=packages/vscode-ide-companion -- --run packages/vscode-ide-companion/src/webview/components/messages/toolcalls/index.test.tsx`
Expected: pass.
### Task 5: Verify the integrated surface
**Files:**
- Modify: `packages/webui/src/index.ts`
**Step 1: Export the new shared component if needed**
- Re-export any new component/types needed by VSCode or `ChatViewer`.
**Step 2: Run package verification**
Run: `npm test --workspace=packages/vscode-ide-companion -- --run qwenSessionUpdateHandler.test.ts useToolCalls.test.tsx packages/vscode-ide-companion/src/webview/components/messages/toolcalls/index.test.tsx`
Run: `npm run check-types --workspace=packages/vscode-ide-companion`
Run: `npm run typecheck --workspace=packages/webui`
Expected: all targeted tests and typechecks pass.

View file

@ -169,6 +169,48 @@ describe('QwenSessionUpdateHandler', () => {
locations: undefined,
});
});
it('forwards rawOutput for structured agent execution updates', () => {
const rawOutput = {
type: 'task_execution',
subagentName: 'Explore',
taskDescription: 'Explore auth logic',
taskPrompt: 'Inspect auth flow implementation',
status: 'running',
toolCalls: [
{
callId: 'child-1',
name: 'read',
status: 'executing',
},
],
};
const toolCallUpdate = {
sessionId: 'test-session',
update: {
sessionUpdate: 'tool_call_update',
toolCallId: 'call-agent',
kind: 'other',
title: 'Launch agent',
status: 'in_progress',
rawOutput,
},
} as SessionNotification;
handler.handleSessionUpdate(toolCallUpdate);
expect(mockCallbacks.onToolCall).toHaveBeenCalledWith({
toolCallId: 'call-agent',
kind: 'other',
title: 'Launch agent',
status: 'in_progress',
rawInput: undefined,
rawOutput,
content: undefined,
locations: undefined,
});
});
});
describe('plan handling', () => {

View file

@ -111,6 +111,7 @@ export class QwenSessionUpdateHandler {
title: (update.title as string) || undefined,
status: (update.status as string) || undefined,
rawInput: update.rawInput,
rawOutput: (update as { rawOutput?: unknown }).rawOutput,
content: update.content as
| Array<Record<string, unknown>>
| undefined,
@ -134,6 +135,7 @@ export class QwenSessionUpdateHandler {
title: (update.title as string) || undefined,
status: (update.status as string) || undefined,
rawInput: update.rawInput,
rawOutput: (update as { rawOutput?: unknown }).rawOutput,
content: update.content as
| Array<Record<string, unknown>>
| undefined,

View file

@ -29,6 +29,7 @@ export interface ToolCallUpdateData {
title?: string;
status?: string;
rawInput?: unknown;
rawOutput?: unknown;
content?: Array<Record<string, unknown>>;
locations?: Array<{ path: string; line?: number | null }>;
timestamp?: number;
@ -88,6 +89,7 @@ export interface ToolCallUpdate {
title?: string;
status?: 'pending' | 'in_progress' | 'completed' | 'failed';
rawInput?: unknown;
rawOutput?: unknown;
content?: Array<{
type: 'content' | 'diff';
content?: {

View file

@ -0,0 +1,151 @@
/**
* @license
* Copyright 2025 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/
/** @vitest-environment jsdom */
import { act } from 'react';
import { createRoot, type Root } from 'react-dom/client';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { ToolCallRouter } from './index.js';
vi.mock('@qwen-code/webui', async () => {
const React = await vi.importActual<typeof import('react')>('react');
const renderLabel = (label: string) =>
function MockTool({
toolCall,
}: {
toolCall: {
title?: string;
rawOutput?: {
taskDescription?: string;
terminateReason?: string;
};
};
}) {
return React.createElement(
'div',
undefined,
`${label}:${toolCall.rawOutput?.taskDescription || toolCall.title || ''}:${toolCall.rawOutput?.terminateReason || ''}`,
);
};
return {
shouldShowToolCall: () => true,
isAgentExecutionToolCall: (toolCall: { rawOutput?: { type?: string } }) =>
toolCall.rawOutput?.type === 'task_execution',
GenericToolCall: renderLabel('generic'),
ThinkToolCall: renderLabel('think'),
SaveMemoryToolCall: renderLabel('memory'),
EditToolCall: renderLabel('edit'),
WriteToolCall: renderLabel('write'),
SearchToolCall: renderLabel('search'),
UpdatedPlanToolCall: renderLabel('plan'),
ShellToolCall: renderLabel('shell'),
ReadToolCall: renderLabel('read'),
WebFetchToolCall: renderLabel('web'),
AgentToolCall: renderLabel('agent'),
};
});
describe('ToolCallRouter agent execution rendering', () => {
let container: HTMLDivElement | null = null;
let root: Root | null = null;
beforeEach(() => {
(
globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }
).IS_REACT_ACT_ENVIRONMENT = true;
container = document.createElement('div');
document.body.appendChild(container);
root = createRoot(container);
});
afterEach(() => {
if (root) {
act(() => {
root?.unmount();
});
root = null;
}
if (container) {
container.remove();
container = null;
}
});
it('renders a dedicated view for structured agent progress and summary', () => {
act(() => {
root?.render(
<ToolCallRouter
toolCall={
{
toolCallId: 'agent-1',
kind: 'other',
title: 'Launch agent',
status: 'completed',
rawOutput: {
type: 'task_execution',
subagentName: 'Explore',
taskDescription: 'Explore auth logic',
taskPrompt: 'Inspect auth flow implementation',
status: 'completed',
toolCalls: [
{
callId: 'child-1',
name: 'read',
status: 'success',
},
{
callId: 'child-2',
name: 'grep',
status: 'success',
},
],
executionSummary: {
totalToolCalls: 2,
totalTokens: 1234,
totalDurationMs: 2200,
},
},
} as never
}
/>,
);
});
expect(container?.textContent).toContain('agent:Explore auth logic');
});
it('renders the agent failure reason from structured rawOutput', () => {
act(() => {
root?.render(
<ToolCallRouter
toolCall={
{
toolCallId: 'agent-2',
kind: 'other',
title: 'Launch agent',
status: 'failed',
rawOutput: {
type: 'task_execution',
subagentName: 'Explore',
taskDescription: 'Explore auth logic',
taskPrompt: 'Inspect auth flow implementation',
status: 'failed',
terminateReason: 'Subagent crashed',
},
} as never
}
/>,
);
});
expect(container?.textContent).toContain(
'agent:Explore auth logic:Subagent crashed',
);
});
});

View file

@ -11,6 +11,8 @@ import type { FC } from 'react';
import {
shouldShowToolCall,
// All ToolCall components from webui
AgentToolCall,
isAgentExecutionToolCall,
GenericToolCall,
ThinkToolCall,
EditToolCall,
@ -21,13 +23,19 @@ import {
ReadToolCall,
WebFetchToolCall,
} from '@qwen-code/webui';
import type { BaseToolCallProps } from '@qwen-code/webui';
import type { BaseToolCallProps, ToolCallData } from '@qwen-code/webui';
/**
* Factory function that returns the appropriate tool call component based on kind
*/
export const getToolCallComponent = (kind: string): FC<BaseToolCallProps> => {
const normalizedKind = kind.toLowerCase();
export const getToolCallComponent = (
toolCall: ToolCallData,
): FC<BaseToolCallProps> => {
if (isAgentExecutionToolCall(toolCall)) {
return AgentToolCall;
}
const normalizedKind = toolCall.kind.toLowerCase();
// Route to specialized components
switch (normalizedKind) {
@ -90,7 +98,7 @@ export const ToolCallRouter: React.FC<BaseToolCallProps> = ({ toolCall }) => {
}
// Get the appropriate component for this kind
const Component = getToolCallComponent(toolCall.kind);
const Component = getToolCallComponent(toolCall);
// Render the specialized component
return <Component toolCall={toolCall} />;

View file

@ -0,0 +1,121 @@
/**
* @license
* Copyright 2025 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/
/** @vitest-environment jsdom */
import { act } from 'react';
import { createRoot, type Root } from 'react-dom/client';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { useToolCalls } from './useToolCalls.js';
import type { ToolCallUpdate } from '../../types/chatTypes.js';
type HookSnapshot = ReturnType<typeof useToolCalls>;
let latestSnapshot: HookSnapshot | null = null;
function HookHarness() {
latestSnapshot = useToolCalls();
return null;
}
describe('useToolCalls', () => {
let container: HTMLDivElement | null = null;
let root: Root | null = null;
beforeEach(() => {
(
globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }
).IS_REACT_ACT_ENVIRONMENT = true;
container = document.createElement('div');
document.body.appendChild(container);
root = createRoot(container);
act(() => {
root?.render(<HookHarness />);
});
});
afterEach(() => {
latestSnapshot = null;
if (root) {
act(() => {
root?.unmount();
});
root = null;
}
if (container) {
container.remove();
container = null;
}
});
it('stores structured rawOutput for agent tool calls across updates', () => {
const startUpdate = {
type: 'tool_call',
toolCallId: 'agent-1',
kind: 'other',
title: 'Launch agent',
status: 'in_progress',
rawOutput: {
type: 'task_execution',
subagentName: 'Explore',
taskDescription: 'Explore auth logic',
taskPrompt: 'Inspect auth flow implementation',
status: 'running',
},
} as ToolCallUpdate & { rawOutput: unknown };
act(() => {
latestSnapshot?.handleToolCallUpdate(startUpdate);
});
expect(latestSnapshot?.toolCalls.get('agent-1')).toMatchObject({
toolCallId: 'agent-1',
kind: 'other',
title: 'Launch agent',
status: 'in_progress',
rawOutput: {
type: 'task_execution',
taskDescription: 'Explore auth logic',
},
});
const completionUpdate = {
type: 'tool_call_update',
toolCallId: 'agent-1',
status: 'completed',
rawOutput: {
type: 'task_execution',
subagentName: 'Explore',
taskDescription: 'Explore auth logic',
taskPrompt: 'Inspect auth flow implementation',
status: 'completed',
executionSummary: {
totalToolCalls: 3,
totalTokens: 1234,
totalDurationMs: 2200,
},
},
} as ToolCallUpdate & { rawOutput: unknown };
act(() => {
latestSnapshot?.handleToolCallUpdate(completionUpdate);
});
expect(latestSnapshot?.toolCalls.get('agent-1')).toMatchObject({
status: 'completed',
rawOutput: {
type: 'task_execution',
status: 'completed',
executionSummary: {
totalToolCalls: 3,
totalTokens: 1234,
totalDurationMs: 2200,
},
},
});
});
});

View file

@ -180,6 +180,7 @@ export const useToolCalls = () => {
title: safeTitle(update.title),
status: update.status || 'pending',
rawInput: update.rawInput as string | object | undefined,
rawOutput: update.rawOutput,
content,
locations: update.locations,
timestamp: resolveTimestamp(update),
@ -216,6 +217,9 @@ export const useToolCalls = () => {
...(update.kind && { kind: update.kind }),
...(update.title && { title: safeTitle(update.title) }),
...(update.status && { status: update.status }),
...(update.rawOutput !== undefined && {
rawOutput: update.rawOutput,
}),
content: mergedContent,
...(update.locations && { locations: update.locations }),
timestamp: nextTimestamp,
@ -227,6 +231,7 @@ export const useToolCalls = () => {
title: update.title ? safeTitle(update.title) : '',
status: update.status || 'pending',
rawInput: update.rawInput as string | object | undefined,
rawOutput: update.rawOutput,
content: updatedContent,
locations: update.locations,
timestamp: resolveTimestamp(update),

View file

@ -15,6 +15,7 @@ import { UserMessage } from '../messages/UserMessage.js';
import { AssistantMessage } from '../messages/Assistant/AssistantMessage.js';
import { ThinkingMessage } from '../messages/ThinkingMessage.js';
import {
AgentToolCall,
GenericToolCall,
ThinkToolCall,
EditToolCall,
@ -25,6 +26,7 @@ import {
ReadToolCall,
WebFetchToolCall,
shouldShowToolCall,
isAgentExecutionToolCall,
} from '../toolcalls/index.js';
import type { ToolCallData as BaseToolCallData } from '../toolcalls/index.js';
import './ChatViewer.css';
@ -145,8 +147,12 @@ function parseTimestamp(isoString: string): number {
/**
* Get the appropriate tool call component based on kind
*/
function getToolCallComponent(kind: string) {
const normalizedKind = kind.toLowerCase();
function getToolCallComponent(toolCall: BaseToolCallData) {
if (isAgentExecutionToolCall(toolCall)) {
return AgentToolCall;
}
const normalizedKind = toolCall.kind.toLowerCase();
switch (normalizedKind) {
case 'read':
@ -313,7 +319,7 @@ export const ChatViewer = forwardRef<ChatViewerHandle, ChatViewerProps>(
// Handle tool calls
if (msg.type === 'tool_call' && msg.toolCall) {
const ToolCallComponent = getToolCallComponent(msg.toolCall.kind);
const ToolCallComponent = getToolCallComponent(msg.toolCall);
if (!ToolCallComponent) {
return null;

View file

@ -0,0 +1,156 @@
/**
* @license
* Copyright 2025 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*
* Dedicated agent tool call component for structured subagent execution output.
*/
import type { FC } from 'react';
import { ToolCallCard, ToolCallRow, safeTitle } from './shared/index.js';
import type {
AgentExecutionRawOutput,
BaseToolCallProps,
ToolCallData,
} from './shared/index.js';
const MAX_VISIBLE_TOOL_CALLS = 5;
export const isAgentExecutionRawOutput = (
value: unknown,
): value is AgentExecutionRawOutput =>
Boolean(
value &&
typeof value === 'object' &&
'type' in value &&
(value as { type?: unknown }).type === 'task_execution' &&
'taskDescription' in value &&
'status' in value,
);
export const isAgentExecutionToolCall = (
toolCall: ToolCallData,
): toolCall is ToolCallData & { rawOutput: AgentExecutionRawOutput } =>
isAgentExecutionRawOutput(toolCall.rawOutput);
const STATUS_LABELS: Record<AgentExecutionRawOutput['status'], string> = {
running: 'Running',
completed: 'Completed',
failed: 'Failed',
cancelled: 'Cancelled',
};
const CHILD_STATUS_LABELS: Record<
NonNullable<AgentExecutionRawOutput['toolCalls']>[number]['status'],
string
> = {
executing: 'Running',
awaiting_approval: 'Awaiting approval',
success: 'Completed',
failed: 'Failed',
};
const formatDuration = (durationMs: number): string => {
if (durationMs < 1000) {
return `${durationMs}ms`;
}
if (durationMs < 60_000) {
return `${(durationMs / 1000).toFixed(durationMs % 1000 === 0 ? 0 : 1)}s`;
}
const minutes = Math.floor(durationMs / 60_000);
const seconds = Math.round((durationMs % 60_000) / 1000);
return `${minutes}m ${seconds}s`;
};
const getHeaderTitle = (
data: AgentExecutionRawOutput,
fallbackTitle: ToolCallData['title'],
): string => data.taskDescription || safeTitle(fallbackTitle) || 'Agent Task';
export const AgentToolCall: FC<BaseToolCallProps> = ({ toolCall }) => {
if (!isAgentExecutionToolCall(toolCall)) {
return null;
}
const data = toolCall.rawOutput;
const visibleToolCalls = data.toolCalls?.slice(-MAX_VISIBLE_TOOL_CALLS) ?? [];
const hiddenToolCallCount = Math.max(
0,
(data.toolCalls?.length ?? 0) - visibleToolCalls.length,
);
return (
<ToolCallCard icon="🤖">
<ToolCallRow label="Agent">
<div className="font-medium text-[var(--app-primary-foreground)]">
{getHeaderTitle(data, toolCall.title)}
</div>
</ToolCallRow>
<ToolCallRow label="Status">
<div className="flex flex-wrap items-center gap-2">
<span className="font-medium">{data.subagentName}</span>
<span className="text-[var(--app-secondary-foreground)]">
{STATUS_LABELS[data.status]}
</span>
</div>
</ToolCallRow>
{visibleToolCalls.length > 0 && (
<ToolCallRow label={data.status === 'running' ? 'Progress' : 'Tools'}>
<div className="flex flex-col gap-1">
{visibleToolCalls.map((childToolCall) => (
<div
key={childToolCall.callId}
className="flex flex-wrap items-center gap-2"
>
<span className="font-mono text-[12px] text-[var(--app-primary-foreground)]">
{childToolCall.name}
</span>
<span className="text-[var(--app-secondary-foreground)]">
{CHILD_STATUS_LABELS[childToolCall.status]}
</span>
{childToolCall.description && (
<span className="text-[var(--app-secondary-foreground)]">
{childToolCall.description}
</span>
)}
{childToolCall.error && (
<span className="text-[#c74e39]">{childToolCall.error}</span>
)}
</div>
))}
{hiddenToolCallCount > 0 && (
<div className="text-[var(--app-secondary-foreground)]">
+{hiddenToolCallCount} more tool calls
</div>
)}
</div>
</ToolCallRow>
)}
{data.executionSummary && (
<ToolCallRow label="Summary">
<div className="flex flex-wrap gap-x-4 gap-y-1">
<span>{data.executionSummary.totalToolCalls} tool calls</span>
<span>
{data.executionSummary.totalTokens.toLocaleString()} tokens
</span>
<span>{formatDuration(data.executionSummary.totalDurationMs)}</span>
</div>
</ToolCallRow>
)}
{(data.status === 'failed' || data.status === 'cancelled') &&
data.terminateReason && (
<ToolCallRow label="Reason">
<div className="text-[#c74e39] font-medium">
{data.terminateReason}
</div>
</ToolCallRow>
)}
</ToolCallCard>
);
};

View file

@ -10,6 +10,11 @@ export * from './shared/index.js';
// Business ToolCall components
export { ThinkToolCall } from './ThinkToolCall.js';
export { GenericToolCall } from './GenericToolCall.js';
export {
AgentToolCall,
isAgentExecutionRawOutput,
isAgentExecutionToolCall,
} from './AgentToolCall.js';
export { EditToolCall } from './EditToolCall.js';
export { WriteToolCall } from './WriteToolCall.js';
export { SearchToolCall } from './SearchToolCall.js';

View file

@ -31,6 +31,11 @@ export {
// Types
export type {
AgentExecutionRawOutput,
AgentExecutionStatus,
AgentExecutionSummary,
AgentExecutionToolCall,
AgentToolCallStatus,
ToolCallContent,
ToolCallLocation,
ToolCallStatus,

View file

@ -37,6 +37,48 @@ export interface ToolCallLocation {
*/
export type ToolCallStatus = 'pending' | 'in_progress' | 'completed' | 'failed';
export type AgentExecutionStatus =
| 'running'
| 'completed'
| 'failed'
| 'cancelled';
export type AgentToolCallStatus =
| 'executing'
| 'awaiting_approval'
| 'success'
| 'failed';
export interface AgentExecutionSummary {
totalToolCalls: number;
totalTokens: number;
totalDurationMs: number;
successfulToolCalls?: number;
failedToolCalls?: number;
successRate?: number;
}
export interface AgentExecutionToolCall {
callId: string;
name: string;
status: AgentToolCallStatus;
error?: string;
description?: string;
}
export interface AgentExecutionRawOutput {
type: 'task_execution';
subagentName: string;
subagentColor?: string;
taskDescription: string;
taskPrompt: string;
status: AgentExecutionStatus;
terminateReason?: string;
result?: string;
executionSummary?: AgentExecutionSummary;
toolCalls?: AgentExecutionToolCall[];
}
/**
* Base tool call data interface
*/
@ -46,6 +88,7 @@ export interface ToolCallData {
title: string | object;
status: ToolCallStatus;
rawInput?: string | object;
rawOutput?: unknown;
content?: ToolCallContent[];
locations?: ToolCallLocation[];
timestamp?: number;

View file

@ -152,6 +152,9 @@ export {
// Business ToolCall components
ThinkToolCall,
GenericToolCall,
AgentToolCall,
isAgentExecutionRawOutput,
isAgentExecutionToolCall,
EditToolCall,
WriteToolCall,
SearchToolCall,
@ -163,6 +166,11 @@ export {
} from './components/toolcalls';
export type {
ToolCallContainerProps,
AgentExecutionRawOutput,
AgentExecutionStatus,
AgentExecutionSummary,
AgentExecutionToolCall,
AgentToolCallStatus,
ToolCallContent,
ToolCallData,
BaseToolCallProps,