diff --git a/apps/cli/.gitignore b/apps/cli/.gitignore index 04c01ba7..21795603 100644 --- a/apps/cli/.gitignore +++ b/apps/cli/.gitignore @@ -1,2 +1,3 @@ node_modules/ -dist/ \ No newline at end of file +dist/ +.vercel diff --git a/apps/cli/bin/app.js b/apps/cli/bin/app.js index 3eeace50..b865bc95 100755 --- a/apps/cli/bin/app.js +++ b/apps/cli/bin/app.js @@ -116,20 +116,4 @@ yargs(hideBin(process.argv)) modelConfig(); } ) - .command( - "update-state ", - "Update state for a run", - (y) => y - .positional("agent", { - type: "string", - description: "The agent to run", - }) - .positional("run_id", { - type: "string", - description: "The run id to update", - }), - (argv) => { - updateState(argv.agent, argv.run_id); - } - ) .parse(); diff --git a/apps/cli/package-lock.json b/apps/cli/package-lock.json index a5dcf009..90c2818a 100644 --- a/apps/cli/package-lock.json +++ b/apps/cli/package-lock.json @@ -1,12 +1,12 @@ { "name": "@rowboatlabs/rowboatx", - "version": "0.15.0", + "version": "0.16.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@rowboatlabs/rowboatx", - "version": "0.15.0", + "version": "0.16.0", "license": "Apache-2.0", "dependencies": { "@ai-sdk/anthropic": "^2.0.44", @@ -14,9 +14,14 @@ "@ai-sdk/openai": "^2.0.53", "@ai-sdk/openai-compatible": "^1.0.27", "@ai-sdk/provider": "^2.0.0", + "@hono/node-server": "^1.19.6", + "@hono/standard-validator": "^0.1.5", "@modelcontextprotocol/sdk": "^1.20.2", "@openrouter/ai-sdk-provider": "^1.2.6", "ai": "^5.0.102", + "awilix": "^12.0.5", + "hono": "^4.10.7", + "hono-openapi": "^1.1.1", "json-schema-to-zod": "^2.6.1", "nanoid": "^5.1.6", "ollama-ai-provider-v2": "^1.5.4", @@ -28,35 +33,17 @@ }, "devDependencies": { "@types/node": "^24.9.1", - "ts-node": "^10.9.2", "typescript": "^5.9.3" } }, "node_modules/@ai-sdk/anthropic": { - "version": "2.0.44", - "resolved": "https://registry.npmjs.org/@ai-sdk/anthropic/-/anthropic-2.0.44.tgz", - "integrity": "sha512-o8TfNXRzO/KZkBrcx+CL9LQsPhx7PHyqzUGjza3TJaF9WxfH1S5UQLAmEw8F7lQoHNLU0IX03WT8o8R/4JbUxQ==", + "version": "2.0.53", + "resolved": "https://registry.npmjs.org/@ai-sdk/anthropic/-/anthropic-2.0.53.tgz", + "integrity": "sha512-ih7NV+OFSNWZCF+tYYD7ovvvM+gv7TRKQblpVohg2ipIwC9Y0TirzocJVREzZa/v9luxUwFbsPji++DUDWWxsg==", "license": "Apache-2.0", "dependencies": { "@ai-sdk/provider": "2.0.0", - "@ai-sdk/provider-utils": "3.0.17" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, - "node_modules/@ai-sdk/anthropic/node_modules/@ai-sdk/provider-utils": { - "version": "3.0.17", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.17.tgz", - "integrity": "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "2.0.0", - "@standard-schema/spec": "^1.0.0", - "eventsource-parser": "^3.0.6" + "@ai-sdk/provider-utils": "3.0.18" }, "engines": { "node": ">=18" @@ -66,13 +53,13 @@ } }, "node_modules/@ai-sdk/gateway": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-2.0.15.tgz", - "integrity": "sha512-i1YVKzC1dg9LGvt+GthhD7NlRhz9J4+ZRj3KELU14IZ/MHPsOBiFeEoCCIDLR+3tqT8/+5nIsK3eZ7DFRfMfdw==", + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-2.0.18.tgz", + "integrity": "sha512-sDQcW+6ck2m0pTIHW6BPHD7S125WD3qNkx/B8sEzJp/hurocmJ5Cni0ybExg6sQMGo+fr/GWOwpHF1cmCdg5rQ==", "license": "Apache-2.0", "dependencies": { "@ai-sdk/provider": "2.0.0", - "@ai-sdk/provider-utils": "3.0.17", + "@ai-sdk/provider-utils": "3.0.18", "@vercel/oidc": "3.0.5" }, "engines": { @@ -82,48 +69,14 @@ "zod": "^3.25.76 || ^4.1.8" } }, - "node_modules/@ai-sdk/gateway/node_modules/@ai-sdk/provider-utils": { - "version": "3.0.17", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.17.tgz", - "integrity": "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "2.0.0", - "@standard-schema/spec": "^1.0.0", - "eventsource-parser": "^3.0.6" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, "node_modules/@ai-sdk/google": { - "version": "2.0.25", - "resolved": "https://registry.npmjs.org/@ai-sdk/google/-/google-2.0.25.tgz", - "integrity": "sha512-tH2rA3428jnY6COoPfKB/BoQMs57sv9t+PEdyIB9ePtlV9dnVUbfKcdKoEcAaVffNZ6pzk8otrQYnu67pyn8TQ==", + "version": "2.0.44", + "resolved": "https://registry.npmjs.org/@ai-sdk/google/-/google-2.0.44.tgz", + "integrity": "sha512-c5dck36FjqiVoeeMJQLTEmUheoURcGTU/nBT6iJu8/nZiKFT/y8pD85KMDRB7RerRYaaQOtslR2d6/5PditiRw==", "license": "Apache-2.0", "dependencies": { "@ai-sdk/provider": "2.0.0", - "@ai-sdk/provider-utils": "3.0.14" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, - "node_modules/@ai-sdk/google/node_modules/@ai-sdk/provider-utils": { - "version": "3.0.14", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.14.tgz", - "integrity": "sha512-CYRU6L7IlR7KslSBVxvlqlybQvXJln/PI57O8swhOaDIURZbjRP2AY3igKgUsrmWqqnFFUHP+AwTN8xqJAknnA==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "2.0.0", - "@standard-schema/spec": "^1.0.0", - "eventsource-parser": "^3.0.5" + "@ai-sdk/provider-utils": "3.0.18" }, "engines": { "node": ">=18" @@ -133,13 +86,13 @@ } }, "node_modules/@ai-sdk/openai": { - "version": "2.0.53", - "resolved": "https://registry.npmjs.org/@ai-sdk/openai/-/openai-2.0.53.tgz", - "integrity": "sha512-GIkR3+Fyif516ftXv+YPSPstnAHhcZxNoR2s8uSHhQ1yBT7I7aQYTVwpjAuYoT3GR+TeP50q7onj2/nDRbT2FQ==", + "version": "2.0.76", + "resolved": "https://registry.npmjs.org/@ai-sdk/openai/-/openai-2.0.76.tgz", + "integrity": "sha512-ryUkhTDVxe3D1GSAGc94vPZsJlSY8ZuBDLkpf4L81Dm7Ik5AgLfhQrZa8+0hD4kp0dxdVaIoxhpa3QOt1CmncA==", "license": "Apache-2.0", "dependencies": { "@ai-sdk/provider": "2.0.0", - "@ai-sdk/provider-utils": "3.0.12" + "@ai-sdk/provider-utils": "3.0.18" }, "engines": { "node": ">=18" @@ -149,30 +102,13 @@ } }, "node_modules/@ai-sdk/openai-compatible": { - "version": "1.0.27", - "resolved": "https://registry.npmjs.org/@ai-sdk/openai-compatible/-/openai-compatible-1.0.27.tgz", - "integrity": "sha512-bpYruxVLhrTbVH6CCq48zMJNeHu6FmHtEedl9FXckEgcIEAi036idFhJlcRwC1jNCwlacbzb8dPD7OAH1EKJaQ==", + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/@ai-sdk/openai-compatible/-/openai-compatible-1.0.28.tgz", + "integrity": "sha512-yKubDxLYtXyGUzkr9lNStf/lE/I+Okc8tmotvyABhsQHHieLKk6oV5fJeRJxhr67Ejhg+FRnwUOxAmjRoFM4dA==", "license": "Apache-2.0", "dependencies": { "@ai-sdk/provider": "2.0.0", - "@ai-sdk/provider-utils": "3.0.17" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, - "node_modules/@ai-sdk/openai-compatible/node_modules/@ai-sdk/provider-utils": { - "version": "3.0.17", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.17.tgz", - "integrity": "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "2.0.0", - "@standard-schema/spec": "^1.0.0", - "eventsource-parser": "^3.0.6" + "@ai-sdk/provider-utils": "3.0.18" }, "engines": { "node": ">=18" @@ -194,14 +130,14 @@ } }, "node_modules/@ai-sdk/provider-utils": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.12.tgz", - "integrity": "sha512-ZtbdvYxdMoria+2SlNarEk6Hlgyf+zzcznlD55EAl+7VZvJaSg2sqPvwArY7L6TfDEDJsnCq0fdhBSkYo0Xqdg==", + "version": "3.0.18", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.18.tgz", + "integrity": "sha512-ypv1xXMsgGcNKUP+hglKqtdDuMg68nWHucPPAhIENrbFAI+xCHiqPVN8Zllxyv1TNZwGWUghPxJXU+Mqps0YRQ==", "license": "Apache-2.0", "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", - "eventsource-parser": "^3.0.5" + "eventsource-parser": "^3.0.6" }, "engines": { "node": ">=18" @@ -210,54 +146,36 @@ "zod": "^3.25.76 || ^4.1.8" } }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, + "node_modules/@hono/node-server": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.6.tgz", + "integrity": "sha512-Shz/KjlIeAhfiuE93NDKVdZ7HdBVLQAfdbaXEaoAVO3ic9ibRSLGIQGkcBbFyuLr+7/1D5ZCINM8B+6IvXeMtw==", "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" + "engines": { + "node": ">=18.14.1" }, - "engines": { - "node": ">=12" + "peerDependencies": { + "hono": "^4" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, + "node_modules/@hono/standard-validator": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@hono/standard-validator/-/standard-validator-0.1.5.tgz", + "integrity": "sha512-EIyZPPwkyLn6XKwFj5NBEWHXhXbgmnVh2ceIFo5GO7gKI9WmzTjPDKnppQB0KrqKeAkq3kpoW4SIbu5X1dgx3w==", "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "peerDependencies": { + "@standard-schema/spec": "1.0.0", + "hono": ">=3.9.0" } }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.20.2.tgz", - "integrity": "sha512-6rqTdFt67AAAzln3NOKsXRmv5ZzPkgbfaebKBqUbts7vK1GZudqnrun5a8d3M/h955cam9RHZ6Jb4Y1XhnmFPg==", + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.24.1.tgz", + "integrity": "sha512-YTg4v6bKSst8EJM8NXHC3nGm8kgHD08IbIBbognUeLAgGLVgLpYrgQswzLQd4OyTL4l614ejhqsDrV1//t02Qw==", "license": "MIT", "dependencies": { - "ajv": "^6.12.6", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", @@ -265,37 +183,67 @@ "eventsource-parser": "^3.0.0", "express": "^5.0.1", "express-rate-limit": "^7.5.0", + "jose": "^6.1.1", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", - "zod": "^3.23.8", - "zod-to-json-schema": "^3.24.1" + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.0" }, "engines": { "node": ">=18" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/zod-to-json-schema": { - "version": "3.24.6", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", - "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", - "license": "ISC", + }, "peerDependencies": { - "zod": "^3.24.1" + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" } }, "node_modules/@openrouter/ai-sdk-provider": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@openrouter/ai-sdk-provider/-/ai-sdk-provider-1.2.6.tgz", - "integrity": "sha512-DExO4FXod5vEdLFpQGsyNva8u3FWHj2IPaP8to+zEGsBEUY7lu5t24uIMxmmLKZ0sYYWAtmTLSV4Y9uOVqQoAg==", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@openrouter/ai-sdk-provider/-/ai-sdk-provider-1.2.8.tgz", + "integrity": "sha512-pQT8AzZBKg9f4bkt4doF486ZlhK0XjKkevrLkiqYgfh1Jplovieu28nK4Y+xy3sF18/mxjqh9/2y6jh01qzLrA==", "license": "Apache-2.0", "dependencies": { "@openrouter/sdk": "^0.1.8" @@ -309,28 +257,12 @@ } }, "node_modules/@openrouter/sdk": { - "version": "0.1.17", - "resolved": "https://registry.npmjs.org/@openrouter/sdk/-/sdk-0.1.17.tgz", - "integrity": "sha512-RFN0sfe83G85MirfpkZSuoX8hLLucemnwqrTr53vlrJmBJZ244CCnuZ33vpVUI8rLg+hP1i/smW6IExzYRDGDg==", + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/@openrouter/sdk/-/sdk-0.1.27.tgz", + "integrity": "sha512-RH//L10bSmc81q25zAZudiI4kNkLgxF2E+WU42vghp3N6TEvZ6F0jK7uT3tOxkEn91gzmMw9YVmDENy7SJsajQ==", "license": "Apache-2.0", "dependencies": { "zod": "^3.25.0 || ^4.0.0" - }, - "peerDependencies": { - "@tanstack/react-query": "^5", - "react": "^18 || ^19", - "react-dom": "^18 || ^19" - }, - "peerDependenciesMeta": { - "@tanstack/react-query": { - "optional": true - }, - "react": { - "optional": true - }, - "react-dom": { - "optional": true - } } }, "node_modules/@opentelemetry/api": { @@ -342,44 +274,111 @@ "node": ">=8.0.0" } }, + "node_modules/@standard-community/standard-json": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@standard-community/standard-json/-/standard-json-0.3.5.tgz", + "integrity": "sha512-4+ZPorwDRt47i+O7RjyuaxHRK/37QY/LmgxlGrRrSTLYoFatEOzvqIc85GTlM18SFZ5E91C+v0o/M37wZPpUHA==", + "license": "MIT", + "peer": true, + "peerDependencies": { + "@standard-schema/spec": "^1.0.0", + "@types/json-schema": "^7.0.15", + "@valibot/to-json-schema": "^1.3.0", + "arktype": "^2.1.20", + "effect": "^3.16.8", + "quansync": "^0.2.11", + "sury": "^10.0.0", + "typebox": "^1.0.17", + "valibot": "^1.1.0", + "zod": "^3.25.0 || ^4.0.0", + "zod-to-json-schema": "^3.24.5" + }, + "peerDependenciesMeta": { + "@valibot/to-json-schema": { + "optional": true + }, + "arktype": { + "optional": true + }, + "effect": { + "optional": true + }, + "sury": { + "optional": true + }, + "typebox": { + "optional": true + }, + "valibot": { + "optional": true + }, + "zod": { + "optional": true + }, + "zod-to-json-schema": { + "optional": true + } + } + }, + "node_modules/@standard-community/standard-openapi": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@standard-community/standard-openapi/-/standard-openapi-0.2.9.tgz", + "integrity": "sha512-htj+yldvN1XncyZi4rehbf9kLbu8os2Ke/rfqoZHCMHuw34kiF3LP/yQPdA0tQ940y8nDq3Iou8R3wG+AGGyvg==", + "license": "MIT", + "peer": true, + "peerDependencies": { + "@standard-community/standard-json": "^0.3.5", + "@standard-schema/spec": "^1.0.0", + "arktype": "^2.1.20", + "effect": "^3.17.14", + "openapi-types": "^12.1.3", + "sury": "^10.0.0", + "typebox": "^1.0.0", + "valibot": "^1.1.0", + "zod": "^3.25.0 || ^4.0.0", + "zod-openapi": "^4" + }, + "peerDependenciesMeta": { + "arktype": { + "optional": true + }, + "effect": { + "optional": true + }, + "sury": { + "optional": true + }, + "typebox": { + "optional": true + }, + "valibot": { + "optional": true + }, + "zod": { + "optional": true + }, + "zod-openapi": { + "optional": true + } + } + }, "node_modules/@standard-schema/spec": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", "license": "MIT" }, - "node_modules/@tsconfig/node10": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", - "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true, - "license": "MIT" + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT", + "peer": true }, "node_modules/@types/node": { - "version": "24.9.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.1.tgz", - "integrity": "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==", + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", "dev": true, "license": "MIT", "dependencies": { @@ -408,41 +407,15 @@ "node": ">= 0.6" } }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/ai": { - "version": "5.0.102", - "resolved": "https://registry.npmjs.org/ai/-/ai-5.0.102.tgz", - "integrity": "sha512-snRK3nS5DESOjjpq7S74g8YszWVMzjagfHqlJWZsbtl9PyOS+2XUd8dt2wWg/jdaq/jh0aU66W1mx5qFjUQyEg==", + "version": "5.0.106", + "resolved": "https://registry.npmjs.org/ai/-/ai-5.0.106.tgz", + "integrity": "sha512-M5obwavxSJJ3tGlAFqI6eltYNJB0D20X6gIBCFx/KVorb/X1fxVVfiZZpZb+Gslu4340droSOjT0aKQFCarNVg==", "license": "Apache-2.0", "dependencies": { - "@ai-sdk/gateway": "2.0.15", + "@ai-sdk/gateway": "2.0.18", "@ai-sdk/provider": "2.0.0", - "@ai-sdk/provider-utils": "3.0.17", + "@ai-sdk/provider-utils": "3.0.18", "@opentelemetry/api": "1.9.0" }, "engines": { @@ -452,39 +425,39 @@ "zod": "^3.25.76 || ^4.1.8" } }, - "node_modules/ai/node_modules/@ai-sdk/provider-utils": { - "version": "3.0.17", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.17.tgz", - "integrity": "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "2.0.0", - "@standard-schema/spec": "^1.0.0", - "eventsource-parser": "^3.0.6" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, "funding": { "type": "github", "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, "node_modules/ansi-regex": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", @@ -509,12 +482,18 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, - "license": "MIT" + "node_modules/awilix": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/awilix/-/awilix-12.0.5.tgz", + "integrity": "sha512-Qf/V/hRo6DK0FoBKJ9QiObasRxHAhcNi0mV6kW2JMawxS3zq6Un+VsZmVAZDUfvB+MjTEiJ2tUJUl4cr0JiUAw==", + "license": "MIT", + "dependencies": { + "camel-case": "^4.1.2", + "fast-glob": "^3.3.3" + }, + "engines": { + "node": ">=16.3.0" + } }, "node_modules/body-parser": { "version": "2.2.1", @@ -540,6 +519,18 @@ "url": "https://opencollective.com/express" } }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -578,6 +569,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "license": "MIT", + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, "node_modules/cliui": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", @@ -593,15 +594,16 @@ } }, "node_modules/content-disposition": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", - "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/content-type": { @@ -644,13 +646,6 @@ "node": ">= 0.10" } }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, - "license": "MIT" - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -691,16 +686,6 @@ "node": ">= 0.8" } }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -812,18 +797,19 @@ } }, "node_modules/express": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", - "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", "dependencies": { "accepts": "^2.0.0", - "body-parser": "^2.2.0", + "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", + "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", @@ -874,16 +860,63 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "license": "MIT" }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "license": "MIT" + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } }, "node_modules/finalhandler": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", - "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -894,7 +927,11 @@ "statuses": "^2.0.1" }, "engines": { - "node": ">= 0.8" + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/forwarded": { @@ -982,6 +1019,18 @@ "node": ">= 0.4" } }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -1018,29 +1067,55 @@ "node": ">= 0.4" } }, + "node_modules/hono": { + "version": "4.10.7", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.10.7.tgz", + "integrity": "sha512-icXIITfw/07Q88nLSkB9aiUrd8rYzSweK681Kjo/TSggaGbOX4RRyxxm71v+3PC8C/j+4rlxGeoTRxQDkaJkUw==", + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/hono-openapi": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/hono-openapi/-/hono-openapi-1.1.1.tgz", + "integrity": "sha512-AC3HNhZYPHhnZdSy2Je7GDoTTNxPos6rKRQKVDBbSilY3cWJPqsxRnN6zA4pU7tfxmQEMTqkiLXbw6sAaemB8Q==", + "license": "MIT", + "peerDependencies": { + "@hono/standard-validator": "^0.1.2", + "@standard-community/standard-json": "^0.3.5", + "@standard-community/standard-openapi": "^0.2.8", + "@types/json-schema": "^7.0.15", + "hono": "^4.8.3", + "openapi-types": "^12.1.3" + }, + "peerDependenciesMeta": { + "@hono/standard-validator": { + "optional": true + }, + "hono": { + "optional": true + } + } + }, "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "license": "MIT", "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { "node": ">= 0.8" - } - }, - "node_modules/http-errors/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/iconv-lite": { @@ -1074,6 +1149,36 @@ "node": ">= 0.10" } }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/is-promise": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", @@ -1086,6 +1191,15 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "license": "ISC" }, + "node_modules/jose": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", + "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/json-schema": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", @@ -1093,26 +1207,28 @@ "license": "(AFL-2.1 OR BSD-3-Clause)" }, "node_modules/json-schema-to-zod": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/json-schema-to-zod/-/json-schema-to-zod-2.6.1.tgz", - "integrity": "sha512-uiHmWH21h9FjKJkRBntfVGTLpYlCZ1n98D0izIlByqQLqpmkQpNTBtfbdP04Na6+43lgsvrShFh2uWLkQDKJuQ==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/json-schema-to-zod/-/json-schema-to-zod-2.7.0.tgz", + "integrity": "sha512-eW59l3NQ6sa3HcB+Ahf7pP6iGU7MY4we5JsPqXQ2ZcIPF8QxSg/lkY8lN0Js/AG0NjMbk+nZGUfHlceiHF+bwQ==", "license": "ISC", "bin": { "json-schema-to-zod": "dist/cjs/cli.js" } }, "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "license": "MIT" }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true, - "license": "ISC" + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } }, "node_modules/math-intrinsics": { "version": "1.1.0", @@ -1144,6 +1260,28 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/mime-db": { "version": "1.54.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", @@ -1154,15 +1292,19 @@ } }, "node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", "dependencies": { "mime-db": "^1.54.0" }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/ms": { @@ -1198,6 +1340,16 @@ "node": ">= 0.6" } }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -1220,9 +1372,9 @@ } }, "node_modules/ollama-ai-provider-v2": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/ollama-ai-provider-v2/-/ollama-ai-provider-v2-1.5.4.tgz", - "integrity": "sha512-OTxzIvxW7GutgkyYe55Y4lJeUbnDjH1jDkAQhjGiynffkDn0wyWbv/dD92A8HX1ni5Ec+i+ksYMXXlVOYPQR4g==", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/ollama-ai-provider-v2/-/ollama-ai-provider-v2-1.5.5.tgz", + "integrity": "sha512-1YwTFdPjhPNHny/DrOHO+s8oVGGIE5Jib61/KnnjPRNWQhVVimrJJdaAX3e6nNRRDXrY5zbb9cfm2+yVvgsrqw==", "license": "Apache-2.0", "dependencies": { "@ai-sdk/provider": "^2.0.0", @@ -1235,23 +1387,6 @@ "zod": "^4.0.16" } }, - "node_modules/ollama-ai-provider-v2/node_modules/@ai-sdk/provider-utils": { - "version": "3.0.17", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.17.tgz", - "integrity": "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "2.0.0", - "@standard-schema/spec": "^1.0.0", - "eventsource-parser": "^3.0.6" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -1273,6 +1408,13 @@ "wrappy": "1" } }, + "node_modules/openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", + "license": "MIT", + "peer": true + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -1282,6 +1424,16 @@ "node": ">= 0.8" } }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -1301,10 +1453,22 @@ "url": "https://opencollective.com/express" } }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/pkce-challenge": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", - "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", "license": "MIT", "engines": { "node": ">=16.20.0" @@ -1323,15 +1487,6 @@ "node": ">= 0.10" } }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", @@ -1347,6 +1502,43 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT", + "peer": true + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -1357,20 +1549,39 @@ } }, "node_modules/raw-body": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", - "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.7.0", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.10" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, "node_modules/router": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", @@ -1387,10 +1598,10 @@ "node": ">= 18" } }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "funding": [ { "type": "github", @@ -1405,7 +1616,10 @@ "url": "https://feross.org/support" } ], - "license": "MIT" + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } }, "node_modules/safer-buffer": { "version": "2.1.2", @@ -1590,6 +1804,18 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -1599,49 +1825,11 @@ "node": ">=0.6" } }, - "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" }, "node_modules/type-is": { "version": "2.0.1", @@ -1687,22 +1875,6 @@ "node": ">= 0.8" } }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true, - "license": "MIT" - }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -1785,24 +1957,23 @@ "node": "^20.19.0 || ^22.12.0 || >=23" } }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/zod": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz", - "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz", + "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.0", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.0.tgz", + "integrity": "sha512-HvWtU2UG41LALjajJrML6uQejQhNJx+JBO9IflpSja4R03iNWfKXrj6W2h7ljuLyc1nKS+9yDyL/9tD1U/yBnQ==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25 || ^4" + } } } } diff --git a/apps/cli/package.json b/apps/cli/package.json index 1f92cb54..7b42ffb9 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -6,7 +6,7 @@ "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "rm -rf dist && tsc", - "copilot": "npm run build && node dist/x.js" + "server": "node dist/server.js" }, "files": [ "dist", @@ -21,7 +21,6 @@ "description": "", "devDependencies": { "@types/node": "^24.9.1", - "ts-node": "^10.9.2", "typescript": "^5.9.3" }, "dependencies": { @@ -30,9 +29,14 @@ "@ai-sdk/openai": "^2.0.53", "@ai-sdk/openai-compatible": "^1.0.27", "@ai-sdk/provider": "^2.0.0", + "@hono/node-server": "^1.19.6", + "@hono/standard-validator": "^0.1.5", "@modelcontextprotocol/sdk": "^1.20.2", "@openrouter/ai-sdk-provider": "^1.2.6", "ai": "^5.0.102", + "awilix": "^12.0.5", + "hono": "^4.10.7", + "hono-openapi": "^1.1.1", "json-schema-to-zod": "^2.6.1", "nanoid": "^5.1.6", "ollama-ai-provider-v2": "^1.5.4", diff --git a/apps/cli/src/application/entities/agent.ts b/apps/cli/src/agents/agents.ts similarity index 100% rename from apps/cli/src/application/entities/agent.ts rename to apps/cli/src/agents/agents.ts diff --git a/apps/cli/src/agents/repo.ts b/apps/cli/src/agents/repo.ts new file mode 100644 index 00000000..615a8afc --- /dev/null +++ b/apps/cli/src/agents/repo.ts @@ -0,0 +1,45 @@ +import { WorkDir } from "../config/config.js"; +import fs from "fs/promises"; +import path from "path"; +import z from "zod"; +import { Agent } from "./agents.js"; + +export interface IAgentsRepo { + list(): Promise[]>; + fetch(id: string): Promise>; + create(agent: z.infer): Promise; + update(id: string, agent: z.infer): Promise; + delete(id: string): Promise; +} + +export class FSAgentsRepo implements IAgentsRepo { + async list(): Promise[]> { + const result: z.infer[] = []; + // list all json files in workdir/agents/ + const agentsDir = path.join(WorkDir, "agents"); + const files = await fs.readdir(agentsDir); + + for (const file of files) { + const contents = await fs.readFile(path.join(agentsDir, file), "utf8"); + result.push(Agent.parse(JSON.parse(contents))); + } + return result; + } + + async fetch(id: string): Promise> { + const contents = await fs.readFile(path.join(WorkDir, "agents", `${id}.json`), "utf8"); + return Agent.parse(JSON.parse(contents)); + } + + async create(agent: z.infer): Promise { + await fs.writeFile(path.join(WorkDir, "agents", `${agent.name}.json`), JSON.stringify(agent, null, 2)); + } + + async update(id: string, agent: z.infer): Promise { + await fs.writeFile(path.join(WorkDir, "agents", `${id}.json`), JSON.stringify(agent, null, 2)); + } + + async delete(id: string): Promise { + await fs.unlink(path.join(WorkDir, "agents", `${id}.json`)); + } +} \ No newline at end of file diff --git a/apps/cli/src/application/lib/agent.ts b/apps/cli/src/agents/runtime.ts similarity index 76% rename from apps/cli/src/application/lib/agent.ts rename to apps/cli/src/agents/runtime.ts index 5ee019ac..530aa6e7 100644 --- a/apps/cli/src/application/lib/agent.ts +++ b/apps/cli/src/agents/runtime.ts @@ -1,19 +1,102 @@ import { jsonSchema, ModelMessage, modelMessageSchema } from "ai"; import fs from "fs"; import path from "path"; -import { getModelConfig, WorkDir } from "../config/config.js"; -import { Agent, ToolAttachment } from "../entities/agent.js"; +import { WorkDir } from "../config/config.js"; +import { Agent, ToolAttachment } from "./agents.js"; import { AssistantContentPart, AssistantMessage, Message, MessageList, ProviderOptions, ToolCallPart, ToolMessage, UserMessage } from "../entities/message.js"; -import { runIdGenerator } from "./run-id-gen.js"; import { LanguageModel, stepCountIs, streamText, tool, Tool, ToolSet } from "ai"; import { z } from "zod"; -import { getProvider } from "./models.js"; import { LlmStepStreamEvent } from "../entities/llm-step-events.js"; -import { execTool } from "./exec-tool.js"; -import { AskHumanRequestEvent, RunEvent, ToolPermissionRequestEvent, ToolPermissionResponseEvent } from "../entities/run-events.js"; -import { BuiltinTools } from "./builtin-tools.js"; -import { CopilotAgent } from "../assistant/agent.js"; -import { isBlocked } from "./command-executor.js"; +import { execTool } from "../application/lib/exec-tool.js"; +import { MessageEvent, AskHumanRequestEvent, RunEvent, ToolInvocationEvent, ToolPermissionRequestEvent, ToolPermissionResponseEvent } from "../entities/run-events.js"; +import { BuiltinTools } from "../application/lib/builtin-tools.js"; +import { CopilotAgent } from "../application/assistant/agent.js"; +import { isBlocked } from "../application/lib/command-executor.js"; +import container from "../di/container.js"; +import { IModelConfigRepo } from "../models/repo.js"; +import { getProvider } from "../models/models.js"; +import { IAgentsRepo } from "./repo.js"; +import { IdGen, IMonotonicallyIncreasingIdGenerator } from "../application/lib/id-gen.js"; +import { IBus } from "../application/lib/bus.js"; +import { IMessageQueue } from "../application/lib/message-queue.js"; +import { IRunsRepo } from "../runs/repo.js"; +import { IRunsLock } from "../runs/lock.js"; + +export interface IAgentRuntime { + trigger(runId: string): Promise; +} + +export class AgentRuntime implements IAgentRuntime { + private runsRepo: IRunsRepo; + private idGenerator: IMonotonicallyIncreasingIdGenerator; + private bus: IBus; + private messageQueue: IMessageQueue; + private modelConfigRepo: IModelConfigRepo; + private runsLock: IRunsLock; + + constructor({ + runsRepo, + idGenerator, + bus, + messageQueue, + modelConfigRepo, + runsLock, + }: { + runsRepo: IRunsRepo; + idGenerator: IMonotonicallyIncreasingIdGenerator; + bus: IBus; + messageQueue: IMessageQueue; + modelConfigRepo: IModelConfigRepo; + runsLock: IRunsLock; + }) { + this.runsRepo = runsRepo; + this.idGenerator = idGenerator; + this.bus = bus; + this.messageQueue = messageQueue; + this.modelConfigRepo = modelConfigRepo; + this.runsLock = runsLock; + } + + async trigger(runId: string): Promise { + if (!await this.runsLock.lock(runId)) { + console.log(`unable to acquire lock on run ${runId}`); + return; + } + try { + while (true) { + let eventCount = 0; + const run = await this.runsRepo.fetch(runId); + if (!run) { + throw new Error(`Run ${runId} not found`); + } + const state = new AgentState(); + for (const event of run.log) { + state.ingest(event); + } + for await (const event of streamAgent({ + state, + idGenerator: this.idGenerator, + runId, + messageQueue: this.messageQueue, + modelConfigRepo: this.modelConfigRepo, + })) { + eventCount++; + if (event.type !== "llm-stream-event") { + await this.runsRepo.appendEvents(runId, [event]); + } + await this.bus.publish(event); + } + + // if no events, break + if (!eventCount) { + break; + } + } + } finally { + await this.runsLock.release(runId); + } + } +} export async function mapAgentTool(t: z.infer): Promise { switch (t.type) { @@ -128,8 +211,8 @@ export class StreamStepMessageBuilder { }); break; case "finish-step": - this.providerOptions = event.providerOptions; - break; + this.providerOptions = event.providerOptions; + break; } } @@ -171,9 +254,8 @@ export async function loadAgent(id: string): Promise> { if (id === "copilot") { return CopilotAgent; } - const agentPath = path.join(WorkDir, "agents", `${id}.json`); - const agent = fs.readFileSync(agentPath, "utf8"); - return Agent.parse(JSON.parse(agent)); + const repo = container.resolve('agentsRepo'); + return await repo.fetch(id); } export function convertFromMessages(messages: z.infer[]): ModelMessage[] { @@ -262,10 +344,9 @@ async function buildTools(agent: z.infer): Promise { } export class AgentState { - logger: RunLogger | null = null; runId: string | null = null; agent: z.infer | null = null; - agentName: string; + agentName: string | null = null; messages: z.infer = []; lastAssistantMsg: z.infer | null = null; subflowStates: Record = {}; @@ -276,20 +357,6 @@ export class AgentState { allowedToolCallIds: Record = {}; deniedToolCallIds: Record = {}; - constructor(agentName: string, runId?: string) { - this.agentName = agentName; - this.runId = runId || runIdGenerator.next(); - this.logger = new RunLogger(this.runId); - if (!runId) { - this.logger.log({ - type: "start", - runId: this.runId, - agentName: this.agentName, - subflow: [], - }); - } - } - getPendingPermissions(): z.infer[] { const response: z.infer[] = []; for (const [id, subflowState] of Object.entries(this.subflowStates)) { @@ -346,6 +413,9 @@ export class AgentState { ingest(event: z.infer) { if (event.subflow.length > 0) { const { subflow, ...rest } = event; + if (!this.subflowStates[subflow[0]]) { + this.subflowStates[subflow[0]] = new AgentState(); + } this.subflowStates[subflow[0]].ingest({ ...rest, subflow: subflow.slice(1), @@ -353,6 +423,10 @@ export class AgentState { return; } switch (event.type) { + case "start": + this.runId = event.runId; + this.agentName = event.agentName; + break; case "message": this.messages.push(event.message); if (event.message.content instanceof Array) { @@ -371,9 +445,6 @@ export class AgentState { this.lastAssistantMsg = event.message; } break; - case "spawn-subflow": - this.subflowStates[event.toolCallId] = new AgentState(event.agentName); - break; case "tool-permission-request": this.pendingToolPermissionRequests[event.toolCall.toolCallId] = event; break; @@ -406,27 +477,33 @@ export class AgentState { break; } } - - ingestAndLog(event: z.infer) { - this.ingest(event); - this.logger!.log(event); - } - - *ingestAndLogAndYield(event: z.infer): Generator, void, unknown> { - this.ingestAndLog(event); - yield event; - } } -export async function* streamAgent(state: AgentState): AsyncGenerator, void, unknown> { - // get model config - const modelConfig = await getModelConfig(); +export async function* streamAgent({ + state, + idGenerator, + runId, + messageQueue, + modelConfigRepo, +}: { + state: AgentState, + idGenerator: IMonotonicallyIncreasingIdGenerator; + runId: string; + messageQueue: IMessageQueue; + modelConfigRepo: IModelConfigRepo; +}): AsyncGenerator, void, unknown> { + async function* processEvent(event: z.infer): AsyncGenerator, void, unknown> { + state.ingest(event); + yield event; + } + + const modelConfig = await modelConfigRepo.getConfig(); if (!modelConfig) { throw new Error("Model config not found"); } // set up agent - const agent = await loadAgent(state.agentName); + const agent = await loadAgent(state.agentName!); // set up tools const tools = await buildTools(agent); @@ -436,9 +513,16 @@ export async function* streamAgent(state: AgentState): AsyncGenerator part.type === "tool-call") ) ) { - // console.error("Nothing to do, exiting (a.)") - return; + let pending = 0; + while(true) { + const msg = await messageQueue.dequeue(runId); + if (!msg) { + break; + } + pending++; + yield *processEvent({ + runId, + type: "message", + messageId: msg.messageId, + message: { + role: "user", + content: msg.message, + }, + subflow: [], + }); + } + // if no msgs found, return + if (!pending) { + return; + } } // execute any pending tool calls @@ -461,7 +565,9 @@ export async function* streamAgent(state: AgentState): AsyncGenerator('modelConfigRepo'); + const config = await repo.getConfig(); const rl = createInterface({ input, output }); try { @@ -333,14 +312,7 @@ export async function modelConfig() { ); const model = modelAns.trim() || modelDefault; - const newConfig = { - providers: { ...(config?.providers || {}) }, - defaults: { - provider: providerName!, - model, - }, - }; - await updateModelConfig(newConfig as any); + await repo.setDefault(providerName!, model); console.log(`Model configuration updated. Provider set to '${providerName}'.`); return; } @@ -391,24 +363,13 @@ export async function modelConfig() { ); const model = modelAns.trim() || modelDefault; - const mergedProviders = { - ...(config?.providers || {}), - [providerName]: { - flavor: selectedFlavor, - ...(apiKey ? { apiKey } : {}), - ...(baseURL ? { baseURL } : {}), - ...(headers ? { headers } : {}), - }, - }; - const newConfig = { - providers: mergedProviders, - defaults: { - provider: providerName, - model, - }, - }; - - await updateModelConfig(newConfig as any); + await repo.upsert(providerName, { + flavor: selectedFlavor, + apiKey, + baseURL, + headers, + }); + await repo.setDefault(providerName, model); renderCurrentModel(providerName, selectedFlavor, model); console.log(`Configuration written to ${WorkDir}/config/models.json. You can also edit this file manually`); } finally { diff --git a/apps/cli/src/application/assistant/agent.ts b/apps/cli/src/application/assistant/agent.ts index afcefafa..94ab982c 100644 --- a/apps/cli/src/application/assistant/agent.ts +++ b/apps/cli/src/application/assistant/agent.ts @@ -1,4 +1,4 @@ -import { Agent, ToolAttachment } from "../entities/agent.js"; +import { Agent, ToolAttachment } from "../../agents/agents.js"; import z from "zod"; import { CopilotInstructions } from "./instructions.js"; import { BuiltinTools } from "../lib/builtin-tools.js"; diff --git a/apps/cli/src/application/assistant/instructions.ts b/apps/cli/src/application/assistant/instructions.ts index 7d8aad14..b6e49cf0 100644 --- a/apps/cli/src/application/assistant/instructions.ts +++ b/apps/cli/src/application/assistant/instructions.ts @@ -1,5 +1,5 @@ import { skillCatalog } from "./skills/index.js"; -import { WorkDir as BASE_DIR } from "../config/config.js"; +import { WorkDir as BASE_DIR } from "../../config/config.js"; export const CopilotInstructions = `You are an intelligent workflow assistant helping users manage their workflows in ${BASE_DIR}. You can also help the user with general tasks. diff --git a/apps/cli/src/application/config/config.ts b/apps/cli/src/application/config/config.ts deleted file mode 100644 index 4702a765..00000000 --- a/apps/cli/src/application/config/config.ts +++ /dev/null @@ -1,63 +0,0 @@ -import path from "path"; -import fs from "fs"; -import { McpServerConfig } from "../entities/mcp.js"; -import { ModelConfig } from "../entities/models.js"; -import { z } from "zod"; -import { homedir } from "os"; - -// Resolve app root relative to compiled file location (dist/...) -export const WorkDir = path.join(homedir(), ".rowboat"); - -let modelConfig: z.infer | null = null; - -const baseMcpConfig: z.infer = { - mcpServers: { - } -}; - -function ensureMcpConfig() { - const configPath = path.join(WorkDir, "config", "mcp.json"); - if (!fs.existsSync(configPath)) { - fs.writeFileSync(configPath, JSON.stringify(baseMcpConfig, null, 2)); - } -} - -function ensureDirs() { - const ensure = (p: string) => { if (!fs.existsSync(p)) fs.mkdirSync(p, { recursive: true }); }; - ensure(WorkDir); - ensure(path.join(WorkDir, "agents")); - ensure(path.join(WorkDir, "config")); - ensureMcpConfig(); -} - -function loadMcpServerConfig(): z.infer { - const configPath = path.join(WorkDir, "config", "mcp.json"); - if (!fs.existsSync(configPath)) return { mcpServers: {} }; - const config = fs.readFileSync(configPath, "utf8"); - return McpServerConfig.parse(JSON.parse(config)); -} - -export async function getModelConfig(): Promise | null> { - if (modelConfig) { - return modelConfig; - } - const configPath = path.join(WorkDir, "config", "models.json"); - try { - const config = await fs.promises.readFile(configPath, "utf8"); - modelConfig = ModelConfig.parse(JSON.parse(config)); - return modelConfig; - } catch (error) { - console.error(`Warning! model config not found!`); - return null; - } -} - -export async function updateModelConfig(config: z.infer) { - modelConfig = config; - const configPath = path.join(WorkDir, "config", "models.json"); - await fs.promises.writeFile(configPath, JSON.stringify(config, null, 2)); -} - -ensureDirs(); -const { mcpServers } = loadMcpServerConfig(); -export const McpServers = mcpServers; \ No newline at end of file diff --git a/apps/cli/src/application/entities/mcp.ts b/apps/cli/src/application/entities/mcp.ts deleted file mode 100644 index e47ebb95..00000000 --- a/apps/cli/src/application/entities/mcp.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { z } from "zod"; - -export const StdioMcpServerConfig = z.object({ - type: z.literal("stdio").optional(), - command: z.string(), - args: z.array(z.string()).optional(), - env: z.record(z.string(), z.string()).optional(), -}); - -export const HttpMcpServerConfig = z.object({ - type: z.literal("http").optional(), - url: z.string(), - headers: z.record(z.string(), z.string()).optional(), -}); - -export const McpServerDefinition = z.union([StdioMcpServerConfig, HttpMcpServerConfig]); - -export const McpServerConfig = z.object({ - mcpServers: z.record(z.string(), McpServerDefinition), -}); diff --git a/apps/cli/src/application/entities/models.ts b/apps/cli/src/application/entities/models.ts deleted file mode 100644 index 0dabad3f..00000000 --- a/apps/cli/src/application/entities/models.ts +++ /dev/null @@ -1,27 +0,0 @@ -import z from "zod"; - -export const Flavor = z.enum([ - "rowboat [free]", - "aigateway", - "anthropic", - "google", - "ollama", - "openai", - "openai-compatible", - "openrouter", -]); - -export const Provider = z.object({ - flavor: Flavor, - apiKey: z.string().optional(), - baseURL: z.string().optional(), - headers: z.record(z.string(), z.string()).optional(), -}); - -export const ModelConfig = z.object({ - providers: z.record(z.string(), Provider), - defaults: z.object({ - provider: z.string(), - model: z.string(), - }), -}); \ No newline at end of file diff --git a/apps/cli/src/application/lib/builtin-tools.ts b/apps/cli/src/application/lib/builtin-tools.ts index e0c02f48..b7839079 100644 --- a/apps/cli/src/application/lib/builtin-tools.ts +++ b/apps/cli/src/application/lib/builtin-tools.ts @@ -1,14 +1,13 @@ import { z, ZodType } from "zod"; import * as fs from "fs/promises"; import * as path from "path"; -import { WorkDir as BASE_DIR } from "../config/config.js"; +import { WorkDir as BASE_DIR } from "../../config/config.js"; import { executeCommand } from "./command-executor.js"; -import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; -import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; -import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"; -import { Client } from "@modelcontextprotocol/sdk/client"; import { resolveSkill, availableSkills } from "../assistant/skills/index.js"; -import { McpServerDefinition, McpServerConfig } from "../entities/mcp.js"; +import { executeTool, listServers, listTools } from "../../mcp/mcp.js"; +import container from "../../di/container.js"; +import { IMcpConfigRepo } from "../..//mcp/repo.js"; +import { McpServerDefinition } from "../../mcp/mcp.js"; const BuiltinToolsSchema = z.record(z.string(), z.object({ description: z.string(), @@ -310,109 +309,33 @@ export const BuiltinTools: z.infer = { description: 'Add or update an MCP server in the configuration with validation. This ensures the server definition is valid before saving.', inputSchema: z.object({ serverName: z.string().describe('Name/alias for the MCP server'), - serverType: z.enum(['stdio', 'http']).describe('Type of MCP server: "stdio" for command-based or "http" for HTTP/SSE-based'), - command: z.string().optional().describe('Command to execute (required for stdio type, e.g., "npx", "python", "node")'), - args: z.array(z.string()).optional().describe('Command arguments (optional, for stdio type)'), - env: z.record(z.string(), z.string()).optional().describe('Environment variables (optional, for stdio type)'), - url: z.string().optional().describe('HTTP/SSE endpoint URL (required for http type)'), - headers: z.record(z.string(), z.string()).optional().describe('HTTP headers (optional, for http type)'), + config: McpServerDefinition, }), - execute: async ({ serverName, serverType, command, args, env, url, headers }: { + execute: async ({ serverName, config }: { serverName: string; - serverType: 'stdio' | 'http'; - command?: string; - args?: string[]; - env?: Record; - url?: string; - headers?: Record; + config: z.infer; }) => { try { - // Build server definition based on type - let serverDef: any; - if (serverType === 'stdio') { - if (!command) { - return { - success: false, - message: 'For stdio type servers, "command" is required. Example: "npx" or "python"', - validationErrors: ['Missing required field: command'], - }; - } - serverDef = { - type: 'stdio', - command, - ...(args ? { args } : {}), - ...(env ? { env } : {}), - }; - } else if (serverType === 'http') { - if (!url) { - return { - success: false, - message: 'For http type servers, "url" is required. Example: "http://localhost:3000/sse"', - validationErrors: ['Missing required field: url'], - }; - } - serverDef = { - type: 'http', - url, - ...(headers ? { headers } : {}), - }; - } else { - return { - success: false, - message: `Invalid serverType: ${serverType}. Must be "stdio" or "http"`, - validationErrors: [`Invalid serverType: ${serverType}`], - }; - } - - // Validate against Zod schema - const validationResult = McpServerDefinition.safeParse(serverDef); + const validationResult = McpServerDefinition.safeParse(config); if (!validationResult.success) { return { success: false, message: 'Server definition failed validation. Check the errors below.', validationErrors: validationResult.error.issues.map((e: any) => `${e.path.join('.')}: ${e.message}`), - providedDefinition: serverDef, + providedDefinition: config, }; } - - // Read existing config - const configPath = path.join(BASE_DIR, 'config', 'mcp.json'); - let currentConfig: z.infer = { mcpServers: {} }; - try { - const content = await fs.readFile(configPath, 'utf-8'); - currentConfig = McpServerConfig.parse(JSON.parse(content)); - } catch (error: any) { - if (error?.code !== 'ENOENT') { - return { - success: false, - message: `Failed to read existing MCP config: ${error.message}`, - }; - } - // File doesn't exist, use empty config - } - - // Check if server already exists - const isUpdate = !!currentConfig.mcpServers[serverName]; - - // Add/update server - currentConfig.mcpServers[serverName] = validationResult.data; - - // Write back to file - await fs.mkdir(path.dirname(configPath), { recursive: true }); - await fs.writeFile(configPath, JSON.stringify(currentConfig, null, 2), 'utf-8'); + + const repo = container.resolve('mcpConfigRepo'); + await repo.upsert(serverName, config); return { success: true, - message: `MCP server '${serverName}' ${isUpdate ? 'updated' : 'added'} successfully`, serverName, - serverType, - isUpdate, - configuration: validationResult.data, }; } catch (error) { return { - success: false, - message: `Failed to add MCP server: ${error instanceof Error ? error.message : 'Unknown error'}`, + error: `Failed to update MCP server: ${error instanceof Error ? error.message : 'Unknown error'}`, }; } }, @@ -421,47 +344,17 @@ export const BuiltinTools: z.infer = { listMcpServers: { description: 'List all available MCP servers from the configuration', inputSchema: z.object({}), - execute: async (): Promise<{ success: boolean, servers: any[], count: number, message: string }> => { + execute: async () => { try { - const configPath = path.join(BASE_DIR, 'config', 'mcp.json'); - - // Check if config exists - try { - await fs.access(configPath); - } catch { - return { - success: true, - servers: [], - count: 0, - message: 'No MCP servers configured yet', - }; - } - - const content = await fs.readFile(configPath, 'utf-8'); - const config = JSON.parse(content); - - const servers = Object.keys(config.mcpServers || {}).map(name => { - const server = config.mcpServers[name]; - return { - name, - type: 'command' in server ? 'stdio' : 'http', - command: server.command, - url: server.url, - }; - }); + const result = await listServers(); return { - success: true, - servers, - count: servers.length, - message: `Found ${servers.length} MCP server(s)`, + result, + count: Object.keys(result.mcpServers).length, }; } catch (error) { return { - success: false, - servers: [], - count: 0, - message: `Failed to list MCP servers: ${error instanceof Error ? error.message : 'Unknown error'}`, + error: `Failed to list MCP servers: ${error instanceof Error ? error.message : 'Unknown error'}`, }; } }, @@ -471,69 +364,19 @@ export const BuiltinTools: z.infer = { description: 'List all available tools from a specific MCP server', inputSchema: z.object({ serverName: z.string().describe('Name of the MCP server to query'), + cursor: z.string().optional(), }), - execute: async ({ serverName }: { serverName: string }) => { + execute: async ({ serverName, cursor }: { serverName: string, cursor?: string }) => { try { - const configPath = path.join(BASE_DIR, 'config', 'mcp.json'); - const content = await fs.readFile(configPath, 'utf-8'); - const config = JSON.parse(content); - - const mcpConfig = config.mcpServers[serverName]; - if (!mcpConfig) { - return { - success: false, - message: `MCP server '${serverName}' not found in configuration`, - }; - } - - // Create transport based on config type - let transport; - if ('command' in mcpConfig) { - transport = new StdioClientTransport({ - command: mcpConfig.command, - args: mcpConfig.args || [], - env: mcpConfig.env || {}, - }); - } else { - try { - transport = new StreamableHTTPClientTransport(new URL(mcpConfig.url)); - } catch { - transport = new SSEClientTransport(new URL(mcpConfig.url)); - } - } - - // Create and connect client - const client = new Client({ - name: 'rowboat-copilot', - version: '1.0.0', - }); - - await client.connect(transport); - - // List available tools - const toolsList = await client.listTools(); - - // Close connection - client.close(); - transport.close(); - - const tools = toolsList.tools.map((t: any) => ({ - name: t.name, - description: t.description || 'No description', - inputSchema: t.inputSchema, - })); - + const result = await listTools(serverName, cursor); return { - success: true, serverName, - tools, - count: tools.length, - message: `Found ${tools.length} tool(s) in MCP server '${serverName}'`, + result, + count: result.tools.length, }; } catch (error) { return { - success: false, - message: `Failed to list MCP tools: ${error instanceof Error ? error.message : 'Unknown error'}`, + error: `Failed to list MCP tools: ${error instanceof Error ? error.message : 'Unknown error'}`, }; } }, @@ -547,108 +390,19 @@ export const BuiltinTools: z.infer = { arguments: z.record(z.string(), z.any()).optional().describe('Arguments to pass to the tool (as key-value pairs matching the tool\'s input schema). MUST include all required parameters from the tool\'s inputSchema.'), }), execute: async ({ serverName, toolName, arguments: args = {} }: { serverName: string, toolName: string, arguments?: Record }) => { - let transport: any; - let client: any; - try { - const configPath = path.join(BASE_DIR, 'config', 'mcp.json'); - const content = await fs.readFile(configPath, 'utf-8'); - const config = JSON.parse(content); - - const mcpConfig = config.mcpServers[serverName]; - if (!mcpConfig) { - return { - success: false, - message: `MCP server '${serverName}' not found in configuration. Use listMcpServers to see available servers.`, - }; - } - - // Create transport based on config type - if ('command' in mcpConfig) { - transport = new StdioClientTransport({ - command: mcpConfig.command, - args: mcpConfig.args || [], - env: mcpConfig.env || {}, - }); - } else { - try { - transport = new StreamableHTTPClientTransport(new URL(mcpConfig.url)); - } catch { - transport = new SSEClientTransport(new URL(mcpConfig.url)); - } - } - - // Create and connect client - client = new Client({ - name: 'rowboat-copilot', - version: '1.0.0', - }); - - await client.connect(transport); - - // Get tool list to validate the tool exists and check schema - const toolsList = await client.listTools(); - const toolDef = toolsList.tools.find((t: any) => t.name === toolName); - - if (!toolDef) { - await client.close(); - transport.close(); - return { - success: false, - message: `Tool '${toolName}' not found in server '${serverName}'. Use listMcpTools to see available tools.`, - availableTools: toolsList.tools.map((t: any) => t.name), - }; - } - - // Validate required parameters - const inputSchema = toolDef.inputSchema; - if (inputSchema && inputSchema.required && Array.isArray(inputSchema.required)) { - const missingParams = inputSchema.required.filter((param: string) => !(param in args)); - if (missingParams.length > 0) { - await client.close(); - transport.close(); - return { - success: false, - message: `Missing required parameters: ${missingParams.join(', ')}`, - requiredParameters: inputSchema.required, - providedArguments: Object.keys(args), - toolSchema: inputSchema, - hint: `Use listMcpTools to see the full schema for '${toolName}' and ensure all required parameters are included in the arguments field.`, - }; - } - } - - // Call the tool - const result = await client.callTool({ - name: toolName, - arguments: args, - }); - - // Close connection - await client.close(); - transport.close(); - + const result = await executeTool(serverName, toolName, args); return { success: true, serverName, toolName, - result: result.content, + result, message: `Successfully executed tool '${toolName}' from server '${serverName}'`, }; } catch (error) { - // Ensure cleanup - try { - if (client) await client.close(); - if (transport) transport.close(); - } catch (cleanupError) { - // Ignore cleanup errors - } - return { success: false, - message: `Failed to execute MCP tool: ${error instanceof Error ? error.message : 'Unknown error'}`, - serverName, - toolName, + error: `Failed to execute MCP tool: ${error instanceof Error ? error.message : 'Unknown error'}`, hint: 'Use listMcpTools to verify the tool exists and check its schema. Ensure all required parameters are provided in the arguments field.', }; } diff --git a/apps/cli/src/application/lib/bus.ts b/apps/cli/src/application/lib/bus.ts new file mode 100644 index 00000000..d49c5d36 --- /dev/null +++ b/apps/cli/src/application/lib/bus.ts @@ -0,0 +1,38 @@ +import { RunEvent } from "../../entities/run-events.js"; +import z from "zod"; + +export interface IBus { + publish(event: z.infer): Promise; + + // subscribe accepts a handler to handle events + // and returns a function to unsubscribe + subscribe(runId: string, handler: (event: z.infer) => Promise): Promise<() => void>; +} + +export class InMemoryBus implements IBus { + private subscribers: Map) => Promise)[]> = new Map(); + + async publish(event: z.infer): Promise { + console.log(this.subscribers); + const pending: Promise[] = []; + for (const subscriber of this.subscribers.get(event.runId) || []) { + pending.push(subscriber(event)); + } + for (const subscriber of this.subscribers.get('*') || []) { + pending.push(subscriber(event)); + } + console.log(pending.length); + await Promise.all(pending); + } + + async subscribe(runId: string, handler: (event: z.infer) => Promise): Promise<() => void> { + if (!this.subscribers.has(runId)) { + this.subscribers.set(runId, []); + } + this.subscribers.get(runId)!.push(handler); + console.log(this.subscribers); + return () => { + this.subscribers.get(runId)!.splice(this.subscribers.get(runId)!.indexOf(handler), 1); + }; + } +} \ No newline at end of file diff --git a/apps/cli/src/application/lib/command-executor.ts b/apps/cli/src/application/lib/command-executor.ts index db030542..814d9801 100644 --- a/apps/cli/src/application/lib/command-executor.ts +++ b/apps/cli/src/application/lib/command-executor.ts @@ -1,6 +1,6 @@ import { exec, execSync } from 'child_process'; import { promisify } from 'util'; -import { getSecurityAllowList, SECURITY_CONFIG_PATH } from '../config/security.js'; +import { getSecurityAllowList, SECURITY_CONFIG_PATH } from '../../config/security.js'; const execPromise = promisify(exec); const COMMAND_SPLIT_REGEX = /(?:\|\||&&|;|\||\n)/; diff --git a/apps/cli/src/application/lib/exec-tool.ts b/apps/cli/src/application/lib/exec-tool.ts index ddd05e52..4ed32883 100644 --- a/apps/cli/src/application/lib/exec-tool.ts +++ b/apps/cli/src/application/lib/exec-tool.ts @@ -1,53 +1,10 @@ -import { ToolAttachment } from "../entities/agent.js"; +import { ToolAttachment } from "../../agents/agents.js"; import { z } from "zod"; -import { McpServers } from "../config/config.js"; -import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; -import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; -import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"; -import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js"; -import { Client } from "@modelcontextprotocol/sdk/client"; import { BuiltinTools } from "./builtin-tools.js"; +import { executeTool } from "../../mcp/mcp.js"; async function execMcpTool(agentTool: z.infer & { type: "mcp" }, input: any): Promise { - // load mcp configuration from the tool - const mcpConfig = McpServers[agentTool.mcpServerName]; - if (!mcpConfig) { - throw new Error(`MCP server ${agentTool.mcpServerName} not found`); - } - - // create transport - let transport: Transport; - if ("command" in mcpConfig) { - transport = new StdioClientTransport({ - command: mcpConfig.command, - args: mcpConfig.args, - env: mcpConfig.env, - }); - } else { - // first try streamable http transport - try { - transport = new StreamableHTTPClientTransport(new URL(mcpConfig.url)); - } catch (error) { - // if that fails, try sse transport - transport = new SSEClientTransport(new URL(mcpConfig.url)); - } - } - - if (!transport) { - throw new Error(`No transport found for ${agentTool.mcpServerName}`); - } - - // create client - const client = new Client({ - name: 'rowboatx', - version: '1.0.0', - }); - await client.connect(transport); - - // call tool - const result = await client.callTool({ name: agentTool.name, arguments: input }); - client.close(); - transport.close(); + const result = await executeTool(agentTool.mcpServerName, agentTool.name, input); return result; } diff --git a/apps/cli/src/application/lib/run-id-gen.ts b/apps/cli/src/application/lib/id-gen.ts similarity index 80% rename from apps/cli/src/application/lib/run-id-gen.ts rename to apps/cli/src/application/lib/id-gen.ts index 4bccf259..e5bdddcd 100644 --- a/apps/cli/src/application/lib/run-id-gen.ts +++ b/apps/cli/src/application/lib/id-gen.ts @@ -1,19 +1,23 @@ -class RunIdGenerator { +export interface IMonotonicallyIncreasingIdGenerator { + next(): Promise; +} + +export class IdGen implements IMonotonicallyIncreasingIdGenerator { private lastMs = 0; private seq = 0; private readonly pid: string; private readonly hostTag: string; - constructor(hostTag: string = "") { + constructor() { this.pid = String(process.pid).padStart(7, "0"); - this.hostTag = hostTag ? `-${hostTag}` : ""; + this.hostTag = ""; } /** * Returns an ISO8601-based, lexicographically sortable id string. * Example: 2025-11-11T04-36-29Z-0001234-h1-000 */ - next(): string { + async next(): Promise { const now = Date.now(); const ms = now >= this.lastMs ? now : this.lastMs; // monotonic clamp this.seq = ms === this.lastMs ? this.seq + 1 : 0; @@ -27,6 +31,4 @@ class RunIdGenerator { const seqStr = String(this.seq).padStart(3, "0"); return `${iso}-${this.pid}${this.hostTag}-${seqStr}`; } -} - -export const runIdGenerator = new RunIdGenerator(); \ No newline at end of file +} \ No newline at end of file diff --git a/apps/cli/src/application/lib/mcp.ts b/apps/cli/src/application/lib/mcp.ts deleted file mode 100644 index 041710f5..00000000 --- a/apps/cli/src/application/lib/mcp.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Client } from '@modelcontextprotocol/sdk/client/index.js'; -import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'; -import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js'; - -export async function getMcpClient(serverUrl: string, serverName: string): Promise { - let client: Client | undefined = undefined; - const baseUrl = new URL(serverUrl); - - // Try to connect using Streamable HTTP transport - try { - client = new Client({ - name: 'streamable-http-client', - version: '1.0.0' - }); - const transport = new StreamableHTTPClientTransport(baseUrl); - await client.connect(transport); - console.log(`[MCP] Connected using Streamable HTTP transport to ${serverName}`); - return client; - } catch (error) { - // If that fails with a 4xx error, try the older SSE transport - console.log(`[MCP] Streamable HTTP connection failed, falling back to SSE transport for ${serverName}`); - client = new Client({ - name: 'sse-client', - version: '1.0.0' - }); - const sseTransport = new SSEClientTransport(baseUrl); - await client.connect(sseTransport); - console.log(`[MCP] Connected using SSE transport to ${serverName}`); - return client; - } -} diff --git a/apps/cli/src/application/lib/message-queue.ts b/apps/cli/src/application/lib/message-queue.ts new file mode 100644 index 00000000..7242a999 --- /dev/null +++ b/apps/cli/src/application/lib/message-queue.ts @@ -0,0 +1,44 @@ +import z from "zod"; +import { IMonotonicallyIncreasingIdGenerator } from "./id-gen.js"; + +const EnqueuedMessage = z.object({ + messageId: z.string(), + message: z.string(), +}); + +export interface IMessageQueue { + enqueue(runId: string, message: string): Promise; + dequeue(runId: string): Promise | null>; +} + +export class InMemoryMessageQueue implements IMessageQueue { + private store: Record[]> = {}; + private idGenerator: IMonotonicallyIncreasingIdGenerator; + + constructor({ + idGenerator, + }: { + idGenerator: IMonotonicallyIncreasingIdGenerator; + }) { + this.idGenerator = idGenerator; + } + + async enqueue(runId: string, message: string): Promise { + if (!this.store[runId]) { + this.store[runId] = []; + } + const id = await this.idGenerator.next(); + this.store[runId].push({ + messageId: id, + message, + }); + return id; + } + + async dequeue(runId: string): Promise | null> { + if (!this.store[runId]) { + return null; + } + return this.store[runId].shift() ?? null; + } +} \ No newline at end of file diff --git a/apps/cli/src/application/lib/step.ts b/apps/cli/src/application/lib/step.ts deleted file mode 100644 index 3fae98bc..00000000 --- a/apps/cli/src/application/lib/step.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { MessageList } from "../entities/message.js"; -import { LlmStepStreamEvent } from "../entities/llm-step-events.js"; -import { z } from "zod"; -import { ToolAttachment } from "../entities/agent.js"; - -export type StepInputT = z.infer; -export type StepOutputT = AsyncGenerator, void, unknown>; - -export interface Step { - execute(input: StepInputT): StepOutputT; - - tools(): Record>; -} \ No newline at end of file diff --git a/apps/cli/src/application/lib/stream-renderer.ts b/apps/cli/src/application/lib/stream-renderer.ts index 5fc83bab..bbf87c4a 100644 --- a/apps/cli/src/application/lib/stream-renderer.ts +++ b/apps/cli/src/application/lib/stream-renderer.ts @@ -1,6 +1,6 @@ import { z } from "zod"; -import { RunEvent } from "../entities/run-events.js"; -import { LlmStepStreamEvent } from "../entities/llm-step-events.js"; +import { RunEvent } from "../../entities/run-events.js"; +import { LlmStepStreamEvent } from "../../entities/llm-step-events.js"; export interface StreamRendererOptions { showHeaders?: boolean; diff --git a/apps/cli/src/config/config.ts b/apps/cli/src/config/config.ts new file mode 100644 index 00000000..94355b54 --- /dev/null +++ b/apps/cli/src/config/config.ts @@ -0,0 +1,15 @@ +import path from "path"; +import fs from "fs"; +import { homedir } from "os"; + +// Resolve app root relative to compiled file location (dist/...) +export const WorkDir = path.join(homedir(), ".rowboat"); + +function ensureDirs() { + const ensure = (p: string) => { if (!fs.existsSync(p)) fs.mkdirSync(p, { recursive: true }); }; + ensure(WorkDir); + ensure(path.join(WorkDir, "agents")); + ensure(path.join(WorkDir, "config")); +} + +ensureDirs(); \ No newline at end of file diff --git a/apps/cli/src/application/config/security.ts b/apps/cli/src/config/security.ts similarity index 100% rename from apps/cli/src/application/config/security.ts rename to apps/cli/src/config/security.ts diff --git a/apps/cli/src/di/container.ts b/apps/cli/src/di/container.ts new file mode 100644 index 00000000..bfcd5a47 --- /dev/null +++ b/apps/cli/src/di/container.ts @@ -0,0 +1,30 @@ +import { asClass, createContainer, InjectionMode } from "awilix"; +import { FSModelConfigRepo, IModelConfigRepo } from "../models/repo.js"; +import { FSMcpConfigRepo, IMcpConfigRepo } from "../mcp/repo.js"; +import { FSAgentsRepo, IAgentsRepo } from "../agents/repo.js"; +import { FSRunsRepo, IRunsRepo } from "../runs/repo.js"; +import { IMonotonicallyIncreasingIdGenerator, IdGen } from "../application/lib/id-gen.js"; +import { IMessageQueue, InMemoryMessageQueue } from "../application/lib/message-queue.js"; +import { IBus, InMemoryBus } from "../application/lib/bus.js"; +import { IRunsLock, InMemoryRunsLock } from "../runs/lock.js"; +import { IAgentRuntime, AgentRuntime } from "../agents/runtime.js"; + +const container = createContainer({ + injectionMode: InjectionMode.PROXY, + strict: true, +}); + +container.register({ + idGenerator: asClass(IdGen).singleton(), + messageQueue: asClass(InMemoryMessageQueue).singleton(), + bus: asClass(InMemoryBus).singleton(), + runsLock: asClass(InMemoryRunsLock).singleton(), + agentRuntime: asClass(AgentRuntime).singleton(), + + mcpConfigRepo: asClass(FSMcpConfigRepo).singleton(), + modelConfigRepo: asClass(FSModelConfigRepo).singleton(), + agentsRepo: asClass(FSAgentsRepo).singleton(), + runsRepo: asClass(FSRunsRepo).singleton(), +}); + +export default container; \ No newline at end of file diff --git a/apps/cli/src/application/entities/example.ts b/apps/cli/src/entities/example.ts similarity index 75% rename from apps/cli/src/application/entities/example.ts rename to apps/cli/src/entities/example.ts index 8857a55c..92e2f3f7 100644 --- a/apps/cli/src/application/entities/example.ts +++ b/apps/cli/src/entities/example.ts @@ -1,6 +1,6 @@ import z from "zod" -import { Agent } from "./agent.js" -import { McpServerDefinition } from "./mcp.js" +import { Agent } from "../agents/agents.js" +import { McpServerDefinition } from "../mcp/mcp.js"; export const Example = z.object({ id: z.string(), diff --git a/apps/cli/src/application/entities/llm-step-events.ts b/apps/cli/src/entities/llm-step-events.ts similarity index 100% rename from apps/cli/src/application/entities/llm-step-events.ts rename to apps/cli/src/entities/llm-step-events.ts diff --git a/apps/cli/src/application/entities/message.ts b/apps/cli/src/entities/message.ts similarity index 100% rename from apps/cli/src/application/entities/message.ts rename to apps/cli/src/entities/message.ts diff --git a/apps/cli/src/application/entities/run-events.ts b/apps/cli/src/entities/run-events.ts similarity index 98% rename from apps/cli/src/application/entities/run-events.ts rename to apps/cli/src/entities/run-events.ts index bdd0c13a..edd6d7e9 100644 --- a/apps/cli/src/application/entities/run-events.ts +++ b/apps/cli/src/entities/run-events.ts @@ -1,16 +1,15 @@ import { LlmStepStreamEvent } from "./llm-step-events.js"; import { Message, ToolCallPart } from "./message.js"; -import { Agent } from "./agent.js"; import z from "zod"; const BaseRunEvent = z.object({ + runId: z.string(), ts: z.iso.datetime().optional(), subflow: z.array(z.string()), }); export const StartEvent = BaseRunEvent.extend({ type: z.literal("start"), - runId: z.string(), agentName: z.string(), }); @@ -27,6 +26,7 @@ export const LlmStreamEvent = BaseRunEvent.extend({ export const MessageEvent = BaseRunEvent.extend({ type: z.literal("message"), + messageId: z.string(), message: Message, }); diff --git a/apps/cli/src/examples/index.ts b/apps/cli/src/examples/index.ts index 428356d2..04122f6b 100644 --- a/apps/cli/src/examples/index.ts +++ b/apps/cli/src/examples/index.ts @@ -1,5 +1,5 @@ import twitterPodcast from './twitter-podcast.json' with { type: 'json' }; -import { Example } from '../application/entities/example.js'; +import { Example } from '../entities/example.js'; import z from 'zod'; export const examples: Record> = { diff --git a/apps/cli/src/mcp/mcp.ts b/apps/cli/src/mcp/mcp.ts new file mode 100644 index 00000000..6e38bd98 --- /dev/null +++ b/apps/cli/src/mcp/mcp.ts @@ -0,0 +1,174 @@ +import container from "../di/container.js"; +import { Client } from "@modelcontextprotocol/sdk/client"; +import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"; +import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; +import z from "zod"; +import { IMcpConfigRepo } from "./repo.js"; +import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js"; +import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; + +export const StdioMcpServerConfig = z.object({ + type: z.literal("stdio").optional(), + command: z.string(), + args: z.array(z.string()).optional(), + env: z.record(z.string(), z.string()).optional(), +}); + +export const HttpMcpServerConfig = z.object({ + type: z.literal("http").optional(), + url: z.string(), + headers: z.record(z.string(), z.string()).optional(), +}); + +export const McpServerDefinition = z.union([StdioMcpServerConfig, HttpMcpServerConfig]); + +export const McpServerConfig = z.object({ + mcpServers: z.record(z.string(), McpServerDefinition), +}); + +const connectionState = z.enum(["disconnected", "connected", "error"]); + +export const McpServerList = z.object({ + mcpServers: z.record(z.string(), z.object({ + config: McpServerDefinition, + state: connectionState, + error: z.string().nullable(), + })), +}); + +/* + inputSchema: { + [x: string]: unknown; + type: "object"; + properties?: Record | undefined; + required?: string[] | undefined; + }; +*/ +export const Tool = z.object({ + name: z.string(), + description: z.string().optional(), + inputSchema: z.object({ + type: z.literal("object"), + properties: z.record(z.string(), z.any()).optional(), + required: z.array(z.string()).optional(), + }), + outputSchema: z.object({ + type: z.literal("object"), + properties: z.record(z.string(), z.any()).optional(), + required: z.array(z.string()).optional(), + }).optional(), +}) + +export const ListToolsResponse = z.object({ + tools: z.array(Tool), + nextCursor: z.string().optional(), +}); + +type mcpState = { + state: z.infer, + client: Client | null, + error: string | null, +}; +const clients: Record = {}; + +async function getClient(serverName: string): Promise { + if (clients[serverName] && clients[serverName].state === "connected") { + return clients[serverName].client!; + } + const repo = container.resolve('mcpConfigRepo'); + const { mcpServers } = await repo.getConfig(); + const config = mcpServers[serverName]; + if (!config) { + throw new Error(`MCP server ${serverName} not found`); + } + let transport: Transport | undefined = undefined; + try { + // create transport + if ("command" in config) { + transport = new StdioClientTransport({ + command: config.command, + args: config.args, + env: config.env, + }); + } else { + try { + transport = new StreamableHTTPClientTransport(new URL(config.url)); + } catch (error) { + // if that fails, try sse transport + transport = new SSEClientTransport(new URL(config.url)); + } + } + + if (!transport) { + throw new Error(`No transport found for ${serverName}`); + } + + // create client + const client = new Client({ + name: 'rowboatx', + version: '1.0.0', + }); + await client.connect(transport); + + // store + clients[serverName] = { + state: "connected", + client, + error: null, + }; + return client; + } catch (error) { + clients[serverName] = { + state: "error", + client: null, + error: error instanceof Error ? error.message : "Unknown error", + }; + transport?.close(); + throw error; + } +} + +export async function cleanup() { + for (const [serverName, { client }] of Object.entries(clients)) { + await client?.transport?.close(); + await client?.close(); + delete clients[serverName]; + } +} + +export async function listServers(): Promise> { + const repo = container.resolve('mcpConfigRepo'); + const { mcpServers } = await repo.getConfig(); + const result: z.infer = { + mcpServers: {}, + }; + for (const [serverName, config] of Object.entries(mcpServers)) { + const state = clients[serverName]; + result.mcpServers[serverName] = { + config, + state: state ? state.state : "disconnected", + error: state ? state.error : null, + }; + } + return result; +} + +export async function listTools(serverName: string, cursor?: string): Promise> { + const client = await getClient(serverName); + const { tools, nextCursor } = await client.listTools({ + cursor, + }); + return { + tools, + nextCursor, + } +} + +export async function executeTool(serverName: string, toolName: string, input: any): Promise { + const client = await getClient(serverName); + const result = await client.callTool({ + name: toolName, + arguments: input, + }); + return result; +} \ No newline at end of file diff --git a/apps/cli/src/mcp/repo.ts b/apps/cli/src/mcp/repo.ts new file mode 100644 index 00000000..d43569af --- /dev/null +++ b/apps/cli/src/mcp/repo.ts @@ -0,0 +1,45 @@ +import { WorkDir } from "../config/config.js"; +import { McpServerConfig } from "./mcp.js"; +import { McpServerDefinition } from "./mcp.js"; +import fs from "fs/promises"; +import path from "path"; +import z from "zod"; + +export interface IMcpConfigRepo { + getConfig(): Promise>; + upsert(serverName: string, config: z.infer): Promise; + delete(serverName: string): Promise; +} + +export class FSMcpConfigRepo implements IMcpConfigRepo { + private readonly configPath = path.join(WorkDir, "config", "mcp.json"); + + constructor() { + this.ensureDefaultConfig(); + } + + private async ensureDefaultConfig(): Promise { + try { + await fs.access(this.configPath); + } catch (error) { + await fs.writeFile(this.configPath, JSON.stringify({ mcpServers: {} }, null, 2)); + } + } + + async getConfig(): Promise> { + const config = await fs.readFile(this.configPath, "utf8"); + return McpServerConfig.parse(JSON.parse(config)); + } + + async upsert(serverName: string, config: z.infer): Promise { + const conf = await this.getConfig(); + conf.mcpServers[serverName] = config; + await fs.writeFile(this.configPath, JSON.stringify(conf, null, 2)); + } + + async delete(serverName: string): Promise { + const conf = await this.getConfig(); + delete conf.mcpServers[serverName]; + await fs.writeFile(this.configPath, JSON.stringify(conf, null, 2)); + } +} \ No newline at end of file diff --git a/apps/cli/src/application/lib/models.ts b/apps/cli/src/models/models.ts similarity index 77% rename from apps/cli/src/application/lib/models.ts rename to apps/cli/src/models/models.ts index 1416d276..d2d846e5 100644 --- a/apps/cli/src/application/lib/models.ts +++ b/apps/cli/src/models/models.ts @@ -6,13 +6,42 @@ import { createAnthropic } from "@ai-sdk/anthropic"; import { createOllama } from "ollama-ai-provider-v2"; import { createOpenRouter } from '@openrouter/ai-sdk-provider'; import { createOpenAICompatible } from '@ai-sdk/openai-compatible'; -import { getModelConfig } from "../config/config.js"; +import { IModelConfigRepo } from "./repo.js"; +import container from "../di/container.js"; +import z from "zod"; + +export const Flavor = z.enum([ + "rowboat [free]", + "aigateway", + "anthropic", + "google", + "ollama", + "openai", + "openai-compatible", + "openrouter", +]); + +export const Provider = z.object({ + flavor: Flavor, + apiKey: z.string().optional(), + baseURL: z.string().optional(), + headers: z.record(z.string(), z.string()).optional(), +}); + +export const ModelConfig = z.object({ + providers: z.record(z.string(), Provider), + defaults: z.object({ + provider: z.string(), + model: z.string(), + }), +}); const providerMap: Record = {}; export async function getProvider(name: string = ""): Promise { // get model conf - const modelConfig = await getModelConfig(); + const repo = container.resolve("modelConfigRepo"); + const modelConfig = await repo.getConfig(); if (!modelConfig) { throw new Error("Model config not found"); } diff --git a/apps/cli/src/models/repo.ts b/apps/cli/src/models/repo.ts new file mode 100644 index 00000000..1041045c --- /dev/null +++ b/apps/cli/src/models/repo.ts @@ -0,0 +1,70 @@ +import { ModelConfig, Provider } from "./models.js"; +import { WorkDir } from "../config/config.js"; +import fs from "fs/promises"; +import path from "path"; +import z from "zod"; + +export interface IModelConfigRepo { + getConfig(): Promise>; + upsert(providerName: string, config: z.infer): Promise; + delete(providerName: string): Promise; + setDefault(providerName: string, model: string): Promise; +} + +const defaultConfig: z.infer = { + providers: { + "openai": { + flavor: "openai", + } + }, + defaults: { + provider: "openai", + model: "gpt-5.1", + } +}; + +export class FSModelConfigRepo implements IModelConfigRepo { + private readonly configPath = path.join(WorkDir, "config", "models.json"); + + constructor() { + this.ensureDefaultConfig(); + } + + private async ensureDefaultConfig(): Promise { + try { + await fs.access(this.configPath); + } catch (error) { + await fs.writeFile(this.configPath, JSON.stringify(defaultConfig, null, 2)); + } + } + + async getConfig(): Promise> { + const config = await fs.readFile(this.configPath, "utf8"); + return ModelConfig.parse(JSON.parse(config)); + } + + private async setConfig(config: z.infer): Promise { + await fs.writeFile(this.configPath, JSON.stringify(config, null, 2)); + } + + async upsert(providerName: string, config: z.infer): Promise { + const conf = await this.getConfig(); + conf.providers[providerName] = config; + await this.setConfig(conf); + } + + async delete(providerName: string): Promise { + const conf = await this.getConfig(); + delete conf.providers[providerName]; + await this.setConfig(conf); + } + + async setDefault(providerName: string, model: string): Promise { + const conf = await this.getConfig(); + conf.defaults = { + provider: providerName, + model, + }; + await this.setConfig(conf); + } +} \ No newline at end of file diff --git a/apps/cli/src/runs/lock.ts b/apps/cli/src/runs/lock.ts new file mode 100644 index 00000000..10515fc6 --- /dev/null +++ b/apps/cli/src/runs/lock.ts @@ -0,0 +1,20 @@ +export interface IRunsLock { + lock(runId: string): Promise; + release(runId: string): Promise; +} + +export class InMemoryRunsLock implements IRunsLock { + private locks: Record = {}; + + async lock(runId: string): Promise { + if (this.locks[runId]) { + return false; + } + this.locks[runId] = true; + return true; + } + + async release(runId: string): Promise { + delete this.locks[runId]; + } +} diff --git a/apps/cli/src/runs/repo.ts b/apps/cli/src/runs/repo.ts new file mode 100644 index 00000000..ed87ebae --- /dev/null +++ b/apps/cli/src/runs/repo.ts @@ -0,0 +1,79 @@ +import { Run } from "./runs.js"; +import z from "zod"; +import { IMonotonicallyIncreasingIdGenerator } from "../application/lib/id-gen.js"; +import { WorkDir } from "../config/config.js"; +import path from "path"; +import fsp from "fs/promises"; +import { RunEvent, StartEvent } from "../entities/run-events.js"; + +export const ListRunsResponse = z.object({ + runs: z.array(Run.pick({ + id: true, + createdAt: true, + agentId: true, + })), + nextCursor: z.string().optional(), +}); + +export const CreateRunOptions = Run.pick({ + agentId: true, +}); + +export interface IRunsRepo { + create(options: z.infer): Promise>; + fetch(id: string): Promise>; + appendEvents(runId: string, events: z.infer[]): Promise; +} + +export class FSRunsRepo implements IRunsRepo { + private idGenerator: IMonotonicallyIncreasingIdGenerator; + constructor({ + idGenerator, + }: { + idGenerator: IMonotonicallyIncreasingIdGenerator; + }) { + this.idGenerator = idGenerator; + } + + async appendEvents(runId: string, events: z.infer[]): Promise { + await fsp.appendFile( + path.join(WorkDir, 'runs', `${runId}.jsonl`), + events.map(event => JSON.stringify(event)).join("\n") + "\n" + ); + } + + async create(options: z.infer): Promise> { + const runId = await this.idGenerator.next(); + const ts = new Date().toISOString(); + const start: z.infer = { + type: "start", + runId, + agentName: options.agentId, + subflow: [], + ts, + }; + await this.appendEvents(runId, [start]); + return { + id: runId, + createdAt: ts, + agentId: options.agentId, + log: [start], + }; + } + + async fetch(id: string): Promise> { + const contents = await fsp.readFile(path.join(WorkDir, 'runs', `${id}.jsonl`), 'utf8'); + const events = contents.split('\n') + .filter(line => line.trim() !== '') + .map(line => RunEvent.parse(JSON.parse(line))); + if (events.length === 0 || events[0].type !== 'start') { + throw new Error('Corrupt run data'); + } + return { + id, + createdAt: events[0].ts!, + agentId: events[0].agentName, + log: events, + }; + } +} \ No newline at end of file diff --git a/apps/cli/src/runs/runs.ts b/apps/cli/src/runs/runs.ts new file mode 100644 index 00000000..e4f8dc84 --- /dev/null +++ b/apps/cli/src/runs/runs.ts @@ -0,0 +1,70 @@ +import z from "zod"; +import container from "../di/container.js"; +import { IMessageQueue } from "../application/lib/message-queue.js"; +import { AskHumanResponseEvent, RunEvent, ToolPermissionResponseEvent } from "../entities/run-events.js"; +import { CreateRunOptions, IRunsRepo } from "./repo.js"; +import { IAgentRuntime } from "../agents/runtime.js"; +import { IBus } from "../application/lib/bus.js"; + +export const ToolPermissionAuthorizePayload = ToolPermissionResponseEvent.pick({ + subflow: true, + toolCallId: true, + response: true, +}); + +export const AskHumanResponsePayload = AskHumanResponseEvent.pick({ + subflow: true, + toolCallId: true, + response: true, +}); + +export const Run = z.object({ + id: z.string(), + createdAt: z.iso.datetime(), + agentId: z.string(), + log: z.array(RunEvent), +}); + +export async function createRun(opts: z.infer): Promise> { + const repo = container.resolve('runsRepo'); + const bus = container.resolve('bus'); + const run = await repo.create(opts); + await bus.publish(run.log[0]); + return run; +} + +export async function createMessage(runId: string, message: string): Promise { + const queue = container.resolve('messageQueue'); + const id = await queue.enqueue(runId, message); + const runtime = container.resolve('agentRuntime'); + runtime.trigger(runId); + return id; +} + +export async function authorizePermission(runId: string, ev: z.infer): Promise { + const repo = container.resolve('runsRepo'); + const event: z.infer = { + ...ev, + runId, + type: "tool-permission-response", + }; + await repo.appendEvents(runId, [event]); + const runtime = container.resolve('agentRuntime'); + runtime.trigger(runId); +} + +export async function replyToHumanInputRequest(runId: string, ev: z.infer): Promise { + const repo = container.resolve('runsRepo'); + const event: z.infer = { + ...ev, + runId, + type: "ask-human-response", + }; + await repo.appendEvents(runId, [event]); + const runtime = container.resolve('agentRuntime'); + runtime.trigger(runId); +} + +export async function stop(runId: string): Promise { + throw new Error('Not implemented'); +} \ No newline at end of file diff --git a/apps/cli/src/server.ts b/apps/cli/src/server.ts new file mode 100644 index 00000000..8bf71d1d --- /dev/null +++ b/apps/cli/src/server.ts @@ -0,0 +1,653 @@ +import { Hono } from 'hono'; +import { serve } from '@hono/node-server' +import { streamSSE } from 'hono/streaming' +import { describeRoute, validator, resolver, openAPIRouteHandler } from "hono-openapi" +import z from 'zod'; +import container from './di/container.js'; +import { executeTool, listServers, listTools, ListToolsResponse, McpServerList } from "./mcp/mcp.js"; +import { McpServerDefinition } from "./mcp/mcp.js"; +import { IMcpConfigRepo } from './mcp/repo.js'; +import { IModelConfigRepo } from './models/repo.js'; +import { ModelConfig, Provider } from "./models/models.js"; +import { IAgentsRepo } from "./agents/repo.js"; +import { Agent } from "./agents/agents.js"; +import { AskHumanResponsePayload, authorizePermission, createMessage, createRun, replyToHumanInputRequest, Run, stop, ToolPermissionAuthorizePayload } from './runs/runs.js'; +import { IRunsRepo, ListRunsResponse, CreateRunOptions } from './runs/repo.js'; +import { IBus } from './application/lib/bus.js'; +import { RunEvent } from './entities/run-events.js'; + +let id = 0; + +const routes = new Hono() + .get( + '/health', + describeRoute({ + summary: 'Health check', + description: 'Check if the server is running', + responses: { + 200: { + description: 'Server is running', + content: { + 'application/json': { + schema: resolver(z.object({ + status: z.literal("ok"), + })), + }, + }, + }, + }, + }), + async (c) => { + return c.json({ status: 'ok' }); + } + ) + .get( + '/mcp', + describeRoute({ + summary: 'List MCP servers', + description: 'List the MCP servers', + responses: { + 200: { + description: 'Server list', + content: { + 'application/json': { + schema: resolver(McpServerList), + }, + }, + }, + }, + }), + async (c) => { + return c.json(await listServers()); + } + ) + .put( + '/mcp/:serverName', + describeRoute({ + summary: 'Upsert MCP server', + description: 'Add or edit MCP server', + responses: { + 200: { + description: 'MCP server added / updated', + content: { + 'application/json': { + schema: resolver(z.object({ + success: z.literal(true), + })), + }, + }, + }, + }, + }), + validator('param', z.object({ + serverName: z.string(), + })), + validator('json', McpServerDefinition), + async (c) => { + const repo = container.resolve('mcpConfigRepo'); + await repo.upsert(c.req.valid('param').serverName, c.req.valid('json')); + return c.json({ success: true }); + } + ) + .delete( + '/mcp/:serverName', + describeRoute({ + summary: 'Delete MCP server', + description: 'Delete a MCP server', + responses: { + 200: { + description: 'MCP server deleted', + content: { + 'application/json': { + schema: resolver(z.object({ + success: z.literal(true), + })), + }, + }, + }, + }, + }), + validator('param', z.object({ + serverName: z.string(), + })), + async (c) => { + const repo = container.resolve('mcpConfigRepo'); + await repo.delete(c.req.valid('param').serverName); + return c.json({ success: true }); + } + ) + .get( + '/mcp/:serverName/tools', + describeRoute({ + summary: 'Get MCP tools', + description: 'Get the MCP tools', + responses: { + 200: { + description: 'MCP tools', + content: { + 'application/json': { + schema: resolver(ListToolsResponse), + }, + }, + }, + }, + }), + validator('query', z.object({ + cursor: z.string().optional(), + })), + validator('param', z.object({ + serverName: z.string(), + })), + async (c) => { + const result = await listTools(c.req.valid('param').serverName, c.req.valid('query').cursor); + return c.json(result); + } + ) + .post( + '/mcp/:serverName/tools/:toolName/execute', + describeRoute({ + summary: 'Execute MCP tool', + description: 'Execute a MCP tool', + responses: { + 200: { + description: 'Tool executed', + content: { + 'application/json': { + schema: resolver(z.object({ + result: z.any(), + })), + }, + }, + }, + }, + }), + validator('param', z.object({ + serverName: z.string(), + toolName: z.string(), + })), + validator('json', z.object({ + input: z.any(), + })), + async (c) => { + const result = await executeTool( + c.req.valid('param').serverName, + c.req.valid('param').toolName, + c.req.valid('json').input + ); + return c.json(result); + } + ) + .get( + '/models', + describeRoute({ + summary: 'Get model config', + description: 'Get the current model and provider configuration', + responses: { + 200: { + description: 'Model config', + content: { + 'application/json': { + schema: resolver(ModelConfig), + }, + }, + }, + }, + }), + async (c) => { + const repo = container.resolve('modelConfigRepo'); + const config = await repo.getConfig(); + return c.json(config); + } + ) + .put( + '/models/providers/:providerName', + describeRoute({ + summary: 'Upsert provider config', + description: 'Add or update a provider configuration', + responses: { + 200: { + description: 'Provider upserted', + content: { + 'application/json': { + schema: resolver(z.object({ + success: z.literal(true), + })), + }, + }, + }, + }, + }), + validator('param', z.object({ + providerName: z.string(), + })), + validator('json', Provider), + async (c) => { + const repo = container.resolve('modelConfigRepo'); + await repo.upsert(c.req.valid('param').providerName, c.req.valid('json')); + return c.json({ success: true }); + } + ) + .delete( + '/models/providers/:providerName', + describeRoute({ + summary: 'Delete provider config', + description: 'Delete a provider configuration', + responses: { + 200: { + description: 'Provider deleted', + content: { + 'application/json': { + schema: resolver(z.object({ + success: z.literal(true), + })), + }, + }, + }, + }, + }), + validator('param', z.object({ + providerName: z.string(), + })), + async (c) => { + const repo = container.resolve('modelConfigRepo'); + await repo.delete(c.req.valid('param').providerName); + return c.json({ success: true }); + } + ) + .put( + '/models/default', + describeRoute({ + summary: 'Set default model', + description: 'Set the default provider and model', + responses: { + 200: { + description: 'Default set', + content: { + 'application/json': { + schema: resolver(z.object({ + success: z.literal(true), + })), + }, + }, + }, + }, + }), + validator('json', z.object({ + provider: z.string(), + model: z.string(), + })), + async (c) => { + const repo = container.resolve('modelConfigRepo'); + const body = c.req.valid('json'); + await repo.setDefault(body.provider, body.model); + return c.json({ success: true }); + } + ) + // GET /agents + .get( + '/agents', + describeRoute({ + summary: 'List agents', + description: 'List all configured agents', + responses: { + 200: { + description: 'Agents list', + content: { + 'application/json': { + schema: resolver(z.array(Agent)), + }, + }, + }, + }, + }), + async (c) => { + const repo = container.resolve('agentsRepo'); + const agents = await repo.list(); + return c.json(agents); + } + ) + // POST /agents/new + .post( + '/agents/new', + describeRoute({ + summary: 'Create agent', + description: 'Create a new agent', + responses: { + 200: { + description: 'Agent created', + content: { + 'application/json': { + schema: resolver(z.object({ + success: z.literal(true), + })), + }, + }, + }, + }, + }), + validator('json', Agent), + async (c) => { + const repo = container.resolve('agentsRepo'); + await repo.create(c.req.valid('json')); + return c.json({ success: true }); + } + ) + // GET /agents/ + .get( + '/agents/:id', + describeRoute({ + summary: 'Get agent', + description: 'Fetch a specific agent by id', + responses: { + 200: { + description: 'Agent', + content: { + 'application/json': { + schema: resolver(Agent), + }, + }, + }, + }, + }), + validator('param', z.object({ + id: z.string(), + })), + async (c) => { + const repo = container.resolve('agentsRepo'); + const agent = await repo.fetch(c.req.valid('param').id); + return c.json(agent); + } + ) + // PUT /agents/ + .put( + '/agents/:id', + describeRoute({ + summary: 'Update agent', + description: 'Update an existing agent', + responses: { + 200: { + description: 'Agent updated', + content: { + 'application/json': { + schema: resolver(z.object({ + success: z.literal(true), + })), + }, + }, + }, + }, + }), + validator('param', z.object({ + id: z.string(), + })), + validator('json', Agent), + async (c) => { + const repo = container.resolve('agentsRepo'); + await repo.update(c.req.valid('param').id, c.req.valid('json')); + return c.json({ success: true }); + } + ) + // DELETE /agents/ + .delete( + '/agents/:id', + describeRoute({ + summary: 'Delete agent', + description: 'Delete an agent by id', + responses: { + 200: { + description: 'Agent deleted', + content: { + 'application/json': { + schema: resolver(z.object({ + success: z.literal(true), + })), + }, + }, + }, + }, + }), + validator('param', z.object({ + id: z.string(), + })), + async (c) => { + const repo = container.resolve('agentsRepo'); + await repo.delete(c.req.valid('param').id); + return c.json({ success: true }); + } + ) + .get( + '/runs/:runId', + describeRoute({ + summary: 'Get run', + description: 'Get a run by id', + responses: { + 200: { + description: 'Run', + content: { + 'application/json': { + schema: resolver(Run), + }, + }, + }, + }, + }), + validator('param', z.object({ + runId: z.string(), + })), + async (c) => { + const repo = container.resolve('runsRepo'); + const run = await repo.fetch(c.req.valid('param').runId); + return c.json(run); + } + ) + .post( + '/runs/new', + describeRoute({ + summary: 'Create run', + description: 'Create a new run', + responses: { + 200: { + description: 'Run created', + content: { + 'application/json': { + schema: resolver(Run), + }, + }, + }, + }, + }), + validator('json', CreateRunOptions), + async (c) => { + const run = await createRun(c.req.valid('json')); + return c.json(run); + } + ) + .post( + '/runs/:runId/messages/new', + describeRoute({ + summary: 'Create a new message', + description: 'Create a new message', + responses: { + 200: { + description: 'Message created', + content: { + 'application/json': { + schema: resolver(z.object({ + messageId: z.string(), + })), + }, + }, + }, + }, + }), + validator('param', z.object({ + runId: z.string(), + })), + validator('json', z.object({ + message: z.string(), + })), + async (c) => { + const messageId = await createMessage(c.req.valid('param').runId, c.req.valid('json').message); + return c.json({ + messageId, + }); + } + ) + .post( + '/runs/:runId/permissions/authorize', + describeRoute({ + summary: 'Authorize permission', + description: 'Authorize a permission', + responses: { + 200: { + description: 'Permission authorized', + content: { + 'application/json': { + schema: resolver(z.object({ + success: z.literal(true), + })), + }, + } + }, + }, + }), + validator('param', z.object({ + runId: z.string(), + })), + validator('json', ToolPermissionAuthorizePayload), + async (c) => { + const response = await authorizePermission( + c.req.valid('param').runId, + c.req.valid('json') + ); + return c.json({ + success: true, + }); + } + ) + .post( + '/runs/:runId/human-input-requests/:requestId/reply', + describeRoute({ + summary: 'Reply to human input request', + description: 'Reply to a human input request', + responses: { + 200: { + description: 'Human input request replied', + }, + }, + }), + validator('param', z.object({ + runId: z.string(), + })), + validator('json', AskHumanResponsePayload), + async (c) => { + const response = await replyToHumanInputRequest( + c.req.valid('param').runId, + c.req.valid('json') + ); + return c.json({ + success: true, + }); + } + ) + .post( + '/runs/:runId/stop', + describeRoute({ + summary: 'Stop run', + description: 'Stop a run', + responses: { + 200: { + description: 'Run stopped', + }, + }, + }), + validator('param', z.object({ + runId: z.string(), + })), + async (c) => { + const response = await stop(c.req.valid('param').runId); + return c.json({ + success: true, + }); + } + ) + .get( + '/stream', + async (c) => { + return streamSSE(c, async (stream) => { + const bus = container.resolve('bus'); + + let id = 0; + let unsub: (() => void) | null = null; + let aborted = false; + + stream.onAbort(() => { + aborted = true; + if (unsub) { + unsub(); + } + }); + + // Subscribe to your bus + unsub = await bus.subscribe('*', async (event) => { + if (aborted) return; + + console.log('got ev', event); + await stream.writeSSE({ + data: JSON.stringify(event), + event: "message", + id: String(id++), + }); + }); + + // Keep the function alive until the client disconnects + while (!aborted) { + await stream.sleep(1000); // any interval is fine + } + }); + } + ) + .get('/sse', async (c) => { + return streamSSE(c, async (stream) => { + while (true) { + const message = `It is ${new Date().toISOString()}` + await stream.writeSSE({ + data: message, + event: 'time-update', + id: String(id++), + }) + await stream.sleep(1000) + } + }) + }) + ; + +const app = new Hono() + .route("/", routes) + .get( + "/openapi.json", + openAPIRouteHandler(routes, { + documentation: { + info: { + title: "Hono", + version: "1.0.0", + description: "RowboatX API", + }, + }, + }), + ); + +// export default app; + +serve({ + fetch: app.fetch, + port: Number(process.env.PORT) || 3000, +}); + +// GET /skills +// POST /skills/new +// GET /skills/ +// PUT /skills/ +// DELETE /skills/ + +// GET /sse \ No newline at end of file