diff --git a/README.md b/README.md index 6377eea..c79de6e 100644 --- a/README.md +++ b/README.md @@ -19,11 +19,12 @@ gh skills clone pelikhan/rig ```ts import { agent, + configureAgent, defineTool, p, s, } from "rig"; -import { addons, oncePerSession, repair, steering, timeout } from "rig/addons"; +import { addons, oncePerAgent, repair, steering, timeout } from "rig/addons"; ``` - `agent(spec)` creates a typed agent function. @@ -31,10 +32,11 @@ import { addons, oncePerSession, repair, steering, timeout } from "rig/addons"; - `p.*` creates declarative prompt intents for prompt templates or inputs. - `p()` and ``p`...` `` create a prompt builder with `var`, `write`, and `region` primitives for assembling prompts. - ``p`...` `` also works in `instructions` to embed prompt intents directly: `` instructions: p`Review ${p.bash("git status")}` ``. -- `defineTool(name, config)` matches the Copilot SDK helper and accepts rig `s.*` schemas for `parameters`. -- `addons` accepts express-like `(context, next)` turn addons for steering, inline validation, and Copilot session access. +- `configureAgent(factory)` selects the SDK adapter used to instantiate runtime agents. +- `defineTool(name, config)` creates an SDK-neutral tool definition and accepts rig `s.*` schemas for `parameters`. +- `addons` accepts express-like `(context, next)` turn addons for steering, inline validation, and runtime agent access. - `rig` starts with no default addons. -- `rig/addons` provides optional addon helpers: `oncePerSession`, `repair`, `steering`, `timeout`, and `addons.{oncePerSession,repair,steering,timeout}`. +- `rig/addons` provides optional addon helpers: `oncePerAgent`, `repair`, `steering`, `timeout`, and `addons.{oncePerAgent,repair,steering,timeout}`. - `p\`...\`` returns a prompt builder and renders intent values when coerced to string; prefer `${p.read(...)}` / `${p.bash(...)}` when the context source is already known. ## Embedding in markdown @@ -188,19 +190,17 @@ const review = agent({ }); ``` -For direct SDK access, use `oncePerSession(...)` to register with the session once: +Use `oncePerAgent(...)` to register with the runtime agent once: ```ts const review = agent({ - addons: oncePerSession((session) => { - session.on?.((event) => { - // custom event handling - }); + addons: oncePerAgent((runtimeAgent) => { + // Register adapter-specific behavior here. }), }); ``` -Per-turn addons still receive `context.session` directly, and you can also register addons after creating the agent: +Per-turn addons receive `context.agent`, and you can also register addons after creating the agent: ```ts const timingAddon = async (context, next) => { @@ -211,9 +211,40 @@ const review = agent({}); review.use(timingAddon); ``` -## Copilot SDK runtime +## Agent implementations -`rig` is specialized for Copilot SDK sessions inside sandboxed agentic workflows. +Rig runs only against a minimal interface: + +```ts +interface Agent { + ask(prompt: string, options?: { signal?: AbortSignal }): Promise; + close(): Promise; +} +``` + +Configure any SDK adapter by passing a factory that creates one `Agent` per invocation: + +```ts +configureAgent(async ({ model, systemMessage, tools }) => { + return new MySdkAgent({ model, systemMessage, tools }); +}); +``` + +Rig includes factories for Copilot, pi-agent, and Anthropic: + +```ts +import { configureAgent } from "rig"; +import { anthropicEngine } from "rig/engines/anthropic"; +import { piEngine } from "rig/engines/pi"; + +configureAgent(piEngine({ provider: "anthropic" })); +// or, using ANTHROPIC_API_KEY: +configureAgent(anthropicEngine()); +``` + +`piEngine()` uses the maintained `@earendil-works/pi-agent-core` package and requires the provider for model lookup. `anthropicEngine()` uses `@anthropic-ai/sdk`. Both adapters preserve conversation state across repair turns and map Rig tools to their SDK tool runners. + +## Copilot SDK adapter By default it connects to an already-running Copilot server via HTTP (`COPILOT_SDK_URI`, then `localhost:7777`). Pass `--server` to spawn the server over stdio when launching a program. diff --git a/package-lock.json b/package-lock.json index 6158bc8..720dad0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,6 +6,9 @@ "": { "name": "rig", "devDependencies": { + "@anthropic-ai/sdk": "^0.113.0", + "@earendil-works/pi-agent-core": "^0.81.1", + "@earendil-works/pi-ai": "^0.81.1", "@github/copilot-sdk": "^1.0.0-beta.9", "@types/node": "^25.9.1", "typescript": "^5.8.0", @@ -13,6 +16,560 @@ "zx": "^8.8.5" } }, + "node_modules/@anthropic-ai/sdk": { + "version": "0.113.0", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.113.0.tgz", + "integrity": "sha512-8YKIP333ypmy8QjtSP691fIqdes3HL/rSXAT4FDZX/HRnkQaNG+iOVBfEQw4vjPNpYHeoy7FSYc2o/jb8b3AnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-schema-to-ts": "^3.1.1", + "standardwebhooks": "^1.0.0" + }, + "bin": { + "anthropic-ai-sdk": "bin/cli" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/client-bedrock-runtime": { + "version": "3.1048.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-runtime/-/client-bedrock-runtime-3.1048.0.tgz", + "integrity": "sha512-u+NT61JZEkRFtpL0CAw1N1dwxnaLgwVXQl/zjJxTGgLyS/jTIdg2SdoEoCTHxgDyCnqa1HEi9QOoE9/pYRNpOQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.974.11", + "@aws-sdk/credential-provider-node": "^3.972.42", + "@aws-sdk/eventstream-handler-node": "^3.972.16", + "@aws-sdk/middleware-eventstream": "^3.972.12", + "@aws-sdk/middleware-websocket": "^3.972.19", + "@aws-sdk/token-providers": "3.1048.0", + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/fetch-http-handler": "^5.4.2", + "@smithy/node-http-handler": "^4.7.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.976.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.976.0.tgz", + "integrity": "sha512-0cjRaEdlVoOrsNb9pP5q1Syyc8pXw5xSj2Np2ryReRTr9FppIIRVSdZK4lbnfmc2Hvgux/xBOUU6baB7z8//uA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.974.2", + "@aws-sdk/xml-builder": "^3.972.36", + "@aws/lambda-invoke-store": "^0.3.0", + "@smithy/core": "^3.29.4", + "@smithy/signature-v4": "^5.6.5", + "@smithy/types": "^4.16.1", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.972.60", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.60.tgz", + "integrity": "sha512-BAkxdoe7tpDDqCghGpuOeHQRbm/2znVvOQm0AvpQbA2tbfMN46doN4zx65fv85ImP3KADwc2zQPmbrlI9MPfMg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.976.0", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.4", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.972.62", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.62.tgz", + "integrity": "sha512-g/0fGqKTb9xpKdd9AtpmV5Eo3DFKbnkpA2+w0peISSlu7NfAoWOuYBFxsu+yWBtxU89ka55ezoZBCbFaS8pjYQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.976.0", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.4", + "@smithy/fetch-http-handler": "^5.6.6", + "@smithy/node-http-handler": "^4.9.6", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/node-http-handler": { + "version": "4.9.9", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.9.9.tgz", + "integrity": "sha512-xVBZ3hptB99iNO9XyWqEhC7KD9bP9UPXhuy3h5Y2ItCfBv160D9IIC/Fmmp3EbnWwit4C+KVqlSE+E29Nk/pPg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.29.7", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.973.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.5.tgz", + "integrity": "sha512-ylubazcRfq2TVus/qXucSXeC42Qdjp5HQxTu68K/BsdMiZlcSLD1zkpoCgApXZX1Y6YJhtGGs7ZHhO/GuIgBlw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.976.0", + "@aws-sdk/credential-provider-env": "^3.972.60", + "@aws-sdk/credential-provider-http": "^3.972.62", + "@aws-sdk/credential-provider-login": "^3.972.67", + "@aws-sdk/credential-provider-process": "^3.972.60", + "@aws-sdk/credential-provider-sso": "^3.973.4", + "@aws-sdk/credential-provider-web-identity": "^3.972.66", + "@aws-sdk/nested-clients": "^3.997.34", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.4", + "@smithy/credential-provider-imds": "^4.4.9", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.972.67", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.67.tgz", + "integrity": "sha512-CCygIKJ9YbI3n84OClSaSppkgKKHVj2TGT33c6FRORZrYNZQ1POmD+ip0FLYokiJAK7sSdc3YVkOsBm90oxWMQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.976.0", + "@aws-sdk/nested-clients": "^3.997.34", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.4", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.972.71", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.71.tgz", + "integrity": "sha512-HIg7Q2osBzajQwL+1Vkyh2E7Gim3eTNb9RHIsOxDGjW0eZg4oEKtRs5sioCnc73ilhaOm4gX2lHVF8J7+nt2rg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "^3.972.60", + "@aws-sdk/credential-provider-http": "^3.972.62", + "@aws-sdk/credential-provider-ini": "^3.973.5", + "@aws-sdk/credential-provider-process": "^3.972.60", + "@aws-sdk/credential-provider-sso": "^3.973.4", + "@aws-sdk/credential-provider-web-identity": "^3.972.66", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.4", + "@smithy/credential-provider-imds": "^4.4.9", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.972.60", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.60.tgz", + "integrity": "sha512-YIo3f99hM43QdYG8hDzwGemnR/pU95b0kramqSJUTleCqaB7+HwKf7YZFHqvOgTqZTPx/mRmNIqoDRr3U0Z3Tw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.976.0", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.4", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.973.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.4.tgz", + "integrity": "sha512-BPdmL8sSBOCv4ngZ+3LHxyc3CNqDCEK37CHioCk7zGrTMY5sUtkH8q+o6qA80nn6w3/fyBPGNE7OIRlmoOxRQA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.976.0", + "@aws-sdk/nested-clients": "^3.997.34", + "@aws-sdk/token-providers": "3.1092.0", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.4", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers": { + "version": "3.1092.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1092.0.tgz", + "integrity": "sha512-hBYUAr6iBLNFcsiWTgtBb0stdSw39VOUq4Sp4A5caCNf66BAZplWN4FleKrVpJx5li2YgdnK2DqoFSMWC642FQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.976.0", + "@aws-sdk/nested-clients": "^3.997.34", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.4", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.972.66", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.66.tgz", + "integrity": "sha512-kSAziJboOmZmsR9/MTbiNjowl2BPes1bQuJpne4qAZ62ubi8fjfr/aupJSQje6udBoYxXTQbsL0e0kby2la3ng==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.976.0", + "@aws-sdk/nested-clients": "^3.997.34", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.4", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/eventstream-handler-node": { + "version": "3.972.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-handler-node/-/eventstream-handler-node-3.972.29.tgz", + "integrity": "sha512-t3tKQRTVXsI2QNPE3CaNjHl0wRO9Xi3acZkAyti2RQsiFmZ9Gi0kArX2ighlRJ1BtDVuul413gThAgzyTfgmWA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.4", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-eventstream": { + "version": "3.972.24", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-eventstream/-/middleware-eventstream-3.972.24.tgz", + "integrity": "sha512-oykin4mDWxNOuYQ7SF1cHzgYeuFEkF4cdRwgvjFFbIklkx09qIFBiOgsORafG9sXZFO3TayMmQuAQYgADXhI8w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.4", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-websocket": { + "version": "3.972.42", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-websocket/-/middleware-websocket-3.972.42.tgz", + "integrity": "sha512-dw+GP8DC7QC2C8tUoK7DI8BnrNAjz8tb+uBHSrD2qJvxkCf58kTtFr98pljSrk+umU4n4HDW4eU2k7C2dWMzsg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.976.0", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.4", + "@smithy/fetch-http-handler": "^5.6.6", + "@smithy/signature-v4": "^5.6.5", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.997.34", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.34.tgz", + "integrity": "sha512-Y9REVrSwmLM+Qy6sZJ7ofMC2S3Hr3tPP/4CzL5U1olPP7OGoF+6+Px0E49cVQBtSxJtyeLJMf0UaBErfeSahAA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.976.0", + "@aws-sdk/signature-v4-multi-region": "^3.996.41", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.4", + "@smithy/fetch-http-handler": "^5.6.6", + "@smithy/node-http-handler": "^4.9.6", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/node-http-handler": { + "version": "4.9.9", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.9.9.tgz", + "integrity": "sha512-xVBZ3hptB99iNO9XyWqEhC7KD9bP9UPXhuy3h5Y2ItCfBv160D9IIC/Fmmp3EbnWwit4C+KVqlSE+E29Nk/pPg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.29.7", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.996.41", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.41.tgz", + "integrity": "sha512-QMUytg+FQMGouc8gHS00KoYih3+N6cqmVI/pQGOIo7Nr7OpQaiXjSYOuL+vsPZ1tymY4LAQ8MYcHJmws5LRxng==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.974.2", + "@smithy/signature-v4": "^5.6.5", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.1048.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1048.0.tgz", + "integrity": "sha512-k0y/GcuesuSfWyUM0WamrGyeZmltRYaPbHO82UDA6mZ/doB+FOHKutikPAtSXMn/hDz970cF+iRuuiYO9VEbAA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.11", + "@aws-sdk/nested-clients": "^3.997.9", + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.974.2", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.974.2.tgz", + "integrity": "sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.965.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.8.tgz", + "integrity": "sha512-uUbMs1cBZPafD0ohUj6EwNf0fPZ534NvBxHox4hjX+0Rxq5paSYUem7+hi833pYrzrcnBATKIYpR02MDXT5M9g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.972.36", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.36.tgz", + "integrity": "sha512-RdGmS1GLrtaTOLE1ElSluMldNrpk9Emq6uYs8SS8iHlu5xTAmM9rRkM91o48+rIRryBtyO9t+uLYCoMG6jVMVA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.3.0.tgz", + "integrity": "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@earendil-works/pi-agent-core": { + "version": "0.81.1", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.81.1.tgz", + "integrity": "sha512-yqbh68CyhqxMov/jUogFJfMqlu2Gd37GAki+tr59YCmAPHfomiCA5ESzusXtpGzABeiZFC/OrRdQ4GwCCOMIHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@earendil-works/pi-ai": "^0.81.1", + "ignore": "7.0.5", + "typebox": "1.1.38", + "yaml": "2.9.0" + }, + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@earendil-works/pi-ai": { + "version": "0.81.1", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.81.1.tgz", + "integrity": "sha512-hzHE7Z8l5mgJk+ke67Lge0rwS2+wbKJrFKl9o5M1R1rh33+cCT7D1AHz1OAtX5wFs90E1/BTGhyJRTUHaMxGvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@anthropic-ai/sdk": "0.91.1", + "@aws-sdk/client-bedrock-runtime": "3.1048.0", + "@google/genai": "1.52.0", + "@mistralai/mistralai": "2.2.6", + "@opentelemetry/api": "1.9.0", + "@smithy/node-http-handler": "4.7.3", + "http-proxy-agent": "7.0.2", + "https-proxy-agent": "7.0.6", + "openai": "6.26.0", + "partial-json": "0.1.7", + "typebox": "1.1.38" + }, + "bin": { + "pi-ai": "dist/cli.js" + }, + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@earendil-works/pi-ai/node_modules/@anthropic-ai/sdk": { + "version": "0.91.1", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.91.1.tgz", + "integrity": "sha512-LAmu761tSN9r66ixvmciswUj/ZC+1Q4iAfpedTfSVLeswRwnY3n2Nb6Tsk+cLPP28aLOPWeMgIuTuCcMC6W/iw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-schema-to-ts": "^3.1.1" + }, + "bin": { + "anthropic-ai-sdk": "bin/cli" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, "node_modules/@emnapi/core": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", @@ -221,6 +778,31 @@ "copilot-win32-x64": "copilot.exe" } }, + "node_modules/@google/genai": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.52.0.tgz", + "integrity": "sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "google-auth-library": "^10.3.0", + "p-retry": "^4.6.2", + "protobufjs": "^7.5.4", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@modelcontextprotocol/sdk": "^1.25.2" + }, + "peerDependenciesMeta": { + "@modelcontextprotocol/sdk": { + "optional": true + } + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", @@ -228,6 +810,27 @@ "dev": true, "license": "MIT" }, + "node_modules/@mistralai/mistralai": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-2.2.6.tgz", + "integrity": "sha512-W8pX7zHxjJvMIpw8JMxeJEleapXX0Q9NPszdNzqkM3MIEoIGPObdodujj+WHteXEvGfaP/AMwlNyRfEzSY6dQQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.40.0", + "ws": "^8.18.0", + "zod": "^3.25.0 || ^4.0.0", + "zod-to-json-schema": "^3.25.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + } + } + }, "node_modules/@napi-rs/wasm-runtime": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", @@ -247,6 +850,26 @@ "@emnapi/runtime": "^1.7.1" } }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.43.0.tgz", + "integrity": "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, "node_modules/@oxc-project/types": { "version": "0.137.0", "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.137.0.tgz", @@ -257,6 +880,72 @@ "url": "https://github.com/sponsors/Boshen" } }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", + "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", + "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.2.tgz", + "integrity": "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/@rolldown/binding-android-arm64": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.3.tgz", @@ -539,6 +1228,141 @@ "dev": true, "license": "MIT" }, + "node_modules/@smithy/core": { + "version": "3.29.7", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.29.7.tgz", + "integrity": "sha512-BiEE2bnnGoPKdlGe3L+gOYORDHFGPuYVRLP7iUow/Sflm0B4hC4XY3FC1MRuc7ltzpW2xNnXopKi34TTkULlKQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.4.12.tgz", + "integrity": "sha512-ZZPDbl/aRp77aycuoMlo3BTayT4CE2a3uoqETYZU5ySnVbhpl5IJiY7dCZedn+ZusyDLqVv44IvKBiXd2/nK0Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.29.7", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.6.9", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.6.9.tgz", + "integrity": "sha512-EJktha5m5MXCwzdXrlWyqb9UCNHNFKlg+PmTpRsdX3dncJPTiqYleM9OKj2mLgdVJHR01d2tU4alG+z2NdH5rQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.29.7", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.7.3", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.7.3.tgz", + "integrity": "sha512-/jPhevcTFPMVl6KNjbaI47iOg1zxC7IsnX4PQDGVZKMFceOXtB8IEYaB7a9VvkP/3oC60WzTeKocvSI7vLT0vA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.3", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.6.8", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.6.8.tgz", + "integrity": "sha512-iGBm6hIwD2MGvVRSgrjVWa4FXtXDq3akxu0DCpnkmBo0xtEHZ/siMRt7ycfZAefYr2UdywUgmGtoRLaq5u56pg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.29.7", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.16.1.tgz", + "integrity": "sha512-0JFs3V2y2M9tKW5na/qxe69Zv+uxLMO7QBbhxF/FHu/Gp2NFZAAL9tWl9PU02xxo07pb3G9FTyjNc6D5uZrJIg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@stablelib/base64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz", + "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@standard-schema/spec": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", @@ -592,6 +1416,13 @@ "undici-types": ">=7.24.0 <7.24.7" } }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "dev": true, + "license": "MIT" + }, "node_modules/@vitest/expect": { "version": "4.1.7", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.7.tgz", @@ -705,16 +1536,71 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "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/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=12" + "node": "*" } }, + "node_modules/bowser": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/chai": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", @@ -732,6 +1618,34 @@ "dev": true, "license": "MIT" }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -742,6 +1656,16 @@ "node": ">=8" } }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/es-module-lexer": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", @@ -769,6 +1693,20 @@ "node": ">=12.0.0" } }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-sha256": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz", + "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==", + "dev": true, + "license": "Unlicense" + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -787,6 +1725,43 @@ } } }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -802,6 +1777,149 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/gaxios": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.2.0.tgz", + "integrity": "sha512-CUVb4wcYe+771XevyH6HtGmXFAGGKkIC3kswAP8Z1JCe0j80JMaTPZH930DWFrvo0atjh18Arc0pEyUCWa5bfg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gcp-metadata": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", + "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^7.0.0", + "google-logging-utils": "^1.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/google-auth-library": { + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.9.0.tgz", + "integrity": "sha512-xtvUqvINPhTaBm7nXqlYPcrMHJPm1lCNdSovxnKKhTm+4JsvQ+KGVYJViLoH9Yxu8w+T0Qv5HubzYT9BLrppJg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^7.1.4", + "gcp-metadata": "8.1.2", + "google-logging-utils": "1.1.3", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/google-logging-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", + "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/json-schema-to-ts": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", + "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "ts-algebra": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/lightningcss": { "version": "1.32.0", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", @@ -1063,6 +2181,13 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -1073,6 +2198,13 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, "node_modules/nanoid": { "version": "3.3.12", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", @@ -1092,6 +2224,46 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, "node_modules/obug": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", @@ -1103,6 +2275,49 @@ ], "license": "MIT" }, + "node_modules/openai": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.26.0.tgz", + "integrity": "sha512-zd23dbWTjiJ6sSAX6s0HrCZi41JwTA1bQVs0wLQPZ2/5o2gxOJA5wh7yOAUgwYybfhDXyhwlpeQf7Mlgx8EOCA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/partial-json": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/partial-json/-/partial-json-0.1.7.tgz", + "integrity": "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==", + "dev": true, + "license": "MIT" + }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -1159,6 +2374,40 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/protobufjs": { + "version": "7.6.5", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.5.tgz", + "integrity": "sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==", + "dev": true, + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.1", + "@protobufjs/fetch": "^1.1.1", + "@protobufjs/float": "^1.0.2", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", + "@types/node": ">=13.7.0", + "long": "^5.3.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/rolldown": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.3.tgz", @@ -1193,6 +2442,27 @@ "@rolldown/binding-win32-x64-msvc": "1.1.3" } }, + "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==", + "dev": true, + "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/siginfo": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", @@ -1217,6 +2487,17 @@ "dev": true, "license": "MIT" }, + "node_modules/standardwebhooks": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/standardwebhooks/-/standardwebhooks-1.0.0.tgz", + "integrity": "sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@stablelib/base64": "^1.0.0", + "fast-sha256": "^1.3.0" + } + }, "node_modules/std-env": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", @@ -1268,13 +2549,26 @@ "node": ">=14.0.0" } }, + "node_modules/ts-algebra": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", + "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", + "dev": true, + "license": "MIT" + }, "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==", "dev": true, - "license": "0BSD", - "optional": true + "license": "0BSD" + }, + "node_modules/typebox": { + "version": "1.1.38", + "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.1.38.tgz", + "integrity": "sha512-pZ0aQPmMmXoUvSbeuWf/Hzsc+avNw/Zd6VeE8CFgkVGWyuHPJvqeJJDeJqLve+K70LvjYIoleGcoJHPT17cWoA==", + "dev": true, + "license": "MIT" }, "node_modules/typescript": { "version": "5.9.3", @@ -1475,6 +2769,16 @@ "node": ">=14.0.0" } }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/why-is-node-running": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", @@ -1492,6 +2796,44 @@ "node": ">=8" } }, + "node_modules/ws": { + "version": "8.21.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz", + "integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, "node_modules/zod": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", @@ -1502,6 +2844,16 @@ "url": "https://github.com/sponsors/colinhacks" } }, + "node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "dev": true, + "license": "ISC", + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } + }, "node_modules/zx": { "version": "8.8.5", "resolved": "https://registry.npmjs.org/zx/-/zx-8.8.5.tgz", diff --git a/package.json b/package.json index 3be2c41..d9ad02e 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,9 @@ "type": "module", "exports": { ".": "./skills/rig/rig.ts", - "./addons": "./skills/rig/addons.ts" + "./addons": "./skills/rig/addons.ts", + "./engines/anthropic": "./skills/rig/engines/anthropic.ts", + "./engines/pi": "./skills/rig/engines/pi.ts" }, "scripts": { "test": "vitest run src/", @@ -14,6 +16,9 @@ "typecheck": "tsc --noEmit" }, "devDependencies": { + "@anthropic-ai/sdk": "^0.113.0", + "@earendil-works/pi-agent-core": "^0.81.1", + "@earendil-works/pi-ai": "^0.81.1", "@github/copilot-sdk": "^1.0.0-beta.9", "@types/node": "^25.9.1", "typescript": "^5.8.0", diff --git a/skills/rig/SKILL.md b/skills/rig/SKILL.md index 7be8b3e..2d455c2 100644 --- a/skills/rig/SKILL.md +++ b/skills/rig/SKILL.md @@ -149,7 +149,7 @@ s.record(s.string) ## Tools -Register custom tools with `defineTool` using the same shape as `@github/copilot-sdk`. Use `s.*` schemas for `parameters`. Rig defaults tools to `skipPermission: true`. +Register custom tools with `defineTool` using an SDK-neutral tool shape. Use `s.*` schemas for `parameters`. Rig defaults tools to `skipPermission: true`. ```ts import { agent, defineTool, s } from "rig"; @@ -279,9 +279,9 @@ const summarize = agent({ ``` Use addons to steer retry prompts when needed (for example `steering()` from `rig/addons`). -Use `oncePerSession()` from `rig/addons` when you need to register with the Copilot session once per call instead of checking `context.turn === 1`. +Use `oncePerAgent()` from `rig/addons` when you need to register with the runtime agent once per call instead of checking `context.turn === 1`. -The addon `context` object contains: `prompt`, `response`, `turn`, `maxTurns`, `signal`, `output`, `nextPrompt`, `error`, `completed`, and `session` (direct Copilot SDK session access). +The addon `context` object contains: `prompt`, `response`, `turn`, `maxTurns`, `signal`, `output`, `nextPrompt`, `error`, `completed`, and `agent`. ## Running programs @@ -332,10 +332,22 @@ For program-file mode stdin coercion: - if root input schema is an object containing `text`, stdin is passed as `{ text: "" }` - otherwise stdin must be valid JSON for the declared input schema -## Copilot SDK runtime +## Agent implementations -`rig` is specialized for Copilot SDK sessions and no longer exposes a custom engine mount API. -By default it connects over HTTP using `COPILOT_SDK_URI`, then `localhost:7777`. +An SDK adapter implements the minimal `Agent` interface with `ask()` and `close()` methods. Register its factory with `configureAgent()`. Rig creates one adapter instance per invocation and does not branch on the underlying SDK. + +Rig provides `copilotEngine()`, `piEngine({ provider })`, and `anthropicEngine()` factories. The pi-agent factory uses `@earendil-works/pi-agent-core`; the Anthropic factory reads `ANTHROPIC_API_KEY` through `@anthropic-ai/sdk`. + +```ts +import { anthropicEngine } from "rig/engines/anthropic"; +import { piEngine } from "rig/engines/pi"; + +configureAgent(piEngine({ provider: "anthropic" })); +// or +configureAgent(anthropicEngine()); +``` + +By default `copilotEngine()` connects over HTTP using `COPILOT_SDK_URI`, then `localhost:7777`. Use `--server` at launch time when you want the harness to start the Copilot server via stdio. ## Patterns to prefer @@ -371,6 +383,6 @@ Use only the current API: - `agent({ name, ... })` - `p.*` and `p\`...\`` from `rig` - `s.*` for explicit schema helpers -- `oncePerSession` / `repair` / `steering` / `timeout` from `rig/addons` for optional addons +- `oncePerAgent` / `repair` / `steering` / `timeout` from `rig/addons` for optional addons Do not add deprecated hooks or compatibility layers. diff --git a/skills/rig/addons.ts b/skills/rig/addons.ts index cdda1a4..76e6bf7 100644 --- a/skills/rig/addons.ts +++ b/skills/rig/addons.ts @@ -1,5 +1,5 @@ import { analyzeResponse, defaultRepairPrompt } from "./rig.ts"; -import type { AgentAddon, AgentAddonContext, CopilotSession } from "./rig.ts"; +import type { Agent, AgentAddon, AgentAddonContext } from "./rig.ts"; const DEFAULT_STEERING_WARNING = "You are running out of turns. This is your final attempt before reaching the turn limit. Please correct your output now."; @@ -11,8 +11,8 @@ export type TimeoutOptions = { timeout: number; }; -export type SessionRegistration = ( - session: CopilotSession, +export type AgentRegistration = ( + agent: Agent, context: AgentAddonContext, ) => void | Promise; @@ -54,12 +54,12 @@ export function timeout(options: TimeoutOptions): AgentAddon { }; } -export function oncePerSession(register: SessionRegistration): AgentAddon { - const seen = new WeakSet(); +export function oncePerAgent(register: AgentRegistration): AgentAddon { + const seen = new WeakSet(); return async (context, next) => { - if (!seen.has(context.session)) { - await register(context.session, context); - seen.add(context.session); + if (!seen.has(context.agent)) { + await register(context.agent, context); + seen.add(context.agent); } await next(); }; @@ -81,7 +81,7 @@ function timeoutSignal(parent?: AbortSignal, timeoutMs?: number): AbortSignal | } export const addons = { - oncePerSession, + oncePerAgent, timeout, repair, steering, diff --git a/skills/rig/engines/anthropic.ts b/skills/rig/engines/anthropic.ts new file mode 100644 index 0000000..cd8b1a3 --- /dev/null +++ b/skills/rig/engines/anthropic.ts @@ -0,0 +1,82 @@ +import Anthropic from "@anthropic-ai/sdk"; +import type { ClientOptions } from "@anthropic-ai/sdk"; +import { betaTool } from "@anthropic-ai/sdk/helpers/beta/json-schema"; +import type { AgentFactory, Tool } from "../rig.ts"; + +export type AnthropicEngineOptions = ClientOptions & { + maxTokens?: number; + maxIterations?: number; +}; + +export function anthropicEngine(options: AnthropicEngineOptions = {}): AgentFactory { + const { maxTokens = 8192, maxIterations, ...clientOptions } = options; + return (agentOptions) => { + const client = new Anthropic(clientOptions); + let messages: any[] = []; + const tools = agentOptions.tools?.map(toAnthropicTool) ?? []; + + return { + async ask(prompt, askOptions = {}) { + const requestMessages = [...messages, { role: "user" as const, content: prompt }]; + const runner = client.beta.messages.toolRunner({ + model: agentOptions.model, + max_tokens: maxTokens, + messages: requestMessages, + tools, + ...(maxIterations !== undefined && { max_iterations: maxIterations }), + ...(agentOptions.systemMessage !== undefined && { system: agentOptions.systemMessage as any }), + }, askOptions.signal ? { signal: askOptions.signal } : undefined); + const response = await runner.runUntilDone(); + messages = [...runner.params.messages]; + return contentText(response.content); + }, + async close() {}, + }; + }; +} + +function toAnthropicTool(tool: Tool) { + return betaTool({ + name: tool.name, + description: tool.description ?? "", + inputSchema: objectToolSchema(tool) as any, + async run(args) { + if (!tool.handler) { + throw new Error(`${tool.name} tool has no handler`); + } + return toolResultText(await tool.handler(args)); + }, + }); +} + +function objectToolSchema(tool: Tool): Record { + const parameters = tool.parameters ?? { type: "object", properties: {} }; + if (parameters["type"] !== "object") { + throw new TypeError(`${tool.name} tool parameters must be an object schema`); + } + return parameters; +} + +function toolResultText(result: unknown): string { + if (typeof result === "string") { + return result; + } + if (result === undefined) { + return ""; + } + return JSON.stringify(result); +} + +function contentText(content: unknown): string { + if (!Array.isArray(content)) { + return typeof content === "string" ? content : ""; + } + return content + .filter((block): block is { type: "text"; text: string } => + block !== null + && typeof block === "object" + && (block as { type?: unknown }).type === "text" + && typeof (block as { text?: unknown }).text === "string") + .map((block) => block.text) + .join(""); +} diff --git a/skills/rig/engines/pi.ts b/skills/rig/engines/pi.ts new file mode 100644 index 0000000..6e2c764 --- /dev/null +++ b/skills/rig/engines/pi.ts @@ -0,0 +1,128 @@ +import { Agent as PiAgent } from "@earendil-works/pi-agent-core"; +import type { AgentTool as PiAgentTool } from "@earendil-works/pi-agent-core"; +import { Type } from "@earendil-works/pi-ai"; +import type { Models } from "@earendil-works/pi-ai"; +import { builtinModels } from "@earendil-works/pi-ai/providers/all"; +import type { AgentFactory, Tool } from "../rig.ts"; + +export type PiEngineOptions = { + provider: string; + models?: Models; +}; + +export function piEngine(options: PiEngineOptions): AgentFactory { + const models = options.models ?? builtinModels(); + return (agentOptions) => { + const model = models.getModel(options.provider, agentOptions.model); + if (!model) { + throw new Error(`Unknown pi-agent model: ${options.provider}/${agentOptions.model}`); + } + const piAgent = new PiAgent({ + streamFn: models.streamSimple.bind(models), + initialState: { + model, + systemPrompt: stringSystemMessage(agentOptions.systemMessage), + tools: agentOptions.tools?.map(toPiTool) ?? [], + }, + }); + + return { + async ask(prompt, askOptions = {}) { + throwIfAborted(askOptions.signal); + const abort = () => piAgent.abort(); + askOptions.signal?.addEventListener("abort", abort, { once: true }); + try { + await piAgent.prompt(prompt); + throwIfAborted(askOptions.signal); + if (piAgent.state.errorMessage) { + throw new Error(piAgent.state.errorMessage); + } + return piResponseText(piAgent.state.messages); + } finally { + askOptions.signal?.removeEventListener("abort", abort); + } + }, + async close() { + piAgent.abort(); + await piAgent.waitForIdle(); + }, + }; + }; +} + +function stringSystemMessage(systemMessage: unknown): string { + if (systemMessage === undefined) { + return ""; + } + if (typeof systemMessage !== "string") { + throw new TypeError("piEngine requires systemMessage to be a string"); + } + return systemMessage; +} + +function toPiTool(tool: Tool): PiAgentTool { + return { + name: tool.name, + label: tool.name, + description: tool.description ?? "", + parameters: Type.Unsafe(objectToolSchema(tool)), + async execute(_toolCallId, params) { + if (!tool.handler) { + throw new Error(`${tool.name} tool has no handler`); + } + const result = await tool.handler(params); + return { + content: [{ type: "text", text: toolResultText(result) }], + details: result, + }; + }, + }; +} + +function objectToolSchema(tool: Tool): Record { + const parameters = tool.parameters ?? { type: "object", properties: {} }; + if (parameters["type"] !== "object") { + throw new TypeError(`${tool.name} tool parameters must be an object schema`); + } + return parameters; +} + +function toolResultText(result: unknown): string { + if (typeof result === "string") { + return result; + } + if (result === undefined) { + return ""; + } + return JSON.stringify(result); +} + +function contentText(content: unknown): string { + if (!Array.isArray(content)) { + return typeof content === "string" ? content : ""; + } + return content + .filter((block): block is { type: "text"; text: string } => + block !== null + && typeof block === "object" + && (block as { type?: unknown }).type === "text" + && typeof (block as { text?: unknown }).text === "string") + .map((block) => block.text) + .join(""); +} + +function piResponseText(messages: readonly unknown[]): string { + for (let index = messages.length - 1; index >= 0; index -= 1) { + const message = messages[index] as { role?: unknown; content?: unknown }; + if (message.role === "assistant") { + return contentText(message.content); + } + } + return ""; +} + +function throwIfAborted(signal?: AbortSignal): void { + if (signal?.aborted) { + throw signal.reason ?? new DOMException("Aborted", "AbortError"); + } +} diff --git a/skills/rig/rig.ts b/skills/rig/rig.ts index 38ccda8..431a841 100644 --- a/skills/rig/rig.ts +++ b/skills/rig/rig.ts @@ -2,16 +2,9 @@ import { basename, dirname, isAbsolute, resolve } from "node:path"; import { fileURLToPath, pathToFileURL } from "node:url"; import { access, mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"; import { execFile } from "node:child_process"; -import { AsyncLocalStorage } from "node:async_hooks"; import { promisify } from "node:util"; -import { CopilotClient, RuntimeConnection, approveAll, defineTool as sdkDefineTool } from "@github/copilot-sdk"; -import type { - CopilotClientOptions, - SystemMessageConfig, - Tool as CopilotTool, - ToolHandler, - ZodSchema, -} from "@github/copilot-sdk"; +import { CopilotClient, RuntimeConnection, approveAll } from "@github/copilot-sdk"; +import type { CopilotClientOptions } from "@github/copilot-sdk"; export type Json = null | boolean | number | string | Json[] | { [key: string]: Json }; export type ValidationResult = { ok: true } | { ok: false; error: string }; @@ -204,16 +197,71 @@ export type CopilotEngineOptions = Omit & { server?: boolean; }; +export type AgentOptions = { + model: string; + systemMessage?: unknown; + tools?: Tool[]; +}; + +export type AgentAskOptions = { + signal?: AbortSignal; +}; + +export interface Agent { + ask(prompt: string, options?: AgentAskOptions): Promise; + close(): Promise; +} + +export type AgentFactory = (options: AgentOptions) => Agent | Promise; + function resolveDefaultCopilotUri(): string { return process.env["COPILOT_SDK_URI"] ?? "localhost:7777"; } -export function copilotEngine(options: CopilotEngineOptions = {}): CopilotClient { +export function copilotEngine(options: CopilotEngineOptions = {}): AgentFactory { const { server, connection, ...clientOptions } = options; - return new CopilotClient({ - ...clientOptions, - connection: connection ?? (server ? RuntimeConnection.forStdio() : RuntimeConnection.forUri(resolveDefaultCopilotUri())), - }); + return async (agentOptions) => { + const client = new CopilotClient({ + ...clientOptions, + connection: connection ?? (server ? RuntimeConnection.forStdio() : RuntimeConnection.forUri(resolveDefaultCopilotUri())), + }); + const session = await client.createSession({ + model: agentOptions.model, + streaming: false, + onPermissionRequest: approveAll, + ...(agentOptions.systemMessage !== undefined && { systemMessage: agentOptions.systemMessage as any }), + ...(agentOptions.tools !== undefined && { tools: agentOptions.tools as any }), + }); + session.on?.((event: unknown) => { + writeEvent(event); + }); + + return { + async ask(prompt, askOptions = {}) { + writeEvent(rigEvent("agent.ask", { prompt })); + const response = await (session.sendAndWait as any)( + askOptions.signal ? { prompt, signal: askOptions.signal } : { prompt }, + ); + return responseText(response); + }, + async close() { + const errors: Error[] = []; + if (session.disconnect) { + try { + await session.disconnect(); + } catch (error) { + errors.push(asError(error)); + } + } + try { + await stopCopilotClient(client); + } catch (error) { + errors.push(asError(error)); + } + throwCleanupErrors(errors, "Failed to close Copilot agent"); + }, + }; + }; } function jsonl(value: unknown): string { @@ -241,10 +289,9 @@ function writeEvent(event: unknown): void { process.stderr.write(`${jsonl(event)}\n`); } -export type CopilotSession = Awaited>; export type AgentAddonContext = { spec: NormalizedAgentSpec; - session: CopilotSession; + agent: Agent; input: unknown; outputSchema: Schema; signal: AbortSignal | undefined; @@ -261,21 +308,23 @@ export type AgentAddon = ( context: AgentAddonContext, next: () => Promise, ) => void | Promise; -export type Tool = CopilotTool; -export type ToolParameters = Schema | ZodSchema | Record; +export type ToolHandler = (args: TArgs) => unknown | Promise; +export type ToolParameters = Schema | Record; +export type Tool = ToolConfig & { name: string }; export type ToolConfig = { description?: string; - parameters?: ToolParameters; + parameters?: ToolParameters; handler?: ToolHandler; overridesBuiltInTool?: boolean; skipPermission?: boolean; }; export function defineTool(name: string, config: ToolConfig): Tool { - return sdkDefineTool(name, { + return { + name, ...normalizeToolConfig(config), parameters: normalizeToolParameters(config.parameters), - }); + }; } export type AgentSpec = { @@ -287,7 +336,7 @@ export type AgentSpec>; - systemMessage?: SystemMessageConfig; + systemMessage?: unknown; tools?: Tool[]; }; /** Internal normalized variant with a guaranteed resolved name. */ @@ -547,15 +596,7 @@ export class AgentError extends Error { } } -let currentCopilotOptions: CopilotEngineOptions | undefined; -type CopilotRunContext = { - client: CopilotClient; -}; -type CopilotSessionHandle = { - session: CopilotSession; - close(): Promise; -}; -const copilotRunStorage = new AsyncLocalStorage(); +let currentAgentFactory: AgentFactory = copilotEngine(); /** * Mounts an engine and executes a rig program file. @@ -565,7 +606,7 @@ export async function launchRigProgram(programPath: string, options: LaunchOptio const cwd = options.cwd ?? process.cwd(); const resolvedPath = isAbsolute(programPath) ? programPath : resolve(cwd, programPath); - configureCopilot(resolveCopilotOptions(cwd, options)); + configureAgent(copilotEngine(resolveCopilotOptions(cwd, options))); await import(pathToFileURL(resolvedPath).href); } @@ -749,7 +790,7 @@ async function runRootAgentFromStdin( await typecheckProgram(resolvedPath, cwd); } - configureCopilot(resolveCopilotOptions(cwd, options)); + configureAgent(copilotEngine(resolveCopilotOptions(cwd, options))); const mod = await import(pathToFileURL(resolvedPath).href); const rootAgent = asRootProgram(mod.default, "launcher-root"); if (!rootAgent) { @@ -781,7 +822,7 @@ async function runProgramCodeFromStdin( if (options.typecheck) { await typecheckProgram(tempProgramPath, cwd); } - configureCopilot(resolveCopilotOptions(cwd, options)); + configureAgent(copilotEngine(resolveCopilotOptions(cwd, options))); const mod = await import(pathToFileURL(tempProgramPath).href); const rootAgent = asRootProgram(mod.default, "launcher-inline-root"); if (!rootAgent) { @@ -866,7 +907,11 @@ export function agent(spec: AgentSpec): AgentFn { const normalizedInput = normalizeInput(input, inputSchema); let prompt = renderPrompt(normalizedSpec, normalizedInput); let lastResponse = ""; - const copilot = await createCopilotSession(runtime.model, runtime.systemMessage, runtime.tools); + const runtimeAgent = await currentAgentFactory({ + model: runtime.model, + ...(runtime.systemMessage !== undefined && { systemMessage: runtime.systemMessage }), + ...(runtime.tools !== undefined && { tools: runtime.tools }), + }); let failure: unknown; try { @@ -874,7 +919,7 @@ export function agent(spec: AgentSpec): AgentFn { throwIfAborted(runtime.signal); const context: AgentAddonContext = { spec: normalizedSpec, - session: copilot.session, + agent: runtimeAgent, input: normalizedInput, outputSchema, signal: runtime.signal, @@ -885,7 +930,10 @@ export function agent(spec: AgentSpec): AgentFn { }; await runAgentAddons(runtime.addons, context, async () => { - lastResponse = await sendCopilotPrompt(copilot.session, context.prompt, context.signal); + lastResponse = await runtimeAgent.ask( + context.prompt, + context.signal ? { signal: context.signal } : undefined, + ); context.response = lastResponse; }); @@ -915,7 +963,7 @@ export function agent(spec: AgentSpec): AgentFn { throw error; } finally { try { - await copilot.close(); + await runtimeAgent.close(); } catch (cleanupError) { if (failure === undefined) { throw cleanupError; @@ -926,31 +974,7 @@ export function agent(spec: AgentSpec): AgentFn { throw new Error(`Agent ${normalizedSpec.name} failed after ${runtime.maxTurns} turns. Last response:\n${lastResponse}`); }; - const fn = (async (input: unknown, options: CallOptions = {}) => { - const existingContext = copilotRunStorage.getStore(); - if (existingContext) { - return invoke(input, options); - } - - const client = copilotEngine(currentCopilotOptions); - return copilotRunStorage.run({ client }, async () => { - let failure: unknown; - try { - return await invoke(input, options); - } catch (error) { - failure = error; - throw error; - } finally { - try { - await stopCopilotClient(client); - } catch (cleanupError) { - if (failure === undefined) { - throw cleanupError; - } - } - } - }); - }) as AgentFn; + const fn = (async (input: unknown, options: CallOptions = {}) => invoke(input, options)) as AgentFn; fn.agentName = normalizedSpec.name; fn.inputSchema = inputSchema; @@ -969,7 +993,7 @@ export function agent(spec: AgentSpec): AgentFn { return fn; } -export type AgentFactory = typeof agent; +export type AgentDefinitionFactory = typeof agent; function validate(value: unknown, schema: Schema): ValidationResult { return validateSchema(value, schema, "$", false); @@ -998,7 +1022,7 @@ function normalizeSpec(specOrName: AgentSpec): NormalizedAgentSpec(parameters: ToolParameters | undefined): ToolParameters | undefined { +function normalizeToolParameters(parameters: ToolParameters | undefined): ToolParameters | undefined { return parameters !== undefined && isSchema(parameters) ? toJsonSchema(parameters) : parameters; } @@ -1361,8 +1385,8 @@ function withOptions, "__rig" | "id" | "mod return options ? { ...value, options: stripSignal(options) } : value; } -function configureCopilot(options: CopilotEngineOptions): void { - currentCopilotOptions = options; +export function configureAgent(factory: AgentFactory): void { + currentAgentFactory = factory; } function asError(error: unknown): Error { @@ -1393,50 +1417,7 @@ async function stopCopilotClient(client: CopilotClient): Promise { throwCleanupErrors(errors, "Failed to stop Copilot client"); } -async function createCopilotSession( - model: string, - systemMessage?: SystemMessageConfig, - tools?: Tool[], -): Promise { - const runContext = copilotRunStorage.getStore(); - const client = runContext?.client; - if (!client) { - throw new Error("No Copilot client found in execution context. Invoke agents through the exported agent function."); - } - const config = { - model, - streaming: false, - onPermissionRequest: approveAll, - ...(systemMessage !== undefined && { systemMessage }), - ...(tools !== undefined && { tools }), - }; - const session = await client.createSession(config); - session.on?.((event: unknown) => { - writeEvent(event); - }); - - return { - session, - async close() { - const errors: Error[] = []; - - if (session.disconnect) { - try { - await session.disconnect(); - } catch (error) { - errors.push(asError(error)); - } - } - - throwCleanupErrors(errors, "Failed to close Copilot session"); - }, - }; -} - -async function sendCopilotPrompt(session: CopilotSession, prompt: string, signal?: AbortSignal): Promise { - const request = signal ? { prompt, signal } : { prompt }; - writeEvent(rigEvent("copilot-ask", { prompt })); - const response = await session.sendAndWait(request); +function responseText(response: unknown): string { if (!response) { return ""; } @@ -1452,7 +1433,7 @@ function resolveCallRuntime(spec: NormalizedAgentSpec, options: CallOp maxTurns: number; signal: AbortSignal | undefined; addons: AgentAddon[]; - systemMessage: SystemMessageConfig | undefined; + systemMessage: unknown; tools: Tool[] | undefined; } { return { diff --git a/src/addons.ts b/src/addons.ts index f2f15af..faa8a63 100644 --- a/src/addons.ts +++ b/src/addons.ts @@ -1,2 +1,2 @@ -export { addons, oncePerSession, repair, steering, timeout } from "../skills/rig/addons.ts"; -export type { SessionRegistration, SteeringOptions, TimeoutOptions } from "../skills/rig/addons.ts"; +export { addons, oncePerAgent, repair, steering, timeout } from "../skills/rig/addons.ts"; +export type { AgentRegistration, SteeringOptions, TimeoutOptions } from "../skills/rig/addons.ts"; diff --git a/src/engines/anthropic.test.ts b/src/engines/anthropic.test.ts new file mode 100644 index 0000000..e86fbeb --- /dev/null +++ b/src/engines/anthropic.test.ts @@ -0,0 +1,122 @@ +import { beforeEach, expect, it, vi } from "vitest"; + +const mocks = vi.hoisted(() => { + const toolRunner = vi.fn(); + const constructor = vi.fn(); + const Anthropic = function (this: unknown, options: unknown) { + constructor(options); + return { beta: { messages: { toolRunner } } }; + }; + const betaTool = vi.fn((tool) => tool); + return { toolRunner, constructor, Anthropic, betaTool }; +}); + +vi.mock("@anthropic-ai/sdk", () => ({ default: mocks.Anthropic })); +vi.mock("@anthropic-ai/sdk/helpers/beta/json-schema", () => ({ betaTool: mocks.betaTool })); + +import { defineTool, s } from "rig"; +import { anthropicEngine } from "rig/engines/anthropic"; + +beforeEach(() => { + mocks.constructor.mockReset(); + mocks.betaTool.mockClear(); + mocks.toolRunner.mockReset(); + mocks.toolRunner.mockImplementation((params) => ({ + async runUntilDone() { + return { content: [{ type: "text", text: `response ${params.messages.length}` }] }; + }, + params: { + messages: [...params.messages, { role: "assistant", content: [{ type: "text", text: "saved" }] }], + }, + })); +}); + +it("creates an Anthropic tool runner and preserves its conversation", async () => { + const signal = new AbortController().signal; + const runtimeAgent = await anthropicEngine({ + apiKey: "test-key", + maxTokens: 2048, + maxIterations: 3, + })({ + model: "claude-test", + systemMessage: "Be concise.", + }); + + await expect(runtimeAgent.ask("first", { signal })).resolves.toBe("response 1"); + await expect(runtimeAgent.ask("second")).resolves.toBe("response 3"); + + expect(mocks.constructor).toHaveBeenCalledWith({ apiKey: "test-key" }); + expect(mocks.toolRunner).toHaveBeenNthCalledWith(1, { + model: "claude-test", + max_tokens: 2048, + max_iterations: 3, + messages: [{ role: "user", content: "first" }], + system: "Be concise.", + tools: [], + }, { signal }); + expect(mocks.toolRunner.mock.calls[1]![0].messages).toEqual([ + { role: "user", content: "first" }, + { role: "assistant", content: [{ type: "text", text: "saved" }] }, + { role: "user", content: "second" }, + ]); +}); + +it("maps Rig tools to Anthropic runnable tools", async () => { + const handler = vi.fn(async ({ value }: { value: string }) => ({ echoed: value })); + const tool = defineTool("echo", { + description: "Echo a value", + parameters: s.object({ value: s.string }), + handler, + }); + + anthropicEngine()({ model: "claude-test", tools: [tool] }); + + expect(mocks.betaTool).toHaveBeenCalledWith(expect.objectContaining({ + name: "echo", + description: "Echo a value", + inputSchema: expect.objectContaining({ type: "object" }), + })); + const anthropicTool = mocks.betaTool.mock.calls[0]![0]; + await expect(anthropicTool.run({ value: "ok" })).resolves.toBe("{\"echoed\":\"ok\"}"); + expect(handler).toHaveBeenCalledWith({ value: "ok" }); +}); + +it("does not retain a failed Anthropic turn", async () => { + mocks.toolRunner + .mockImplementationOnce(() => ({ + runUntilDone: vi.fn().mockRejectedValue(new Error("request failed")), + params: { messages: [] }, + })); + const runtimeAgent = await anthropicEngine()({ model: "claude-test" }); + + await expect(runtimeAgent.ask("failed")).rejects.toThrow("request failed"); + await expect(runtimeAgent.ask("retry")).resolves.toBe("response 1"); + + expect(mocks.toolRunner.mock.calls[1]![0].messages).toEqual([ + { role: "user", content: "retry" }, + ]); +}); + +it("uses Anthropic request defaults and returns all text blocks", async () => { + mocks.toolRunner.mockImplementationOnce((params) => ({ + async runUntilDone() { + return { + content: [ + { type: "text", text: "first" }, + { type: "tool_use", id: "tool-1" }, + { type: "text", text: " second" }, + ], + }; + }, + params, + })); + const runtimeAgent = await anthropicEngine()({ model: "claude-test" }); + + await expect(runtimeAgent.ask("hello")).resolves.toBe("first second"); + expect(mocks.toolRunner).toHaveBeenCalledWith({ + model: "claude-test", + max_tokens: 8192, + messages: [{ role: "user", content: "hello" }], + tools: [], + }, undefined); +}); diff --git a/src/engines/copilot.test.ts b/src/engines/copilot.test.ts index ff0485d..27aa413 100644 --- a/src/engines/copilot.test.ts +++ b/src/engines/copilot.test.ts @@ -23,6 +23,10 @@ import { copilotEngine } from "rig"; beforeEach(() => { mocks.createSession.mockReset(); + mocks.createSession.mockResolvedValue({ + sendAndWait: vi.fn(), + disconnect: vi.fn(), + }); mocks.forUri.mockClear(); mocks.forUri.mockImplementation(() => ({ kind: "uri", url: "localhost:7777" })); mocks.forStdio.mockClear(); @@ -33,18 +37,22 @@ beforeEach(() => { }); it("uses a URI (HTTP) connection by default", async () => { - copilotEngine().createSession({ model: "gpt-5", streaming: false } as any); + await copilotEngine()({ model: "gpt-5" }); expect(mocks.forUri).toHaveBeenCalledWith("localhost:7777"); expect(mocks.copilotClientCtor).toHaveBeenCalledWith({ connection: { kind: "uri", url: "localhost:7777" } }); - expect(mocks.createSession).toHaveBeenCalledWith({ model: "gpt-5", streaming: false }); + expect(mocks.createSession).toHaveBeenCalledWith({ + model: "gpt-5", + streaming: false, + onPermissionRequest: mocks.approveAll, + }); }); it("uses COPILOT_SDK_URI when set", async () => { process.env["COPILOT_SDK_URI"] = "http://127.0.0.1:4141"; mocks.forUri.mockImplementation(((url: string) => ({ kind: "uri", url })) as any); - copilotEngine().createSession({ model: "gpt-5", streaming: false } as any); + await copilotEngine()({ model: "gpt-5" }); expect(mocks.forUri).toHaveBeenCalledWith("http://127.0.0.1:4141"); expect(mocks.copilotClientCtor).toHaveBeenCalledWith({ connection: { kind: "uri", url: "http://127.0.0.1:4141" } }); @@ -53,7 +61,7 @@ it("uses COPILOT_SDK_URI when set", async () => { it("preserves explicit client options", async () => { const connection = { kind: "uri", url: "127.0.0.1:8765" } as const; - copilotEngine({ connection, workingDirectory: "/tmp/rig" }); + await copilotEngine({ connection, workingDirectory: "/tmp/rig" })({ model: "gpt-5" }); expect(mocks.forUri).not.toHaveBeenCalled(); expect(mocks.copilotClientCtor).toHaveBeenCalledWith({ @@ -69,22 +77,22 @@ it("subscribes to all Copilot SDK events and logs JSONL to stderr", async () => }); mocks.createSession.mockResolvedValue({ on, sendAndWait: vi.fn() }); - await copilotEngine().createSession({ model: "gpt-4.1", streaming: false } as any); + await copilotEngine()({ model: "gpt-4.1" }); expect(mocks.copilotClientCtor).toHaveBeenCalledTimes(1); expect(mocks.createSession).toHaveBeenCalledTimes(1); }); -it("creates and subscribes only once per engine session", async () => { - const client = copilotEngine(); - await client.createSession({ model: "gpt-4.1", streaming: false } as any); - await client.createSession({ model: "gpt-4.1", streaming: false } as any); +it("creates one Copilot session per agent implementation", async () => { + const createAgent = copilotEngine(); + await createAgent({ model: "gpt-4.1" }); + await createAgent({ model: "gpt-4.1" }); expect(mocks.createSession).toHaveBeenCalledTimes(2); }); it("uses a stdio connection when server option is true", async () => { - copilotEngine({ server: true }).createSession({ model: "gpt-4.1", streaming: false } as any); + await copilotEngine({ server: true })({ model: "gpt-4.1" }); expect(mocks.forStdio).toHaveBeenCalledOnce(); expect(mocks.forUri).not.toHaveBeenCalled(); expect(mocks.copilotClientCtor).toHaveBeenCalledWith({ connection: { kind: "stdio" } }); diff --git a/src/engines/pi.test.ts b/src/engines/pi.test.ts new file mode 100644 index 0000000..c535ff4 --- /dev/null +++ b/src/engines/pi.test.ts @@ -0,0 +1,111 @@ +import { beforeEach, expect, it, vi } from "vitest"; + +const mocks = vi.hoisted(() => { + const abort = vi.fn(); + const prompt = vi.fn(); + const waitForIdle = vi.fn(); + const state = { messages: [] as unknown[] }; + const constructor = vi.fn(); + const Agent = function (this: unknown, options: unknown) { + constructor(options); + return { abort, prompt, waitForIdle, state }; + }; + return { abort, prompt, waitForIdle, state, constructor, Agent }; +}); + +vi.mock("@earendil-works/pi-agent-core", () => ({ Agent: mocks.Agent })); + +import { defineTool, s } from "rig"; +import { piEngine } from "rig/engines/pi"; + +beforeEach(() => { + mocks.abort.mockReset(); + mocks.prompt.mockReset(); + mocks.waitForIdle.mockReset(); + mocks.waitForIdle.mockResolvedValue(undefined); + mocks.constructor.mockReset(); + mocks.state.messages = [{ role: "assistant", content: [{ type: "text", text: "pi response" }] }]; + delete (mocks.state as { errorMessage?: string }).errorMessage; +}); + +it("creates a pi-agent with the configured model, system prompt, and tools", async () => { + const model = { id: "test-model" }; + const models = { + getModel: vi.fn(() => model), + streamSimple: vi.fn(), + }; + const handler = vi.fn(async ({ value }: { value: string }) => ({ echoed: value })); + const tool = defineTool("echo", { + description: "Echo a value", + parameters: s.object({ value: s.string }), + handler, + }); + + const runtimeAgent = await piEngine({ provider: "test", models: models as any })({ + model: "test-model", + systemMessage: "Be concise.", + tools: [tool], + }); + const result = await runtimeAgent.ask("hello"); + + expect(models.getModel).toHaveBeenCalledWith("test", "test-model"); + expect(mocks.constructor).toHaveBeenCalledWith(expect.objectContaining({ + initialState: expect.objectContaining({ + model, + systemPrompt: "Be concise.", + tools: [expect.objectContaining({ name: "echo", label: "echo", description: "Echo a value" })], + }), + })); + expect(mocks.prompt).toHaveBeenCalledWith("hello"); + expect(result).toBe("pi response"); + + const options = mocks.constructor.mock.calls[0]![0] as any; + await expect(options.initialState.tools[0].execute("call-1", { value: "ok" })).resolves.toEqual({ + content: [{ type: "text", text: "{\"echoed\":\"ok\"}" }], + details: { echoed: "ok" }, + }); + expect(handler).toHaveBeenCalledWith({ value: "ok" }); +}); + +it("aborts and waits for the pi-agent when closed", async () => { + const models = { getModel: vi.fn(() => ({ id: "test-model" })), streamSimple: vi.fn() }; + const runtimeAgent = await piEngine({ provider: "test", models: models as any })({ model: "test-model" }); + + await runtimeAgent.close(); + + expect(mocks.abort).toHaveBeenCalledOnce(); + expect(mocks.waitForIdle).toHaveBeenCalledOnce(); +}); + +it("propagates pi-agent provider failures", async () => { + const models = { getModel: vi.fn(() => ({ id: "test-model" })), streamSimple: vi.fn() }; + const runtimeAgent = await piEngine({ provider: "test", models: models as any })({ model: "test-model" }); + (mocks.state as { errorMessage?: string }).errorMessage = "provider failed"; + + await expect(runtimeAgent.ask("hello")).rejects.toThrow("provider failed"); +}); + +it("aborts an in-flight pi-agent prompt", async () => { + const models = { getModel: vi.fn(() => ({ id: "test-model" })), streamSimple: vi.fn() }; + const runtimeAgent = await piEngine({ provider: "test", models: models as any })({ model: "test-model" }); + const controller = new AbortController(); + mocks.prompt.mockImplementation(async () => controller.abort(new Error("cancelled"))); + + await expect(runtimeAgent.ask("hello", { signal: controller.signal })).rejects.toThrow("cancelled"); + expect(mocks.abort).toHaveBeenCalledOnce(); +}); + +it("rejects non-string pi-agent system messages", () => { + const models = { getModel: vi.fn(() => ({ id: "test-model" })), streamSimple: vi.fn() }; + const factory = piEngine({ provider: "test", models: models as any }); + + expect(() => factory({ model: "test-model", systemMessage: [] as any })) + .toThrow("piEngine requires systemMessage to be a string"); +}); + +it("rejects unknown pi-agent models", () => { + const models = { getModel: vi.fn(() => undefined), streamSimple: vi.fn() }; + const factory = piEngine({ provider: "test", models: models as any }); + + expect(() => factory({ model: "missing" })).toThrow("Unknown pi-agent model: test/missing"); +}); diff --git a/src/rig.test.ts b/src/rig.test.ts index 1adacf4..f601c61 100644 --- a/src/rig.test.ts +++ b/src/rig.test.ts @@ -20,7 +20,6 @@ const mocks = vi.hoisted(() => { })); const forUri = vi.fn(() => ({ kind: "uri", url: "localhost:7777" })); const forStdio = vi.fn(() => ({ kind: "stdio" })); - const sdkDefineTool = vi.fn((name: string, config: Record) => ({ name, ...config })); const copilotClientCtor = vi.fn(); const CopilotClient = function (this: unknown, options: unknown) { copilotClientCtor(options); @@ -39,7 +38,6 @@ const mocks = vi.hoisted(() => { stopClient, forUri, forStdio, - sdkDefineTool, copilotClientCtor, CopilotClient, setSendAndWaitImpl, @@ -51,23 +49,22 @@ vi.mock("@github/copilot-sdk", () => ({ approveAll: mocks.approveAll, CopilotClient: mocks.CopilotClient, RuntimeConnection: { forUri: mocks.forUri, forStdio: mocks.forStdio }, - defineTool: mocks.sdkDefineTool, })); -import { AgentError, PromptBuilder, agent, defineTool, p, s, toJsonSchema } from "rig"; -import { oncePerSession, repair, steering, timeout } from "rig/addons"; +import { AgentError, PromptBuilder, agent, configureAgent, copilotEngine, defineTool, p, s, toJsonSchema } from "rig"; +import { oncePerAgent, repair, steering, timeout } from "rig/addons"; beforeEach(() => { mocks.createSession.mockClear(); mocks.approveAll.mockClear(); mocks.forUri.mockClear(); mocks.forStdio.mockClear(); - mocks.sdkDefineTool.mockClear(); mocks.copilotClientCtor.mockClear(); mocks.disconnectSession.mockClear(); mocks.stopClient.mockClear(); mocks.setOnImpl(undefined); mocks.setSendAndWaitImpl(async () => JSON.stringify("default")); + configureAgent(copilotEngine()); vi.restoreAllMocks(); }); @@ -178,6 +175,25 @@ describe("agent", () => { }); describe("agent invocation", () => { + it("runs against a configured SDK-neutral agent implementation", async () => { + const ask = vi.fn(async () => JSON.stringify({ text: "custom" })); + const close = vi.fn(async () => {}); + const factory = vi.fn(() => ({ ask, close })); + configureAgent(factory); + const greet = agent({ + name: "greeter", + model: "custom-model", + input: s.object({ text: s.string }), + output: s.object({ text: s.string }), + }); + + await expect(greet({ text: "Hi" })).resolves.toEqual({ text: "custom" }); + expect(factory).toHaveBeenCalledWith({ model: "custom-model" }); + expect(ask).toHaveBeenCalledWith(expect.stringContaining("Hi"), undefined); + expect(close).toHaveBeenCalledOnce(); + expect(mocks.copilotClientCtor).not.toHaveBeenCalled(); + }); + it("calls the copilot sdk and returns validated data", async () => { mocks.setSendAndWaitImpl(async () => ({ text: "hello world" })); const greet = agent({ @@ -206,7 +222,7 @@ describe("agent invocation", () => { expect(mocks.stopClient).toHaveBeenCalledTimes(1); }); - it("reuses one client for nested agent invocations while creating model-specific sessions", async () => { + it("creates one SDK agent implementation for each nested agent invocation", async () => { const child = agent({ name: "child", model: "o3-mini", @@ -229,14 +245,14 @@ describe("agent invocation", () => { }); await expect(parent({ text: "parent" })).resolves.toEqual({ text: "parent-ok" }); - expect(mocks.copilotClientCtor).toHaveBeenCalledTimes(1); + expect(mocks.copilotClientCtor).toHaveBeenCalledTimes(2); expect(mocks.createSession).toHaveBeenCalledTimes(2); expect(mocks.createSession.mock.calls).toEqual([ [{ model: "gpt-4.1", streaming: false, onPermissionRequest: mocks.approveAll }], [{ model: "o3-mini", streaming: false, onPermissionRequest: mocks.approveAll }], ]); expect(mocks.disconnectSession).toHaveBeenCalledTimes(2); - expect(mocks.stopClient).toHaveBeenCalledTimes(1); + expect(mocks.stopClient).toHaveBeenCalledTimes(2); }); it("logs raw Copilot SDK events and rig ask events as JSONL", async () => { @@ -258,17 +274,17 @@ describe("agent invocation", () => { expect(logs).toHaveLength(2); expect(logs[0]).toEqual({ type: "session.idle", data: { done: true } }); expect(logs[1]).toMatchObject({ - type: "rig.copilot-ask", + type: "rig.agent.ask", data: { prompt: expect.stringContaining("Hi") }, }); }); - it("exposes the Copilot session through an addon", async () => { + it("exposes the SDK-neutral agent through an addon", async () => { const addon = vi.fn(async (context, next) => { await next(); - expect(context.session).toMatchObject({ - sendAndWait: expect.any(Function), - disconnect: expect.any(Function), + expect(context.agent).toMatchObject({ + ask: expect.any(Function), + close: expect.any(Function), }); }); mocks.setSendAndWaitImpl(async () => ({ text: "hello world" })); @@ -487,13 +503,7 @@ describe("agent invocation", () => { handler, }); - expect(mocks.sdkDefineTool).toHaveBeenCalledWith("lookup_issue", { - description: "Look up an issue by id.", - parameters: toJsonSchema(s.object({ issue: s.string })), - handler, - skipPermission: true, - }); - expect(lookupIssue).toMatchObject({ + expect(lookupIssue).toEqual({ name: "lookup_issue", description: "Look up an issue by id.", parameters: toJsonSchema(s.object({ issue: s.string })), @@ -503,12 +513,14 @@ describe("agent invocation", () => { }); it("preserves explicit tool permission overrides", () => { - defineTool("lookup_issue", { + const lookupIssue = defineTool("lookup_issue", { skipPermission: false, }); - expect(mocks.sdkDefineTool).toHaveBeenCalledWith("lookup_issue", { + expect(lookupIssue).toEqual({ + name: "lookup_issue", skipPermission: false, + parameters: undefined, }); }); @@ -720,7 +732,7 @@ describe("agent invocation", () => { ); }); - it("registers with the Copilot session once per call", async () => { + it("registers with the runtime agent once per call", async () => { let turns = 0; const register = vi.fn(); mocks.setSendAndWaitImpl(async () => { @@ -732,8 +744,8 @@ describe("agent invocation", () => { name: "review", maxTurns: 2, addons: [ - oncePerSession(async (session, context) => { - register(session, context.turn); + oncePerAgent(async (runtimeAgent, context) => { + register(runtimeAgent, context.turn); }), repair, ], @@ -743,8 +755,8 @@ describe("agent invocation", () => { expect(register).toHaveBeenCalledTimes(1); expect(register).toHaveBeenCalledWith( expect.objectContaining({ - sendAndWait: expect.any(Function), - disconnect: expect.any(Function), + ask: expect.any(Function), + close: expect.any(Function), }), 1, ); diff --git a/src/samples/57-complex-integration-sonnet.ts b/src/samples/57-complex-integration-sonnet.ts index b047cf2..d558203 100644 --- a/src/samples/57-complex-integration-sonnet.ts +++ b/src/samples/57-complex-integration-sonnet.ts @@ -1,5 +1,5 @@ import { agent, defineTool, p, s } from "rig"; -import { oncePerSession, steering, timeout } from "rig/addons"; +import { oncePerAgent, steering, timeout } from "rig/addons"; const summarizeText = defineTool<{ text: string }>("summarize_text", { description: "Create a concise summary from text.", @@ -50,7 +50,7 @@ const complexIntegration = agent({ tools: [summarizeText], agents: { planner }, addons: [ - oncePerSession(async () => {}), + oncePerAgent(async () => {}), timeout({ timeout: 45_000 }), steering(), ], diff --git a/tsconfig.json b/tsconfig.json index acf139c..d26a452 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,6 +20,8 @@ "noEmit": true, "paths": { "rig": ["./skills/rig/rig.ts"], + "rig/engines/anthropic": ["./skills/rig/engines/anthropic.ts"], + "rig/engines/pi": ["./skills/rig/engines/pi.ts"], "rig/*": ["./src/*.ts"] } }, diff --git a/vitest.config.ts b/vitest.config.ts index 553fe18..95231e0 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -5,6 +5,8 @@ export default defineConfig({ resolve: { alias: [ { find: /^rig$/, replacement: resolve(__dirname, "skills/rig/rig.ts") }, + { find: /^rig\/engines\/anthropic$/, replacement: resolve(__dirname, "skills/rig/engines/anthropic.ts") }, + { find: /^rig\/engines\/pi$/, replacement: resolve(__dirname, "skills/rig/engines/pi.ts") }, { find: /^rig\/(.*)$/, replacement: resolve(__dirname, "src/$1") }, ], },