naisys
Version:
NAISYS - Autonomous AI agent runner with built-in context management and cost tracking
262 lines • 9.36 kB
JavaScript
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