UNPKG

naisys

Version:

NAISYS - Autonomous AI agent runner with built-in context management and cost tracking

262 lines 9.36 kB
import { Type } from "@google/genai"; export function createCommandTools({ agentConfig }) { const escapedQuoteRegex = /"/g; const escapedBackslashRegex = /\\/g; const multipleCommandsEnabled = agentConfig().multipleCommandsEnabled; // Common description strings const COMMENT_DESCRIPTION = "High level commentary and/or reasoning. Use an empty string if no comment is required."; const SINGLE_COMMAND_DESCRIPTION = "Single Shell or NAISYS command to execute next."; const COMMAND_LIST_DESCRIPTION = "Ordered list of shell or NAISYS commands to execute next."; const TOOL_DESCRIPTION = "Return the commands to run next along with an optional comment explaining the plan."; const commandProperties = multipleCommandsEnabled ? { comment: { type: "string", description: COMMENT_DESCRIPTION, }, commandList: { type: "array", description: COMMAND_LIST_DESCRIPTION, items: { type: "string", }, }, } : { comment: { type: "string", description: COMMENT_DESCRIPTION, }, command: { type: "string", description: SINGLE_COMMAND_DESCRIPTION, }, }; const requiredProperties = multipleCommandsEnabled ? ["comment", "commandList"] : ["comment", "command"]; const consoleToolOpenAI = { type: "function", function: { name: "submit_commands", description: TOOL_DESCRIPTION, parameters: { type: "object", properties: commandProperties, required: requiredProperties, }, }, }; // Anthropic-compatible tool definition const consoleToolAnthropic = { name: "submit_commands", description: TOOL_DESCRIPTION, input_schema: { type: "object", properties: commandProperties, required: requiredProperties, }, }; // Google-compatible tool definition const googleCommandProperties = multipleCommandsEnabled ? { comment: { type: Type.STRING, description: COMMENT_DESCRIPTION, }, commandList: { type: Type.ARRAY, description: COMMAND_LIST_DESCRIPTION, items: { type: Type.STRING, }, }, } : { comment: { type: Type.STRING, description: COMMENT_DESCRIPTION, }, command: { type: Type.STRING, description: SINGLE_COMMAND_DESCRIPTION, }, }; const consoleToolGoogle = { name: "submit_commands", description: TOOL_DESCRIPTION, parameters: { type: Type.OBJECT, properties: googleCommandProperties, required: requiredProperties, }, }; function getCommandsFromOpenAiToolUse(toolCalls) { if (!Array.isArray(toolCalls) || toolCalls.length === 0) { return undefined; } for (const toolCall of toolCalls) { if (!toolCall || typeof toolCall !== "object") { continue; } const toolType = toolCall.type; if (toolType !== "function") { continue; } const functionCall = toolCall.function; if (!functionCall || typeof functionCall !== "object") { continue; } if (functionCall.name !== consoleToolOpenAI.function.name) { continue; } if (typeof functionCall.arguments !== "string") { continue; } let parsedArgs; try { parsedArgs = JSON.parse(functionCall.arguments); } catch { continue; } if (!parsedArgs || typeof parsedArgs !== "object") { continue; } const comment = parsedArgs.comment; const commandList = parsedArgs.commandList; const command = parsedArgs.command; const commands = []; if (typeof comment === "string" && comment.trim()) { commands.push(buildCommentCommand(comment.trim())); } if (multipleCommandsEnabled) { pushCommandListEntries(commandList, commands); } else if (typeof command === "string" && command.trim()) { commands.push(command.trim()); } if (commands.length) { return commands; } } return undefined; } function getCommandsFromAnthropicToolUse(contentBlocks) { if (!Array.isArray(contentBlocks) || contentBlocks.length === 0) { return undefined; } for (const block of contentBlocks) { if (!block || typeof block !== "object") { continue; } const blockType = block.type; if (blockType !== "tool_use") { continue; } const toolUse = block; if (toolUse.name !== consoleToolAnthropic.name) { continue; } const input = toolUse.input; if (!input || typeof input !== "object") { continue; } const comment = input.comment; const commandList = input.commandList; const command = input.command; const commands = []; if (typeof comment === "string" && comment.trim()) { commands.push(buildCommentCommand(comment.trim())); } if (multipleCommandsEnabled) { pushCommandListEntries(commandList, commands); } else if (typeof command === "string" && command.trim()) { commands.push(command.trim()); } if (commands.length) { return commands; } } return undefined; } function getCommandsFromGoogleToolUse(functionCalls) { if (!Array.isArray(functionCalls) || functionCalls.length === 0) { return undefined; } for (const functionCall of functionCalls) { if (!functionCall || typeof functionCall !== "object") { continue; } const name = functionCall.name; const args = functionCall.args; if (name !== consoleToolGoogle.name) { continue; } if (!args || typeof args !== "object") { continue; } const comment = args.comment; const commandList = args.commandList; const command = args.command; const commands = []; if (typeof comment === "string" && comment.trim()) { commands.push(buildCommentCommand(comment.trim())); } if (multipleCommandsEnabled) { pushCommandListEntries(commandList, commands); } else if (typeof command === "string" && command.trim()) { commands.push(command.trim()); } if (commands.length) { return commands; } } return undefined; } // For ns-* entries, split on newlines so a bundle like // `ns-desktop click 1 2\nns-desktop wait 1` becomes one command per line — // otherwise downstream stringArgv flattens \n to space and the bundle parses // as a single call with garbage args. Shell entries must NOT be split: the // shell wrapper sources multi-line commands as a script, so heredocs and // quoted multi-line strings (e.g. `msg=$(cat <<'EOF' ... EOF)`) need to // arrive intact or each line gets fed to the shell separately and the // heredoc hangs waiting for EOF. function pushCommandListEntries(commandList, commands) { if (!Array.isArray(commandList)) return; for (const entry of commandList) { if (typeof entry !== "string") continue; const trimmed = entry.trim(); if (!trimmed) continue; if (!/^ns-/i.test(trimmed)) { commands.push(trimmed); continue; } for (const line of trimmed.split(/\r?\n/)) { const t = line.trim(); if (t) commands.push(t); } } } function buildCommentCommand(comment) { const escaped = comment .replace(escapedBackslashRegex, "\\\\") .replace(escapedQuoteRegex, '\\"'); return `ns-comment "${escaped}"`; } return { consoleToolOpenAI, consoleToolAnthropic, consoleToolGoogle, getCommandsFromOpenAiToolUse, getCommandsFromAnthropicToolUse, getCommandsFromGoogleToolUse, }; } //# sourceMappingURL=commandTool.js.map