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

105 lines 6.18 kB
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; import { Box, Text, useInput } from 'ink'; import SelectInput from 'ink-select-input'; import React from 'react'; import { useTerminalWidth } from '../hooks/useTerminalWidth.js'; import { useTheme } from '../hooks/useTheme.js'; import { getToolManager } from '../message-handler.js'; import { toolFormatters } from '../tools/index.js'; import { formatError } from '../utils/error-formatter.js'; import { getLogger } from '../utils/logging/index.js'; import { parseToolArguments } from '../utils/tool-args-parser.js'; export default function ToolConfirmation({ toolCall, onConfirm, onCancel, }) { const boxWidth = useTerminalWidth(); const { colors } = useTheme(); const [formatterPreview, setFormatterPreview] = React.useState(null); const [isLoadingPreview, setIsLoadingPreview] = React.useState(false); const [hasFormatterError, setHasFormatterError] = React.useState(false); const [hasValidationError, setHasValidationError] = React.useState(false); const [_validationError, setValidationError] = React.useState(null); // Get MCP tool info for display const toolManager = getToolManager(); const mcpInfo = toolManager?.getMCPToolInfo(toolCall.function.name) || { isMCPTool: false, }; // Load formatter preview React.useEffect(() => { const loadPreview = async () => { // Run validator first if available if (toolManager) { const validator = toolManager.getToolValidator(toolCall.function.name); if (validator) { try { // Parse arguments if they're a JSON string const parsedArgs = parseToolArguments(toolCall.function.arguments); const validationResult = await validator(parsedArgs); if (!validationResult.valid) { setValidationError(validationResult.error); setHasValidationError(true); setFormatterPreview(_jsx(Text, { color: colors.error, children: validationResult.error })); return; } } catch (error) { const logger = getLogger(); logger.error({ error: formatError(error) }, 'Error running validator'); const errorMsg = `Validation error: ${formatError(error)}`; setValidationError(errorMsg); setHasValidationError(true); setFormatterPreview(_jsx(Text, { color: colors.error, children: errorMsg })); return; } } } const formatter = toolFormatters[toolCall.function.name]; if (formatter) { setIsLoadingPreview(true); try { // Parse arguments if they're a JSON string const parsedArgs = parseToolArguments(toolCall.function.arguments); const preview = await formatter(parsedArgs); setFormatterPreview(preview); } catch (error) { const logger = getLogger(); logger.error({ error: formatError(error) }, 'Error loading formatter preview'); setHasFormatterError(true); setFormatterPreview(_jsxs(Text, { color: colors.error, children: ["Error: ", String(error)] })); } finally { setIsLoadingPreview(false); } } }; void loadPreview(); }, [toolCall, toolManager, colors.error]); // Handle escape key to cancel useInput((_inputChar, key) => { if (key.escape) { onCancel(); } }); // Auto-handle errors without user interaction React.useEffect(() => { if (hasFormatterError && !hasValidationError) { // Automatically cancel the tool execution only for formatter crashes onConfirm(false); } if (hasValidationError) { // Automatically proceed to execution phase where the validator // will fail again and pass the error back to the model to correct onConfirm(true); } }, [hasFormatterError, hasValidationError, onConfirm]); const options = [ { label: '✓ Yes, execute this tool', value: true }, { label: '✗ No, cancel execution', value: false }, ]; const handleSelect = (item) => { onConfirm(item.value); }; return (_jsx(Box, { width: boxWidth, marginBottom: 1, children: _jsxs(Box, { flexDirection: "column", children: [isLoadingPreview && (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: colors.secondary, children: "Loading preview..." }) })), formatterPreview && !isLoadingPreview && (_jsx(Box, { marginBottom: 1, flexDirection: "column", children: _jsx(Box, { children: React.isValidElement(formatterPreview) ? (formatterPreview) : (_jsx(Text, { color: colors.text, children: String(formatterPreview) })) }) })), !hasFormatterError && !hasValidationError && (_jsxs(_Fragment, { children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: colors.tool, children: `Do you want to execute ${mcpInfo.isMCPTool ? `MCP tool "${toolCall.function.name}" from server "${mcpInfo.serverName}"` : `tool "${toolCall.function.name}"`}?` }) }), _jsx(SelectInput, { items: options, onSelect: handleSelect }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.secondary, children: "Press Escape to cancel" }) })] })), hasFormatterError && !hasValidationError && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: colors.error, children: "Tool execution cancelled due to formatter error." }), _jsx(Text, { color: colors.secondary, children: "Press Escape to continue" })] }))] }) })); } //# sourceMappingURL=tool-confirmation.js.map