UNPKG

@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

133 lines 5.65 kB
import { jsx as _jsx } from "react/jsx-runtime"; import { ErrorMessage } from '../../../components/message-box.js'; import { formatError } from '../../../utils/error-formatter.js'; import { parseToolArguments } from '../../../utils/tool-args-parser.js'; import { ALWAYS_EXPANDED_TOOLS, displayToolResult, } from '../../../utils/tool-result-display.js'; /** * Validates and executes a single tool call. * Returns the tool call paired with its result for sequential post-processing. */ const executeOne = async (toolCall, toolManager, processToolUse) => { try { // Run validator if available const validator = toolManager?.getToolValidator(toolCall.function.name); if (validator) { const parsedArgs = parseToolArguments(toolCall.function.arguments); const validationResult = await validator(parsedArgs); if (!validationResult.valid) { return { toolCall, result: { tool_call_id: toolCall.id, role: 'tool', name: toolCall.function.name, content: `Validation failed: ${formatError(validationResult.error)}`, }, validationError: validationResult.error, }; } } const result = await processToolUse(toolCall); return { toolCall, result }; } catch (error) { return { toolCall, result: { tool_call_id: toolCall.id, role: 'tool', name: toolCall.function.name, content: `Error: ${formatError(error)}`, }, }; } }; /** * Groups consecutive read-only tools for parallel execution. * Non-read-only tools form single-item groups to preserve ordering. * * Example: [read, read, write, read, read] → [[read, read], [write], [read, read]] */ const groupByReadOnly = (tools, toolManager) => { const groups = []; let currentGroup = []; let currentIsReadOnly = null; for (const toolCall of tools) { const isReadOnly = toolManager?.isReadOnly(toolCall.function.name) ?? false; if (isReadOnly && currentIsReadOnly === true) { // Continue the current read-only group currentGroup.push(toolCall); } else { // Start a new group if (currentGroup.length > 0) { groups.push(currentGroup); } currentGroup = [toolCall]; currentIsReadOnly = isReadOnly; } } if (currentGroup.length > 0) { groups.push(currentGroup); } return groups; }; /** * Executes tools directly without confirmation. * Read-only tools in consecutive groups are executed in parallel. * Non-read-only tools are executed sequentially to preserve ordering. * Results are displayed in the original input order. * * @returns Array of tool results from executed tools */ export const executeToolsDirectly = async (toolsToExecuteDirectly, toolManager, conversationStateManager, addToChatQueue, getNextComponentKey, options) => { // Import processToolUse here to avoid circular dependencies const { processToolUse } = await import('../../../message-handler.js'); // Group consecutive read-only tools for parallel execution const groups = groupByReadOnly(toolsToExecuteDirectly, toolManager); const directResults = []; for (const group of groups) { const isReadOnlyGroup = toolManager?.isReadOnly(group[0].function.name) ?? false; let executions; if (isReadOnlyGroup && group.length > 1) { // Parallel execution for consecutive read-only tools executions = await Promise.all(group.map(toolCall => executeOne(toolCall, toolManager, processToolUse))); } else { // Sequential execution for non-read-only tools (or single-item groups) executions = []; for (const toolCall of group) { executions.push(await executeOne(toolCall, toolManager, processToolUse)); } } // Display results in order for (const { toolCall, result, validationError } of executions) { directResults.push(result); // Update conversation state conversationStateManager.current.updateAfterToolExecution(toolCall, result.content); if (validationError) { // Display validation error (always shown in full) addToChatQueue(_jsx(ErrorMessage, { message: validationError, hideBox: true }, `validation-error-${toolCall.id}-${Date.now()}`)); } else if (options?.compactDisplay && !ALWAYS_EXPANDED_TOOLS.has(result.name)) { // In compact mode, signal the count callback for live display // (skip for tools that should always show expanded output) const isError = result.content.startsWith('Error: '); if (isError) { // Errors always shown in full await displayToolResult(toolCall, result, toolManager, addToChatQueue, getNextComponentKey); } else { options.onCompactToolCount?.(result.name); } } else { // Full display mode await displayToolResult(toolCall, result, toolManager, addToChatQueue, getNextComponentKey); } } } return directResults; }; //# sourceMappingURL=tool-executor.js.map