diff --git a/assets/functions/scripts/run-agent.ts b/assets/functions/scripts/run-agent.ts new file mode 100644 index 0000000..425b5df --- /dev/null +++ b/assets/functions/scripts/run-agent.ts @@ -0,0 +1,164 @@ +#!/usr/bin/env tsx + +// Usage: ./{agent_name}.ts + +import { readFileSync, writeFileSync, existsSync } from "fs"; +import { join } from "path"; +import { pathToFileURL } from "url"; + +async function main(): Promise { + const { agentFunc, rawData } = parseArgv(); + const agentData = parseRawData(rawData); + + const configDir = "{config_dir}"; + setupEnv(configDir, agentFunc); + + const agentToolsPath = join(configDir, "agents", "{agent_name}", "tools.ts"); + await run(agentToolsPath, agentFunc, agentData); +} + +function parseRawData(data: string): Record { + if (!data) { + throw new Error("No JSON data"); + } + + try { + return JSON.parse(data); + } catch { + throw new Error("Invalid JSON data"); + } +} + +function parseArgv(): { agentFunc: string; rawData: string } { + const agentFunc = process.argv[2]; + + const toolDataFile = process.env["LLM_TOOL_DATA_FILE"]; + let agentData: string; + if (toolDataFile && existsSync(toolDataFile)) { + agentData = readFileSync(toolDataFile, "utf-8"); + } else { + agentData = process.argv[3]; + } + + if (!agentFunc || !agentData) { + process.stderr.write("Usage: ./{agent_name}.ts \n"); + process.exit(1); + } + + return { agentFunc, rawData: agentData }; +} + +function setupEnv(configDir: string, agentFunc: string): void { + loadEnv(join(configDir, ".env")); + process.env["LLM_ROOT_DIR"] = configDir; + process.env["LLM_AGENT_NAME"] = "{agent_name}"; + process.env["LLM_AGENT_FUNC"] = agentFunc; + process.env["LLM_AGENT_ROOT_DIR"] = join(configDir, "agents", "{agent_name}"); + process.env["LLM_AGENT_CACHE_DIR"] = join(configDir, "cache", "{agent_name}"); +} + +function loadEnv(filePath: string): void { + let lines: string[]; + try { + lines = readFileSync(filePath, "utf-8").split("\n"); + } catch { + return; + } + + for (const raw of lines) { + const line = raw.trim(); + if (line.startsWith("#") || !line) { + continue; + } + + const eqIdx = line.indexOf("="); + if (eqIdx === -1) { + continue; + } + + const key = line.slice(0, eqIdx).trim(); + if (key in process.env) { + continue; + } + + let value = line.slice(eqIdx + 1).trim(); + if ( + (value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'")) + ) { + value = value.slice(1, -1); + } + process.env[key] = value; + } +} + +async function run( + agentPath: string, + agentFunc: string, + agentData: Record, +): Promise { + const mod = await import(pathToFileURL(agentPath).href); + + if (typeof mod[agentFunc] !== "function") { + throw new Error(`No module function '${agentFunc}' at '${agentPath}'`); + } + + const value = await mod[agentFunc](agentData); + returnToLlm(value); + dumpResult(`{agent_name}:${agentFunc}`); +} + +function returnToLlm(value: unknown): void { + if (value === null || value === undefined) { + return; + } + + const output = process.env["LLM_OUTPUT"]; + const write = (s: string) => { + if (output) { + writeFileSync(output, s, "utf-8"); + } else { + process.stdout.write(s); + } + }; + + if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") { + write(String(value)); + } else if (typeof value === "object") { + write(JSON.stringify(value, null, 2)); + } +} + +function dumpResult(name: string): void { + const dumpResults = process.env["LLM_DUMP_RESULTS"]; + const llmOutput = process.env["LLM_OUTPUT"]; + + if (!dumpResults || !llmOutput || !process.stdout.isTTY) { + return; + } + + try { + const pattern = new RegExp(`\\b(${dumpResults})\\b`); + if (!pattern.test(name)) { + return; + } + } catch { + return; + } + + let data: string; + try { + data = readFileSync(llmOutput, "utf-8"); + } catch { + return; + } + + process.stdout.write( + `\x1b[2m----------------------\n${data}\n----------------------\x1b[0m\n`, + ); +} + +main().catch((err) => { + process.stderr.write(`${err}\n`); + process.exit(1); +}); diff --git a/assets/functions/scripts/run-tool.ts b/assets/functions/scripts/run-tool.ts new file mode 100644 index 0000000..f3d2887 --- /dev/null +++ b/assets/functions/scripts/run-tool.ts @@ -0,0 +1,159 @@ +#!/usr/bin/env tsx + +// Usage: ./{function_name}.ts + +import { readFileSync, writeFileSync, existsSync, statSync } from "fs"; +import { join, basename } from "path"; +import { pathToFileURL } from "url"; + +async function main(): Promise { + const rawData = parseArgv(); + const toolData = parseRawData(rawData); + + const rootDir = "{root_dir}"; + setupEnv(rootDir); + + const toolPath = "{tool_path}.ts"; + await run(toolPath, "run", toolData); +} + +function parseRawData(data: string): Record { + if (!data) { + throw new Error("No JSON data"); + } + + try { + return JSON.parse(data); + } catch { + throw new Error("Invalid JSON data"); + } +} + +function parseArgv(): string { + const toolDataFile = process.env["LLM_TOOL_DATA_FILE"]; + if (toolDataFile && existsSync(toolDataFile)) { + return readFileSync(toolDataFile, "utf-8"); + } + + const toolData = process.argv[2]; + + if (!toolData) { + process.stderr.write("Usage: ./{function_name}.ts \n"); + process.exit(1); + } + + return toolData; +} + +function setupEnv(rootDir: string): void { + loadEnv(join(rootDir, ".env")); + process.env["LLM_ROOT_DIR"] = rootDir; + process.env["LLM_TOOL_NAME"] = "{function_name}"; + process.env["LLM_TOOL_CACHE_DIR"] = join(rootDir, "cache", "{function_name}"); +} + +function loadEnv(filePath: string): void { + let lines: string[]; + try { + lines = readFileSync(filePath, "utf-8").split("\n"); + } catch { + return; + } + + for (const raw of lines) { + const line = raw.trim(); + if (line.startsWith("#") || !line) { + continue; + } + + const eqIdx = line.indexOf("="); + if (eqIdx === -1) { + continue; + } + + const key = line.slice(0, eqIdx).trim(); + if (key in process.env) { + continue; + } + + let value = line.slice(eqIdx + 1).trim(); + if ( + (value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'")) + ) { + value = value.slice(1, -1); + } + process.env[key] = value; + } +} + +async function run( + toolPath: string, + toolFunc: string, + toolData: Record, +): Promise { + const mod = await import(pathToFileURL(toolPath).href); + + if (typeof mod[toolFunc] !== "function") { + throw new Error(`No module function '${toolFunc}' at '${toolPath}'`); + } + + const value = await mod[toolFunc](toolData); + returnToLlm(value); + dumpResult("{function_name}"); +} + +function returnToLlm(value: unknown): void { + if (value === null || value === undefined) { + return; + } + + const output = process.env["LLM_OUTPUT"]; + const write = (s: string) => { + if (output) { + writeFileSync(output, s, "utf-8"); + } else { + process.stdout.write(s); + } + }; + + if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") { + write(String(value)); + } else if (typeof value === "object") { + write(JSON.stringify(value, null, 2)); + } +} + +function dumpResult(name: string): void { + const dumpResults = process.env["LLM_DUMP_RESULTS"]; + const llmOutput = process.env["LLM_OUTPUT"]; + + if (!dumpResults || !llmOutput || !process.stdout.isTTY) { + return; + } + + try { + const pattern = new RegExp(`\\b(${dumpResults})\\b`); + if (!pattern.test(name)) { + return; + } + } catch { + return; + } + + let data: string; + try { + data = readFileSync(llmOutput, "utf-8"); + } catch { + return; + } + + process.stdout.write( + `\x1b[2m----------------------\n${data}\n----------------------\x1b[0m\n`, + ); +} + +main().catch((err) => { + process.stderr.write(`${err}\n`); + process.exit(1); +});