diff --git a/.github/workflows/tiny-agents-publish.yml b/.github/workflows/tiny-agents-publish.yml new file mode 100644 index 000000000..2a55acde7 --- /dev/null +++ b/.github/workflows/tiny-agents-publish.yml @@ -0,0 +1,77 @@ +name: Tiny Agents - Version and Release + +on: + workflow_dispatch: + inputs: + newversion: + type: choice + description: "Semantic Version Bump Type" + default: patch + options: + - patch + - minor + - major + bypass_deps_check: + type: boolean + description: "Bypass dependency checking" + default: false + +concurrency: + group: "push-to-main" # Consider changing this if tiny-agents has its own release concurrency group + +defaults: + run: + working-directory: packages/tiny-agents + +jobs: + version_and_release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + token: ${{ secrets.BOT_ACCESS_TOKEN }} + - run: npm install -g corepack@latest && corepack enable + - uses: actions/setup-node@v3 + with: + node-version: "20" + cache: "pnpm" + cache-dependency-path: | + packages/tiny-agents/pnpm-lock.yaml + packages/doc-internal/pnpm-lock.yaml + registry-url: "https://registry.npmjs.org" + - run: pnpm install + - run: git config --global user.name machineuser + - run: git config --global user.email infra+machineuser@huggingface.co + - run: | + PACKAGE_VERSION=$(node -p "require('./package.json').version") + BUMPED_VERSION=$(node -p "require('semver').inc('$PACKAGE_VERSION', '${{ github.event.inputs.newversion }}')") + # Update package.json with the new version + node -e "const fs = require('fs'); const package = JSON.parse(fs.readFileSync('./package.json')); package.version = '$BUMPED_VERSION'; fs.writeFileSync('./package.json', JSON.stringify(package, null, '\t') + '\n');" + pnpm --filter doc-internal run fix-cdn-versions + git add ../.. + git commit -m "🔖 @huggingface/tiny-agents $BUMPED_VERSION" + git tag "tiny-agents-v$BUMPED_VERSION" + + # Add checks for dependencies if needed, similar to hub-publish.yml + - if: ${{ !github.event.inputs.bypass_deps_check }} + name: "Check Deps are published before publishing this package" + run: pnpm -w check-deps inference && pnpm -w check-deps tasks # Review if these specific deps apply to tiny-agents + + - run: pnpm publish --no-git-checks . + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + - run: (git pull --rebase && git push --follow-tags) || (git pull --rebase && git push --follow-tags) + # hack - reuse actions/setup-node@v3 just to set a new registry + - uses: actions/setup-node@v3 + with: + node-version: "20" + registry-url: "https://npm.pkg.github.com" + # Disable for now, until github supports PATs for writing github packages (https://github.com/github/roadmap/issues/558) + # - run: pnpm publish --no-git-checks . + # env: + # NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: "Update Doc" + uses: peter-evans/repository-dispatch@v2 + with: + event-type: doc-build + token: ${{ secrets.BOT_ACCESS_TOKEN }} diff --git a/README.md b/README.md index 27ae266ec..624811331 100644 --- a/README.md +++ b/README.md @@ -241,8 +241,8 @@ const agent = new Agent({ ], }); - await agent.loadTools(); + for await (const chunk of agent.run("What are the top 5 trending models on Hugging Face?")) { if ("choices" in chunk) { const delta = chunk.choices[0]?.delta; diff --git a/packages/mcp-client/src/index.ts b/packages/mcp-client/src/index.ts index 1803f7bec..c582e121d 100644 --- a/packages/mcp-client/src/index.ts +++ b/packages/mcp-client/src/index.ts @@ -1,3 +1,4 @@ export * from "./McpClient"; export * from "./Agent"; export type { ChatCompletionInputMessageTool } from "./McpClient"; +export type { ServerConfig } from "./types"; diff --git a/packages/tiny-agents/.eslintignore b/packages/tiny-agents/.eslintignore new file mode 100644 index 000000000..53c37a166 --- /dev/null +++ b/packages/tiny-agents/.eslintignore @@ -0,0 +1 @@ +dist \ No newline at end of file diff --git a/packages/tiny-agents/.prettierignore b/packages/tiny-agents/.prettierignore new file mode 100644 index 000000000..cac0c6949 --- /dev/null +++ b/packages/tiny-agents/.prettierignore @@ -0,0 +1,4 @@ +pnpm-lock.yaml +# In order to avoid code samples to have tabs, they don't display well on npm +README.md +dist \ No newline at end of file diff --git a/packages/tiny-agents/README.md b/packages/tiny-agents/README.md new file mode 100644 index 000000000..e22fc3b41 --- /dev/null +++ b/packages/tiny-agents/README.md @@ -0,0 +1,110 @@ +# @huggingface/tiny-agents + +![meme](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/tiny-agents/legos.png) + +A squad of lightweight composable AI applications built on Hugging Face's Inference Client and MCP stack. + +## Installation + +```bash +npm install @huggingface/tiny-agents +# or +pnpm add @huggingface/tiny-agents +``` + +## CLI Usage + +```bash +npx @huggingface/tiny-agents [command] "agent/id" + +``` + +``` +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 +``` + +## Define your own agent + +The simplest way to create your own agent is to create a folder containing an `agent.json` file: + +```bash +mkdir my-agent +touch my-agent/agent.json +``` + +```json +{ + "model": "Qwen/Qwen2.5-72B-Instruct", // model id + "provider": "nebius", // or you can also use a local endpoint base url with `endpointUrl` + "servers": [ + { + "type": "stdio", + "config": { + "command": "npx", + "args": ["@playwright/mcp@latest"] + } + } + ] +} +``` + +Where `servers` is a list of MCP servers (we support Stdio, SSE, and HTTP servers). + +Optionally, you can add a `PROMPT.md` file to override the default Agent prompt. + +Then just point tiny-agents to your local folder: + +```bash +npx @huggingface/tiny-agents run ./my-agent +``` + +Voilà! 🔥 + +> [!NOTE] +> Note: you can open a PR in the huggingface.js repo to share your agent with the community, just upload it inside the `src/agents/` directory. + +### Advanced: Programmatic Usage + +```typescript +import { Agent } from '@huggingface/tiny-agents'; + +const HF_TOKEN = "hf_..."; + +// Create an Agent +const agent = new Agent({ + provider: "auto", + model: "Qwen/Qwen2.5-72B-Instruct", + apiKey: HF_TOKEN, + servers: [ + { + // Playwright MCP + command: "npx", + args: ["@playwright/mcp@latest"], + }, + ], +}); + +await agent.loadTools(); + +// Use the Agent +for await (const chunk of agent.run("What are the top 5 trending models on Hugging Face?")) { + if ("choices" in chunk) { + const delta = chunk.choices[0]?.delta; + if (delta.content) { + console.log(delta.content); + } + } +} +``` + + +## License + +MIT diff --git a/packages/tiny-agents/package.json b/packages/tiny-agents/package.json new file mode 100644 index 000000000..432cb2a75 --- /dev/null +++ b/packages/tiny-agents/package.json @@ -0,0 +1,60 @@ +{ + "name": "@huggingface/tiny-agents", + "packageManager": "pnpm@10.10.0", + "version": "0.1.0", + "description": "Lightweight, composable agents for AI applications", + "repository": "https://github.com/huggingface/huggingface.js.git", + "publishConfig": { + "access": "public" + }, + "main": "./dist/index.js", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "bin": { + "tiny-agents": "./dist/cli.js" + }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "require": "./dist/index.js", + "import": "./dist/index.mjs" + } + }, + "engines": { + "node": ">=18" + }, + "source": "index.ts", + "scripts": { + "lint": "eslint --quiet --fix --ext .cjs,.ts .", + "lint:check": "eslint --ext .cjs,.ts .", + "format": "prettier --write .", + "format:check": "prettier --check .", + "prepublishOnly": "pnpm run build", + "build": "tsup src/index.ts --format cjs,esm --clean && tsc --emitDeclarationOnly --declaration", + "prepare": "pnpm run build", + "test": "vitest run", + "check": "tsc", + "cli": "tsx src/cli.ts" + }, + "files": [ + "src", + "dist", + "tsconfig.json" + ], + "keywords": [ + "huggingface", + "agent", + "ai", + "llm", + "tiny-agent" + ], + "author": "Hugging Face", + "license": "MIT", + "dependencies": { + "@huggingface/inference": "workspace:^", + "@huggingface/mcp-client": "workspace:^", + "@huggingface/tasks": "workspace:^", + "@modelcontextprotocol/sdk": "^1.11.4", + "zod": "^3.25.7" + } +} diff --git a/packages/tiny-agents/pnpm-lock.yaml b/packages/tiny-agents/pnpm-lock.yaml new file mode 100644 index 000000000..3e9aca9ca --- /dev/null +++ b/packages/tiny-agents/pnpm-lock.yaml @@ -0,0 +1,736 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@huggingface/inference': + specifier: workspace:^ + version: link:../inference + '@huggingface/mcp-client': + specifier: workspace:^ + version: link:../mcp-client + '@huggingface/tasks': + specifier: workspace:^ + version: link:../tasks + '@modelcontextprotocol/sdk': + specifier: ^1.11.4 + version: 1.11.4 + zod: + specifier: ^3.25.7 + version: 3.25.7 + +packages: + + '@modelcontextprotocol/sdk@1.11.4': + resolution: {integrity: sha512-OTbhe5slIjiOtLxXhKalkKGhIQrwvhgCDs/C2r8kcBTy5HR/g43aDQU0l7r8O0VGbJPTNJvDc7ZdQMdQDJXmbw==} + engines: {node: '>=18'} + + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + + ajv@8.17.1: + resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + + body-parser@2.2.0: + resolution: {integrity: sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==} + engines: {node: '>=18'} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + content-disposition@1.0.0: + resolution: {integrity: sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==} + engines: {node: '>= 0.6'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + cors@2.8.5: + resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} + engines: {node: '>= 0.10'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + debug@4.4.1: + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + eventsource-parser@3.0.2: + resolution: {integrity: sha512-6RxOBZ/cYgd8usLwsEl+EC09Au/9BcmCKYF2/xbml6DNczf7nv0MQb+7BA2F+li6//I+28VNlQR37XfQtcAJuA==} + engines: {node: '>=18.0.0'} + + eventsource@3.0.7: + resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} + engines: {node: '>=18.0.0'} + + express-rate-limit@7.5.0: + resolution: {integrity: sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==} + engines: {node: '>= 16'} + peerDependencies: + express: ^4.11 || 5 || ^5.0.0-beta.1 + + express@5.1.0: + resolution: {integrity: sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==} + engines: {node: '>= 18'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-uri@3.0.6: + resolution: {integrity: sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==} + + finalhandler@2.1.0: + resolution: {integrity: sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==} + engines: {node: '>= 0.8'} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@3.0.1: + resolution: {integrity: sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==} + engines: {node: '>= 0.6'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-to-regexp@8.2.0: + resolution: {integrity: sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==} + engines: {node: '>=16'} + + pkce-challenge@5.0.0: + resolution: {integrity: sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==} + engines: {node: '>=16.20.0'} + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + qs@6.14.0: + resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} + engines: {node: '>=0.6'} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@3.0.0: + resolution: {integrity: sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==} + engines: {node: '>= 0.8'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + send@1.2.0: + resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==} + engines: {node: '>= 18'} + + serve-static@2.2.0: + resolution: {integrity: sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==} + engines: {node: '>= 18'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + type-is@2.0.1: + resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} + engines: {node: '>= 0.6'} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + zod-to-json-schema@3.24.5: + resolution: {integrity: sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==} + peerDependencies: + zod: ^3.24.1 + + zod@3.25.7: + resolution: {integrity: sha512-YGdT1cVRmKkOg6Sq7vY7IkxdphySKnXhaUmFI4r4FcuFVNgpCb9tZfNwXbT6BPjD5oz0nubFsoo9pIqKrDcCvg==} + +snapshots: + + '@modelcontextprotocol/sdk@1.11.4': + dependencies: + ajv: 8.17.1 + content-type: 1.0.5 + cors: 2.8.5 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + express: 5.1.0 + express-rate-limit: 7.5.0(express@5.1.0) + pkce-challenge: 5.0.0 + raw-body: 3.0.0 + zod: 3.25.7 + zod-to-json-schema: 3.24.5(zod@3.25.7) + transitivePeerDependencies: + - supports-color + + accepts@2.0.0: + dependencies: + mime-types: 3.0.1 + negotiator: 1.0.0 + + ajv@8.17.1: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.0.6 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + body-parser@2.2.0: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 4.4.1 + http-errors: 2.0.0 + iconv-lite: 0.6.3 + on-finished: 2.4.1 + qs: 6.14.0 + raw-body: 3.0.0 + type-is: 2.0.1 + transitivePeerDependencies: + - supports-color + + bytes@3.1.2: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + content-disposition@1.0.0: + dependencies: + safe-buffer: 5.2.1 + + content-type@1.0.5: {} + + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + + cors@2.8.5: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + debug@4.4.1: + dependencies: + ms: 2.1.3 + + depd@2.0.0: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + ee-first@1.1.1: {} + + encodeurl@2.0.0: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + escape-html@1.0.3: {} + + etag@1.8.1: {} + + eventsource-parser@3.0.2: {} + + eventsource@3.0.7: + dependencies: + eventsource-parser: 3.0.2 + + express-rate-limit@7.5.0(express@5.1.0): + dependencies: + express: 5.1.0 + + express@5.1.0: + dependencies: + accepts: 2.0.0 + body-parser: 2.2.0 + content-disposition: 1.0.0 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.1 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.0 + fresh: 2.0.0 + http-errors: 2.0.0 + merge-descriptors: 2.0.0 + mime-types: 3.0.1 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.14.0 + range-parser: 1.2.1 + router: 2.2.0 + send: 1.2.0 + serve-static: 2.2.0 + statuses: 2.0.1 + type-is: 2.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + fast-deep-equal@3.1.3: {} + + fast-uri@3.0.6: {} + + finalhandler@2.1.0: + dependencies: + debug: 4.4.1 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + + forwarded@0.2.0: {} + + fresh@2.0.0: {} + + function-bind@1.1.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + gopd@1.2.0: {} + + has-symbols@1.1.0: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + http-errors@2.0.0: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + inherits@2.0.4: {} + + ipaddr.js@1.9.1: {} + + is-promise@4.0.0: {} + + isexe@2.0.0: {} + + json-schema-traverse@1.0.0: {} + + math-intrinsics@1.1.0: {} + + media-typer@1.1.0: {} + + merge-descriptors@2.0.0: {} + + mime-db@1.54.0: {} + + mime-types@3.0.1: + dependencies: + mime-db: 1.54.0 + + ms@2.1.3: {} + + negotiator@1.0.0: {} + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + parseurl@1.3.3: {} + + path-key@3.1.1: {} + + path-to-regexp@8.2.0: {} + + pkce-challenge@5.0.0: {} + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + qs@6.14.0: + dependencies: + side-channel: 1.1.0 + + range-parser@1.2.1: {} + + raw-body@3.0.0: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.6.3 + unpipe: 1.0.0 + + require-from-string@2.0.2: {} + + router@2.2.0: + dependencies: + debug: 4.4.1 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.2.0 + transitivePeerDependencies: + - supports-color + + safe-buffer@5.2.1: {} + + safer-buffer@2.1.2: {} + + send@1.2.0: + dependencies: + debug: 4.4.1 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.0 + mime-types: 3.0.1 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + + serve-static@2.2.0: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.0 + transitivePeerDependencies: + - supports-color + + setprototypeof@1.2.0: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + statuses@2.0.1: {} + + toidentifier@1.0.1: {} + + type-is@2.0.1: + dependencies: + content-type: 1.0.5 + media-typer: 1.1.0 + mime-types: 3.0.1 + + unpipe@1.0.0: {} + + vary@1.1.2: {} + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + wrappy@1.0.2: {} + + zod-to-json-schema@3.24.5(zod@3.25.7): + dependencies: + zod: 3.25.7 + + zod@3.25.7: {} diff --git a/packages/tiny-agents/src/agents/julien-c/flux-schnell-generator/agent.json b/packages/tiny-agents/src/agents/julien-c/flux-schnell-generator/agent.json new file mode 100644 index 000000000..ba78c614d --- /dev/null +++ b/packages/tiny-agents/src/agents/julien-c/flux-schnell-generator/agent.json @@ -0,0 +1,12 @@ +{ + "model": "Qwen/Qwen3-32B", + "provider": "novita", + "servers": [ + { + "type": "sse", + "config": { + "url": "https://evalstate-flux1-schnell.hf.space/gradio_api/mcp/sse" + } + } + ] +} diff --git a/packages/tiny-agents/src/agents/julien-c/local-coder/PROMPT.md b/packages/tiny-agents/src/agents/julien-c/local-coder/PROMPT.md new file mode 100644 index 000000000..e880614bf --- /dev/null +++ b/packages/tiny-agents/src/agents/julien-c/local-coder/PROMPT.md @@ -0,0 +1,5 @@ +You are an agent - please keep going until the user’s query is completely resolved, before ending your turn and yielding back to the user. Only terminate your turn when you are sure that the problem is solved, or if you need more info from the user to solve the problem. + +If you are not sure about anything pertaining to the user’s request, use your tools to read files and gather the relevant information: do NOT guess or make up an answer. + +You MUST plan extensively before each function call, and reflect extensively on the outcomes of the previous function calls. DO NOT do this entire process by making function calls only, as this can impair your ability to solve the problem and think insightfully. diff --git a/packages/tiny-agents/src/agents/julien-c/local-coder/agent.json b/packages/tiny-agents/src/agents/julien-c/local-coder/agent.json new file mode 100644 index 000000000..31a926839 --- /dev/null +++ b/packages/tiny-agents/src/agents/julien-c/local-coder/agent.json @@ -0,0 +1,13 @@ +{ + "model": "Qwen/Qwen2.5-72B-Instruct", + "provider": "nebius", + "servers": [ + { + "type": "stdio", + "config": { + "command": "npx", + "args": ["@playwright/mcp@latest"] + } + } + ] +} diff --git a/packages/tiny-agents/src/cli.ts b/packages/tiny-agents/src/cli.ts new file mode 100644 index 000000000..d33fcb507 --- /dev/null +++ b/packages/tiny-agents/src/cli.ts @@ -0,0 +1,150 @@ +#!/usr/bin/env node +import { dirname, join } from "node:path"; +import { parseArgs } from "node:util"; +import { lstat, readFile } from "node:fs/promises"; +import { z } from "zod"; +import { PROVIDERS_OR_POLICIES } from "@huggingface/inference"; +import { Agent } from "@huggingface/mcp-client"; +import { version as packageVersion } from "../package.json"; +import { ServerConfigSchema } from "./lib/types"; +import { debug, error } from "./lib/utils"; +import { mainCliLoop } from "./lib/mainCliLoop"; + +const 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(); + +const CLI_COMMANDS = ["run", "serve"] as const; +function isValidCommand(command: string): command is (typeof CLI_COMMANDS)[number] { + return (CLI_COMMANDS as unknown as string[]).includes(command); +} + +const FILENAME_CONFIG = "agent.json"; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const FILENAME_PROMPT = "PROMPT.md"; + +async function loadConfigFrom(loadFrom: string): Promise<{ configJson: string; prompt?: string }> { + try { + /// First try it as a local file path, then as a local directory, then we will try as a path inside the repo itself + return { + configJson: await readFile(loadFrom, { encoding: "utf8" }), + }; + } catch { + if ((await lstat(loadFrom)).isDirectory()) { + /// local directory + try { + let prompt: string | undefined; + 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: string | undefined; + 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 }, + positionals, + } = parseArgs({ + options: { + help: { + type: "boolean", + short: "h", + }, + version: { + type: "boolean", + short: "v", + }, + }, + allowPositionals: true, + }); + if (version) { + console.log(packageVersion); + 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 = z.object({ + model: z.string(), + provider: z.enum(PROVIDERS_OR_POLICIES), + servers: z.array(ServerConfigSchema), + }); + + let config: z.infer; + 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({ + provider: config.provider, + model: config.model, + apiKey: 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); + // main loop from mcp-client/cli.ts + await mainCliLoop(agent); + } +} + +main(); diff --git a/packages/tiny-agents/src/index.ts b/packages/tiny-agents/src/index.ts new file mode 100644 index 000000000..4f4ef44e7 --- /dev/null +++ b/packages/tiny-agents/src/index.ts @@ -0,0 +1 @@ +export { Agent } from "@huggingface/mcp-client"; diff --git a/packages/tiny-agents/src/lib/mainCliLoop.ts b/packages/tiny-agents/src/lib/mainCliLoop.ts new file mode 100644 index 000000000..cdc20056c --- /dev/null +++ b/packages/tiny-agents/src/lib/mainCliLoop.ts @@ -0,0 +1,86 @@ +import * as readline from "node:readline/promises"; +import { stdin, stdout } from "node:process"; +import { ANSI } from "./utils"; +import type { ChatCompletionStreamOutput } from "@huggingface/tasks"; +import type { Agent } from "../index"; + +/** + * From mcp-client/cli.ts + */ +export async function mainCliLoop(agent: Agent): Promise { + 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) { + // close the whole process + await agent.cleanup(); + stdout.write("\n"); + rl.close(); + } else { + // otherwise, it means a request is underway + 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:\n`); + 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 as ChatCompletionStreamOutput).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(`\n`); + } + if (deltaToolCall.function.name) { + stdout.write(deltaToolCall.function.name + " "); + } + if (deltaToolCall.function.arguments) { + stdout.write(deltaToolCall.function.arguments); + } + } + stdout.write(ANSI.RESET); + } + } else { + /// Tool call info + stdout.write("\n\n"); + stdout.write(ANSI.GREEN); + stdout.write(`Tool[${chunk.name}] ${chunk.tool_call_id}\n`); + stdout.write(chunk.content); + stdout.write(ANSI.RESET); + stdout.write("\n\n"); + } + } + stdout.write("\n"); + } +} diff --git a/packages/tiny-agents/src/lib/types.ts b/packages/tiny-agents/src/lib/types.ts new file mode 100644 index 000000000..6c3d7bce8 --- /dev/null +++ b/packages/tiny-agents/src/lib/types.ts @@ -0,0 +1,37 @@ +import { z } from "zod"; + +export const 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(), + }), + }), +]); + +export type ServerConfig = z.infer; diff --git a/packages/tiny-agents/src/lib/utils.ts b/packages/tiny-agents/src/lib/utils.ts new file mode 100644 index 000000000..069aa7571 --- /dev/null +++ b/packages/tiny-agents/src/lib/utils.ts @@ -0,0 +1,19 @@ +import { inspect } from "util"; + +export function debug(...args: unknown[]): void { + if (process.env.DEBUG) { + console.debug(inspect(args, { depth: Infinity, colors: true })); + } +} + +export function error(...args: unknown[]): void { + console.error(ANSI.RED + args.join("") + ANSI.RESET); +} + +export const ANSI = { + BLUE: "\x1b[34m", + GRAY: "\x1b[90m", + GREEN: "\x1b[32m", + RED: "\x1b[31m", + RESET: "\x1b[0m", +}; diff --git a/packages/tiny-agents/test/ServerConfigSchema.spec.ts b/packages/tiny-agents/test/ServerConfigSchema.spec.ts new file mode 100644 index 000000000..f7455e40d --- /dev/null +++ b/packages/tiny-agents/test/ServerConfigSchema.spec.ts @@ -0,0 +1,16 @@ +import { describe, expect, it } from "vitest"; +import { ServerConfigSchema } from "../src/lib/types"; + +describe("ServerConfigSchema", () => { + it("You can parse a server config", async () => { + const config = ServerConfigSchema.parse({ + type: "stdio", + config: { + command: "npx", + args: ["@playwright/mcp@latest"], + }, + }); + expect(config).toBeDefined(); + expect(config.type).toBe("stdio"); + }); +}); diff --git a/packages/tiny-agents/tsconfig.json b/packages/tiny-agents/tsconfig.json new file mode 100644 index 000000000..8274efe5c --- /dev/null +++ b/packages/tiny-agents/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "lib": ["ES2022", "DOM"], + "module": "CommonJS", + "moduleResolution": "node", + "target": "ES2022", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "skipLibCheck": true, + "noImplicitOverride": true, + "outDir": "./dist", + "declaration": true, + "declarationMap": true, + "resolveJsonModule": true + }, + "include": ["src", "test"], + "exclude": ["dist"] +} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 94ca7a932..08e651bb7 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -13,3 +13,4 @@ packages: - "packages/space-header" - "packages/ollama-utils" - "packages/mcp-client" + - "packages/tiny-agents"