1import React, { useState } from "react";
2import { LLMContent } from "../types";
3
4interface SubagentToolProps {
5 // For tool_use (pending state)
6 toolInput?: unknown; // { slug: string, prompt: string, timeout_seconds?: number, wait?: boolean }
7 isRunning?: boolean;
8
9 // For tool_result (completed state)
10 toolResult?: LLMContent[];
11 hasError?: boolean;
12 executionTime?: string;
13 displayData?: { slug?: string; conversation_id?: string };
14}
15
16function SubagentTool({
17 toolInput,
18 isRunning,
19 toolResult,
20 hasError,
21 executionTime,
22 displayData,
23}: SubagentToolProps) {
24 const [isExpanded, setIsExpanded] = useState(false);
25
26 // Extract fields from toolInput
27 const input =
28 typeof toolInput === "object" && toolInput !== null
29 ? (toolInput as { slug?: string; prompt?: string; timeout_seconds?: number; wait?: boolean })
30 : {};
31
32 const slug = input.slug || displayData?.slug || "subagent";
33 const prompt = input.prompt || "";
34 const wait = input.wait !== false;
35 const timeout = input.timeout_seconds || 60;
36
37 // Extract result text
38 const resultText =
39 toolResult
40 ?.filter((r) => r.Type === 2) // ContentTypeText
41 .map((r) => r.Text)
42 .join("\n") || "";
43
44 // Truncate prompt for display
45 const truncateText = (text: string, maxLen: number = 60) => {
46 if (!text) return "";
47 const firstLine = text.split("\n")[0];
48 if (firstLine.length <= maxLen) return firstLine;
49 return firstLine.substring(0, maxLen) + "...";
50 };
51
52 const displayPrompt = truncateText(prompt);
53 const isComplete = !isRunning && toolResult !== undefined;
54
55 return (
56 <div className="tool" data-testid={isComplete ? "tool-call-completed" : "tool-call-running"}>
57 <div className="tool-header" onClick={() => setIsExpanded(!isExpanded)}>
58 <div className="tool-summary">
59 <span className={`tool-emoji ${isRunning ? "running" : ""}`}>⚡</span>
60 <span className="tool-name">subagent</span>
61 {isComplete && hasError && <span className="tool-error">✗</span>}
62 {isComplete && !hasError && <span className="tool-success">✓</span>}
63 <span className="tool-command">
64 Subagent '{slug}' {isRunning ? (wait ? "running..." : "started") : ""}
65 {displayPrompt && !isRunning && ` ${displayPrompt}`}
66 </span>
67 </div>
68 <button
69 className="tool-toggle"
70 aria-label={isExpanded ? "Collapse" : "Expand"}
71 aria-expanded={isExpanded}
72 >
73 <svg
74 width="12"
75 height="12"
76 viewBox="0 0 12 12"
77 fill="none"
78 xmlns="http://www.w3.org/2000/svg"
79 style={{
80 transform: isExpanded ? "rotate(90deg)" : "rotate(0deg)",
81 transition: "transform 0.2s",
82 }}
83 >
84 <path
85 d="M4.5 3L7.5 6L4.5 9"
86 stroke="currentColor"
87 strokeWidth="1.5"
88 strokeLinecap="round"
89 strokeLinejoin="round"
90 />
91 </svg>
92 </button>
93 </div>
94
95 {isExpanded && (
96 <div className="tool-details">
97 <div className="tool-section">
98 <div className="tool-label">
99 Prompt to '{slug}':
100 {!wait && <span className="tool-badge">fire-and-forget</span>}
101 {timeout !== 60 && <span className="tool-badge">timeout: {timeout}s</span>}
102 </div>
103 <div className="tool-code">{prompt || "(no prompt)"}</div>
104 </div>
105
106 {isComplete && (
107 <div className="tool-section">
108 <div className="tool-label">
109 Response:
110 {executionTime && <span className="tool-time">{executionTime}</span>}
111 </div>
112 <div className={`tool-code ${hasError ? "error" : ""}`}>
113 {resultText || "(no response)"}
114 </div>
115 </div>
116 )}
117
118 {displayData?.conversation_id && (
119 <div className="tool-section">
120 <div className="tool-label">Conversation:</div>
121 <div className="tool-code">
122 <a
123 href={`/c/${slug}`}
124 onClick={(e) => {
125 e.preventDefault();
126 // Navigate to the subagent conversation
127 window.history.pushState({}, "", `/c/${slug}`);
128 window.dispatchEvent(new PopStateEvent("popstate"));
129 }}
130 style={{ color: "var(--link-color)", textDecoration: "underline" }}
131 >
132 View subagent conversation →
133 </a>
134 </div>
135 </div>
136 )}
137 </div>
138 )}
139 </div>
140 );
141}
142
143export default SubagentTool;