forked from bots-garden/ori
| ✨ Introduce new feature(s): ACP web client — Go backend (agent, bridge, httpserver, mockagent) + React SPA (Zed-like agent panel), tests, quality gate PASS | 1 | /** |
| 2 | * ToolCallCard shows one tool call the agent is running, in the spirit of | |
| 3 | * Zed's agent panel: a collapsible card with the tool kind, a live status, | |
| 4 | * touched locations, and the call's output (text, or a file diff). | |
| 5 | * | |
| 6 | * @example | |
| 7 | * <ToolCallCard call={{ toolCallId: "c1", title: "Reading main.go", | |
| 8 | * toolKind: "read", status: "in_progress", contents: [], locations: [] }} /> | |
| 9 | */ | |
| 10 | ||
| 11 | import type { ToolCall } from "../reducer"; | |
| ✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme | 12 | import { InlineText } from "./InlineText"; |
| ✨ Introduce new feature(s): ACP web client — Go backend (agent, bridge, httpserver, mockagent) + React SPA (Zed-like agent panel), tests, quality gate PASS | 13 | import type { AcpToolCallContent } from "../protocol"; |
| 14 | import { Markdown } from "./Markdown"; | |
| Add copy-paste functionality for code blocks and completion widgets | 15 | import { CopyButton } from "./CopyButton"; |
| ✨ Introduce new feature(s): ACP web client — Go backend (agent, bridge, httpserver, mockagent) + React SPA (Zed-like agent panel), tests, quality gate PASS | 16 | |
| 17 | const kindIcons: Record<string, string> = { | |
| 18 | read: "📖", | |
| 19 | edit: "✏️", | |
| 20 | delete: "🗑️", | |
| 21 | move: "📦", | |
| 22 | search: "🔍", | |
| 23 | execute: "⚡", | |
| 24 | think: "💭", | |
| 25 | fetch: "🌐", | |
| 26 | switch_mode: "🔀", | |
| 27 | other: "🔧", | |
| 28 | }; | |
| 29 | ||
| 30 | const statusLabels: Record<string, string> = { | |
| 31 | pending: "pending", | |
| 32 | in_progress: "running", | |
| 33 | completed: "done", | |
| 34 | failed: "failed", | |
| 35 | }; | |
| 36 | ||
| ✨ Switch the ACP adapter to @agentclientprotocol/claude-agent-acp (template 0.0.2) | 37 | /** |
| 38 | * contentText flattens one tool-call content block into plain text for the | |
| 39 | * copy button: a "content" block's text, or a diff's path / old / new | |
| 40 | * sections. Anything else (terminal handles, …) yields "". | |
| 41 | * | |
| 42 | * @example | |
| 43 | * contentText({ type: "content", content: { type: "text", text: "hi" } }) // "hi" | |
| 44 | */ | |
| 45 | export function contentText(content: AcpToolCallContent): string { | |
| 46 | switch (content.type) { | |
| 47 | case "content": | |
| 48 | return content.content?.text ?? ""; | |
| 49 | case "diff": | |
| 50 | return diffText(content); | |
| 51 | default: | |
| 52 | return ""; | |
| 53 | } | |
| 54 | } | |
| 55 | ||
| 56 | function diffText(diff: { | |
| 57 | path?: string; | |
| 58 | oldText?: string | null; | |
| 59 | newText?: string | null; | |
| 60 | }): string { | |
| 61 | const parts: string[] = []; | |
| 62 | if (diff.path) parts.push(`Path: ${diff.path}`); | |
| 63 | if (diff.oldText) parts.push(`Old:\n${diff.oldText}`); | |
| 64 | if (diff.newText) parts.push(`New:\n${diff.newText}`); | |
| 65 | return parts.join("\n\n"); | |
| 66 | } | |
| 67 | ||
| 68 | /** | |
| 69 | * toolContentText joins the textual content of a whole tool call, skipping | |
| 70 | * empty blocks, so the copy button copies exactly what the card shows. | |
| 71 | * | |
| 72 | * @example | |
| 73 | * toolContentText([]) // "" | |
| 74 | */ | |
| 75 | export function toolContentText(contents: AcpToolCallContent[]): string { | |
| 76 | return contents.map(contentText).filter(Boolean).join("\n\n"); | |
| 77 | } | |
| 78 | ||
| ✨ Introduce new feature(s): ACP web client — Go backend (agent, bridge, httpserver, mockagent) + React SPA (Zed-like agent panel), tests, quality gate PASS | 79 | export function ToolCallCard({ call }: { call: ToolCall }) { |
| ✨ Switch the ACP adapter to @agentclientprotocol/claude-agent-acp (template 0.0.2) | 80 | const copyText = toolContentText(call.contents); |
| Add copy-paste functionality for code blocks and completion widgets | 81 | |
| ✨ Introduce new feature(s): ACP web client — Go backend (agent, bridge, httpserver, mockagent) + React SPA (Zed-like agent panel), tests, quality gate PASS | 82 | return ( |
| 83 | <details className={`tool tool-${call.status}`}> | |
| 84 | <summary className="tool-summary"> | |
| 85 | <span className="tool-icon" aria-hidden> | |
| 86 | {kindIcons[call.toolKind] ?? kindIcons.other} | |
| 87 | </span> | |
| ✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme | 88 | <span className="tool-title"> |
| 89 | <InlineText text={call.title} /> | |
| 90 | </span> | |
| ✨ Introduce new feature(s): ACP web client — Go backend (agent, bridge, httpserver, mockagent) + React SPA (Zed-like agent panel), tests, quality gate PASS | 91 | <span className={`tool-status tool-status-${call.status}`}> |
| 92 | {statusLabels[call.status] ?? call.status} | |
| 93 | </span> | |
| ✨ Switch the ACP adapter to @agentclientprotocol/claude-agent-acp (template 0.0.2) | 94 | {copyText && <CopyButton content={copyText} label="Copy tool output" />} |
| ✨ Introduce new feature(s): ACP web client — Go backend (agent, bridge, httpserver, mockagent) + React SPA (Zed-like agent panel), tests, quality gate PASS | 95 | </summary> |
| 96 | <div className="tool-body"> | |
| 97 | {call.locations.length > 0 && ( | |
| 98 | <ul className="tool-locations"> | |
| 99 | {call.locations.map((location, index) => ( | |
| 100 | // biome-ignore lint/suspicious/noArrayIndexKey: tool-call locations carry no id and the list is replaced wholesale on every update. | |
| 101 | <li key={index}> | |
| 102 | <code> | |
| 103 | {location.path} | |
| 104 | {location.line != null ? `:${location.line}` : ""} | |
| 105 | </code> | |
| 106 | </li> | |
| 107 | ))} | |
| 108 | </ul> | |
| 109 | )} | |
| 110 | {call.contents.map((content, index) => ( | |
| 111 | // biome-ignore lint/suspicious/noArrayIndexKey: tool-call contents carry no id and the list is replaced wholesale on every update. | |
| ✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme | 112 | <ToolContent key={index} content={content} toolKind={call.toolKind} /> |
| ✨ Introduce new feature(s): ACP web client — Go backend (agent, bridge, httpserver, mockagent) + React SPA (Zed-like agent panel), tests, quality gate PASS | 113 | ))} |
| 114 | </div> | |
| 115 | </details> | |
| 116 | ); | |
| 117 | } | |
| 118 | ||
| ✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme | 119 | function ToolContent({ |
| 120 | content, | |
| 121 | toolKind, | |
| 122 | }: { content: AcpToolCallContent; toolKind: string }) { | |
| ✨ Introduce new feature(s): ACP web client — Go backend (agent, bridge, httpserver, mockagent) + React SPA (Zed-like agent panel), tests, quality gate PASS | 123 | switch (content.type) { |
| 124 | case "content": | |
| ✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme | 125 | return ( |
| 126 | <TextToolContent text={content.content?.text} toolKind={toolKind} /> | |
| 127 | ); | |
| ✨ Introduce new feature(s): ACP web client — Go backend (agent, bridge, httpserver, mockagent) + React SPA (Zed-like agent panel), tests, quality gate PASS | 128 | case "diff": |
| 129 | return ( | |
| 130 | <div className="tool-diff"> | |
| 131 | <code className="tool-diff-path">{content.path}</code> | |
| 132 | {content.oldText != null && ( | |
| 133 | <pre className="tool-diff-old"> | |
| 134 | {prefixLines(content.oldText, "- ")} | |
| 135 | </pre> | |
| 136 | )} | |
| 137 | <pre className="tool-diff-new"> | |
| 138 | {prefixLines(content.newText ?? "", "+ ")} | |
| 139 | </pre> | |
| 140 | </div> | |
| 141 | ); | |
| 142 | case "terminal": | |
| 143 | return <p className="tool-terminal">terminal: {content.terminalId}</p>; | |
| 144 | default: | |
| 145 | return null; | |
| 146 | } | |
| 147 | } | |
| 148 | ||
| ✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme | 149 | /** |
| 150 | * TextToolContent renders a tool call's textual output. System-command | |
| 151 | * output (kind "execute") is verbatim text: it goes into a monospace block, | |
| 152 | * never through the markdown pipeline. | |
| 153 | */ | |
| 154 | function TextToolContent({ | |
| 155 | text, | |
| 156 | toolKind, | |
| 157 | }: { text: string | undefined; toolKind: string }) { | |
| 158 | if (!text) { | |
| 159 | return null; | |
| 160 | } | |
| 161 | if (toolKind === "execute") { | |
| 162 | return <pre className="tool-output">{text}</pre>; | |
| 163 | } | |
| 164 | return <Markdown text={text} />; | |
| 165 | } | |
| 166 | ||
| ✨ Introduce new feature(s): ACP web client — Go backend (agent, bridge, httpserver, mockagent) + React SPA (Zed-like agent panel), tests, quality gate PASS | 167 | function prefixLines(text: string, prefix: string): string { |
| 168 | return text | |
| 169 | .split("\n") | |
| 170 | .map((line) => prefix + line) | |
| 171 | .join("\n"); | |
| 172 | } |