@nanocollective/nanocoder
Version:
A local-first CLI coding agent that brings the power of agentic coding tools like Claude Code and Gemini CLI to local models or controlled APIs like OpenRouter
144 lines • 6.93 kB
JavaScript
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
import { Box, Text } from 'ink';
import React from 'react';
import { ErrorMessage } from '../components/message-box.js';
import ToolMessage from '../components/tool-message.js';
import { useTheme } from '../hooks/useTheme.js';
import { parseToolArguments } from '../utils/tool-args-parser.js';
/**
* Tools that should always show expanded (full formatter) output,
* even when compact display mode is enabled.
*/
export const ALWAYS_EXPANDED_TOOLS = new Set([
'create_task',
'list_tasks',
'update_task',
'delete_task',
]);
/**
* Compact tool result display - shows "⚒ toolName description" in tool color.
*/
function CompactToolResult({ toolName, description, }) {
const { colors } = useTheme();
return (_jsxs(Text, { color: colors.tool, children: ['\u2692', " ", description] }));
}
/**
* Generate a compact grouped description for N calls of the same tool.
* Always uses count-based phrasing for consistency.
*/
function getGroupedCompactDescription(toolName, count) {
const s = count === 1 ? '' : 's';
switch (toolName) {
case 'read_file':
return `Read ${count} file${s}`;
case 'write_file':
return `Wrote ${count} file${s}`;
case 'string_replace':
return `Made ${count} edit${s}`;
case 'execute_bash':
return `Ran ${count} command${s}`;
case 'search_file_contents':
return `Searched for ${count} pattern${s}`;
case 'find_files':
return `Ran ${count} file search${count === 1 ? '' : 'es'}`;
case 'list_directory':
return `Listed ${count} director${count === 1 ? 'y' : 'ies'}`;
case 'web_search':
return `Ran ${count} web search${count === 1 ? '' : 'es'}`;
case 'fetch_url':
return `Fetched ${count} URL${s}`;
case 'git_status':
case 'git_diff':
case 'git_log':
return `Ran ${count} git command${s}`;
case 'lsp_get_diagnostics':
return `Got diagnostics ${count} time${s}`;
case 'ask_question':
return `Asked ${count} question${s}`;
default:
return `Executed ${toolName} \u00d7 ${count}`;
}
}
/**
* Live display component for running compact tool counts.
* Shows accumulated counts during execution (e.g. "⚒ Read 7 files").
* Rendered in the live area (not Static) so it updates in-place.
*/
export function LiveCompactCounts({ counts }) {
const { colors } = useTheme();
return (_jsx(Box, { flexDirection: "column", marginBottom: 1, children: Object.entries(counts).map(([toolName, count]) => (_jsxs(Text, { color: colors.tool, children: ['\u2692', " ", getGroupedCompactDescription(toolName, count)] }, toolName))) }));
}
/**
* Flush accumulated compact counts to the static chat queue.
* Called when the conversation loop finishes to persist the summary.
*/
export function displayCompactCountsSummary(counts, addToChatQueue, getNextComponentKey) {
const entries = Object.entries(counts);
for (let i = 0; i < entries.length; i++) {
const [toolName, count] = entries[i];
const isLast = i === entries.length - 1;
const description = getGroupedCompactDescription(toolName, count);
if (isLast) {
// Last entry gets wrapped in a flex-column Box with marginBottom
// to separate from subsequent content (same pattern as ToolMessage)
addToChatQueue(_jsx(Box, { flexDirection: "column", marginBottom: 1, children: _jsx(CompactToolResult, { toolName: toolName, description: description }) }, `tool-compact-summary-${toolName}-${getNextComponentKey()}`));
}
else {
addToChatQueue(_jsx(CompactToolResult, { toolName: toolName, description: description }, `tool-compact-summary-${toolName}-${getNextComponentKey()}`));
}
}
}
/**
* Display tool result with proper formatting
* Extracted to eliminate duplication between useChatHandler and useToolHandler
*
* @param toolCall - The tool call that was executed
* @param result - The result from tool execution
* @param toolManager - The tool manager instance (for formatters)
* @param addToChatQueue - Function to add components to chat queue
* @param getNextComponentKey - Function to generate unique React keys
* @param compact - When true, show one-liner instead of full formatter output
*/
export async function displayToolResult(toolCall, result, toolManager, addToChatQueue, getNextComponentKey, compact) {
// Check if this is an error result
const isError = result.content.startsWith('Error: ');
if (isError) {
// Display as error message - always shown in full
const errorMessage = result.content.replace(/^Error: /, '');
addToChatQueue(_jsx(ErrorMessage, { message: errorMessage, hideBox: true }, `tool-error-${result.tool_call_id}-${getNextComponentKey()}-${Date.now()}`));
return;
}
// Compact mode: show count-based one-liner instead of full formatter output
// (skip for tools that should always show expanded output)
if (compact && !ALWAYS_EXPANDED_TOOLS.has(result.name)) {
const description = getGroupedCompactDescription(result.name, 1);
addToChatQueue(_jsx(CompactToolResult, { toolName: result.name, description: description }, `tool-compact-${result.tool_call_id}-${getNextComponentKey()}`));
return;
}
if (toolManager) {
const formatter = toolManager.getToolFormatter(result.name);
if (formatter) {
try {
const parsedArgs = parseToolArguments(toolCall.function.arguments);
const formattedResult = await formatter(parsedArgs, result.content);
if (React.isValidElement(formattedResult)) {
addToChatQueue(React.cloneElement(formattedResult, {
key: `tool-result-${result.tool_call_id}-${getNextComponentKey()}-${Date.now()}`,
}));
}
else {
addToChatQueue(_jsx(ToolMessage, { title: `⚒ ${result.name}`, message: String(formattedResult), hideBox: true }, `tool-result-${result.tool_call_id}-${getNextComponentKey()}-${Date.now()}`));
}
}
catch {
// If formatter fails, show raw result
addToChatQueue(_jsx(ToolMessage, { title: `⚒ ${result.name}`, message: result.content, hideBox: true }, `tool-result-${result.tool_call_id}-${getNextComponentKey()}`));
}
}
else {
// No formatter, show raw result
addToChatQueue(_jsx(ToolMessage, { title: `⚒ ${result.name}`, message: result.content, hideBox: true }, `tool-result-${result.tool_call_id}-${getNextComponentKey()}`));
}
}
}
//# sourceMappingURL=tool-result-display.js.map