import { getConfig } from "../agent-types.js"; import type { AgentInvocation, SubagentType } from "../types.js"; import type { Theme } from "./theme.js"; const TOOL_DISPLAY: Record = { read: "reading", bash: "running command", edit: "editing", write: "writing", grep: "searching", find: "finding files", ls: "listing", glob: "matching patterns", webSearch: "searching web", webFetch: "fetching URL", }; export function formatTokens(count: number): string { if (count >= 1_000_000) return `${(count / 1_000_000).toFixed(1)}M token`; if (count >= 1_000) return `${(count / 1_000).toFixed(1)}k token`; return `${count} token`; } export function formatSessionTokens( tokens: number, percent: number | null, theme: Theme, compactions = 0, ): string { const tokenStr = formatTokens(tokens); const annot: string[] = []; if (percent !== null) { const color = percent >= 85 ? "error" : percent >= 70 ? "warning" : "dim"; annot.push(theme.fg(color, `${Math.round(percent)}%`)); } if (compactions > 0) { annot.push(theme.fg("dim", `↻${compactions}`)); } if (annot.length === 0) return tokenStr; return `${tokenStr} (${annot.join(" · ")})`; } export function formatTurns(turnCount: number, maxTurns?: number | null): string { return maxTurns == null ? `⟳${turnCount}` : `⟳${turnCount}≤${maxTurns}`; } export function formatMs(ms: number): string { return `${(ms / 1000).toFixed(1)}s`; } export function formatDuration(startedAt: number, completedAt?: number): string { if (completedAt) return formatMs(completedAt - startedAt); return `${formatMs(Date.now() - startedAt)} (running)`; } export function getDisplayName(type: SubagentType): string { return getConfig(type).displayName; } export function getPromptModeLabel(type: SubagentType): string | undefined { const config = getConfig(type); return config.promptMode === "append" ? "twin" : undefined; } export function buildInvocationTags( invocation: AgentInvocation | undefined, ): { modelName?: string; tags: string[] } { const tags: string[] = []; if (!invocation) return { tags }; if (invocation.thinking) tags.push(`thinking: ${invocation.thinking}`); if (invocation.isolated) tags.push("isolated"); if (invocation.isolation === "worktree") tags.push("worktree"); if (invocation.inheritContext) tags.push("inherit context"); if (invocation.runInBackground) tags.push("background"); if (invocation.maxTurns != null) tags.push(`max turns: ${invocation.maxTurns}`); return { modelName: invocation.modelName, tags }; } function truncateLine(text: string, len = 60): string { const line = text.split("\n").find(l => l.trim())?.trim() ?? ""; if (line.length <= len) return line; return `${line.slice(0, len)}…`; } export function describeActivity(activeTools: Map, responseText?: string): string { if (activeTools.size > 0) { const groups = new Map(); for (const toolName of activeTools.values()) { const action = TOOL_DISPLAY[toolName] ?? toolName; groups.set(action, (groups.get(action) ?? 0) + 1); } const parts: string[] = []; for (const [action, count] of groups) { if (count > 1) { parts.push(`${action} ${count} ${action === "searching" ? "patterns" : "files"}`); } else { parts.push(action); } } return `${parts.join(", ")}…`; } if (responseText && responseText.trim().length > 0) { return truncateLine(responseText); } return "thinking…"; }