@huggingface/tiny-agents
Version:
Lightweight, composable agents for AI applications
282 lines (273 loc) • 8.05 kB
JavaScript
// 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();