UNPKG

@huggingface/tiny-agents

Version:

Lightweight, composable agents for AI applications

282 lines (273 loc) 8.05 kB
#!/usr/bin/env node // src/cli.ts import { dirname, join } from "path"; import { parseArgs } from "util"; import { lstat, readFile } from "fs/promises"; import { z as z2 } from "zod"; import { PROVIDERS_OR_POLICIES } from "@huggingface/inference"; import { Agent } from "@huggingface/mcp-client"; // package.json var version = "0.2.3"; // src/lib/types.ts import { z } from "zod"; var ServerConfigSchema = z.discriminatedUnion("type", [ z.object({ type: z.literal("stdio"), config: z.object({ command: z.string(), args: z.array(z.string()).optional(), env: z.record(z.string()).optional(), cwd: z.string().optional() }) }), z.object({ type: z.literal("http"), config: z.object({ url: z.union([z.string(), z.string().url()]), options: z.object({ /** * Session ID for the connection. This is used to identify the session on the server. * When not provided and connecting to a server that supports session IDs, the server will generate a new session ID. */ sessionId: z.string().optional() }).optional() }) }), z.object({ type: z.literal("sse"), config: z.object({ url: z.union([z.string(), z.string().url()]), options: z.object({}).optional() }) }) ]); // src/lib/utils.ts import { inspect } from "util"; function debug(...args) { if (process.env.DEBUG) { console.debug(inspect(args, { depth: Infinity, colors: true })); } } function error(...args) { console.error(ANSI.RED + args.join("") + ANSI.RESET); } var ANSI = { BLUE: "\x1B[34m", GRAY: "\x1B[90m", GREEN: "\x1B[32m", RED: "\x1B[31m", RESET: "\x1B[0m" }; // src/lib/mainCliLoop.ts import * as readline from "readline/promises"; import { stdin, stdout } from "process"; async function mainCliLoop(agent) { const rl = readline.createInterface({ input: stdin, output: stdout }); let abortController = new AbortController(); let waitingForInput = false; async function waitForInput() { waitingForInput = true; const input = await rl.question("> "); waitingForInput = false; return input; } rl.on("SIGINT", async () => { if (waitingForInput) { await agent.cleanup(); stdout.write("\n"); rl.close(); } else { abortController.abort(); abortController = new AbortController(); stdout.write("\n"); stdout.write(ANSI.GRAY); stdout.write("Ctrl+C a second time to exit"); stdout.write(ANSI.RESET); stdout.write("\n"); } }); process.on("uncaughtException", (err) => { stdout.write("\n"); rl.close(); throw err; }); await agent.loadTools(); stdout.write(ANSI.BLUE); stdout.write(`Agent loaded with ${agent.availableTools.length} tools: `); stdout.write(agent.availableTools.map((t) => `- ${t.function.name}`).join("\n")); stdout.write(ANSI.RESET); stdout.write("\n"); while (true) { const input = await waitForInput(); for await (const chunk of agent.run(input, { abortSignal: abortController.signal })) { if ("choices" in chunk) { const delta = chunk.choices[0]?.delta; if (delta.content) { stdout.write(delta.content); } if (delta.tool_calls) { stdout.write(ANSI.GRAY); for (const deltaToolCall of delta.tool_calls) { if (deltaToolCall.id) { stdout.write(`<Tool ${deltaToolCall.id}> `); } if (deltaToolCall.function.name) { stdout.write(deltaToolCall.function.name + " "); } if (deltaToolCall.function.arguments) { stdout.write(deltaToolCall.function.arguments); } } stdout.write(ANSI.RESET); } } else { stdout.write("\n\n"); stdout.write(ANSI.GREEN); stdout.write(`Tool[${chunk.name}] ${chunk.tool_call_id} `); stdout.write(chunk.content); stdout.write(ANSI.RESET); stdout.write("\n\n"); } } stdout.write("\n"); } } // src/cli.ts var USAGE_HELP = ` Usage: tiny-agents [flags] tiny-agents run "agent/id" tiny-agents serve "agent/id" Available Commands: run Run the Agent in command-line serve Run the Agent as an OpenAI-compatible HTTP server Flags: -h, --help help for tiny-agents -v, --version Show version information `.trim(); var CLI_COMMANDS = ["run", "serve"]; function isValidCommand(command) { return CLI_COMMANDS.includes(command); } var FILENAME_CONFIG = "agent.json"; var FILENAME_PROMPT = "PROMPT.md"; async function loadConfigFrom(loadFrom) { try { return { configJson: await readFile(loadFrom, { encoding: "utf8" }) }; } catch { if ((await lstat(loadFrom)).isDirectory()) { try { let prompt; try { prompt = await readFile(join(loadFrom, FILENAME_PROMPT), { encoding: "utf8" }); } catch { debug(`PROMPT.md not found in ${loadFrom}, continuing without prompt template`); } return { configJson: await readFile(join(loadFrom, FILENAME_CONFIG), { encoding: "utf8" }), prompt }; } catch { error(`Config file not found in specified local directory.`); process.exit(1); } } const srcDir = dirname(__filename); const configDir = join(srcDir, "agents", loadFrom); try { let prompt; try { prompt = await readFile(join(configDir, FILENAME_PROMPT), { encoding: "utf8" }); } catch { debug(`PROMPT.md not found in ${configDir}, continuing without prompt template`); } return { configJson: await readFile(join(configDir, FILENAME_CONFIG), { encoding: "utf8" }), prompt }; } catch { error(`Config file not found in tiny-agents repo! Loading from the HF Hub is not implemented yet`); process.exit(1); } } } async function main() { const { values: { help, version: version2 }, positionals } = parseArgs({ options: { help: { type: "boolean", short: "h" }, version: { type: "boolean", short: "v" } }, allowPositionals: true }); if (version2) { console.log(version); process.exit(0); } const command = positionals[0]; const loadFrom = positionals[1]; if (help) { console.log(USAGE_HELP); process.exit(0); } if (positionals.length !== 2 || !isValidCommand(command)) { error(`You need to call run or serve, followed by an agent id (local path or Hub identifier).`); console.log(USAGE_HELP); process.exit(1); } const { configJson, prompt } = await loadConfigFrom(loadFrom); const ConfigSchema = z2.object({ model: z2.string(), provider: z2.enum(PROVIDERS_OR_POLICIES).optional(), endpointUrl: z2.string().optional(), apiKey: z2.string().optional(), servers: z2.array(ServerConfigSchema) }).refine((data) => data.provider !== void 0 || data.endpointUrl !== void 0, { message: "At least one of 'provider' or 'endpointUrl' is required" }); let config; try { const parsedConfig = JSON.parse(configJson); config = ConfigSchema.parse(parsedConfig); } catch (err) { error("Invalid configuration file:", err instanceof Error ? err.message : err); process.exit(1); } const agent = new Agent( config.endpointUrl ? { endpointUrl: config.endpointUrl, model: config.model, apiKey: config.apiKey ?? process.env.API_KEY ?? process.env.HF_TOKEN, servers: config.servers, prompt } : { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion provider: config.provider, model: config.model, apiKey: config.apiKey ?? process.env.API_KEY ?? process.env.HF_TOKEN, servers: config.servers, prompt } ); if (command === "serve") { error(`Serve is not implemented yet, coming soon!`); process.exit(1); } else { debug(agent); await mainCliLoop(agent); } } main();