nandi/oripublic Fork 0
2434cc7fb724f02b34c0189dfd5fb30c1a8a68e3
Commits
Clone
git clone https://git.rickub.com/nandi/ori.git
git clone ssh://git@rickub.com/nandi/ori.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

forked from bots-garden/ori

ToolCallCard.tsx · 102 lines · 2.9 KBTypeScript Blame HistoryRaw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
/**
 * ToolCallCard shows one tool call the agent is running, in the spirit of
 * Zed's agent panel: a collapsible card with the tool kind, a live status,
 * touched locations, and the call's output (text, or a file diff).
 *
 * @example
 * <ToolCallCard call={{ toolCallId: "c1", title: "Reading main.go",
 *   toolKind: "read", status: "in_progress", contents: [], locations: [] }} />
 */

import type { ToolCall } from "../reducer";
import type { AcpToolCallContent } from "../protocol";
import { Markdown } from "./Markdown";

const kindIcons: Record<string, string> = {
	read: "📖",
	edit: "✏️",
	delete: "🗑️",
	move: "📦",
	search: "🔍",
	execute: "⚡",
	think: "💭",
	fetch: "🌐",
	switch_mode: "🔀",
	other: "🔧",
};

const statusLabels: Record<string, string> = {
	pending: "pending",
	in_progress: "running",
	completed: "done",
	failed: "failed",
};

export function ToolCallCard({ call }: { call: ToolCall }) {
	return (
		<details className={`tool tool-${call.status}`}>
			<summary className="tool-summary">
				<span className="tool-icon" aria-hidden>
					{kindIcons[call.toolKind] ?? kindIcons.other}
				</span>
				<span className="tool-title">{call.title}</span>
				<span className={`tool-status tool-status-${call.status}`}>
					{statusLabels[call.status] ?? call.status}
				</span>
			</summary>
			<div className="tool-body">
				{call.locations.length > 0 && (
					<ul className="tool-locations">
						{call.locations.map((location, index) => (
							// biome-ignore lint/suspicious/noArrayIndexKey: tool-call locations carry no id and the list is replaced wholesale on every update.
							<li key={index}>
								<code>
									{location.path}
									{location.line != null ? `:${location.line}` : ""}
								</code>
							</li>
						))}
					</ul>
				)}
				{call.contents.map((content, index) => (
					// biome-ignore lint/suspicious/noArrayIndexKey: tool-call contents carry no id and the list is replaced wholesale on every update.
					<ToolContent key={index} content={content} />
				))}
			</div>
		</details>
	);
}

function ToolContent({ content }: { content: AcpToolCallContent }) {
	switch (content.type) {
		case "content":
			return content.content?.text ? (
				<Markdown text={content.content.text} />
			) : null;
		case "diff":
			return (
				<div className="tool-diff">
					<code className="tool-diff-path">{content.path}</code>
					{content.oldText != null && (
						<pre className="tool-diff-old">
							{prefixLines(content.oldText, "- ")}
						</pre>
					)}
					<pre className="tool-diff-new">
						{prefixLines(content.newText ?? "", "+ ")}
					</pre>
				</div>
			);
		case "terminal":
			return <p className="tool-terminal">terminal: {content.terminalId}</p>;
		default:
			return null;
	}
}

function prefixLines(text: string, prefix: string): string {
	return text
		.split("\n")
		.map((line) => prefix + line)
		.join("\n");
}