@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
JavaScript
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