From c08c00ed7dff8744ba36053996434a98d6f11adb Mon Sep 17 00:00:00 2001 From: Andrew Ho Date: Sun, 12 Apr 2026 05:59:39 -0700 Subject: [PATCH 1/8] feat(runtime): refactor iMessage runtime with provider failover and typing controls --- .gitignore | 2 + .nvmrc | 1 + README.md | 7 + package-lock.json | 6126 +++++++++++++++++++--------- package.json | 12 +- src/doctor.ts | 237 ++ src/index.ts | 574 +++ src/lib/bluebubbles.ts | 293 ++ src/lib/codex-auth.ts | 70 + src/lib/config.ts | 208 + src/lib/delivery.ts | 49 + src/lib/logging.ts | 100 + src/lib/outbound.ts | 309 ++ src/lib/provider.ts | 112 + src/lib/reply-policy.ts | 379 ++ src/lib/split.test.ts | 105 + src/lib/workspace.ts | 299 ++ src/login.ts | 6 + src/mastra/agents/index.ts | 25 + src/mastra/agents/weather-agent.ts | 49 - src/mastra/index.ts | 47 +- 21 files changed, 6952 insertions(+), 2058 deletions(-) create mode 100644 .nvmrc create mode 100644 src/doctor.ts create mode 100644 src/index.ts create mode 100644 src/lib/bluebubbles.ts create mode 100644 src/lib/codex-auth.ts create mode 100644 src/lib/config.ts create mode 100644 src/lib/delivery.ts create mode 100644 src/lib/logging.ts create mode 100644 src/lib/outbound.ts create mode 100644 src/lib/provider.ts create mode 100644 src/lib/reply-policy.ts create mode 100644 src/lib/split.test.ts create mode 100644 src/lib/workspace.ts create mode 100644 src/login.ts create mode 100644 src/mastra/agents/index.ts delete mode 100644 src/mastra/agents/weather-agent.ts diff --git a/.gitignore b/.gitignore index 2a9ae6f..7dd7384 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ dist *.db-* .netlify .vercel +src/mastra/public/mastra.duckdb +src/mastra/public/mastra.duckdb.wal diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..a4a7a41 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v24.14.0 diff --git a/README.md b/README.md index 29bbfce..537a7a4 100644 --- a/README.md +++ b/README.md @@ -25,3 +25,10 @@ If you're new to AI agents, check out our [course](https://mastra.ai/course) and [Mastra Cloud](https://cloud.mastra.ai/) gives you a serverless agent environment with atomic deployments. Access your agents from anywhere and monitor performance. Make sure they don't go off the rails with evals and tracing. Check out the [deployment guide](https://mastra.ai/docs/deployment/overview) for more details. + +## TODO + +- Add Mastra evals for iMessage reply quality after the OpenClaw-style prompt flow settles. +- Cover short-greeting behavior like `hi` / `hey` / `yo` and assert one short natural bubble. +- Cover `[MSG]` behavior and assert delimiters never leak into sent text. +- Cover multi-bubble timing and assert later bubbles are staggered enough to feel human. diff --git a/package-lock.json b/package-lock.json index 2f7e1dd..4ad5f9a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,10 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "@ai-sdk/anthropic": "^3.0.69", + "@ai-sdk/openai": "^3.0.52", + "@jgoon/bluebubbles": "^1.1.0", + "@mariozechner/pi-ai": "^0.66.1", "@mastra/core": "^1.24.1", "@mastra/duckdb": "^1.1.1", "@mastra/evals": "^1.2.1", @@ -21,6 +25,7 @@ "devDependencies": { "@types/node": "^25.5.2", "mastra": "^1.5.0", + "tsx": "^4.21.0", "typescript": "^6.0.2" }, "engines": { @@ -43,6 +48,62 @@ "node": ">=18" } }, + "node_modules/@ai-sdk/anthropic": { + "version": "3.0.69", + "resolved": "https://registry.npmjs.org/@ai-sdk/anthropic/-/anthropic-3.0.69.tgz", + "integrity": "sha512-LshR7X3pFugY0o41G2VKTmg1XoGpSl7uoYWfzk6zjVZLhCfeFiwgpOga+eTV4XY1VVpZwKVqRnkDbIL7K2eH5g==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "3.0.8", + "@ai-sdk/provider-utils": "4.0.23" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@ai-sdk/anthropic/node_modules/@ai-sdk/provider": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-3.0.8.tgz", + "integrity": "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ==", + "license": "Apache-2.0", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ai-sdk/openai": { + "version": "3.0.52", + "resolved": "https://registry.npmjs.org/@ai-sdk/openai/-/openai-3.0.52.tgz", + "integrity": "sha512-4Rr8NCGmfWTz6DCUvixn9UmyZcMatiHn0zWoMzI3JCUe9R1P/vsPOpCBALKoSzVYOjyJnhtnVIbfUKujcS39uw==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "3.0.8", + "@ai-sdk/provider-utils": "4.0.23" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@ai-sdk/openai/node_modules/@ai-sdk/provider": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-3.0.8.tgz", + "integrity": "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ==", + "license": "Apache-2.0", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@ai-sdk/provider": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-2.0.1.tgz", @@ -55,6 +116,23 @@ "node": ">=18" } }, + "node_modules/@ai-sdk/provider-utils": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.23.tgz", + "integrity": "sha512-z8GlDaCmRSDlqkMF2f4/RFgWxdarvIbyuk+m6WXT1LYgsnGiXRJGTD2Z1+SDl3LqtFuRtGX1aghYvQLoHL/9pg==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "3.0.8", + "@standard-schema/spec": "^1.1.0", + "eventsource-parser": "^3.0.6" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, "node_modules/@ai-sdk/provider-utils-v5": { "name": "@ai-sdk/provider-utils", "version": "3.0.20", @@ -103,6 +181,18 @@ "node": ">=18" } }, + "node_modules/@ai-sdk/provider-utils/node_modules/@ai-sdk/provider": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-3.0.8.tgz", + "integrity": "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ==", + "license": "Apache-2.0", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@ai-sdk/provider-v5": { "name": "@ai-sdk/provider", "version": "2.0.1", @@ -129,1192 +219,1271 @@ "node": ">=18" } }, - "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", - "dev": true, + "node_modules/@anthropic-ai/sdk": { + "version": "0.73.0", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.73.0.tgz", + "integrity": "sha512-URURVzhxXGJDGUGFunIOtBlSl7KWvZiAAKY/ttTkZAkXT9bTPqdk2eK0b8qqSxXpikh3QKPnPYpiyX98zf5ebw==", "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" + "json-schema-to-ts": "^3.1.1" }, - "engines": { - "node": ">=6.9.0" + "bin": { + "anthropic-ai-sdk": "bin/cli" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } } }, - "node_modules/@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", - "dev": true, - "license": "MIT", + "node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=16.0.0" } }, - "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", - "dev": true, - "license": "MIT", + "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==", + "license": "Apache-2.0", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" + "@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/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "node_modules/@aws-crypto/sha256-browser/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==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", - "dev": true, - "license": "MIT", + "node_modules/@aws-crypto/sha256-browser/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==", + "license": "Apache-2.0", "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", - "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", - "dev": true, - "license": "MIT", + "node_modules/@aws-crypto/sha256-browser/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==", + "license": "Apache-2.0", "dependencies": { - "@babel/types": "^7.27.3" + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", - "dev": true, - "license": "MIT", + "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==", + "license": "Apache-2.0", "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=16.0.0" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", + "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==", + "license": "Apache-2.0", "dependencies": { - "yallist": "^3.0.2" + "tslib": "^2.6.2" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "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==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" } }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", - "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", - "dev": true, - "license": "MIT", + "node_modules/@aws-crypto/util/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==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-member-expression-to-functions": "^7.28.5", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/helper-replace-supers": "^7.28.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.28.6", - "semver": "^6.3.1" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "node_modules/@aws-crypto/util/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==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "dev": true, - "license": "MIT", + "node_modules/@aws-crypto/util/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==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", - "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/client-bedrock-runtime": { + "version": "3.1027.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-runtime/-/client-bedrock-runtime-3.1027.0.tgz", + "integrity": "sha512-Qcda5Z5Vb3LPVt7zNycEiiAo9Blk0JpEPJwz/sUBJby6/0zvTlo+/FIXlwYZ3TJHSgKCYiCaBqAB0WRlWDfLfQ==", + "license": "Apache-2.0", "dependencies": { - "@babel/traverse": "^7.28.5", - "@babel/types": "^7.28.5" + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/credential-provider-node": "^3.972.30", + "@aws-sdk/eventstream-handler-node": "^3.972.13", + "@aws-sdk/middleware-eventstream": "^3.972.9", + "@aws-sdk/middleware-host-header": "^3.972.9", + "@aws-sdk/middleware-logger": "^3.972.9", + "@aws-sdk/middleware-recursion-detection": "^3.972.10", + "@aws-sdk/middleware-user-agent": "^3.972.29", + "@aws-sdk/middleware-websocket": "^3.972.15", + "@aws-sdk/region-config-resolver": "^3.972.11", + "@aws-sdk/token-providers": "3.1027.0", + "@aws-sdk/types": "^3.973.7", + "@aws-sdk/util-endpoints": "^3.996.6", + "@aws-sdk/util-user-agent-browser": "^3.972.9", + "@aws-sdk/util-user-agent-node": "^3.973.15", + "@smithy/config-resolver": "^4.4.14", + "@smithy/core": "^3.23.14", + "@smithy/eventstream-serde-browser": "^4.2.13", + "@smithy/eventstream-serde-config-resolver": "^4.3.13", + "@smithy/eventstream-serde-node": "^4.2.13", + "@smithy/fetch-http-handler": "^5.3.16", + "@smithy/hash-node": "^4.2.13", + "@smithy/invalid-dependency": "^4.2.13", + "@smithy/middleware-content-length": "^4.2.13", + "@smithy/middleware-endpoint": "^4.4.29", + "@smithy/middleware-retry": "^4.5.0", + "@smithy/middleware-serde": "^4.2.17", + "@smithy/middleware-stack": "^4.2.13", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/node-http-handler": "^4.5.2", + "@smithy/protocol-http": "^5.3.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "@smithy/url-parser": "^4.2.13", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.45", + "@smithy/util-defaults-mode-node": "^4.2.49", + "@smithy/util-endpoints": "^3.3.4", + "@smithy/util-middleware": "^4.2.13", + "@smithy/util-retry": "^4.3.0", + "@smithy/util-stream": "^4.5.22", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.973.27", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.27.tgz", + "integrity": "sha512-CUZ5m8hwMCH6OYI4Li/WgMfIEx10Q2PLI9Y3XOUTPGZJ53aZ0007jCv+X/ywsaERyKPdw5MRZWk877roQksQ4A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@aws-sdk/xml-builder": "^3.972.17", + "@smithy/core": "^3.23.14", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/property-provider": "^4.2.13", + "@smithy/protocol-http": "^5.3.13", + "@smithy/signature-v4": "^5.3.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-middleware": "^4.2.13", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=20.0.0" } }, - "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.972.25", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.25.tgz", + "integrity": "sha512-6QfI0wv4jpG5CrdO/AO0JfZ2ux+tKwJPrUwmvxXF50vI5KIypKVGNF6b4vlkYEnKumDTI1NX2zUBi8JoU5QU3A==", + "license": "Apache-2.0", "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=20.0.0" } }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.972.27", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.27.tgz", + "integrity": "sha512-3V3Usj9Gs93h865DqN4M2NWJhC5kXU9BvZskfN3+69omuYlE3TZxOEcVQtBGLOloJB7BVfJKXVLqeNhOzHqSlQ==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/types": "^3.973.7", + "@smithy/fetch-http-handler": "^5.3.16", + "@smithy/node-http-handler": "^4.5.2", + "@smithy/property-provider": "^4.2.13", + "@smithy/protocol-http": "^5.3.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "@smithy/util-stream": "^4.5.22", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": ">=20.0.0" } }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", - "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.972.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.29.tgz", + "integrity": "sha512-SiBuAnXecCbT/OpAf3vqyI/AVE3mTaYr9ShXLybxZiPLBiPCCOIWSGAtYYGQWMRvobBTiqOewaB+wcgMMZI2Aw==", + "license": "Apache-2.0", "dependencies": { - "@babel/types": "^7.27.1" + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/credential-provider-env": "^3.972.25", + "@aws-sdk/credential-provider-http": "^3.972.27", + "@aws-sdk/credential-provider-login": "^3.972.29", + "@aws-sdk/credential-provider-process": "^3.972.25", + "@aws-sdk/credential-provider-sso": "^3.972.29", + "@aws-sdk/credential-provider-web-identity": "^3.972.29", + "@aws-sdk/nested-clients": "^3.996.19", + "@aws-sdk/types": "^3.973.7", + "@smithy/credential-provider-imds": "^4.2.13", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.972.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.29.tgz", + "integrity": "sha512-OGOslTbOlxXexKMqhxCEbBQbUIfuhGxU5UXw3Fm56ypXHvrXH4aTt/xb5Y884LOoteP1QST1lVZzHfcTnWhiPQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/nested-clients": "^3.996.19", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/protocol-http": "^5.3.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=20.0.0" } }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", - "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.972.30", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.30.tgz", + "integrity": "sha512-FMnAnWxc8PG+ZrZ2OBKzY4luCUJhe9CG0B9YwYr4pzrYGLXBS2rl+UoUvjGbAwiptxRL6hyA3lFn03Bv1TLqTw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "^3.972.25", + "@aws-sdk/credential-provider-http": "^3.972.27", + "@aws-sdk/credential-provider-ini": "^3.972.29", + "@aws-sdk/credential-provider-process": "^3.972.25", + "@aws-sdk/credential-provider-sso": "^3.972.29", + "@aws-sdk/credential-provider-web-identity": "^3.972.29", + "@aws-sdk/types": "^3.973.7", + "@smithy/credential-provider-imds": "^4.2.13", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=20.0.0" } }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", - "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.972.25", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.25.tgz", + "integrity": "sha512-HR7ynNRdNhNsdVCOCegy1HsfsRzozCOPtD3RzzT1JouuaHobWyRfJzCBue/3jP7gECHt+kQyZUvwg/cYLWurNQ==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.28.5", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/traverse": "^7.28.6" + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.972.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.29.tgz", + "integrity": "sha512-HWv4SEq3jZDYPlwryZVef97+U8CxxRos5mK8sgGO1dQaFZpV5giZLzqGE5hkDmh2csYcBO2uf5XHjPTpZcJlig==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/nested-clients": "^3.996.19", + "@aws-sdk/token-providers": "3.1026.0", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "engines": { + "node": ">=20.0.0" } }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", - "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers": { + "version": "3.1026.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1026.0.tgz", + "integrity": "sha512-Ieq/HiRrbEtrYP387Nes0XlR7H1pJiJOZKv+QyQzMYpvTiDs0VKy2ZB3E2Zf+aFovWmeE7lRE4lXyF7dYM6GgA==", + "license": "Apache-2.0", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/nested-clients": "^3.996.19", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=20.0.0" } }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.972.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.29.tgz", + "integrity": "sha512-PdMBza1WEKEUPFEmMGCfnU2RYCz9MskU2e8JxjyUOsMKku7j9YaDKvbDi2dzC0ihFoM6ods2SbhfAAro+Gwlew==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/nested-clients": "^3.996.19", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=20.0.0" } }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/eventstream-handler-node": { + "version": "3.972.13", + "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-handler-node/-/eventstream-handler-node-3.972.13.tgz", + "integrity": "sha512-2Pi1kD0MDkMAxDHqvpi/hKMs9hXUYbj2GLEjCwy+0jzfLChAsF50SUYnOeTI+RztA+Ic4pnLAdB03f1e8nggxQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@smithy/eventstream-codec": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=20.0.0" } }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/middleware-eventstream": { + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-eventstream/-/middleware-eventstream-3.972.9.tgz", + "integrity": "sha512-ypgOvpWxQTCnQyDHGxnTviqqANE7FIIzII7VczJnTPCJcJlu17hMQXnvE47aKSKsawVJAaaRsyOEbHQuLJF9ng==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=20.0.0" } }, - "node_modules/@babel/helpers": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", - "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.9.tgz", + "integrity": "sha512-je5vRdNw4SkuTnmRbFZLdye4sQ0faLt8kwka5wnnSU30q1mHO4X+idGEJOOE+Tn1ME7Oryn05xxkDvIb3UaLaQ==", + "license": "Apache-2.0", "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0" + "@aws-sdk/types": "^3.973.7", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=20.0.0" } }, - "node_modules/@babel/parser": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", - "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.9.tgz", + "integrity": "sha512-HsVgDrruhqI28RkaXALm8grJ7Agc1wF6Et0xh6pom8NdO2VdO/SD9U/tPwUjewwK/pVoka+EShBxyCvgsPCtog==", + "license": "Apache-2.0", "dependencies": { - "@babel/types": "^7.29.0" + "@aws-sdk/types": "^3.973.7", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" }, - "bin": { - "parser": "bin/babel-parser.js" + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.972.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.10.tgz", + "integrity": "sha512-RVQQbq5orQ/GHUnXvqEOj2HHPBJm+mM+ySwZKS5UaLBwra5ugRtiH09PLUoOZRl7a1YzaOzXSuGbn9iD5j60WQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.0.0" + "node": ">=20.0.0" } }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", - "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.972.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.29.tgz", + "integrity": "sha512-f/sIRzuTfEjg6NsbMYvye2VsmnQoNgntntleQyx5uGacUYzszbfIlO3GcI6G6daWUmTm0IDZc11qMHWwF0o0mQ==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/types": "^3.973.7", + "@aws-sdk/util-endpoints": "^3.996.6", + "@smithy/core": "^3.23.14", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "@smithy/util-retry": "^4.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-websocket": { + "version": "3.972.15", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-websocket/-/middleware-websocket-3.972.15.tgz", + "integrity": "sha512-hsZ35FORQsN5hwNdMD6zWmHCphbXkDxO6j+xwCUiuMb0O6gzS/PWgttQNl1OAn7h/uqZAMUG4yOS0wY/yhAieg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@aws-sdk/util-format-url": "^3.972.9", + "@smithy/eventstream-codec": "^4.2.13", + "@smithy/eventstream-serde-browser": "^4.2.13", + "@smithy/fetch-http-handler": "^5.3.16", + "@smithy/protocol-http": "^5.3.13", + "@smithy/signature-v4": "^5.3.13", + "@smithy/types": "^4.14.0", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">= 14.0.0" } }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", - "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/nested-clients": { + "version": "3.996.19", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.996.19.tgz", + "integrity": "sha512-uFkmCDXvmQYLanlYdOFS0+MQWkrj9wPMt/ZCc/0J0fjPim6F5jBVBmEomvGY/j77ILW6GTPwN22Jc174Mhkw6Q==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/middleware-host-header": "^3.972.9", + "@aws-sdk/middleware-logger": "^3.972.9", + "@aws-sdk/middleware-recursion-detection": "^3.972.10", + "@aws-sdk/middleware-user-agent": "^3.972.29", + "@aws-sdk/region-config-resolver": "^3.972.11", + "@aws-sdk/types": "^3.973.7", + "@aws-sdk/util-endpoints": "^3.996.6", + "@aws-sdk/util-user-agent-browser": "^3.972.9", + "@aws-sdk/util-user-agent-node": "^3.973.15", + "@smithy/config-resolver": "^4.4.14", + "@smithy/core": "^3.23.14", + "@smithy/fetch-http-handler": "^5.3.16", + "@smithy/hash-node": "^4.2.13", + "@smithy/invalid-dependency": "^4.2.13", + "@smithy/middleware-content-length": "^4.2.13", + "@smithy/middleware-endpoint": "^4.4.29", + "@smithy/middleware-retry": "^4.5.0", + "@smithy/middleware-serde": "^4.2.17", + "@smithy/middleware-stack": "^4.2.13", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/node-http-handler": "^4.5.2", + "@smithy/protocol-http": "^5.3.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "@smithy/url-parser": "^4.2.13", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.45", + "@smithy/util-defaults-mode-node": "^4.2.49", + "@smithy/util-endpoints": "^3.3.4", + "@smithy/util-middleware": "^4.2.13", + "@smithy/util-retry": "^4.3.0", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.972.11", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.11.tgz", + "integrity": "sha512-6Q8B1dcx6BBqUTY1Mc/eROKA0FImEEY5VPSd6AGPEUf0ErjExz4snVqa9kNJSoVDV1rKaNf3qrWojgcKW+SdDg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@smithy/config-resolver": "^4.4.14", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.1027.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1027.0.tgz", + "integrity": "sha512-mI3Jm14cM5sNKc7aNX3cqJe/rFQ2Zzx7x5W8WUtxj2lVxcH2RGYhqI3hK9nnImY6Ec5MeGXCVPjl/q6Mz5HmSA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/nested-clients": "^3.996.19", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=20.0.0" } }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", - "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/types": { + "version": "3.973.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.7.tgz", + "integrity": "sha512-reXRwoJ6CfChoqAsBszUYajAF8Z2LRE+CRcKocvFSMpIiLOtYU3aJ9trmn6VVPAzbbY5LXF+FfmUslbXk1SYFg==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.996.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.6.tgz", + "integrity": "sha512-2nUQ+2ih7CShuKHpGSIYvvAIOHy52dOZguYG36zptBukhw6iFwcvGfG0tes0oZFWQqEWvgZe9HLWaNlvXGdOrg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@smithy/types": "^4.14.0", + "@smithy/url-parser": "^4.2.13", + "@smithy/util-endpoints": "^3.3.4", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=20.0.0" } }, - "node_modules/@babel/plugin-transform-typescript": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz", - "integrity": "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/util-format-url": { + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.972.9.tgz", + "integrity": "sha512-fNJXHrs0ZT7Wx0KGIqKv7zLxlDXt2vqjx9z6oKUQFmpE5o4xxnSryvVHfHpIifYHWKz94hFccIldJ0YSZjlCBw==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-create-class-features-plugin": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/plugin-syntax-typescript": "^7.28.6" + "@aws-sdk/types": "^3.973.7", + "@smithy/querystring-builder": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.965.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.5.tgz", + "integrity": "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=20.0.0" } }, - "node_modules/@babel/preset-typescript": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz", - "integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.9.tgz", + "integrity": "sha512-sn/LMzTbGjYqCCF24390WxPd6hkpoSptiUn5DzVp4cD71yqw+yGEGm1YCxyEoPXyc8qciM8UzLJcZBFslxo5Uw==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-syntax-jsx": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.27.1", - "@babel/plugin-transform-typescript": "^7.28.5" + "@aws-sdk/types": "^3.973.7", + "@smithy/types": "^4.14.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.973.15", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.15.tgz", + "integrity": "sha512-fYn3s9PtKdgQkczGZCFMgkNEe8aq1JCVbnRqjqN9RSVW43xn2RV9xdcZ3z01a48Jpkuh/xCmBKJxdLOo4Ozg7w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "^3.972.29", + "@aws-sdk/types": "^3.973.7", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/types": "^4.14.0", + "@smithy/util-config-provider": "^4.2.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=20.0.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } } }, - "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/xml-builder": { + "version": "3.972.17", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.17.tgz", + "integrity": "sha512-Ra7hjqAZf1OXRRMueB13qex7mFJRDK/pgCvdSFemXBT8KCGnQDPoKzHY1SjN+TjJVmnpSF14W5tJ1vDamFu+Gg==", + "license": "Apache-2.0", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" + "@smithy/types": "^4.14.0", + "fast-xml-parser": "5.5.8", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=20.0.0" } }, - "node_modules/@babel/traverse": { + "node_modules/@aws/lambda-invoke-store": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.4.tgz", + "integrity": "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@babel/code-frame": { "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", - "debug": "^4.3.1" + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/types": { + "node_modules/@babel/compat-data": { "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@clack/core": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@clack/core/-/core-1.2.0.tgz", - "integrity": "sha512-qfxof/3T3t9DPU/Rj3OmcFyZInceqj/NVtO9rwIuJqCUgh32gwPjpFQQp/ben07qKlhpwq7GzfWpST4qdJ5Drg==", + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", "dependencies": { - "fast-wrap-ansi": "^0.1.3", - "sisteransi": "^1.0.5" + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" } }, - "node_modules/@clack/prompts": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-1.2.0.tgz", - "integrity": "sha512-4jmztR9fMqPMjz6H/UZXj0zEmE43ha1euENwkckKKel4XpSfokExPo5AiVStdHSAlHekz4d0CA/r45Ok1E4D3w==", + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "license": "MIT", - "dependencies": { - "@clack/core": "1.2.0", - "fast-string-width": "^1.1.0", - "fast-wrap-ansi": "^0.1.3", - "sisteransi": "^1.0.5" + "license": "ISC", + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/@duckdb/node-api": { - "version": "1.4.2-r.1", - "resolved": "https://registry.npmjs.org/@duckdb/node-api/-/node-api-1.4.2-r.1.tgz", - "integrity": "sha512-8Ef4R/Lq+rXTpcqZIJZe6ALfgMGxy88HmiPvRpWrRw1fUTy85x1U0NnGrqCklZsmWyZUdPwVYj90MQOF2MY/TA==", + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, "license": "MIT", "dependencies": { - "@duckdb/node-bindings": "1.4.2-r.1" + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@duckdb/node-bindings": { - "version": "1.4.2-r.1", - "resolved": "https://registry.npmjs.org/@duckdb/node-bindings/-/node-bindings-1.4.2-r.1.tgz", - "integrity": "sha512-z0pJCdEnIj0Famkip6w7JZ/A17nm5VcYc6H7isOHXfEnPhBQ9PBusUTFgIzl6+3J8HOFQOv0szJq46zldbsHfQ==", + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, "license": "MIT", - "optionalDependencies": { - "@duckdb/node-bindings-darwin-arm64": "1.4.2-r.1", - "@duckdb/node-bindings-darwin-x64": "1.4.2-r.1", - "@duckdb/node-bindings-linux-arm64": "1.4.2-r.1", - "@duckdb/node-bindings-linux-x64": "1.4.2-r.1", - "@duckdb/node-bindings-win32-x64": "1.4.2-r.1" + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@duckdb/node-bindings-darwin-arm64": { - "version": "1.4.2-r.1", - "resolved": "https://registry.npmjs.org/@duckdb/node-bindings-darwin-arm64/-/node-bindings-darwin-arm64-1.4.2-r.1.tgz", - "integrity": "sha512-C4yBI3zBX7iZ9iq8zJDvPatXA+2xM22sg1kX3fx76nG+qTqKtU/dGFVYL7Fx6cBYxBO1ZZ8Y+vjgYb6/bich8A==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@duckdb/node-bindings-darwin-x64": { - "version": "1.4.2-r.1", - "resolved": "https://registry.npmjs.org/@duckdb/node-bindings-darwin-x64/-/node-bindings-darwin-x64-1.4.2-r.1.tgz", - "integrity": "sha512-dgTSBuEfWyhymYovsGoRESjqcJuZWwJqla9l89SSsBDrGYcUp+EHsXUF73oUCspzTesT2lOvHrDufO8pKgtudw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@duckdb/node-bindings-linux-arm64": { - "version": "1.4.2-r.1", - "resolved": "https://registry.npmjs.org/@duckdb/node-bindings-linux-arm64/-/node-bindings-linux-arm64-1.4.2-r.1.tgz", - "integrity": "sha512-r2Ml1LvU2vkVMx4YU04T79FjGkYg8YMVtbOz7j740SZGIi5Cch5P1/oy48jJBWRqoaXuqimpYWeTZiGVeCQiZA==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } }, - "node_modules/@duckdb/node-bindings-linux-x64": { - "version": "1.4.2-r.1", - "resolved": "https://registry.npmjs.org/@duckdb/node-bindings-linux-x64/-/node-bindings-linux-x64-1.4.2-r.1.tgz", - "integrity": "sha512-ed5KiNIu1rqSva5rgo4PRVYSmBolAMVqGFWsYWLoRZ94ka2F/dHwJNkarCTmBFCEDGVZWzWzjRyWTQgYTvQxTg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } }, - "node_modules/@duckdb/node-bindings-win32-x64": { - "version": "1.4.2-r.1", - "resolved": "https://registry.npmjs.org/@duckdb/node-bindings-win32-x64/-/node-bindings-win32-x64-1.4.2-r.1.tgz", - "integrity": "sha512-kIIT8tuoW3mzr9EPcdSoKfv9CgOhTqRryHDI10Z0nuhc9UhqOWPUM/LnSUebfo1mdD9Drm7YrKCKYxNTr5dqBQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", - "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", - "cpu": [ - "ppc64" - ], + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", + "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "aix" - ], + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.6", + "semver": "^6.3.1" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@esbuild/android-arm": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", - "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", - "cpu": [ - "arm" - ], + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" + "license": "ISC", + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/@esbuild/android-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", - "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/android-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", - "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", - "cpu": [ - "x64" - ], + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "dependencies": { + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", - "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", - "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", - "cpu": [ - "x64" - ], + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", - "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], + "dependencies": { + "@babel/types": "^7.27.1" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", - "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", - "cpu": [ - "x64" - ], + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/linux-arm": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", - "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", - "cpu": [ - "arm" - ], + "node_modules/@babel/helper-replace-supers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", + "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.28.6" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", - "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", - "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", - "cpu": [ - "ia32" - ], + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", - "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", - "cpu": [ - "loong64" - ], + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", - "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", - "cpu": [ - "mips64el" - ], + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", - "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", - "cpu": [ - "ppc64" - ], + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", - "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", - "cpu": [ - "riscv64" - ], + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, "engines": { - "node": ">=18" + "node": ">=6.0.0" } }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", - "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", - "cpu": [ - "s390x" - ], + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/linux-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", - "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", - "cpu": [ - "x64" - ], + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", - "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", + "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], + "dependencies": { + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", - "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", - "cpu": [ - "x64" - ], + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz", + "integrity": "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.28.6" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", - "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/preset-typescript": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz", + "integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.28.5" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", - "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", - "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", - "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", - "cpu": [ - "x64" - ], + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", - "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", - "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", - "cpu": [ - "ia32" - ], + "node_modules/@clack/core": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@clack/core/-/core-1.2.0.tgz", + "integrity": "sha512-qfxof/3T3t9DPU/Rj3OmcFyZInceqj/NVtO9rwIuJqCUgh32gwPjpFQQp/ben07qKlhpwq7GzfWpST4qdJ5Drg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" + "dependencies": { + "fast-wrap-ansi": "^0.1.3", + "sisteransi": "^1.0.5" } }, - "node_modules/@esbuild/win32-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", - "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", - "cpu": [ - "x64" - ], + "node_modules/@clack/prompts": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-1.2.0.tgz", + "integrity": "sha512-4jmztR9fMqPMjz6H/UZXj0zEmE43ha1euENwkckKKel4XpSfokExPo5AiVStdHSAlHekz4d0CA/r45Ok1E4D3w==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@clack/core": "1.2.0", + "fast-string-width": "^1.1.0", + "fast-wrap-ansi": "^0.1.3", + "sisteransi": "^1.0.5" } }, - "node_modules/@expo/devcert": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@expo/devcert/-/devcert-1.2.1.tgz", - "integrity": "sha512-qC4eaxmKMTmJC2ahwyui6ud8f3W60Ss7pMkpBq40Hu3zyiAaugPXnZ24145U7K36qO9UHdZUVxsCvIpz2RYYCA==", - "dev": true, + "node_modules/@duckdb/node-api": { + "version": "1.4.2-r.1", + "resolved": "https://registry.npmjs.org/@duckdb/node-api/-/node-api-1.4.2-r.1.tgz", + "integrity": "sha512-8Ef4R/Lq+rXTpcqZIJZe6ALfgMGxy88HmiPvRpWrRw1fUTy85x1U0NnGrqCklZsmWyZUdPwVYj90MQOF2MY/TA==", "license": "MIT", "dependencies": { - "@expo/sudo-prompt": "^9.3.1", - "debug": "^3.1.0" + "@duckdb/node-bindings": "1.4.2-r.1" } }, - "node_modules/@expo/devcert/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, + "node_modules/@duckdb/node-bindings": { + "version": "1.4.2-r.1", + "resolved": "https://registry.npmjs.org/@duckdb/node-bindings/-/node-bindings-1.4.2-r.1.tgz", + "integrity": "sha512-z0pJCdEnIj0Famkip6w7JZ/A17nm5VcYc6H7isOHXfEnPhBQ9PBusUTFgIzl6+3J8HOFQOv0szJq46zldbsHfQ==", "license": "MIT", - "dependencies": { - "ms": "^2.1.1" + "optionalDependencies": { + "@duckdb/node-bindings-darwin-arm64": "1.4.2-r.1", + "@duckdb/node-bindings-darwin-x64": "1.4.2-r.1", + "@duckdb/node-bindings-linux-arm64": "1.4.2-r.1", + "@duckdb/node-bindings-linux-x64": "1.4.2-r.1", + "@duckdb/node-bindings-win32-x64": "1.4.2-r.1" } }, - "node_modules/@expo/sudo-prompt": { - "version": "9.3.2", - "resolved": "https://registry.npmjs.org/@expo/sudo-prompt/-/sudo-prompt-9.3.2.tgz", - "integrity": "sha512-HHQigo3rQWKMDzYDLkubN5WQOYXJJE2eNqIQC2axC2iO3mHdwnIR7FgZVvHWtBwAdzBgAP0ECp8KqS8TiMKvgw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@hono/node-server": { - "version": "1.19.13", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.13.tgz", - "integrity": "sha512-TsQLe4i2gvoTtrHje625ngThGBySOgSK3Xo2XRYOdqGN1teR8+I7vchQC46uLJi8OF62YTYA3AhSpumtkhsaKQ==", - "license": "MIT", - "engines": { - "node": ">=18.14.1" - }, - "peerDependencies": { - "hono": "^4" - } - }, - "node_modules/@hono/node-ws": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@hono/node-ws/-/node-ws-1.3.0.tgz", - "integrity": "sha512-ju25YbbvLuXdqBCmLZLqnNYu1nbHIQjoyUqA8ApZOeL1k4skuiTcw5SW77/5SUYo2Xi2NVBJoVlfQurnKEp03Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ws": "^8.17.0" - }, - "engines": { - "node": ">=18.14.1" - }, - "peerDependencies": { - "@hono/node-server": "^1.19.2", - "hono": "^4.6.0" - } - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/ttlcache": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-2.1.4.tgz", - "integrity": "sha512-7kMz0BJpMvgAMkyglums7B2vtrn5g0a0am77JY0GjkZZNetOBCFn7AG7gKCwT0QPiXyxW7YIQSgtARknUEOcxQ==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=12" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@libsql/client": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/@libsql/client/-/client-0.15.15.tgz", - "integrity": "sha512-twC0hQxPNHPKfeOv3sNT6u2pturQjLcI+CnpTM0SjRpocEGgfiZ7DWKXLNnsothjyJmDqEsBQJ5ztq9Wlu470w==", - "license": "MIT", - "dependencies": { - "@libsql/core": "^0.15.14", - "@libsql/hrana-client": "^0.7.0", - "js-base64": "^3.7.5", - "libsql": "^0.5.22", - "promise-limit": "^2.7.0" - } - }, - "node_modules/@libsql/core": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/@libsql/core/-/core-0.15.15.tgz", - "integrity": "sha512-C88Z6UKl+OyuKKPwz224riz02ih/zHYI3Ho/LAcVOgjsunIRZoBw7fjRfaH9oPMmSNeQfhGklSG2il1URoOIsA==", - "license": "MIT", - "dependencies": { - "js-base64": "^3.7.5" - } - }, - "node_modules/@libsql/darwin-arm64": { - "version": "0.5.29", - "resolved": "https://registry.npmjs.org/@libsql/darwin-arm64/-/darwin-arm64-0.5.29.tgz", - "integrity": "sha512-K+2RIB1OGFPYQbfay48GakLhqf3ArcbHqPFu7EZiaUcRgFcdw8RoltsMyvbj5ix2fY0HV3Q3Ioa/ByvQdaSM0A==", + "node_modules/@duckdb/node-bindings-darwin-arm64": { + "version": "1.4.2-r.1", + "resolved": "https://registry.npmjs.org/@duckdb/node-bindings-darwin-arm64/-/node-bindings-darwin-arm64-1.4.2-r.1.tgz", + "integrity": "sha512-C4yBI3zBX7iZ9iq8zJDvPatXA+2xM22sg1kX3fx76nG+qTqKtU/dGFVYL7Fx6cBYxBO1ZZ8Y+vjgYb6/bich8A==", "cpu": [ "arm64" ], @@ -1324,10 +1493,10 @@ "darwin" ] }, - "node_modules/@libsql/darwin-x64": { - "version": "0.5.29", - "resolved": "https://registry.npmjs.org/@libsql/darwin-x64/-/darwin-x64-0.5.29.tgz", - "integrity": "sha512-OtT+KFHsKFy1R5FVadr8FJ2Bb1mghtXTyJkxv0trocq7NuHntSki1eUbxpO5ezJesDvBlqFjnWaYYY516QNLhQ==", + "node_modules/@duckdb/node-bindings-darwin-x64": { + "version": "1.4.2-r.1", + "resolved": "https://registry.npmjs.org/@duckdb/node-bindings-darwin-x64/-/node-bindings-darwin-x64-1.4.2-r.1.tgz", + "integrity": "sha512-dgTSBuEfWyhymYovsGoRESjqcJuZWwJqla9l89SSsBDrGYcUp+EHsXUF73oUCspzTesT2lOvHrDufO8pKgtudw==", "cpu": [ "x64" ], @@ -1337,43 +1506,12 @@ "darwin" ] }, - "node_modules/@libsql/hrana-client": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@libsql/hrana-client/-/hrana-client-0.7.0.tgz", - "integrity": "sha512-OF8fFQSkbL7vJY9rfuegK1R7sPgQ6kFMkDamiEccNUvieQ+3urzfDFI616oPl8V7T9zRmnTkSjMOImYCAVRVuw==", - "license": "MIT", - "dependencies": { - "@libsql/isomorphic-fetch": "^0.3.1", - "@libsql/isomorphic-ws": "^0.1.5", - "js-base64": "^3.7.5", - "node-fetch": "^3.3.2" - } - }, - "node_modules/@libsql/isomorphic-fetch": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@libsql/isomorphic-fetch/-/isomorphic-fetch-0.3.1.tgz", - "integrity": "sha512-6kK3SUK5Uu56zPq/Las620n5aS9xJq+jMBcNSOmjhNf/MUvdyji4vrMTqD7ptY7/4/CAVEAYDeotUz60LNQHtw==", - "license": "MIT", - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@libsql/isomorphic-ws": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@libsql/isomorphic-ws/-/isomorphic-ws-0.1.5.tgz", - "integrity": "sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg==", - "license": "MIT", - "dependencies": { - "@types/ws": "^8.5.4", - "ws": "^8.13.0" - } - }, - "node_modules/@libsql/linux-arm-gnueabihf": { - "version": "0.5.29", - "resolved": "https://registry.npmjs.org/@libsql/linux-arm-gnueabihf/-/linux-arm-gnueabihf-0.5.29.tgz", - "integrity": "sha512-CD4n4zj7SJTHso4nf5cuMoWoMSS7asn5hHygsDuhRl8jjjCTT3yE+xdUvI4J7zsyb53VO5ISh4cwwOtf6k2UhQ==", + "node_modules/@duckdb/node-bindings-linux-arm64": { + "version": "1.4.2-r.1", + "resolved": "https://registry.npmjs.org/@duckdb/node-bindings-linux-arm64/-/node-bindings-linux-arm64-1.4.2-r.1.tgz", + "integrity": "sha512-r2Ml1LvU2vkVMx4YU04T79FjGkYg8YMVtbOz7j740SZGIi5Cch5P1/oy48jJBWRqoaXuqimpYWeTZiGVeCQiZA==", "cpu": [ - "arm" + "arm64" ], "license": "MIT", "optional": true, @@ -1381,12 +1519,12 @@ "linux" ] }, - "node_modules/@libsql/linux-arm-musleabihf": { - "version": "0.5.29", - "resolved": "https://registry.npmjs.org/@libsql/linux-arm-musleabihf/-/linux-arm-musleabihf-0.5.29.tgz", - "integrity": "sha512-2Z9qBVpEJV7OeflzIR3+l5yAd4uTOLxklScYTwpZnkm2vDSGlC1PRlueLaufc4EFITkLKXK2MWBpexuNJfMVcg==", + "node_modules/@duckdb/node-bindings-linux-x64": { + "version": "1.4.2-r.1", + "resolved": "https://registry.npmjs.org/@duckdb/node-bindings-linux-x64/-/node-bindings-linux-x64-1.4.2-r.1.tgz", + "integrity": "sha512-ed5KiNIu1rqSva5rgo4PRVYSmBolAMVqGFWsYWLoRZ94ka2F/dHwJNkarCTmBFCEDGVZWzWzjRyWTQgYTvQxTg==", "cpu": [ - "arm" + "x64" ], "license": "MIT", "optional": true, @@ -1394,1324 +1532,2823 @@ "linux" ] }, - "node_modules/@libsql/linux-arm64-gnu": { - "version": "0.5.29", - "resolved": "https://registry.npmjs.org/@libsql/linux-arm64-gnu/-/linux-arm64-gnu-0.5.29.tgz", - "integrity": "sha512-gURBqaiXIGGwFNEaUj8Ldk7Hps4STtG+31aEidCk5evMMdtsdfL3HPCpvys+ZF/tkOs2MWlRWoSq7SOuCE9k3w==", + "node_modules/@duckdb/node-bindings-win32-x64": { + "version": "1.4.2-r.1", + "resolved": "https://registry.npmjs.org/@duckdb/node-bindings-win32-x64/-/node-bindings-win32-x64-1.4.2-r.1.tgz", + "integrity": "sha512-kIIT8tuoW3mzr9EPcdSoKfv9CgOhTqRryHDI10Z0nuhc9UhqOWPUM/LnSUebfo1mdD9Drm7YrKCKYxNTr5dqBQ==", "cpu": [ - "arm64" + "x64" ], "license": "MIT", "optional": true, "os": [ - "linux" + "win32" ] }, - "node_modules/@libsql/linux-arm64-musl": { - "version": "0.5.29", - "resolved": "https://registry.npmjs.org/@libsql/linux-arm64-musl/-/linux-arm64-musl-0.5.29.tgz", - "integrity": "sha512-fwgYZ0H8mUkyVqXZHF3mT/92iIh1N94Owi/f66cPVNsk9BdGKq5gVpoKO+7UxaNzuEH1roJp2QEwsCZMvBLpqg==", + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", "cpu": [ - "arm64" + "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" - ] - }, - "node_modules/@libsql/linux-x64-gnu": { - "version": "0.5.29", - "resolved": "https://registry.npmjs.org/@libsql/linux-x64-gnu/-/linux-x64-gnu-0.5.29.tgz", - "integrity": "sha512-y14V0vY0nmMC6G0pHeJcEarcnGU2H6cm21ZceRkacWHvQAEhAG0latQkCtoS2njFOXiYIg+JYPfAoWKbi82rkg==", - "cpu": [ - "x64" + "aix" ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": ">=18" + } }, - "node_modules/@libsql/linux-x64-musl": { - "version": "0.5.29", - "resolved": "https://registry.npmjs.org/@libsql/linux-x64-musl/-/linux-x64-musl-0.5.29.tgz", - "integrity": "sha512-gquqwA/39tH4pFl+J9n3SOMSymjX+6kZ3kWgY3b94nXFTwac9bnFNMffIomgvlFaC4ArVqMnOZD3nuJ3H3VO1w==", + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", "cpu": [ - "x64" + "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" - ] + "android" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@libsql/win32-x64-msvc": { - "version": "0.5.29", - "resolved": "https://registry.npmjs.org/@libsql/win32-x64-msvc/-/win32-x64-msvc-0.5.29.tgz", - "integrity": "sha512-4/0CvEdhi6+KjMxMaVbFM2n2Z44escBRoEYpR+gZg64DdetzGnYm8mcNLcoySaDJZNaBd6wz5DNdgRmcI4hXcg==", + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", "cpu": [ - "x64" + "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ - "win32" - ] + "android" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@lukeed/csprng": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz", - "integrity": "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==", + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/@lukeed/uuid": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@lukeed/uuid/-/uuid-2.0.1.tgz", - "integrity": "sha512-qC72D4+CDdjGqJvkFMMEAtancHUQ7/d/tAiHf64z8MopFDmcrtbcJuerDtFceuAfQJ2pDSfCKCtbqoGBNnwg0w==", + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@lukeed/csprng": "^1.1.0" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/@mastra/core": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/@mastra/core/-/core-1.24.1.tgz", - "integrity": "sha512-Y0a0HZl7AMsuqrKM1OLePTJRaCr71w0RcUbCuaTgGN527jUTpfKmj9KJoNKqx9OTYUgZQ5ssNJMM6oDxsj2gxQ==", - "license": "Apache-2.0", - "dependencies": { - "@a2a-js/sdk": "~0.2.5", - "@ai-sdk/provider-utils-v5": "npm:@ai-sdk/provider-utils@3.0.20", - "@ai-sdk/provider-utils-v6": "npm:@ai-sdk/provider-utils@4.0.0", - "@ai-sdk/provider-v5": "npm:@ai-sdk/provider@2.0.1", - "@ai-sdk/provider-v6": "npm:@ai-sdk/provider@3.0.5", - "@ai-sdk/ui-utils-v5": "npm:@ai-sdk/ui-utils@1.2.11", - "@isaacs/ttlcache": "^2.1.4", - "@lukeed/uuid": "^2.0.1", - "@mastra/schema-compat": "1.2.7", - "@modelcontextprotocol/sdk": "^1.27.1", - "@sindresorhus/slugify": "^2.2.1", - "@standard-schema/spec": "^1.1.0", - "ajv": "^8.18.0", - "chat": "^4.23.0", - "dotenv": "^17.3.1", - "execa": "^9.6.1", - "gray-matter": "^4.0.3", - "hono": "^4.12.8", - "hono-openapi": "^1.3.0", - "ignore": "^7.0.5", - "js-tiktoken": "^1.0.21", - "json-schema": "^0.4.0", - "lru-cache": "^11.2.7", - "p-map": "^7.0.4", - "p-retry": "^7.1.1", - "picomatch": "^4.0.3", - "radash": "^12.1.1", - "tokenx": "^1.3.0", - "ws": "^8.19.0", - "xxhash-wasm": "^1.1.0" - }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=22.13.0" - }, - "peerDependencies": { - "zod": "^3.25.0 || ^4.0.0" + "node": ">=18" } }, - "node_modules/@mastra/core/node_modules/@ai-sdk/provider": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-1.1.3.tgz", - "integrity": "sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg==", - "license": "Apache-2.0", - "dependencies": { - "json-schema": "^0.4.0" - }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { "node": ">=18" } }, - "node_modules/@mastra/core/node_modules/@ai-sdk/ui-utils-v5": { - "name": "@ai-sdk/ui-utils", - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@ai-sdk/ui-utils/-/ui-utils-1.2.11.tgz", - "integrity": "sha512-3zcwCc8ezzFlwp3ZD15wAPjf2Au4s3vAbKsXQVyhxODHcmu0iyPO2Eua6D/vicq/AUm/BAo60r97O6HU+EI0+w==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "1.1.3", - "@ai-sdk/provider-utils": "2.2.8", - "zod-to-json-schema": "^3.24.1" - }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.23.8" } }, - "node_modules/@mastra/core/node_modules/@ai-sdk/ui-utils-v5/node_modules/@ai-sdk/provider-utils": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-2.2.8.tgz", - "integrity": "sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "1.1.3", - "nanoid": "^3.3.8", - "secure-json-parse": "^2.7.0" - }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.23.8" } }, - "node_modules/@mastra/core/node_modules/@standard-community/standard-openapi": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@standard-community/standard-openapi/-/standard-openapi-0.2.9.tgz", - "integrity": "sha512-htj+yldvN1XncyZi4rehbf9kLbu8os2Ke/rfqoZHCMHuw34kiF3LP/yQPdA0tQ940y8nDq3Iou8R3wG+AGGyvg==", + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", - "peer": true, - "peerDependencies": { - "@standard-community/standard-json": "^0.3.5", - "@standard-schema/spec": "^1.0.0", - "arktype": "^2.1.20", - "effect": "^3.17.14", - "openapi-types": "^12.1.3", - "sury": "^10.0.0", - "typebox": "^1.0.0", - "valibot": "^1.1.0", - "zod": "^3.25.0 || ^4.0.0", - "zod-openapi": "^4" - }, - "peerDependenciesMeta": { - "arktype": { - "optional": true - }, - "effect": { - "optional": true - }, - "sury": { - "optional": true - }, - "typebox": { - "optional": true - }, - "valibot": { - "optional": true - }, - "zod": { - "optional": true - }, - "zod-openapi": { - "optional": true - } + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@mastra/core/node_modules/hono-openapi": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/hono-openapi/-/hono-openapi-1.3.0.tgz", - "integrity": "sha512-xDvCWpWEIv0weEmnl3EjRQzqbHIO8LnfzMuYOCmbuyE5aes6aXxLg4vM3ybnoZD5TiTUkA6PuRQPJs3R7WRBig==", + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, "license": "MIT", - "peerDependencies": { - "@hono/standard-validator": "^0.2.0", - "@standard-community/standard-json": "^0.3.5", - "@standard-community/standard-openapi": "^0.2.9", - "@types/json-schema": "^7.0.15", - "hono": "^4.8.3", - "openapi-types": "^12.1.3" - }, - "peerDependenciesMeta": { - "@hono/standard-validator": { - "optional": true - }, - "hono": { - "optional": true - } + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@mastra/core/node_modules/secure-json-parse": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", - "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", - "license": "BSD-3-Clause" - }, - "node_modules/@mastra/deployer": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/@mastra/deployer/-/deployer-1.24.1.tgz", - "integrity": "sha512-cyepN5XOKaz5xW9fOepiUS6+WmMwx2x7yT4UJgavpc8nn9iHpNynG+bPM9hpXA1w8ezuqMazf9noTqIoXQ+S9Q==", + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@babel/core": "^7.29.0", - "@babel/preset-typescript": "^7.28.5", - "@babel/traverse": "^7.29.0", - "@hono/node-ws": "^1.3.0", - "@mastra/server": "1.24.1", - "@optimize-lodash/rollup-plugin": "^5.1.0", - "@rollup/plugin-alias": "6.0.0", - "@rollup/plugin-commonjs": "29.0.2", - "@rollup/plugin-esm-shim": "0.1.8", - "@rollup/plugin-json": "6.1.0", - "@rollup/plugin-node-resolve": "16.0.3", - "@rollup/plugin-virtual": "3.0.2", - "@sindresorhus/slugify": "^2.2.1", - "@types/babel__traverse": "^7.28.0", - "empathic": "^2.0.0", - "esbuild": "^0.27.4", - "find-workspaces": "^0.3.1", - "fs-extra": "^11.3.4", - "hono": "^4.12.8", - "local-pkg": "^1.1.2", - "resolve-from": "^5.0.0", - "resolve.exports": "^2.0.3", - "rollup": "^4.59.0", - "rollup-plugin-esbuild": "^6.2.1", - "strip-json-comments": "^5.0.3", - "tinyglobby": "^0.2.15", - "typescript-paths": "^1.5.1", - "ws": "^8.18.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=22.13.0" - }, - "peerDependencies": { - "@mastra/core": ">=1.0.0-0 <2.0.0-0", - "zod": "^3.25.0 || ^4.0.0" + "node": ">=18" } }, - "node_modules/@mastra/duckdb": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@mastra/duckdb/-/duckdb-1.1.1.tgz", - "integrity": "sha512-fLga4uFfiGtmEFKMtjV0lbUOdf+SSOcSwXpQdLgE+cV2ZRzg/nCgsJgtpjLOsTVhsbO6ZInG7aR94/EXiOUCrA==", - "license": "Apache-2.0", - "dependencies": { - "@duckdb/node-api": "1.4.2-r.1" - }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=22.13.0" - }, - "peerDependencies": { - "@mastra/core": ">=1.0.0-0 <2.0.0-0" + "node": ">=18" } }, - "node_modules/@mastra/evals": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@mastra/evals/-/evals-1.2.1.tgz", - "integrity": "sha512-wiXRiHctOBUqtguDbw2ypt3BCrhNDJbQYUKrSSaeNnGrVNrFG6LH2rJgy+TdM/6fBEPzLw/sK3E4s4S11DlptQ==", - "license": "Apache-2.0", - "dependencies": { - "compromise": "^14.15.0", - "keyword-extractor": "^0.0.28", - "sentiment": "^5.0.2", - "string-similarity": "^4.0.4" - }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=22.13.0" - }, - "peerDependencies": { - "@mastra/core": ">=1.0.0-0 <2.0.0-0", - "zod": "^3.25.0 || ^4.0.0" + "node": ">=18" } }, - "node_modules/@mastra/libsql": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@mastra/libsql/-/libsql-1.8.0.tgz", - "integrity": "sha512-uZXdL9QIubLg5QDfEE70cEDsPrMIOMyfK/c8SgsMAXCknAYI5i7IWPkWY6tN5329S3wVMPHvol78LNI2euYxEQ==", - "license": "Apache-2.0", - "dependencies": { - "@libsql/client": "^0.15.15" - }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=22.13.0" - }, - "peerDependencies": { - "@mastra/core": ">=1.0.0-0 <2.0.0-0" + "node": ">=18" } }, - "node_modules/@mastra/loggers": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@mastra/loggers/-/loggers-1.1.1.tgz", - "integrity": "sha512-zszCHjYlnADYeFLaOIvQH/c86Wdn+tX0WTboc6K6NvPXa5dIq9TI4qzdy5IdhQn1auSzrgbCYRKdJnZKKoJSpw==", - "license": "Apache-2.0", - "dependencies": { - "pino": "^10.3.1", - "pino-pretty": "^13.1.3" - }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=22.13.0" - }, - "peerDependencies": { - "@mastra/core": ">=1.0.0-0 <2.0.0-0" + "node": ">=18" } }, - "node_modules/@mastra/memory": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@mastra/memory/-/memory-1.15.0.tgz", - "integrity": "sha512-mPsdfzjb7AYwfsuT74CTGkRdLCKGjgQ42Pe09wHQbRe0JUpK1XsA7rjSNUPTaMYqbu8N9F6/z16WnsXMtaGytQ==", - "license": "Apache-2.0", - "dependencies": { - "@mastra/schema-compat": "1.2.7", - "async-mutex": "^0.5.0", - "image-size": "^2.0.2", - "json-schema": "^0.4.0", - "lru-cache": "^11.2.7", - "probe-image-size": "^7.2.3", - "tokenx": "^1.3.0", - "xxhash-wasm": "^1.1.0" - }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=22.13.0" - }, - "peerDependencies": { - "@mastra/core": ">=1.4.1-0 <2.0.0-0", - "zod": "^3.25.0 || ^4.0.0" + "node": ">=18" } }, - "node_modules/@mastra/observability": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@mastra/observability/-/observability-1.9.0.tgz", - "integrity": "sha512-7ULvHgBeYAdmQAQAnLfPmpeSU3o5MEbv/w7KFeYNWoJEBfOXDPKwMuhgKsJF4PqPfo9OuCoeWIt4OlskGO7gdQ==", - "license": "Apache-2.0", + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">=22.13.0" - }, - "peerDependencies": { - "@mastra/core": ">=1.16.0-0 <2.0.0-0" + "node": ">=18" } }, - "node_modules/@mastra/schema-compat": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@mastra/schema-compat/-/schema-compat-1.2.7.tgz", - "integrity": "sha512-t63E0f5HcH8neXPfs3D5x4qqQM6Pf/pbhFUVk0cTC0bFo6609sT/+189I+2HY4sbAF3uzurOgy2fXIS4vfMkOA==", - "license": "Apache-2.0", - "dependencies": { - "json-schema-to-zod": "^2.7.0", - "zod-from-json-schema": "^0.5.2", - "zod-from-json-schema-v3": "npm:zod-from-json-schema@^0.0.5", - "zod-to-json-schema": "^3.25.1" - }, - "engines": { - "node": ">=22.13.0" - }, - "peerDependencies": { - "zod": "^3.25.0 || ^4.0.0" - } - }, - "node_modules/@mastra/server": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/@mastra/server/-/server-1.24.1.tgz", - "integrity": "sha512-gSNVzJ62THcmInWnxEdwzzDDgjFjTr2aQ7ImFNKnBzSFQ7kNuV1kS6M/xwHOZk0esEYfpkhpk6S/znx9L1/iLg==", + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "hono": "^4.12.8" - }, - "engines": { - "node": ">=22.13.0" - }, - "peerDependencies": { - "@mastra/core": ">=1.13.2-0 <2.0.0-0", - "zod": "^3.25.0 || ^4.0.0" - } - }, - "node_modules/@modelcontextprotocol/sdk": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", - "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", "license": "MIT", - "dependencies": { - "@hono/node-server": "^1.19.9", - "ajv": "^8.17.1", - "ajv-formats": "^3.0.1", - "content-type": "^1.0.5", - "cors": "^2.8.5", - "cross-spawn": "^7.0.5", - "eventsource": "^3.0.2", - "eventsource-parser": "^3.0.0", - "express": "^5.2.1", - "express-rate-limit": "^8.2.1", - "hono": "^4.11.4", - "jose": "^6.1.3", - "json-schema-typed": "^8.0.2", - "pkce-challenge": "^5.0.0", - "raw-body": "^3.0.0", - "zod": "^3.25 || ^4.0", - "zod-to-json-schema": "^3.25.1" - }, + "optional": true, + "os": [ + "netbsd" + ], "engines": { "node": ">=18" - }, - "peerDependencies": { - "@cfworker/json-schema": "^4.1.1", - "zod": "^3.25 || ^4.0" - }, - "peerDependenciesMeta": { - "@cfworker/json-schema": { - "optional": true - }, - "zod": { - "optional": false - } } }, - "node_modules/@modelcontextprotocol/sdk/node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">= 0.6" + "node": ">=18" } }, - "node_modules/@modelcontextprotocol/sdk/node_modules/content-disposition": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", - "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], "engines": { "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" } }, - "node_modules/@modelcontextprotocol/sdk/node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], "engines": { - "node": ">=6.6.0" + "node": ">=18" } }, - "node_modules/@modelcontextprotocol/sdk/node_modules/express": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", - "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.1", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "depd": "^2.0.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, + "optional": true, + "os": [ + "sunos" + ], "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">=18" } }, - "node_modules/@modelcontextprotocol/sdk/node_modules/finalhandler": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", - "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">=18" } }, - "node_modules/@modelcontextprotocol/sdk/node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 0.8" + "node": ">=18" } }, - "node_modules/@modelcontextprotocol/sdk/node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@modelcontextprotocol/sdk/node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" + "node_modules/@expo/devcert": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@expo/devcert/-/devcert-1.2.1.tgz", + "integrity": "sha512-qC4eaxmKMTmJC2ahwyui6ud8f3W60Ss7pMkpBq40Hu3zyiAaugPXnZ24145U7K36qO9UHdZUVxsCvIpz2RYYCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@expo/sudo-prompt": "^9.3.1", + "debug": "^3.1.0" } }, - "node_modules/@modelcontextprotocol/sdk/node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "node_modules/@expo/devcert/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, "license": "MIT", "dependencies": { - "mime-db": "^1.54.0" + "ms": "^2.1.1" + } + }, + "node_modules/@expo/sudo-prompt": { + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/@expo/sudo-prompt/-/sudo-prompt-9.3.2.tgz", + "integrity": "sha512-HHQigo3rQWKMDzYDLkubN5WQOYXJJE2eNqIQC2axC2iO3mHdwnIR7FgZVvHWtBwAdzBgAP0ECp8KqS8TiMKvgw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@google/genai": { + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.49.0.tgz", + "integrity": "sha512-hO69Zl0H3x+L0KL4stl1pLYgnqnwHoLqtKy6MRlNnW8TAxjqMdOUVafomKd4z1BePkzoxJWbYILny9a2Zk43VQ==", + "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": ">=18" + "node": ">=20.0.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "peerDependencies": { + "@modelcontextprotocol/sdk": "^1.25.2" + }, + "peerDependenciesMeta": { + "@modelcontextprotocol/sdk": { + "optional": true + } } }, - "node_modules/@modelcontextprotocol/sdk/node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "node_modules/@google/genai/node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", "license": "MIT", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, "engines": { - "node": ">= 0.6" + "node": ">=8" } }, - "node_modules/@modelcontextprotocol/sdk/node_modules/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", - "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "node_modules/@hono/node-server": { + "version": "1.19.13", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.13.tgz", + "integrity": "sha512-TsQLe4i2gvoTtrHje625ngThGBySOgSK3Xo2XRYOdqGN1teR8+I7vchQC46uLJi8OF62YTYA3AhSpumtkhsaKQ==", "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.1", - "mime-types": "^3.0.2", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.2" - }, "engines": { - "node": ">= 18" + "node": ">=18.14.1" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "peerDependencies": { + "hono": "^4" } }, - "node_modules/@modelcontextprotocol/sdk/node_modules/serve-static": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", - "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "node_modules/@hono/node-ws": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@hono/node-ws/-/node-ws-1.3.0.tgz", + "integrity": "sha512-ju25YbbvLuXdqBCmLZLqnNYu1nbHIQjoyUqA8ApZOeL1k4skuiTcw5SW77/5SUYo2Xi2NVBJoVlfQurnKEp03Q==", + "dev": true, "license": "MIT", "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" + "ws": "^8.17.0" }, "engines": { - "node": ">= 18" + "node": ">=18.14.1" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "peerDependencies": { + "@hono/node-server": "^1.19.2", + "hono": "^4.6.0" } }, - "node_modules/@neon-rs/load": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/@neon-rs/load/-/load-0.0.4.tgz", - "integrity": "sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==", - "license": "MIT" - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" }, "engines": { - "node": ">= 8" + "node": ">=12" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", + "node_modules/@isaacs/ttlcache": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-2.1.4.tgz", + "integrity": "sha512-7kMz0BJpMvgAMkyglums7B2vtrn5g0a0am77JY0GjkZZNetOBCFn7AG7gKCwT0QPiXyxW7YIQSgtARknUEOcxQ==", + "license": "BlueOak-1.0.0", "engines": { - "node": ">= 8" + "node": ">=12" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, + "node_modules/@jgoon/bluebubbles": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@jgoon/bluebubbles/-/bluebubbles-1.1.0.tgz", + "integrity": "sha512-xH5Bsu/IPDwjtXa/ShFli6uFolJLcfg730NAJfRTtPFTv3/hpGi3Lr+XwpuqBRJv17Ydis97yUIXilax4CKVXg==", "license": "MIT", "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" + "openapi-fetch": "^0.17.0" }, "engines": { - "node": ">= 8" + "node": ">=18.0.0" } }, - "node_modules/@optimize-lodash/rollup-plugin": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@optimize-lodash/rollup-plugin/-/rollup-plugin-5.1.0.tgz", - "integrity": "sha512-dBQYGH8+n4Z/877e61PJteVZEc+U1wfRF6IhKqW0cABRIsqMxpWynyov6M6Sd4IUjru0dnh0EG55X86JO6WakQ==", + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, "license": "MIT", "dependencies": { - "@optimize-lodash/transform": "3.0.6", - "@rollup/pluginutils": "^5.1.0" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "rollup": ">= 4.x" + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@optimize-lodash/transform": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@optimize-lodash/transform/-/transform-3.0.6.tgz", - "integrity": "sha512-9+qMSaDpahC0+vX2ChM46/ls6a5Ankqs6RTLrHSaFpm7o1mFanP82e+jm9/0o5D660ueK8dWJGPCXQrBxBNNWA==", + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", "dev": true, "license": "MIT", "dependencies": { - "estree-walker": "^2.0.2", - "magic-string": "~0.30.11" - }, - "engines": { - "node": ">= 12" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@pinojs/redact": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", - "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", - "license": "MIT" - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, "license": "MIT", - "optional": true, "engines": { - "node": ">=14" + "node": ">=6.0.0" } }, - "node_modules/@posthog/core": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@posthog/core/-/core-1.7.1.tgz", - "integrity": "sha512-kjK0eFMIpKo9GXIbts8VtAknsoZ18oZorANdtuTj1CbgS28t4ZVq//HAWhnxEuXRTrtkd+SUJ6Ux3j2Af8NCuA==", + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.6" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@rollup/plugin-alias": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-alias/-/plugin-alias-6.0.0.tgz", - "integrity": "sha512-tPCzJOtS7uuVZd+xPhoy5W4vThe6KWXNmsFCNktaAh5RTqcLiSfT4huPQIXkgJ6YCOjJHvecOAzQxLFhPxKr+g==", - "dev": true, + "node_modules/@libsql/client": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/@libsql/client/-/client-0.15.15.tgz", + "integrity": "sha512-twC0hQxPNHPKfeOv3sNT6u2pturQjLcI+CnpTM0SjRpocEGgfiZ7DWKXLNnsothjyJmDqEsBQJ5ztq9Wlu470w==", "license": "MIT", - "engines": { - "node": ">=20.19.0" - }, - "peerDependencies": { - "rollup": ">=4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } + "dependencies": { + "@libsql/core": "^0.15.14", + "@libsql/hrana-client": "^0.7.0", + "js-base64": "^3.7.5", + "libsql": "^0.5.22", + "promise-limit": "^2.7.0" } }, - "node_modules/@rollup/plugin-commonjs": { - "version": "29.0.2", - "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-29.0.2.tgz", - "integrity": "sha512-S/ggWH1LU7jTyi9DxZOKyxpVd4hF/OZ0JrEbeLjXk/DFXwRny0tjD2c992zOUYQobLrVkRVMDdmHP16HKP7GRg==", - "dev": true, + "node_modules/@libsql/core": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/@libsql/core/-/core-0.15.15.tgz", + "integrity": "sha512-C88Z6UKl+OyuKKPwz224riz02ih/zHYI3Ho/LAcVOgjsunIRZoBw7fjRfaH9oPMmSNeQfhGklSG2il1URoOIsA==", "license": "MIT", "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "commondir": "^1.0.1", - "estree-walker": "^2.0.2", - "fdir": "^6.2.0", - "is-reference": "1.2.1", - "magic-string": "^0.30.3", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=16.0.0 || 14 >= 14.17" - }, - "peerDependencies": { - "rollup": "^2.68.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } + "js-base64": "^3.7.5" } }, - "node_modules/@rollup/plugin-esm-shim": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/@rollup/plugin-esm-shim/-/plugin-esm-shim-0.1.8.tgz", - "integrity": "sha512-xEU0b/BShgDDSPjidhJd4R74J9xZ9jLVtFWNGtsUXyEsdwwwB1a3XOAwwGaNIyUHD6EhxPO21JMfUmJWoMn7SA==", - "dev": true, + "node_modules/@libsql/darwin-arm64": { + "version": "0.5.29", + "resolved": "https://registry.npmjs.org/@libsql/darwin-arm64/-/darwin-arm64-0.5.29.tgz", + "integrity": "sha512-K+2RIB1OGFPYQbfay48GakLhqf3ArcbHqPFu7EZiaUcRgFcdw8RoltsMyvbj5ix2fY0HV3Q3Ioa/ByvQdaSM0A==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "magic-string": "^0.30.3", - "mlly": "^1.7.4" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@rollup/plugin-json": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", - "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==", - "dev": true, + "node_modules/@libsql/darwin-x64": { + "version": "0.5.29", + "resolved": "https://registry.npmjs.org/@libsql/darwin-x64/-/darwin-x64-0.5.29.tgz", + "integrity": "sha512-OtT+KFHsKFy1R5FVadr8FJ2Bb1mghtXTyJkxv0trocq7NuHntSki1eUbxpO5ezJesDvBlqFjnWaYYY516QNLhQ==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.1.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@rollup/plugin-node-resolve": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.3.tgz", - "integrity": "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==", - "dev": true, + "node_modules/@libsql/hrana-client": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@libsql/hrana-client/-/hrana-client-0.7.0.tgz", + "integrity": "sha512-OF8fFQSkbL7vJY9rfuegK1R7sPgQ6kFMkDamiEccNUvieQ+3urzfDFI616oPl8V7T9zRmnTkSjMOImYCAVRVuw==", "license": "MIT", "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "@types/resolve": "1.20.2", - "deepmerge": "^4.2.2", - "is-module": "^1.0.0", - "resolve": "^1.22.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.78.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } + "@libsql/isomorphic-fetch": "^0.3.1", + "@libsql/isomorphic-ws": "^0.1.5", + "js-base64": "^3.7.5", + "node-fetch": "^3.3.2" } }, - "node_modules/@rollup/plugin-virtual": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@rollup/plugin-virtual/-/plugin-virtual-3.0.2.tgz", - "integrity": "sha512-10monEYsBp3scM4/ND4LNH5Rxvh3e/cVeL3jWTgZ2SrQ+BmUoQcopVQvnaMcOnykb1VkxUFuDAN+0FnpTFRy2A==", - "dev": true, + "node_modules/@libsql/isomorphic-fetch": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@libsql/isomorphic-fetch/-/isomorphic-fetch-0.3.1.tgz", + "integrity": "sha512-6kK3SUK5Uu56zPq/Las620n5aS9xJq+jMBcNSOmjhNf/MUvdyji4vrMTqD7ptY7/4/CAVEAYDeotUz60LNQHtw==", "license": "MIT", "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } + "node": ">=18.0.0" } }, - "node_modules/@rollup/pluginutils": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", - "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", - "dev": true, + "node_modules/@libsql/isomorphic-ws": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@libsql/isomorphic-ws/-/isomorphic-ws-0.1.5.tgz", + "integrity": "sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg==", "license": "MIT", "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } + "@types/ws": "^8.5.4", + "ws": "^8.13.0" } }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz", - "integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==", + "node_modules/@libsql/linux-arm-gnueabihf": { + "version": "0.5.29", + "resolved": "https://registry.npmjs.org/@libsql/linux-arm-gnueabihf/-/linux-arm-gnueabihf-0.5.29.tgz", + "integrity": "sha512-CD4n4zj7SJTHso4nf5cuMoWoMSS7asn5hHygsDuhRl8jjjCTT3yE+xdUvI4J7zsyb53VO5ISh4cwwOtf6k2UhQ==", "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ - "android" + "linux" ] }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz", - "integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==", + "node_modules/@libsql/linux-arm-musleabihf": { + "version": "0.5.29", + "resolved": "https://registry.npmjs.org/@libsql/linux-arm-musleabihf/-/linux-arm-musleabihf-0.5.29.tgz", + "integrity": "sha512-2Z9qBVpEJV7OeflzIR3+l5yAd4uTOLxklScYTwpZnkm2vDSGlC1PRlueLaufc4EFITkLKXK2MWBpexuNJfMVcg==", "cpu": [ - "arm64" + "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ - "android" + "linux" ] }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz", - "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==", + "node_modules/@libsql/linux-arm64-gnu": { + "version": "0.5.29", + "resolved": "https://registry.npmjs.org/@libsql/linux-arm64-gnu/-/linux-arm64-gnu-0.5.29.tgz", + "integrity": "sha512-gURBqaiXIGGwFNEaUj8Ldk7Hps4STtG+31aEidCk5evMMdtsdfL3HPCpvys+ZF/tkOs2MWlRWoSq7SOuCE9k3w==", "cpu": [ "arm64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz", - "integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==", - "cpu": [ - "x64" - ], - "dev": true, "license": "MIT", "optional": true, "os": [ - "darwin" + "linux" ] }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz", - "integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==", + "node_modules/@libsql/linux-arm64-musl": { + "version": "0.5.29", + "resolved": "https://registry.npmjs.org/@libsql/linux-arm64-musl/-/linux-arm64-musl-0.5.29.tgz", + "integrity": "sha512-fwgYZ0H8mUkyVqXZHF3mT/92iIh1N94Owi/f66cPVNsk9BdGKq5gVpoKO+7UxaNzuEH1roJp2QEwsCZMvBLpqg==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ - "freebsd" + "linux" ] }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz", - "integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==", + "node_modules/@libsql/linux-x64-gnu": { + "version": "0.5.29", + "resolved": "https://registry.npmjs.org/@libsql/linux-x64-gnu/-/linux-x64-gnu-0.5.29.tgz", + "integrity": "sha512-y14V0vY0nmMC6G0pHeJcEarcnGU2H6cm21ZceRkacWHvQAEhAG0latQkCtoS2njFOXiYIg+JYPfAoWKbi82rkg==", "cpu": [ "x64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz", - "integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==", - "cpu": [ - "arm" - ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ] }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz", - "integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==", + "node_modules/@libsql/linux-x64-musl": { + "version": "0.5.29", + "resolved": "https://registry.npmjs.org/@libsql/linux-x64-musl/-/linux-x64-musl-0.5.29.tgz", + "integrity": "sha512-gquqwA/39tH4pFl+J9n3SOMSymjX+6kZ3kWgY3b94nXFTwac9bnFNMffIomgvlFaC4ArVqMnOZD3nuJ3H3VO1w==", "cpu": [ - "arm" + "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ] }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz", - "integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==", + "node_modules/@libsql/win32-x64-msvc": { + "version": "0.5.29", + "resolved": "https://registry.npmjs.org/@libsql/win32-x64-msvc/-/win32-x64-msvc-0.5.29.tgz", + "integrity": "sha512-4/0CvEdhi6+KjMxMaVbFM2n2Z44escBRoEYpR+gZg64DdetzGnYm8mcNLcoySaDJZNaBd6wz5DNdgRmcI4hXcg==", "cpu": [ - "arm64" + "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" + "win32" ] }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz", - "integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@lukeed/csprng": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz", + "integrity": "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": ">=8" + } }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz", - "integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==", - "cpu": [ - "loong64" - ], - "dev": true, + "node_modules/@lukeed/uuid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@lukeed/uuid/-/uuid-2.0.1.tgz", + "integrity": "sha512-qC72D4+CDdjGqJvkFMMEAtancHUQ7/d/tAiHf64z8MopFDmcrtbcJuerDtFceuAfQJ2pDSfCKCtbqoGBNnwg0w==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@lukeed/csprng": "^1.1.0" + }, + "engines": { + "node": ">=8" + } }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz", - "integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==", - "cpu": [ - "loong64" - ], - "dev": true, + "node_modules/@mariozechner/pi-ai": { + "version": "0.66.1", + "resolved": "https://registry.npmjs.org/@mariozechner/pi-ai/-/pi-ai-0.66.1.tgz", + "integrity": "sha512-7IZHvpsFdKEBkTmjNrdVL7JLUJVIpha6bwTr12cZ5XyDrxij06wP6Ncpnf4HT5BXAzD5w2JnoqTOSbMEIZj3dg==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@anthropic-ai/sdk": "^0.73.0", + "@aws-sdk/client-bedrock-runtime": "^3.983.0", + "@google/genai": "^1.40.0", + "@mistralai/mistralai": "1.14.1", + "@sinclair/typebox": "^0.34.41", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "chalk": "^5.6.2", + "openai": "6.26.0", + "partial-json": "^0.1.7", + "proxy-agent": "^6.5.0", + "undici": "^7.19.1", + "zod-to-json-schema": "^3.24.6" + }, + "bin": { + "pi-ai": "dist/cli.js" + }, + "engines": { + "node": ">=20.0.0" + } }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz", - "integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==", - "cpu": [ - "ppc64" - ], - "dev": true, + "node_modules/@mariozechner/pi-ai/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz", - "integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "node_modules/@mastra/core": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/@mastra/core/-/core-1.24.1.tgz", + "integrity": "sha512-Y0a0HZl7AMsuqrKM1OLePTJRaCr71w0RcUbCuaTgGN527jUTpfKmj9KJoNKqx9OTYUgZQ5ssNJMM6oDxsj2gxQ==", + "license": "Apache-2.0", + "dependencies": { + "@a2a-js/sdk": "~0.2.5", + "@ai-sdk/provider-utils-v5": "npm:@ai-sdk/provider-utils@3.0.20", + "@ai-sdk/provider-utils-v6": "npm:@ai-sdk/provider-utils@4.0.0", + "@ai-sdk/provider-v5": "npm:@ai-sdk/provider@2.0.1", + "@ai-sdk/provider-v6": "npm:@ai-sdk/provider@3.0.5", + "@ai-sdk/ui-utils-v5": "npm:@ai-sdk/ui-utils@1.2.11", + "@isaacs/ttlcache": "^2.1.4", + "@lukeed/uuid": "^2.0.1", + "@mastra/schema-compat": "1.2.7", + "@modelcontextprotocol/sdk": "^1.27.1", + "@sindresorhus/slugify": "^2.2.1", + "@standard-schema/spec": "^1.1.0", + "ajv": "^8.18.0", + "chat": "^4.23.0", + "dotenv": "^17.3.1", + "execa": "^9.6.1", + "gray-matter": "^4.0.3", + "hono": "^4.12.8", + "hono-openapi": "^1.3.0", + "ignore": "^7.0.5", + "js-tiktoken": "^1.0.21", + "json-schema": "^0.4.0", + "lru-cache": "^11.2.7", + "p-map": "^7.0.4", + "p-retry": "^7.1.1", + "picomatch": "^4.0.3", + "radash": "^12.1.1", + "tokenx": "^1.3.0", + "ws": "^8.19.0", + "xxhash-wasm": "^1.1.0" + }, + "engines": { + "node": ">=22.13.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz", - "integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "node_modules/@mastra/core/node_modules/@ai-sdk/provider": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-1.1.3.tgz", + "integrity": "sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg==", + "license": "Apache-2.0", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=18" + } }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz", - "integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "node_modules/@mastra/core/node_modules/@ai-sdk/ui-utils-v5": { + "name": "@ai-sdk/ui-utils", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@ai-sdk/ui-utils/-/ui-utils-1.2.11.tgz", + "integrity": "sha512-3zcwCc8ezzFlwp3ZD15wAPjf2Au4s3vAbKsXQVyhxODHcmu0iyPO2Eua6D/vicq/AUm/BAo60r97O6HU+EI0+w==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "1.1.3", + "@ai-sdk/provider-utils": "2.2.8", + "zod-to-json-schema": "^3.24.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.23.8" + } }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz", - "integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "node_modules/@mastra/core/node_modules/@ai-sdk/ui-utils-v5/node_modules/@ai-sdk/provider-utils": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-2.2.8.tgz", + "integrity": "sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "1.1.3", + "nanoid": "^3.3.8", + "secure-json-parse": "^2.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.23.8" + } }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz", - "integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@mastra/core/node_modules/@standard-community/standard-openapi": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@standard-community/standard-openapi/-/standard-openapi-0.2.9.tgz", + "integrity": "sha512-htj+yldvN1XncyZi4rehbf9kLbu8os2Ke/rfqoZHCMHuw34kiF3LP/yQPdA0tQ940y8nDq3Iou8R3wG+AGGyvg==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "peer": true, + "peerDependencies": { + "@standard-community/standard-json": "^0.3.5", + "@standard-schema/spec": "^1.0.0", + "arktype": "^2.1.20", + "effect": "^3.17.14", + "openapi-types": "^12.1.3", + "sury": "^10.0.0", + "typebox": "^1.0.0", + "valibot": "^1.1.0", + "zod": "^3.25.0 || ^4.0.0", + "zod-openapi": "^4" + }, + "peerDependenciesMeta": { + "arktype": { + "optional": true + }, + "effect": { + "optional": true + }, + "sury": { + "optional": true + }, + "typebox": { + "optional": true + }, + "valibot": { + "optional": true + }, + "zod": { + "optional": true + }, + "zod-openapi": { + "optional": true + } + } + }, + "node_modules/@mastra/core/node_modules/hono-openapi": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/hono-openapi/-/hono-openapi-1.3.0.tgz", + "integrity": "sha512-xDvCWpWEIv0weEmnl3EjRQzqbHIO8LnfzMuYOCmbuyE5aes6aXxLg4vM3ybnoZD5TiTUkA6PuRQPJs3R7WRBig==", + "license": "MIT", + "peerDependencies": { + "@hono/standard-validator": "^0.2.0", + "@standard-community/standard-json": "^0.3.5", + "@standard-community/standard-openapi": "^0.2.9", + "@types/json-schema": "^7.0.15", + "hono": "^4.8.3", + "openapi-types": "^12.1.3" + }, + "peerDependenciesMeta": { + "@hono/standard-validator": { + "optional": true + }, + "hono": { + "optional": true + } + } + }, + "node_modules/@mastra/core/node_modules/secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", + "license": "BSD-3-Clause" + }, + "node_modules/@mastra/deployer": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/@mastra/deployer/-/deployer-1.24.1.tgz", + "integrity": "sha512-cyepN5XOKaz5xW9fOepiUS6+WmMwx2x7yT4UJgavpc8nn9iHpNynG+bPM9hpXA1w8ezuqMazf9noTqIoXQ+S9Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@babel/core": "^7.29.0", + "@babel/preset-typescript": "^7.28.5", + "@babel/traverse": "^7.29.0", + "@hono/node-ws": "^1.3.0", + "@mastra/server": "1.24.1", + "@optimize-lodash/rollup-plugin": "^5.1.0", + "@rollup/plugin-alias": "6.0.0", + "@rollup/plugin-commonjs": "29.0.2", + "@rollup/plugin-esm-shim": "0.1.8", + "@rollup/plugin-json": "6.1.0", + "@rollup/plugin-node-resolve": "16.0.3", + "@rollup/plugin-virtual": "3.0.2", + "@sindresorhus/slugify": "^2.2.1", + "@types/babel__traverse": "^7.28.0", + "empathic": "^2.0.0", + "esbuild": "^0.27.4", + "find-workspaces": "^0.3.1", + "fs-extra": "^11.3.4", + "hono": "^4.12.8", + "local-pkg": "^1.1.2", + "resolve-from": "^5.0.0", + "resolve.exports": "^2.0.3", + "rollup": "^4.59.0", + "rollup-plugin-esbuild": "^6.2.1", + "strip-json-comments": "^5.0.3", + "tinyglobby": "^0.2.15", + "typescript-paths": "^1.5.1", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=22.13.0" + }, + "peerDependencies": { + "@mastra/core": ">=1.0.0-0 <2.0.0-0", + "zod": "^3.25.0 || ^4.0.0" + } + }, + "node_modules/@mastra/duckdb": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@mastra/duckdb/-/duckdb-1.1.1.tgz", + "integrity": "sha512-fLga4uFfiGtmEFKMtjV0lbUOdf+SSOcSwXpQdLgE+cV2ZRzg/nCgsJgtpjLOsTVhsbO6ZInG7aR94/EXiOUCrA==", + "license": "Apache-2.0", + "dependencies": { + "@duckdb/node-api": "1.4.2-r.1" + }, + "engines": { + "node": ">=22.13.0" + }, + "peerDependencies": { + "@mastra/core": ">=1.0.0-0 <2.0.0-0" + } + }, + "node_modules/@mastra/evals": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@mastra/evals/-/evals-1.2.1.tgz", + "integrity": "sha512-wiXRiHctOBUqtguDbw2ypt3BCrhNDJbQYUKrSSaeNnGrVNrFG6LH2rJgy+TdM/6fBEPzLw/sK3E4s4S11DlptQ==", + "license": "Apache-2.0", + "dependencies": { + "compromise": "^14.15.0", + "keyword-extractor": "^0.0.28", + "sentiment": "^5.0.2", + "string-similarity": "^4.0.4" + }, + "engines": { + "node": ">=22.13.0" + }, + "peerDependencies": { + "@mastra/core": ">=1.0.0-0 <2.0.0-0", + "zod": "^3.25.0 || ^4.0.0" + } + }, + "node_modules/@mastra/libsql": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@mastra/libsql/-/libsql-1.8.0.tgz", + "integrity": "sha512-uZXdL9QIubLg5QDfEE70cEDsPrMIOMyfK/c8SgsMAXCknAYI5i7IWPkWY6tN5329S3wVMPHvol78LNI2euYxEQ==", + "license": "Apache-2.0", + "dependencies": { + "@libsql/client": "^0.15.15" + }, + "engines": { + "node": ">=22.13.0" + }, + "peerDependencies": { + "@mastra/core": ">=1.0.0-0 <2.0.0-0" + } + }, + "node_modules/@mastra/loggers": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@mastra/loggers/-/loggers-1.1.1.tgz", + "integrity": "sha512-zszCHjYlnADYeFLaOIvQH/c86Wdn+tX0WTboc6K6NvPXa5dIq9TI4qzdy5IdhQn1auSzrgbCYRKdJnZKKoJSpw==", + "license": "Apache-2.0", + "dependencies": { + "pino": "^10.3.1", + "pino-pretty": "^13.1.3" + }, + "engines": { + "node": ">=22.13.0" + }, + "peerDependencies": { + "@mastra/core": ">=1.0.0-0 <2.0.0-0" + } + }, + "node_modules/@mastra/memory": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@mastra/memory/-/memory-1.15.0.tgz", + "integrity": "sha512-mPsdfzjb7AYwfsuT74CTGkRdLCKGjgQ42Pe09wHQbRe0JUpK1XsA7rjSNUPTaMYqbu8N9F6/z16WnsXMtaGytQ==", + "license": "Apache-2.0", + "dependencies": { + "@mastra/schema-compat": "1.2.7", + "async-mutex": "^0.5.0", + "image-size": "^2.0.2", + "json-schema": "^0.4.0", + "lru-cache": "^11.2.7", + "probe-image-size": "^7.2.3", + "tokenx": "^1.3.0", + "xxhash-wasm": "^1.1.0" + }, + "engines": { + "node": ">=22.13.0" + }, + "peerDependencies": { + "@mastra/core": ">=1.4.1-0 <2.0.0-0", + "zod": "^3.25.0 || ^4.0.0" + } + }, + "node_modules/@mastra/observability": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@mastra/observability/-/observability-1.9.0.tgz", + "integrity": "sha512-7ULvHgBeYAdmQAQAnLfPmpeSU3o5MEbv/w7KFeYNWoJEBfOXDPKwMuhgKsJF4PqPfo9OuCoeWIt4OlskGO7gdQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=22.13.0" + }, + "peerDependencies": { + "@mastra/core": ">=1.16.0-0 <2.0.0-0" + } + }, + "node_modules/@mastra/schema-compat": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@mastra/schema-compat/-/schema-compat-1.2.7.tgz", + "integrity": "sha512-t63E0f5HcH8neXPfs3D5x4qqQM6Pf/pbhFUVk0cTC0bFo6609sT/+189I+2HY4sbAF3uzurOgy2fXIS4vfMkOA==", + "license": "Apache-2.0", + "dependencies": { + "json-schema-to-zod": "^2.7.0", + "zod-from-json-schema": "^0.5.2", + "zod-from-json-schema-v3": "npm:zod-from-json-schema@^0.0.5", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=22.13.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + }, + "node_modules/@mastra/server": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/@mastra/server/-/server-1.24.1.tgz", + "integrity": "sha512-gSNVzJ62THcmInWnxEdwzzDDgjFjTr2aQ7ImFNKnBzSFQ7kNuV1kS6M/xwHOZk0esEYfpkhpk6S/znx9L1/iLg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "hono": "^4.12.8" + }, + "engines": { + "node": ">=22.13.0" + }, + "peerDependencies": { + "@mastra/core": ">=1.13.2-0 <2.0.0-0", + "zod": "^3.25.0 || ^4.0.0" + } + }, + "node_modules/@mistralai/mistralai": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-1.14.1.tgz", + "integrity": "sha512-IiLmmZFCCTReQgPAT33r7KQ1nYo5JPdvGkrkZqA8qQ2qB1GHgs5LoP5K2ICyrjnpw2n8oSxMM/VP+liiKcGNlQ==", + "dependencies": { + "ws": "^8.18.0", + "zod": "^3.25.0 || ^4.0.0", + "zod-to-json-schema": "^3.24.1" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@neon-rs/load": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@neon-rs/load/-/load-0.0.4.tgz", + "integrity": "sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==", + "license": "MIT" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@optimize-lodash/rollup-plugin": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@optimize-lodash/rollup-plugin/-/rollup-plugin-5.1.0.tgz", + "integrity": "sha512-dBQYGH8+n4Z/877e61PJteVZEc+U1wfRF6IhKqW0cABRIsqMxpWynyov6M6Sd4IUjru0dnh0EG55X86JO6WakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@optimize-lodash/transform": "3.0.6", + "@rollup/pluginutils": "^5.1.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "rollup": ">= 4.x" + } + }, + "node_modules/@optimize-lodash/transform": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@optimize-lodash/transform/-/transform-3.0.6.tgz", + "integrity": "sha512-9+qMSaDpahC0+vX2ChM46/ls6a5Ankqs6RTLrHSaFpm7o1mFanP82e+jm9/0o5D660ueK8dWJGPCXQrBxBNNWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "estree-walker": "^2.0.2", + "magic-string": "~0.30.11" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", + "license": "MIT" + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@posthog/core": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@posthog/core/-/core-1.7.1.tgz", + "integrity": "sha512-kjK0eFMIpKo9GXIbts8VtAknsoZ18oZorANdtuTj1CbgS28t4ZVq//HAWhnxEuXRTrtkd+SUJ6Ux3j2Af8NCuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.6" + } + }, + "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==", + "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==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "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==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "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==", + "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==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, + "node_modules/@rollup/plugin-alias": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-alias/-/plugin-alias-6.0.0.tgz", + "integrity": "sha512-tPCzJOtS7uuVZd+xPhoy5W4vThe6KWXNmsFCNktaAh5RTqcLiSfT4huPQIXkgJ6YCOjJHvecOAzQxLFhPxKr+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "rollup": ">=4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-commonjs": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-29.0.2.tgz", + "integrity": "sha512-S/ggWH1LU7jTyi9DxZOKyxpVd4hF/OZ0JrEbeLjXk/DFXwRny0tjD2c992zOUYQobLrVkRVMDdmHP16HKP7GRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "commondir": "^1.0.1", + "estree-walker": "^2.0.2", + "fdir": "^6.2.0", + "is-reference": "1.2.1", + "magic-string": "^0.30.3", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=16.0.0 || 14 >= 14.17" + }, + "peerDependencies": { + "rollup": "^2.68.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-esm-shim": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@rollup/plugin-esm-shim/-/plugin-esm-shim-0.1.8.tgz", + "integrity": "sha512-xEU0b/BShgDDSPjidhJd4R74J9xZ9jLVtFWNGtsUXyEsdwwwB1a3XOAwwGaNIyUHD6EhxPO21JMfUmJWoMn7SA==", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.3", + "mlly": "^1.7.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-json": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", + "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.3.tgz", + "integrity": "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-virtual": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-virtual/-/plugin-virtual-3.0.2.tgz", + "integrity": "sha512-10monEYsBp3scM4/ND4LNH5Rxvh3e/cVeL3jWTgZ2SrQ+BmUoQcopVQvnaMcOnykb1VkxUFuDAN+0FnpTFRy2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz", + "integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz", + "integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz", + "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz", + "integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz", + "integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz", + "integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz", + "integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz", + "integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz", + "integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz", + "integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz", + "integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz", + "integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz", + "integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz", + "integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz", + "integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz", + "integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz", + "integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz", + "integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz", + "integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz", + "integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz", + "integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz", + "integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz", + "integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz", + "integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz", + "integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "license": "MIT" + }, + "node_modules/@sinclair/typebox": { + "version": "0.34.49", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", + "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", + "license": "MIT" + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@sindresorhus/slugify": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@sindresorhus/slugify/-/slugify-2.2.1.tgz", + "integrity": "sha512-MkngSCRZ8JdSOCHRaYd+D01XhvU3Hjy6MGl06zhOk614hp9EOAp5gIkBeQg7wtmxpitU6eAL4kdiRMcJa2dlrw==", + "license": "MIT", + "dependencies": { + "@sindresorhus/transliterate": "^1.0.0", + "escape-string-regexp": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@sindresorhus/transliterate": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/transliterate/-/transliterate-1.6.0.tgz", + "integrity": "sha512-doH1gimEu3A46VX6aVxpHTeHrytJAG6HgdxntYnCFiIFHEM/ZGpG8KiZGBChchjQmG0XFIBL552kBTjVcMZXwQ==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@smithy/config-resolver": { + "version": "4.4.14", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.14.tgz", + "integrity": "sha512-N55f8mPEccpzKetUagdvmAy8oohf0J5cuj9jLI1TaSceRlq0pJsIZepY3kmAXAhyxqXPV6hDerDQhqQPKWgAoQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.13", + "@smithy/types": "^4.14.0", + "@smithy/util-config-provider": "^4.2.2", + "@smithy/util-endpoints": "^3.3.4", + "@smithy/util-middleware": "^4.2.13", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.23.14", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.14.tgz", + "integrity": "sha512-vJ0IhpZxZAkFYOegMKSrxw7ujhhT2pass/1UEcZ4kfl5srTAqtPU5I7MdYQoreVas3204ykCiNhY1o7Xlz6Yyg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "@smithy/url-parser": "^4.2.13", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-middleware": "^4.2.13", + "@smithy/util-stream": "^4.5.22", + "@smithy/util-utf8": "^4.2.2", + "@smithy/uuid": "^1.1.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.13.tgz", + "integrity": "sha512-wboCPijzf6RJKLOvnjDAiBxGSmSnGXj35o5ZAWKDaHa/cvQ5U3ZJ13D4tMCE8JG4dxVAZFy/P0x/V9CwwdfULQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.13", + "@smithy/property-provider": "^4.2.13", + "@smithy/types": "^4.14.0", + "@smithy/url-parser": "^4.2.13", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-codec": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.13.tgz", + "integrity": "sha512-vYahwBAtRaAcFbOmE9aLr12z7RiHYDSLcnogSdxfm7kKfsNa3wH+NU5r7vTeB5rKvLsWyPjVX8iH94brP7umiQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.14.0", + "@smithy/util-hex-encoding": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-browser": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.13.tgz", + "integrity": "sha512-wwybfcOX0tLqCcBP378TIU9IqrDuZq/tDV48LlZNydMpCnqnYr+hWBAYbRE+rFFf/p7IkDJySM3bgiMKP2ihPg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "4.3.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.13.tgz", + "integrity": "sha512-ied1lO559PtAsMJzg2TKRlctLnEi1PfkNeMMpdwXDImk1zV9uvS/Oxoy/vcy9uv1GKZAjDAB5xT6ziE9fzm5wA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-node": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.13.tgz", + "integrity": "sha512-hFyK+ORJrxAN3RYoaD6+gsGDQjeix8HOEkosoajvXYZ4VeqonM3G4jd9IIRm/sWGXUKmudkY9KdYjzosUqdM8A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-universal": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.13.tgz", + "integrity": "sha512-kRrq4EKLGeOxhC2CBEhRNcu1KSzNJzYY7RK3S7CxMPgB5dRrv55WqQOtRwQxQLC04xqORFLUgnDlc6xrNUULaA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-codec": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.3.16", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.16.tgz", + "integrity": "sha512-nYDRUIvNd4mFmuXraRWt6w5UsZTNqtj4hXJA/iiOD4tuseIdLP9Lq38teH/SZTcIFCa2f+27o7hYpIsWktJKEQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.13", + "@smithy/querystring-builder": "^4.2.13", + "@smithy/types": "^4.14.0", + "@smithy/util-base64": "^4.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.13.tgz", + "integrity": "sha512-4/oy9h0jjmY80a2gOIo75iLl8TOPhmtx4E2Hz+PfMjvx/vLtGY4TMU/35WRyH2JHPfT5CVB38u4JRow7gnmzJA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.13.tgz", + "integrity": "sha512-jvC0RB/8BLj2SMIkY0Npl425IdnxZJxInpZJbu563zIRnVjpDMXevU3VMCRSabaLB0kf/eFIOusdGstrLJ8IDg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.13.tgz", + "integrity": "sha512-IPMLm/LE4AZwu6qiE8Rr8vJsWhs9AtOdySRXrOM7xnvclp77Tyh7hMs/FRrMf26kgIe67vFJXXOSmVxS7oKeig==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "4.4.29", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.29.tgz", + "integrity": "sha512-R9Q/58U+qBiSARGWbAbFLczECg/RmysRksX6Q8BaQEpt75I7LI6WGDZnjuC9GXSGKljEbA7N118LhGaMbfrTXw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.14", + "@smithy/middleware-serde": "^4.2.17", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "@smithy/url-parser": "^4.2.13", + "@smithy/util-middleware": "^4.2.13", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.5.0.tgz", + "integrity": "sha512-/NzISn4grj/BRFVua/xnQwF+7fakYZgimpw2dfmlPgcqecBMKxpB9g5mLYRrmBD5OrPoODokw4Vi1hrSR4zRyw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.14", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/protocol-http": "^5.3.13", + "@smithy/service-error-classification": "^4.2.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "@smithy/util-middleware": "^4.2.13", + "@smithy/util-retry": "^4.3.0", + "@smithy/uuid": "^1.1.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.2.17", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.17.tgz", + "integrity": "sha512-0T2mcaM6v9W1xku86Dk0bEW7aEseG6KenFkPK98XNw0ZhOqOiD1MrMsdnQw9QsL3/Oa85T53iSMlm0SZdSuIEQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.14", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.13.tgz", + "integrity": "sha512-g72jN/sGDLyTanrCLH9fhg3oysO3f7tQa6eWWsMyn2BiYNCgjF24n4/I9wff/5XidFvjj9ilipAoQrurTUrLvw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.3.13", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.13.tgz", + "integrity": "sha512-iGxQ04DsKXLckbgnX4ipElrOTk+IHgTyu0q0WssZfYhDm9CQWHmu6cOeI5wmWRxpXbBDhIIfXMWz5tPEtcVqbw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.5.2.tgz", + "integrity": "sha512-/oD7u8M0oj2ZTFw7GkuuHWpIxtWdLlnyNkbrWcyVYhd5RJNDuczdkb0wfnQICyNFrVPlr8YHOhamjNy3zidhmA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.13", + "@smithy/querystring-builder": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.13.tgz", + "integrity": "sha512-bGzUCthxRmezuxkbu9wD33wWg9KX3hJpCXpQ93vVkPrHn9ZW6KNNdY5xAUWNuRCwQ+VyboFuWirG1lZhhkcyRQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "5.3.13", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.13.tgz", + "integrity": "sha512-+HsmuJUF4u8POo6s8/a2Yb/AQ5t/YgLovCuHF9oxbocqv+SZ6gd8lC2duBFiCA/vFHoHQhoq7QjqJqZC6xOxxg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.13.tgz", + "integrity": "sha512-tG4aOYFCZdPMjbgfhnIQ322H//ojujldp1SrHPHpBSb3NqgUp3dwiUGRJzie87hS1DYwWGqDuPaowoDF+rYCbQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "@smithy/util-uri-escape": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.13.tgz", + "integrity": "sha512-hqW3Q4P+CDzUyQ87GrboGMeD7XYNMOF+CuTwu936UQRB/zeYn3jys8C3w+wMkDfY7CyyyVwZQ5cNFoG0x1pYmA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.13.tgz", + "integrity": "sha512-a0s8XZMfOC/qpqq7RCPvJlk93rWFrElH6O++8WJKz0FqnA4Y7fkNi/0mnGgSH1C4x6MFsuBA8VKu4zxFrMe5Vw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.4.8", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.8.tgz", + "integrity": "sha512-VZCZx2bZasxdqxVgEAhREvDSlkatTPnkdWy1+Kiy8w7kYPBosW0V5IeDwzDUMvWBt56zpK658rx1cOBFOYaPaw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.3.13", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.13.tgz", + "integrity": "sha512-YpYSyM0vMDwKbHD/JA7bVOF6kToVRpa+FM5ateEVRpsTNu564g1muBlkTubXhSKKYXInhpADF46FPyrZcTLpXg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.2", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-middleware": "^4.2.13", + "@smithy/util-uri-escape": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "4.12.9", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.12.9.tgz", + "integrity": "sha512-ovaLEcTU5olSeHcRXcxV6viaKtpkHZumn6Ps0yn7dRf2rRSfy794vpjOtrWDO0d1auDSvAqxO+lyhERSXQ03EQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.14", + "@smithy/middleware-endpoint": "^4.4.29", + "@smithy/middleware-stack": "^4.2.13", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "@smithy/util-stream": "^4.5.22", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.0.tgz", + "integrity": "sha512-OWgntFLW88kx2qvf/c/67Vno1yuXm/f9M7QFAtVkkO29IJXGBIg0ycEaBTH0kvCtwmvZxRujrgP5a86RvsXJAQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.13.tgz", + "integrity": "sha512-2G03yoboIRZlZze2+PT4GZEjgwQsJjUgn6iTsvxA02bVceHR6vp4Cuk7TUnPFWKF+ffNUk3kj4COwkENS2K3vw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-base64": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.2.tgz", + "integrity": "sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz", - "integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "node_modules/@smithy/util-body-length-browser": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.2.tgz", + "integrity": "sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz", - "integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] + "node_modules/@smithy/util-body-length-node": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.3.tgz", + "integrity": "sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz", - "integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] + "node_modules/@smithy/util-buffer-from": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz", - "integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "node_modules/@smithy/util-config-provider": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.2.tgz", + "integrity": "sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz", - "integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.3.45", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.45.tgz", + "integrity": "sha512-ag9sWc6/nWZAuK3Wm9KlFJUnRkXLrXn33RFjIAmCTFThqLHY+7wCst10BGq56FxslsDrjhSie46c8OULS+BiIw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz", - "integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.2.49", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.49.tgz", + "integrity": "sha512-jlN6vHwE8gY5AfiFBavtD3QtCX2f7lM3BKkz7nFKSNfFR5nXLXLg6sqXTJEEyDwtxbztIDBQCfjsGVXlIru2lQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/config-resolver": "^4.4.14", + "@smithy/credential-provider-imds": "^4.2.13", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/property-provider": "^4.2.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz", - "integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "node_modules/@smithy/util-endpoints": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.3.4.tgz", + "integrity": "sha512-BKoR/ubPp9KNKFxPpg1J28N1+bgu8NGAtJblBP7yHy8yQPBWhIAv9+l92SlQLpolGm71CVO+btB60gTgzT0wog==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@sec-ant/readable-stream": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", - "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", - "license": "MIT" + "node_modules/@smithy/util-hex-encoding": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.2.tgz", + "integrity": "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@sindresorhus/merge-streams": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", - "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", - "license": "MIT", + "node_modules/@smithy/util-middleware": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.13.tgz", + "integrity": "sha512-GTooyrlmRTqvUen4eK7/K1p6kryF7bnDfq6XsAbIsf2mo51B/utaH+XThY6dKgNCWzMAaH/+OLmqaBuLhLWRow==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-retry": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.3.0.tgz", + "integrity": "sha512-tSOPQNT/4KfbvqeMovWC3g23KSYy8czHd3tlN+tOYVNIDLSfxIsrPJihYi5TpNcoV789KWtgChUVedh2y6dDPg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@sindresorhus/slugify": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@sindresorhus/slugify/-/slugify-2.2.1.tgz", - "integrity": "sha512-MkngSCRZ8JdSOCHRaYd+D01XhvU3Hjy6MGl06zhOk614hp9EOAp5gIkBeQg7wtmxpitU6eAL4kdiRMcJa2dlrw==", - "license": "MIT", + "node_modules/@smithy/util-stream": { + "version": "4.5.22", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.22.tgz", + "integrity": "sha512-3H8iq/0BfQjUs2/4fbHZ9aG9yNzcuZs24LPkcX1Q7Z+qpqaGM8+qbGmE8zo9m2nCRgamyvS98cHdcWvR6YUsew==", + "license": "Apache-2.0", "dependencies": { - "@sindresorhus/transliterate": "^1.0.0", - "escape-string-regexp": "^5.0.0" + "@smithy/fetch-http-handler": "^5.3.16", + "@smithy/node-http-handler": "^4.5.2", + "@smithy/types": "^4.14.0", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=12" + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-uri-escape": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.2.tgz", + "integrity": "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@sindresorhus/transliterate": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/transliterate/-/transliterate-1.6.0.tgz", - "integrity": "sha512-doH1gimEu3A46VX6aVxpHTeHrytJAG6HgdxntYnCFiIFHEM/ZGpG8KiZGBChchjQmG0XFIBL552kBTjVcMZXwQ==", - "license": "MIT", + "node_modules/@smithy/util-utf8": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", + "license": "Apache-2.0", "dependencies": { - "escape-string-regexp": "^5.0.0" + "@smithy/util-buffer-from": "^4.2.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=12" + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/uuid": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.2.tgz", + "integrity": "sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=18.0.0" } }, "node_modules/@standard-community/standard-json": { @@ -2766,6 +4403,12 @@ "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", "license": "MIT" }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "license": "MIT" + }, "node_modules/@types/babel__traverse": { "version": "7.28.0", "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", @@ -2906,6 +4549,12 @@ "dev": true, "license": "MIT" }, + "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==", + "license": "MIT" + }, "node_modules/@types/send": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", @@ -3003,6 +4652,15 @@ "node": ">=0.4.0" } }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/ajv": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", @@ -3211,6 +4869,18 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", @@ -3398,6 +5068,24 @@ "node": ">=6.0.0" } }, + "node_modules/basic-ftp": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.2.1.tgz", + "integrity": "sha512-0yaL8JdxTknKDILitVpfYfV2Ob6yb3udX/hK97M7I3jOeznBNxQPtVvTUtnhUkyHlxFWyr5Lvknmgzoc7jf+1Q==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/body-parser": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", @@ -3422,6 +5110,12 @@ "url": "https://opencollective.com/express" } }, + "node_modules/bowser": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", + "license": "MIT" + }, "node_modules/boxen": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.0.0.tgz", @@ -3537,6 +5231,12 @@ "node": ">=8.0.0" } }, + "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==", + "license": "BSD-3-Clause" + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -4163,6 +5863,20 @@ "node": ">=0.10.0" } }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "license": "MIT", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -4246,6 +5960,15 @@ "dev": true, "license": "MIT" }, + "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==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -4404,10 +6127,31 @@ "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", "license": "MIT", "engines": { - "node": ">=12" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "optionalDependencies": { + "source-map": "~0.6.1" } }, "node_modules/esprima": { @@ -4423,6 +6167,15 @@ "node": ">=4" } }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, "node_modules/estree-walker": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", @@ -4430,6 +6183,15 @@ "dev": true, "license": "MIT" }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -4793,6 +6555,41 @@ "fast-string-width": "^1.1.0" } }, + "node_modules/fast-xml-builder": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.4.tgz", + "integrity": "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "path-expression-matcher": "^1.1.3" + } + }, + "node_modules/fast-xml-parser": { + "version": "5.5.8", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.8.tgz", + "integrity": "sha512-Z7Fh2nVQSb2d+poDViM063ix2ZGt9jmY1nWhPfHBOK2Hgnb/OW3P4Et3P/81SEej0J7QbWtJqxO05h8QYfK7LQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "fast-xml-builder": "^1.1.4", + "path-expression-matcher": "^1.2.0", + "strnum": "^2.2.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/fastq": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", @@ -5003,6 +6800,34 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gaxios": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz", + "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==", + "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==", + "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/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -5092,6 +6917,29 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/get-uri": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", + "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", + "license": "MIT", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/get-uri/node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/glob": { "version": "10.5.0", "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", @@ -5127,6 +6975,32 @@ "node": ">= 6" } }, + "node_modules/google-auth-library": { + "version": "10.6.2", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.2.tgz", + "integrity": "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==", + "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==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -5239,6 +7113,32 @@ "url": "https://opencollective.com/express" } }, + "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==", + "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==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/human-signals": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", @@ -5613,12 +7513,34 @@ "node": ">=6" } }, + "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==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, "node_modules/json-schema": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", "license": "(AFL-2.1 OR BSD-3-Clause)" }, + "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==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "ts-algebra": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/json-schema-to-zod": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/json-schema-to-zod/-/json-schema-to-zod-2.8.1.tgz", @@ -5666,6 +7588,27 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "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==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/keyword-extractor": { "version": "0.0.28", "resolved": "https://registry.npmjs.org/keyword-extractor/-/keyword-extractor-0.0.28.tgz", @@ -5811,6 +7754,12 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "license": "MIT" }, + "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==", + "license": "Apache-2.0" + }, "node_modules/longest-streak": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", @@ -6884,6 +8833,15 @@ "node": ">= 0.6" } }, + "node_modules/netmask": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.1.1.tgz", + "integrity": "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/node-domexception": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", @@ -7044,11 +9002,31 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/openai": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.26.0.tgz", + "integrity": "sha512-zd23dbWTjiJ6sSAX6s0HrCZi41JwTA1bQVs0wLQPZ2/5o2gxOJA5wh7yOAUgwYybfhDXyhwlpeQf7Mlgx8EOCA==", + "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/openapi-fetch": { "version": "0.17.0", "resolved": "https://registry.npmjs.org/openapi-fetch/-/openapi-fetch-0.17.0.tgz", "integrity": "sha512-PsbZR1wAPcG91eEthKhN+Zn92FMHxv+/faECIwjXdxfTODGSGegYv0sc1Olz+HYPvKOuoXfp+0pA2XVt2cI0Ig==", - "dev": true, "license": "MIT", "dependencies": { "openapi-typescript-helpers": "^0.1.0" @@ -7065,7 +9043,6 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/openapi-typescript-helpers/-/openapi-typescript-helpers-0.1.0.tgz", "integrity": "sha512-OKTGPthhivLw/fHz6c3OPtg72vi86qaMlqbJuVJ23qOvQ+53uw1n7HdmkJFibloF7QEjDrDkzJiOJuockM/ljw==", - "dev": true, "license": "MIT" }, "node_modules/p-map": { @@ -7095,6 +9072,38 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/pac-proxy-agent": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", + "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", + "license": "MIT", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "license": "MIT", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -7123,6 +9132,27 @@ "node": ">= 0.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==", + "license": "MIT" + }, + "node_modules/path-expression-matcher": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.4.0.tgz", + "integrity": "sha512-s4DQMxIdhj3jLFWd9LxHOplj4p9yQ4ffMGowFf3cpEgrrJjEhN0V5nxw4Ye1EViAGDoL4/1AeO6qHpqYPOzE4Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/path-is-inside": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", @@ -7378,6 +9408,30 @@ "integrity": "sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==", "license": "ISC" }, + "node_modules/protobufjs": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", + "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -7391,6 +9445,40 @@ "node": ">= 0.10" } }, + "node_modules/proxy-agent": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", + "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.6", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.1.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/pump": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", @@ -7706,6 +9794,15 @@ "node": ">=10" } }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -8238,6 +10335,44 @@ "dev": true, "license": "MIT" }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "license": "MIT", + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/sonic-boom": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", @@ -8247,6 +10382,16 @@ "atomic-sleep": "^1.0.0" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/split2": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", @@ -8461,6 +10606,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strnum": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.3.tgz", + "integrity": "sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, "node_modules/suffix-thumb": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/suffix-thumb/-/suffix-thumb-5.0.2.tgz", @@ -8593,12 +10750,38 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/ts-algebra": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", + "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", + "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==", "license": "0BSD" }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/type-fest": { "version": "2.19.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", @@ -8682,6 +10865,15 @@ "dev": true, "license": "MIT" }, + "node_modules/undici": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.7.tgz", + "integrity": "sha512-H/nlJ/h0ggGC+uRL3ovD+G0i4bqhvsDOpbDv7At5eFLlj2b41L8QliGbnl2H7SnDiYhENphh1tQFJZf+MyfLsQ==", + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/undici-types": { "version": "7.18.2", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", diff --git a/package.json b/package.json index 8478c9e..f42e0b6 100644 --- a/package.json +++ b/package.json @@ -4,10 +4,13 @@ "description": "", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", + "login": "tsx src/login.ts", + "doctor": "tsx src/doctor.ts", + "start": "tsx src/index.ts", "dev": "mastra dev", "build": "mastra build", - "start": "mastra start" + "eval:imessage": "tsx src/mastra/evals/imessage-evals.ts", + "eval:imessage:live": "tsx src/mastra/evals/imessage-evals.ts --live" }, "keywords": [], "author": "", @@ -17,6 +20,10 @@ "node": ">=22.13.0" }, "dependencies": { + "@ai-sdk/anthropic": "^3.0.69", + "@ai-sdk/openai": "^3.0.52", + "@jgoon/bluebubbles": "^1.1.0", + "@mariozechner/pi-ai": "^0.66.1", "@mastra/core": "^1.24.1", "@mastra/duckdb": "^1.1.1", "@mastra/evals": "^1.2.1", @@ -29,6 +36,7 @@ "devDependencies": { "@types/node": "^25.5.2", "mastra": "^1.5.0", + "tsx": "^4.21.0", "typescript": "^6.0.2" } } diff --git a/src/doctor.ts b/src/doctor.ts new file mode 100644 index 0000000..0c647f4 --- /dev/null +++ b/src/doctor.ts @@ -0,0 +1,237 @@ +import { existsSync } from "fs"; +import { spawnSync } from "child_process"; +import { createClient } from "@jgoon/bluebubbles"; +import { loadConfig } from "./lib/config.js"; +import { loadAuth } from "./lib/codex-auth.js"; +import { ensureWorkspaceSeeded, loadWorkspaceContext } from "./lib/workspace.js"; + +type Status = "PASS" | "WARN" | "FAIL"; + +type CheckResult = { + status: Status; + name: string; + details: string; +}; + +function printResult(result: CheckResult) { + console.log(`${result.status} ${result.name}: ${result.details}`); +} + +function hasFailures(results: CheckResult[]): boolean { + return results.some((result) => result.status === "FAIL"); +} + +function runCommand(args: string[]): { ok: boolean; stdout: string; stderr: string } { + const [command, ...rest] = args; + const result = spawnSync(command, rest, { + encoding: "utf8", + }); + + return { + ok: result.status === 0, + stdout: result.stdout?.trim() ?? "", + stderr: result.stderr?.trim() ?? "", + }; +} + +function checkConfig(): CheckResult[] { + const config = loadConfig(); + const results: CheckResult[] = []; + + const allowFromCount = config.bluebubbles.allowFrom.length; + results.push({ + status: allowFromCount > 0 ? "PASS" : "FAIL", + name: "bluebubbles config", + details: + allowFromCount > 0 + ? `server=${config.bluebubbles.serverUrl} webhookPort=${config.bluebubbles.webhookPort} allowFrom=${allowFromCount}` + : "allowFrom is empty", + }); + + results.push({ + status: config.provider.model ? "PASS" : "FAIL", + name: "agent config", + details: `model=${config.provider.model} baseURL=${config.provider.baseURL ?? "codex-oauth"} workspace=${config.agent.workspaceDir}`, + }); + + return results; +} + +function checkAuth(): CheckResult { + const auth = loadAuth(); + const credentials = auth["openai-codex"]; + + if (!credentials) { + return { + status: "FAIL", + name: "codex auth", + details: "missing openai-codex credentials in ~/.agent/auth.json; run npm run login", + }; + } + + const accountId = (credentials.accountId as string | undefined) ?? ""; + return { + status: "PASS", + name: "codex auth", + details: accountId ? `openai-codex credentials present for account ${accountId}` : "openai-codex credentials present", + }; +} + +function checkWorkspace(): CheckResult[] { + const config = loadConfig(); + ensureWorkspaceSeeded(config.agent.workspaceDir); + const files = loadWorkspaceContext(config.agent.workspaceDir); + const names = new Set(files.map((file) => file.path)); + const required = ["AGENTS.md", "SOUL.md", "IDENTITY.md", "USER.md"]; + + const results: CheckResult[] = []; + for (const fileName of required) { + results.push({ + status: names.has(fileName) ? "PASS" : "WARN", + name: `workspace ${fileName}`, + details: names.has(fileName) + ? `loaded from ${config.agent.workspaceDir}/${fileName}` + : `${fileName} not found in ${config.agent.workspaceDir}`, + }); + } + + results.push({ + status: files.length > 0 ? "PASS" : "FAIL", + name: "workspace context", + details: `${files.length} injected context file(s) available`, + }); + + return results; +} + +async function checkBlueBubbles(): Promise { + const config = loadConfig(); + const results: CheckResult[] = []; + + try { + const response = await fetch(config.bluebubbles.serverUrl, { method: "GET" }); + results.push({ + status: response.ok ? "PASS" : "FAIL", + name: "bluebubbles reachability", + details: `${config.bluebubbles.serverUrl} responded with HTTP ${response.status}`, + }); + } catch (error) { + results.push({ + status: "FAIL", + name: "bluebubbles reachability", + details: `${config.bluebubbles.serverUrl} fetch failed: ${error instanceof Error ? error.message : String(error)}`, + }); + } + + try { + const client = createClient({ baseUrl: config.bluebubbles.serverUrl }); + const { data, error } = await client.POST("/api/v1/chat/query", { + params: { query: { password: config.bluebubbles.password } }, + body: { + limit: 1, + offset: 0, + }, + }); + + if (error) { + results.push({ + status: "FAIL", + name: "bluebubbles api auth", + details: JSON.stringify(error), + }); + } else { + const count = Array.isArray(data?.data) ? data.data.length : 0; + results.push({ + status: "PASS", + name: "bluebubbles api auth", + details: `chat query succeeded; returned ${count} chat(s)`, + }); + } + } catch (error) { + results.push({ + status: "FAIL", + name: "bluebubbles api auth", + details: error instanceof Error ? error.message : String(error), + }); + } + + return results; +} + +function checkProcesses(): CheckResult[] { + const config = loadConfig(); + const results: CheckResult[] = []; + + const openClaw = runCommand(["pgrep", "-af", "openclaw-gateway"]); + if (openClaw.ok && openClaw.stdout) { + results.push({ + status: "WARN", + name: "openclaw process", + details: `openclaw-gateway is running: ${openClaw.stdout}`, + }); + } else { + results.push({ + status: "PASS", + name: "openclaw process", + details: "openclaw-gateway not detected", + }); + } + + const listeners = runCommand(["lsof", "-nP", `-iTCP:${config.bluebubbles.webhookPort}`, "-sTCP:LISTEN"]); + if (listeners.ok && listeners.stdout) { + results.push({ + status: "PASS", + name: "webhook listener", + details: listeners.stdout.split("\n").slice(0, 2).join(" | "), + }); + } else { + results.push({ + status: "WARN", + name: "webhook listener", + details: `nothing is currently listening on :${config.bluebubbles.webhookPort}`, + }); + } + + return results; +} + +function checkFiles(): CheckResult[] { + const home = process.env.HOME ?? ""; + const paths = [ + `${home}/.agent/config.json`, + `${home}/.agent/auth.json`, + ]; + + return paths.map((path) => ({ + status: existsSync(path) ? "PASS" : "FAIL", + name: `file ${path.split("/").slice(-2).join("/")}`, + details: existsSync(path) ? path : `missing ${path}`, + })); +} + +async function main() { + console.log("Agent doctor\n"); + + const results: CheckResult[] = [ + ...checkFiles(), + ...checkConfig(), + checkAuth(), + ...checkWorkspace(), + ...(await checkBlueBubbles()), + ...checkProcesses(), + ]; + + for (const result of results) { + printResult(result); + } + + const failCount = results.filter((result) => result.status === "FAIL").length; + const warnCount = results.filter((result) => result.status === "WARN").length; + console.log(`\nSummary: ${results.length - failCount - warnCount} pass, ${warnCount} warn, ${failCount} fail`); + + if (hasFailures(results)) { + process.exitCode = 1; + } +} + +await main(); diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..b3e9f18 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,574 @@ +import { loadConfig } from "./lib/config.js"; +import { startWebhook, sendMessage, markRead, startTyping, stopTyping } from "./lib/bluebubbles.js"; +import { buildOutboundBubbles, createPrivateBubbleParser, finalizeOutboundBubble } from "./lib/outbound.js"; +import { buildAgents } from "./mastra/agents/index.js"; +import { buildProviderOptions, isRateLimitError } from "./lib/provider.js"; +import { buildHistoryContext, buildInjectedPrompt, loadWorkspaceContext, type HistoryEntry } from "./lib/workspace.js"; +import { getModuleLogger } from "./lib/logging.js"; +import { deliverBubblesWithTyping } from "./lib/delivery.js"; +import { + applyReplyPolicy, + buildConversationStyleState, + computeResponseTokenBudget, + detectReplyIntent, + type ReplyIntent, + type ConversationStyleState, +} from "./lib/reply-policy.js"; + +const config = loadConfig(); +const { serverUrl, password, webhookPort, allowFrom } = config.bluebubbles; +const { + workspaceDir, + webhookDebounceMs, + responseTimingMultiplier, + maxBubblesPerReply, + recentHistoryLimit, + typingDelayProfile, + systemPrompt, +} = config.agent; +const logger = getModuleLogger("runtime"); + +// Track active typing chats so we can clear them on shutdown +const typingChats = new Set(); +const typingTurnOwners = new Map(); +const recentOutbound = new Map>(); +const pendingConversationHistory = new Map(); +const activeChatTurns = new Map(); +const RECENT_OUTBOUND_TTL_MS = 20_000; +const resolvedTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone || "America/Los_Angeles"; +let nextTurnId = 1; + +type TurnTiming = { + inboundAt: number; + providerStartAt?: number; + fallbackAt?: number; + firstChunkAt?: number; + streamCompleteAt?: number; + lastBubbleSentAt?: number; + stopTypingAt?: number; +}; + +type ReplyDeliveryContext = { + inboundText: string; + intent: ReplyIntent; + styleState: ConversationStyleState; +}; + +type AgentList = Awaited>; +type AgentStreamResult = Awaited>; + +// Cache workspace context and prompt at startup — these files don't change mid-session +const cachedContextFiles = loadWorkspaceContext(workspaceDir); +const cachedBaseInstructions = systemPrompt?.trim() + ? `${systemPrompt.trim()}\n\nYou are Tien inside this runtime. In chat, text like a close friend, not a formal assistant.` + : "You are Tien inside this runtime. In chat, text like a close friend, not a formal assistant."; +const cachedInstructions = buildInjectedPrompt({ + baseInstructions: cachedBaseInstructions, + contextFiles: cachedContextFiles, + userTimezone: resolvedTimezone, +}); + +async function clearAllTyping() { + await Promise.allSettled( + [...typingChats].map((chatGuid) => stopTyping(serverUrl, password, chatGuid)) + ); + typingChats.clear(); + typingTurnOwners.clear(); +} + +for (const sig of ["SIGTERM", "SIGINT"] as const) { + process.on(sig, async () => { + await clearAllTyping(); + process.exit(0); + }); +} + +function delay(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +function randomBetween(min: number, max: number): number { + return min + Math.random() * (max - min); +} + +function claimChatTurn(chatGuid: string): number { + const turnId = nextTurnId++; + activeChatTurns.set(chatGuid, turnId); + return turnId; +} + +function isCurrentTurn(chatGuid: string, turnId: number): boolean { + return activeChatTurns.get(chatGuid) === turnId; +} + +async function startTypingForTurn(chatGuid: string, turnId: number): Promise { + if (!isCurrentTurn(chatGuid, turnId)) { + return false; + } + + typingTurnOwners.set(chatGuid, turnId); + typingChats.add(chatGuid); + try { + await startTyping(serverUrl, password, chatGuid); + return true; + } catch (error) { + logger.errorWithStack("startTyping failed", error, { chatGuid, turnId }); + return false; + } +} + +async function stopTypingForTurn(chatGuid: string, turnId: number): Promise { + if (typingTurnOwners.get(chatGuid) !== turnId) { + return false; + } + + try { + await stopTyping(serverUrl, password, chatGuid); + typingTurnOwners.delete(chatGuid); + typingChats.delete(chatGuid); + return true; + } catch (error) { + logger.errorWithStack("stopTyping failed", error, { chatGuid, turnId }); + return false; + } +} + +async function forceStopTyping(chatGuid: string, context: Record): Promise { + try { + await stopTyping(serverUrl, password, chatGuid); + typingTurnOwners.delete(chatGuid); + typingChats.delete(chatGuid); + return true; + } catch (error) { + logger.errorWithStack("force stopTyping failed", error, context); + return false; + } +} + +function normalizeOutboundText(text: string): string { + return text.replace(/\s+/g, " ").trim().toLowerCase(); +} + +function appendPendingHistory(chatGuid: string, entry: HistoryEntry) { + if (recentHistoryLimit <= 0) { + return; + } + + const history = pendingConversationHistory.get(chatGuid) ?? []; + history.push(entry); + while (history.length > recentHistoryLimit) { + history.shift(); + } + pendingConversationHistory.set(chatGuid, history); +} + +function getPendingHistory(chatGuid: string): HistoryEntry[] { + if (recentHistoryLimit <= 0) { + return []; + } + return [...(pendingConversationHistory.get(chatGuid) ?? [])]; +} + +function clearPendingHistory(chatGuid: string) { + pendingConversationHistory.delete(chatGuid); +} + +function getRecentTextsBySender(entries: HistoryEntry[], sender: HistoryEntry["sender"], limit = 6): string[] { + return entries + .filter((entry) => entry.sender === sender) + .map((entry) => entry.body) + .slice(-limit); +} + +function rememberOutbound(chatGuid: string, text: string) { + const normalizedText = normalizeOutboundText(text); + const entries = recentOutbound.get(chatGuid) ?? []; + const now = Date.now(); + const freshEntries = entries.filter((entry) => now - entry.sentAt <= RECENT_OUTBOUND_TTL_MS); + freshEntries.push({ normalizedText, sentAt: now }); + recentOutbound.set(chatGuid, freshEntries); +} + +function isRecentOutboundReflection(chatGuid: string, text: string): boolean { + const normalizedText = normalizeOutboundText(text); + if (!normalizedText) { + return false; + } + + const entries = recentOutbound.get(chatGuid) ?? []; + const now = Date.now(); + const freshEntries = entries.filter((entry) => now - entry.sentAt <= RECENT_OUTBOUND_TTL_MS); + recentOutbound.set(chatGuid, freshEntries); + return freshEntries.some((entry) => entry.normalizedText === normalizedText); +} + +function computeBubbleDelayMs(text: string, bubbleIndex: number): number { + const visibleChars = text.replace(/\s+/g, " ").trim().length; + const perChar = randomBetween( + typingDelayProfile.perCharMinMs, + typingDelayProfile.perCharMaxMs + ); + const overhead = randomBetween( + typingDelayProfile.bubbleOverheadMinMs, + typingDelayProfile.bubbleOverheadMaxMs + ); + const jitter = randomBetween(typingDelayProfile.jitterMin, typingDelayProfile.jitterMax); + const progressivePause = bubbleIndex > 0 ? bubbleIndex * 120 : 0; + const raw = (visibleChars * perChar + overhead + progressivePause) * jitter; + + return Math.round(Math.min(typingDelayProfile.maxMs, Math.max(typingDelayProfile.minMs, raw))); +} + +function computeFirstBubbleDelayMs(text: string): number { + const visibleChars = text.replace(/\s+/g, " ").trim().length; + const perChar = randomBetween( + Math.max(8, typingDelayProfile.perCharMinMs * 0.35), + Math.max(12, typingDelayProfile.perCharMaxMs * 0.5), + ); + const overhead = randomBetween( + Math.max(40, typingDelayProfile.bubbleOverheadMinMs * 0.35), + Math.max(80, typingDelayProfile.bubbleOverheadMaxMs * 0.55), + ); + const jitter = randomBetween(0.92, 1.08); + const raw = (visibleChars * perChar + overhead) * jitter; + const minDelay = Math.max(280, Math.round(typingDelayProfile.minMs * 0.8)); + const maxDelay = Math.min(typingDelayProfile.maxMs, 1800); + return Math.round(Math.min(maxDelay, Math.max(minDelay, raw))); +} + +function computeInterBubbleDelayMs(text: string, bubbleIndex: number): number { + const baseDelay = computeBubbleDelayMs(text, bubbleIndex); + const interBubbleFloor = randomBetween( + typingDelayProfile.interBubbleMinMs, + typingDelayProfile.interBubbleMaxMs + ); + return Math.round(Math.max(baseDelay, interBubbleFloor)); +} + +function applyResponseTimingMultiplier(delayMs: number): number { + return Math.max(240, Math.round(delayMs * responseTimingMultiplier)); +} + +async function deliverStreamingReply( + result: AgentStreamResult, + chatGuid: string, + turnId: number, + timing: TurnTiming, + context: ReplyDeliveryContext, +): Promise<{ rawReply: string; finalReply: string; bubbles: string[]; delivered: string[] }> { + const parser = createPrivateBubbleParser(); + const bubbles: string[] = []; + const deferDeliveryForIntent = true; + const deliveryMaxBubbles = + context.intent === "recommendation" ? Math.max(maxBubblesPerReply, 6) : maxBubblesPerReply; + let bubbleIndex = 0; + let deliveryChain = Promise.resolve(); + let typingStarted = false; + + const queueBubble = (rawBubble: string) => { + const bubble = finalizeOutboundBubble(rawBubble); + if (!bubble) { + return; + } + + if (bubbles.length >= deliveryMaxBubbles) { + logger.warn("dropping bubble beyond maxBubblesPerReply", { + chatGuid, + turnId, + maxBubblesPerReply: deliveryMaxBubbles, + droppedText: bubble, + }); + return; + } + + const currentIndex = bubbleIndex++; + bubbles.push(bubble); + deliveryChain = deliveryChain.then(async () => { + const bubbleText = bubbles[currentIndex]; + if (!bubbleText) { + return; + } + + await deliverBubblesWithTyping({ + bubbles: [bubbleText], + computeDelayMs: (text, bubbleIdx) => { + const absoluteIndex = currentIndex + bubbleIdx; + const baseDelay = + absoluteIndex === 0 + ? computeFirstBubbleDelayMs(text) + : computeInterBubbleDelayMs(text, absoluteIndex); + return applyResponseTimingMultiplier(baseDelay); + }, + hooks: { + startTyping: currentIndex > 0 + ? async () => { + await startTypingForTurn(chatGuid, turnId); + } + : undefined, + stopTyping: currentIndex === bubbles.length - 1 + ? async () => { + if (!isCurrentTurn(chatGuid, turnId)) { + return; + } + const stopped = await stopTypingForTurn(chatGuid, turnId); + if (stopped) { + timing.stopTypingAt = Date.now(); + } + } + : undefined, + send: async (text) => { + logger.debug("outbound bubble send", { + chatGuid, + turnId, + bubbleIndex: currentIndex, + text, + }); + await sendMessage(serverUrl, password, chatGuid, text); + timing.lastBubbleSentAt = Date.now(); + rememberOutbound(chatGuid, text); + }, + delay, + isCurrentTurn: () => isCurrentTurn(chatGuid, turnId), + }, + onBubblePending: (text, _, delayMs) => { + logger.debug("outbound bubble pending", { + chatGuid, + turnId, + bubbleIndex: currentIndex, + delayMs, + text, + }); + }, + }); + }); + }; + + try { + for await (const chunk of result.textStream) { + if (timing.firstChunkAt == null) { + timing.firstChunkAt = Date.now(); + typingStarted = await startTypingForTurn(chatGuid, turnId); + } + process.stdout.write(chunk); + const finalizedBubbles = parser.push(chunk); + if (!deferDeliveryForIntent) { + for (const bubble of finalizedBubbles) { + queueBubble(bubble); + } + } + } + + if (result.error) { + throw result.error; + } + timing.streamCompleteAt = Date.now(); + } catch (err) { + logger.errorWithStack("error during stream consumption", err, { chatGuid, turnId }); + } + + const rawReply = parser.getRawText().trim(); + logger.debug("raw buffered reply", { text: rawReply }); + + let finalReply = rawReply; + const trailingBubbles = parser.flush(); + if (deferDeliveryForIntent) { + const policyResult = applyReplyPolicy({ + rawText: rawReply, + inboundText: context.inboundText, + intent: context.intent, + styleState: context.styleState, + }); + finalReply = policyResult.text || rawReply; + const intentBubbles = buildOutboundBubbles({ + text: finalReply, + textChunkLimit: config.agent.textChunkLimit, + maxBubbles: deliveryMaxBubbles, + }); + for (const bubble of intentBubbles) { + queueBubble(bubble); + } + logger.debug("reply policy applied", { + chatGuid, + turnId, + intent: context.intent, + typingStarted, + rawReply, + finalReply, + intentBubbles, + }); + } else { + for (const bubble of trailingBubbles) { + queueBubble(bubble); + } + } + + await deliveryChain; + logger.debug("outbound bubbles built", { + chatGuid, + turnId, + bubbleCount: bubbles.length, + bubbles, + }); + + return { rawReply, finalReply, bubbles, delivered: [...bubbles] }; +} + +const agents = await buildAgents(); +logger.info("agent ready", { + primary: { model: config.provider.model, baseURL: config.provider.baseURL ?? "codex-oauth" }, + fallbacks: config.fallbacks.map((f) => ({ model: f.model, baseURL: f.baseURL ?? "codex-oauth" })), +}); + +startWebhook( + webhookPort, + password, + allowFrom, + webhookDebounceMs, + async (sender, chatGuid, text, msgGuid) => { + if (isRecentOutboundReflection(chatGuid, text)) { + logger.debug("drop reflected outbound", { sender, chatGuid, text }); + typingChats.delete(chatGuid); + typingTurnOwners.delete(chatGuid); + await stopTyping(serverUrl, password, chatGuid).catch((error) => { + logger.errorWithStack("stopTyping failed for reflected outbound", error, { sender, chatGuid }); + }); + return; + } + + const turnId = claimChatTurn(chatGuid); + const timing: TurnTiming = { inboundAt: Date.now() }; + + logger.info("inbound message", { sender, chatGuid, text, msgGuid, turnId }); + + appendPendingHistory(chatGuid, { + sender: "user", + body: text, + timestamp: Date.now(), + messageId: msgGuid, + }); + const historyEntries = getPendingHistory(chatGuid); + const historyBeforeCurrent = historyEntries.slice(0, -1); + const replyIntent = detectReplyIntent(text); + const responseTokenBudget = computeResponseTokenBudget(text, replyIntent); + const styleState = buildConversationStyleState({ + recentAssistantTexts: getRecentTextsBySender(historyBeforeCurrent, "assistant"), + recentUserTexts: [...getRecentTextsBySender(historyBeforeCurrent, "user"), text], + }); + const historyContext = buildHistoryContext({ + entries: historyBeforeCurrent, + currentMessage: text, + }); + + markRead(serverUrl, password, chatGuid).catch(() => {}); + typingTurnOwners.delete(chatGuid); + typingChats.delete(chatGuid); + await forceStopTyping(chatGuid, { sender, chatGuid, turnId, phase: "pre-turn-clear" }); + + try { + let streamResult: AgentStreamResult | undefined; + for (let i = 0; i < agents.length; i++) { + try { + timing.providerStartAt = Date.now(); + if (i > 0 && timing.fallbackAt == null) { + timing.fallbackAt = timing.providerStartAt; + } + streamResult = await agents[i]!.stream( + historyContext, + buildProviderOptions(cachedInstructions, i > 0, responseTokenBudget), + ); + + if (streamResult.error) { + throw streamResult.error; + } + + if (i > 0) logger.info("using fallback provider", { fallbackIndex: i }); + break; + } catch (err) { + if (isRateLimitError(err) && i < agents.length - 1) { + logger.warn("rate limit hit, trying fallback", { attemptIndex: i, fallbackIndex: i + 1 }); + continue; + } + throw err; + } + } + if (!streamResult) throw new Error("all providers exhausted"); + + const { rawReply, finalReply, bubbles, delivered } = await deliverStreamingReply( + streamResult, + chatGuid, + turnId, + timing, + { + inboundText: text, + intent: replyIntent, + styleState, + }, + ); + if (!isCurrentTurn(chatGuid, turnId)) { + logger.warn("stale turn replaced before delivery", { sender, chatGuid, turnId }); + return; + } + + if (delivered.length > 0) { + appendPendingHistory(chatGuid, { + sender: "assistant", + body: delivered.join("\n"), + timestamp: Date.now(), + }); + } else { + clearPendingHistory(chatGuid); + } + + logger.info("turn complete", { + sender, + chatGuid, + turnId, + intent: replyIntent, + responseTokenBudget, + rawReply, + finalReply, + bubbleCount: bubbles.length, + deliveredCount: delivered.length, + timing: { + inboundToProviderMs: timing.providerStartAt ? timing.providerStartAt - timing.inboundAt : undefined, + providerToFirstChunkMs: + timing.providerStartAt && timing.firstChunkAt ? timing.firstChunkAt - timing.providerStartAt : undefined, + streamDurationMs: + timing.firstChunkAt && timing.streamCompleteAt ? timing.streamCompleteAt - timing.firstChunkAt : undefined, + firstChunkToLastBubbleMs: + timing.firstChunkAt && timing.lastBubbleSentAt ? timing.lastBubbleSentAt - timing.firstChunkAt : undefined, + postSendStopTypingMs: + timing.lastBubbleSentAt && timing.stopTypingAt ? timing.stopTypingAt - timing.lastBubbleSentAt : undefined, + fallbackUsed: timing.fallbackAt != null, + }, + }); + } finally { + const isTurnActive = isCurrentTurn(chatGuid, turnId); + const ownsTyping = typingTurnOwners.get(chatGuid) === turnId; + if (isTurnActive || ownsTyping) { + let stopped = false; + if (timing.stopTypingAt == null) { + stopped = await stopTypingForTurn(chatGuid, turnId); + if (!stopped) { + stopped = await forceStopTyping(chatGuid, { + sender, + chatGuid, + turnId, + phase: "finally-fallback", + isTurnActive, + ownsTyping, + }); + } + if (stopped) { + timing.stopTypingAt = Date.now(); + } + } + } + if (isTurnActive) { + activeChatTurns.delete(chatGuid); + typingChats.delete(chatGuid); + } + } + }, + () => {} +); diff --git a/src/lib/bluebubbles.ts b/src/lib/bluebubbles.ts new file mode 100644 index 0000000..e9d8ac3 --- /dev/null +++ b/src/lib/bluebubbles.ts @@ -0,0 +1,293 @@ +import http from "http"; +import { createClient } from "@jgoon/bluebubbles"; +import { isBlueBubblesWebhookPayload } from "@jgoon/bluebubbles/webhooks"; +import { getModuleLogger } from "./logging.js"; + +export type MessageHandler = ( + sender: string, + chatGuid: string, + text: string, + msgGuid: string +) => Promise; + +export type PendingMessageHandler = ( + sender: string, + chatGuid: string, + text: string, + msgGuid: string +) => void | Promise; + +type PendingInboundMessage = { + sender: string; + chatGuid: string; + text: string; + msgGuid: string; + timer: NodeJS.Timeout; +}; + +const seenInboundMessages = new Map(); +const pendingInboundMessages = new Map(); +const recentInboundTexts = new Map(); +const SEEN_INBOUND_TTL_MS = 2 * 60 * 1000; +const REPEATED_SHORT_MESSAGE_TTL_MS = 15 * 1000; +const REPEATED_SHORT_MESSAGE_MAX_LENGTH = 12; +const logger = getModuleLogger("bluebubbles-webhook"); + +function pruneSeenInboundMessages(now: number) { + for (const [key, timestamp] of seenInboundMessages) { + if (now - timestamp > SEEN_INBOUND_TTL_MS) { + seenInboundMessages.delete(key); + } + } +} + +function pruneRecentInboundTexts(now: number) { + for (const [key, timestamp] of recentInboundTexts) { + if (now - timestamp > REPEATED_SHORT_MESSAGE_TTL_MS) { + recentInboundTexts.delete(key); + } + } +} + +function mergeInboundText(existingText: string, nextText: string): string { + const current = existingText.trim(); + const incoming = nextText.trim(); + + if (!current) return incoming; + if (!incoming) return current; + if (current === incoming) return current; + if (incoming.includes(current)) return incoming; + if (current.includes(incoming)) return current; + return `${current}\n${incoming}`; +} + +function normalizeInboundText(text: string): string { + return text.replace(/\s+/g, " ").trim().toLowerCase(); +} + +function canonicalizeShortMessage(text: string): string { + const normalized = normalizeInboundText(text) + .replace(/[^\p{L}\p{N}\s]/gu, "") + .replace(/(.)\1{1,}/gu, "$1") + .replace(/\s+/g, " ") + .trim(); + + const compact = normalized.replace(/\s+/g, ""); + if (/^(hi|hey|hello|heya|hiya|yo|sup|whatsup|wassup)$/.test(compact)) { + return "__greeting__"; + } + + return normalized; +} + +function isShortRepeatCandidate(text: string): boolean { + const normalized = normalizeInboundText(text); + return normalized.length > 0 && normalized.length <= REPEATED_SHORT_MESSAGE_MAX_LENGTH; +} + +function isNearDuplicateShortMessage(left: string, right: string): boolean { + if (!isShortRepeatCandidate(left) || !isShortRepeatCandidate(right)) { + return false; + } + + const canonicalLeft = canonicalizeShortMessage(left); + const canonicalRight = canonicalizeShortMessage(right); + return Boolean(canonicalLeft) && canonicalLeft === canonicalRight; +} + +function looksLikeUrl(text: string): boolean { + return /https?:\/\/|www\./i.test(text); +} + +function shouldCoalesceMessages(existingText: string, nextText: string): boolean { + const current = existingText.trim(); + const incoming = nextText.trim(); + + if (!current || !incoming) { + return true; + } + + const normalizedCurrent = normalizeInboundText(current); + const normalizedIncoming = normalizeInboundText(incoming); + if (!normalizedCurrent || !normalizedIncoming) { + return true; + } + + if (normalizedCurrent === normalizedIncoming) { + return true; + } + + if (isNearDuplicateShortMessage(current, incoming)) { + return true; + } + + if ( + normalizedCurrent.includes(normalizedIncoming) || + normalizedIncoming.includes(normalizedCurrent) + ) { + return true; + } + + return looksLikeUrl(current) || looksLikeUrl(incoming); +} + +export function startWebhook( + port: number, + password: string, + allowFrom: string[], + debounceMs: number, + onMessage: MessageHandler, + onPendingMessage?: PendingMessageHandler, +) { + http + .createServer((req, res) => { + const url = new URL(req.url ?? "/", "http://localhost"); + + if (url.searchParams.get("guid") !== password) { + res.writeHead(401).end("unauthorized"); + return; + } + + let body = ""; + req.on("data", (chunk) => (body += chunk)); + req.on("end", () => { + res.writeHead(200).end("ok"); + + let payload: unknown; + try { + payload = JSON.parse(body); + } catch { + return; + } + + if (!isBlueBubblesWebhookPayload(payload)) return; + if (payload.type !== "new-message") return; + + const msg = payload.data; + if (msg.isFromMe) return; + + const sender = msg.handle?.address; + if (!sender || !allowFrom.includes(sender)) return; + + const text = msg.text?.trim(); + if (!text) return; + + const chatGuid = msg.chats?.[0]?.guid; + if (!chatGuid) return; + + const now = Date.now(); + pruneSeenInboundMessages(now); + pruneRecentInboundTexts(now); + + const dedupeKey = `${sender}::${chatGuid}::${msg.guid}`; + if (seenInboundMessages.has(dedupeKey)) { + return; + } + seenInboundMessages.set(dedupeKey, now); + + const normalizedText = normalizeInboundText(text); + const repeatKey = `${sender}::${chatGuid}::${ + isShortRepeatCandidate(text) ? canonicalizeShortMessage(text) : normalizedText + }`; + if (isShortRepeatCandidate(text)) { + const lastSeenAt = recentInboundTexts.get(repeatKey); + if (typeof lastSeenAt === "number" && now - lastSeenAt <= REPEATED_SHORT_MESSAGE_TTL_MS) { + return; + } + recentInboundTexts.set(repeatKey, now); + } + + const coalesceKey = `${sender}::${chatGuid}`; + const existing = pendingInboundMessages.get(coalesceKey); + if (existing) { + if (shouldCoalesceMessages(existing.text, text)) { + clearTimeout(existing.timer); + } else { + pendingInboundMessages.delete(coalesceKey); + clearTimeout(existing.timer); + onMessage(existing.sender, existing.chatGuid, existing.text, existing.msgGuid).catch( + (err) => + logger.errorWithStack("webhook handler error", err, { + sender: existing.sender, + chatGuid: existing.chatGuid, + msgGuid: existing.msgGuid, + }) + ); + } + } + + const nextMessage: PendingInboundMessage = { + sender, + chatGuid, + text: + existing && shouldCoalesceMessages(existing.text, text) + ? mergeInboundText(existing.text, text) + : text, + msgGuid: msg.guid, + timer: setTimeout(() => { + pendingInboundMessages.delete(coalesceKey); + onMessage(sender, chatGuid, nextMessage.text, nextMessage.msgGuid).catch((err) => + logger.errorWithStack("webhook handler error", err, { + sender, + chatGuid, + msgGuid: nextMessage.msgGuid, + }) + ); + }, debounceMs), + }; + + pendingInboundMessages.set(coalesceKey, nextMessage); + if (!existing || !shouldCoalesceMessages(existing.text, text)) { + Promise.resolve(onPendingMessage?.(sender, chatGuid, nextMessage.text, nextMessage.msgGuid)).catch( + (err) => + logger.errorWithStack("pending message handler error", err, { + sender, + chatGuid, + msgGuid: nextMessage.msgGuid, + }) + ); + } + }); + }) + .listen(port, () => logger.info("webhook listening", { port })); +} + +export async function sendMessage( + serverUrl: string, + password: string, + chatGuid: string, + text: string +) { + const client = createClient({ baseUrl: serverUrl }); + const { error } = await client.POST("/api/v1/message/text", { + params: { query: { password } }, + body: { + chatGuid, + message: text, + tempGuid: crypto.randomUUID(), + }, + }); + if (error) throw new Error(`BlueBubbles send error: ${JSON.stringify(error)}`); +} + +export async function markRead(serverUrl: string, password: string, chatGuid: string) { + const client = createClient({ baseUrl: serverUrl }); + await client.POST("/api/v1/chat/{chatGuid}/read", { + params: { path: { chatGuid }, query: { password } }, + body: {}, + }); +} + +export async function startTyping(serverUrl: string, password: string, chatGuid: string) { + const client = createClient({ baseUrl: serverUrl }); + await client.POST("/api/v1/chat/{chatGuid}/typing", { + params: { path: { chatGuid }, query: { password } }, + }); +} + +export async function stopTyping(serverUrl: string, password: string, chatGuid: string) { + const client = createClient({ baseUrl: serverUrl }); + await client.DELETE("/api/v1/chat/{chatGuid}/typing", { + params: { path: { chatGuid }, query: { password } }, + }); +} diff --git a/src/lib/codex-auth.ts b/src/lib/codex-auth.ts new file mode 100644 index 0000000..f000bac --- /dev/null +++ b/src/lib/codex-auth.ts @@ -0,0 +1,70 @@ +import { createOpenAI } from "@ai-sdk/openai"; +import { + getOAuthApiKey, + loginOpenAICodex, + type OAuthCredentials, +} from "@mariozechner/pi-ai/oauth"; +import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs"; +import { join } from "path"; +import { createInterface } from "readline"; +import { exec } from "child_process"; + +const AUTH_DIR = join(process.env.HOME!, ".agent"); +const AUTH_PATH = join(AUTH_DIR, "auth.json"); + +type AuthStore = Record; + +export function loadAuth(): AuthStore { + if (!existsSync(AUTH_PATH)) return {}; + return JSON.parse(readFileSync(AUTH_PATH, "utf-8")); +} + +export function saveAuth(auth: AuthStore) { + mkdirSync(AUTH_DIR, { recursive: true }); + writeFileSync(AUTH_PATH, JSON.stringify(auth, null, 2), { mode: 0o600 }); +} + +export async function getCodexProvider() { + const auth = loadAuth(); + const result = await getOAuthApiKey("openai-codex", auth); + if (!result) throw new Error("Not logged in. Run: npm run login"); + + saveAuth({ ...auth, "openai-codex": result.newCredentials }); + + const accountId = (result.newCredentials.accountId as string | undefined) ?? ""; + + return createOpenAI({ + baseURL: "https://chatgpt.com/backend-api/codex", + apiKey: result.apiKey, + headers: { + "ChatGPT-Account-Id": accountId, + "OpenAI-Beta": "responses=experimental", + }, + }); +} + +async function readLine(prompt: string): Promise { + const rl = createInterface({ input: process.stdin, output: process.stdout }); + return new Promise((resolve) => { + rl.question(prompt, (answer) => { + rl.close(); + resolve(answer.trim()); + }); + }); +} + +export async function login() { + const credentials = await loginOpenAICodex({ + onAuth: ({ url }) => { + console.log("\nOpening browser for Codex OAuth..."); + exec(`open "${url}"`); + }, + onPrompt: async (p) => readLine(p.message + " "), + onProgress: (msg) => console.log(msg), + }); + + const auth = loadAuth(); + auth["openai-codex"] = credentials; + saveAuth(auth); + console.log("Logged in. Credentials saved to", AUTH_PATH); +} diff --git a/src/lib/config.ts b/src/lib/config.ts new file mode 100644 index 0000000..8a64d16 --- /dev/null +++ b/src/lib/config.ts @@ -0,0 +1,208 @@ +import { readFileSync, existsSync } from "fs"; +import { join } from "path"; +import type { LogLevel } from "@mastra/loggers"; + +export interface TypingDelayProfile { + minMs: number; + maxMs: number; + perCharMinMs: number; + perCharMaxMs: number; + bubbleOverheadMinMs: number; + bubbleOverheadMaxMs: number; + jitterMin: number; + jitterMax: number; + interBubbleMinMs: number; + interBubbleMaxMs: number; +} + +export interface ProviderConfig { + model: string; + /** Custom base URL. When set, uses apiKey instead of Codex OAuth (e.g. OpenRouter). */ + baseURL?: string; + /** API key. Falls back to OPENROUTER_API_KEY / OPENAI_API_KEY env vars when baseURL is set. */ + apiKey?: string; + /** Extra request headers (e.g. HTTP-Referer for OpenRouter). */ + headers?: Record; +} + +export interface Config { + logging: { + level: LogLevel; + prettyPrint: boolean; + verbose: boolean; + }; + bluebubbles: { + serverUrl: string; + password: string; + webhookPort: number; + allowFrom: string[]; + }; + provider: ProviderConfig; + fallbacks: ProviderConfig[]; + agent: { + workspaceDir: string; + webhookDebounceMs: number; + responseTimingMultiplier: number; + maxBubblesPerReply: number; + recentHistoryLimit: number; + textChunkLimit: number; + typingDelayProfile: TypingDelayProfile; + systemPrompt?: string; + }; +} + +const CONFIG_PATH = join(process.env.HOME!, ".agent", "config.json"); +const DEFAULT_WORKSPACE_DIR = join(process.env.HOME!, ".agent", "workspace"); + +const DEFAULT_TYPING_DELAY_PROFILE: TypingDelayProfile = { + minMs: 450, + maxMs: 4200, + perCharMinMs: 55, + perCharMaxMs: 90, + bubbleOverheadMinMs: 200, + bubbleOverheadMaxMs: 450, + jitterMin: 0.88, + jitterMax: 1.15, + interBubbleMinMs: 700, + interBubbleMaxMs: 1800, +}; + +function expandHome(pathValue: string | undefined): string { + if (!pathValue) { + return DEFAULT_WORKSPACE_DIR; + } + if (pathValue === "~") { + return process.env.HOME!; + } + if (pathValue.startsWith("~/")) { + return join(process.env.HOME!, pathValue.slice(2)); + } + return pathValue; +} + +export function loadConfig(): Config { + if (!existsSync(CONFIG_PATH)) { + throw new Error(`Config not found at ${CONFIG_PATH}`); + } + const raw = JSON.parse(readFileSync(CONFIG_PATH, "utf-8")) as any; + const logging = raw.logging; + const bluebubbles = raw.bluebubbles; + const agent = raw.agent; + + if (!bluebubbles?.serverUrl || !bluebubbles.password || !bluebubbles.webhookPort) { + throw new Error(`Invalid BlueBubbles config in ${CONFIG_PATH}`); + } + + // Resolve provider — top-level "provider" block, or legacy "agent.model" fallback + const rawProvider = raw.provider; + const model = rawProvider?.model ?? agent?.model; + if (!model) throw new Error(`No model specified in ${CONFIG_PATH}`); + const providerConfig: ProviderConfig = { + model, + baseURL: typeof rawProvider?.baseURL === "string" ? rawProvider.baseURL : undefined, + apiKey: typeof rawProvider?.apiKey === "string" ? rawProvider.apiKey : undefined, + headers: + rawProvider?.headers && typeof rawProvider.headers === "object" ? rawProvider.headers : undefined, + }; + + const profile = agent?.typingDelayProfile ?? {}; + const verbose = logging?.verbose === true || agent?.verbose === true; + + return { + logging: { + level: + logging?.level === "debug" || + logging?.level === "info" || + logging?.level === "warn" || + logging?.level === "error" + ? logging.level + : verbose + ? "debug" + : "info", + prettyPrint: logging?.prettyPrint !== false, + verbose, + }, + bluebubbles: { + serverUrl: bluebubbles.serverUrl, + password: bluebubbles.password, + webhookPort: bluebubbles.webhookPort, + allowFrom: Array.isArray(bluebubbles.allowFrom) + ? bluebubbles.allowFrom.filter((entry: unknown): entry is string => typeof entry === "string") + : [], + }, + provider: providerConfig, + fallbacks: Array.isArray(raw.fallbacks) + ? raw.fallbacks + .filter((f: any) => typeof f?.model === "string") + .map((f: any): ProviderConfig => ({ + model: f.model, + baseURL: typeof f.baseURL === "string" ? f.baseURL : undefined, + apiKey: typeof f.apiKey === "string" ? f.apiKey : undefined, + headers: f.headers && typeof f.headers === "object" ? f.headers : undefined, + })) + : [], + agent: { + workspaceDir: expandHome(agent?.workspaceDir), + webhookDebounceMs: + typeof agent.webhookDebounceMs === "number" && agent.webhookDebounceMs >= 0 + ? agent.webhookDebounceMs + : 350, + responseTimingMultiplier: + typeof (agent as any).responseTimingMultiplier === "number" && + (agent as any).responseTimingMultiplier > 0 + ? Math.min(1, (agent as any).responseTimingMultiplier) + : 0.8, + maxBubblesPerReply: + typeof agent.maxBubblesPerReply === "number" && agent.maxBubblesPerReply > 0 + ? agent.maxBubblesPerReply + : 3, + recentHistoryLimit: + typeof agent.recentHistoryLimit === "number" && agent.recentHistoryLimit >= 0 + ? agent.recentHistoryLimit + : 12, + textChunkLimit: + typeof agent.textChunkLimit === "number" && agent.textChunkLimit > 0 + ? agent.textChunkLimit + : 280, + typingDelayProfile: { + minMs: + typeof profile.minMs === "number" ? profile.minMs : DEFAULT_TYPING_DELAY_PROFILE.minMs, + maxMs: + typeof profile.maxMs === "number" ? profile.maxMs : DEFAULT_TYPING_DELAY_PROFILE.maxMs, + perCharMinMs: + typeof profile.perCharMinMs === "number" + ? profile.perCharMinMs + : DEFAULT_TYPING_DELAY_PROFILE.perCharMinMs, + perCharMaxMs: + typeof profile.perCharMaxMs === "number" + ? profile.perCharMaxMs + : DEFAULT_TYPING_DELAY_PROFILE.perCharMaxMs, + bubbleOverheadMinMs: + typeof profile.bubbleOverheadMinMs === "number" + ? profile.bubbleOverheadMinMs + : DEFAULT_TYPING_DELAY_PROFILE.bubbleOverheadMinMs, + bubbleOverheadMaxMs: + typeof profile.bubbleOverheadMaxMs === "number" + ? profile.bubbleOverheadMaxMs + : DEFAULT_TYPING_DELAY_PROFILE.bubbleOverheadMaxMs, + jitterMin: + typeof profile.jitterMin === "number" + ? profile.jitterMin + : DEFAULT_TYPING_DELAY_PROFILE.jitterMin, + jitterMax: + typeof profile.jitterMax === "number" + ? profile.jitterMax + : DEFAULT_TYPING_DELAY_PROFILE.jitterMax, + interBubbleMinMs: + typeof profile.interBubbleMinMs === "number" + ? profile.interBubbleMinMs + : DEFAULT_TYPING_DELAY_PROFILE.interBubbleMinMs, + interBubbleMaxMs: + typeof profile.interBubbleMaxMs === "number" + ? profile.interBubbleMaxMs + : DEFAULT_TYPING_DELAY_PROFILE.interBubbleMaxMs, + }, + systemPrompt: typeof agent.systemPrompt === "string" ? agent.systemPrompt : undefined, + }, + }; +} diff --git a/src/lib/delivery.ts b/src/lib/delivery.ts new file mode 100644 index 0000000..10f470b --- /dev/null +++ b/src/lib/delivery.ts @@ -0,0 +1,49 @@ +export type BubbleDeliveryHooks = { + startTyping?: () => Promise; + stopTyping?: () => Promise; + send: (text: string, bubbleIndex: number) => Promise; + delay: (ms: number) => Promise; + isCurrentTurn: () => boolean; +}; + +export type BubbleDeliveryOptions = { + bubbles: string[]; + computeDelayMs: (text: string, bubbleIndex: number) => number; + hooks: BubbleDeliveryHooks; + onBubblePending?: (text: string, bubbleIndex: number, delayMs: number) => void; + onBubbleSent?: (text: string, bubbleIndex: number) => void; +}; + +export async function deliverBubblesWithTyping(options: BubbleDeliveryOptions): Promise { + const delivered: string[] = []; + try { + for (let i = 0; i < options.bubbles.length; i += 1) { + const bubbleText = options.bubbles[i]; + if (!bubbleText || !options.hooks.isCurrentTurn()) { + continue; + } + + if (i > 0) { + await options.hooks.startTyping?.(); + } + + const delayMs = options.computeDelayMs(bubbleText, i); + options.onBubblePending?.(bubbleText, i, delayMs); + await options.hooks.delay(delayMs); + + if (!options.hooks.isCurrentTurn()) { + continue; + } + + await options.hooks.send(bubbleText, i); + delivered.push(bubbleText); + options.onBubbleSent?.(bubbleText, i); + } + } finally { + if (options.hooks.isCurrentTurn()) { + await options.hooks.stopTyping?.(); + } + } + + return delivered; +} diff --git a/src/lib/logging.ts b/src/lib/logging.ts new file mode 100644 index 0000000..e250700 --- /dev/null +++ b/src/lib/logging.ts @@ -0,0 +1,100 @@ +import { PinoLogger } from "@mastra/loggers"; +import { loadConfig } from "./config.js"; + +type LogBindings = Record; + +type SerializableError = { + name: string; + message: string; + stack?: string[]; + cause?: SerializableError | unknown; +}; + +export interface AppLogger { + child(bindings: LogBindings): AppLogger; + debug(message: string, payload?: LogBindings): void; + info(message: string, payload?: LogBindings): void; + warn(message: string, payload?: LogBindings): void; + error(message: string, payload?: LogBindings): void; + errorWithStack(message: string, error: unknown, payload?: LogBindings): void; +} + +let baseLogger: PinoLogger | undefined; +let verbose = false; + +function serializeError(error: unknown): SerializableError | unknown { + if (!(error instanceof Error)) { + return error; + } + + const serialized: SerializableError = { + name: error.name, + message: error.message, + }; + + if (verbose && error.stack) { + serialized.stack = error.stack + .split("\n") + .map((line) => line.trimEnd()) + .filter(Boolean); + } + + const cause = (error as Error & { cause?: unknown }).cause; + if (typeof cause !== "undefined") { + serialized.cause = serializeError(cause); + } + + return serialized; +} + +function wrapLogger(logger: PinoLogger): AppLogger { + return { + child(bindings: LogBindings) { + return wrapLogger(logger.child(bindings)); + }, + debug(message: string, payload?: LogBindings) { + logger.debug(message, payload); + }, + info(message: string, payload?: LogBindings) { + logger.info(message, payload); + }, + warn(message: string, payload?: LogBindings) { + logger.warn(message, payload); + }, + error(message: string, payload?: LogBindings) { + logger.error(message, payload); + }, + errorWithStack(message: string, error: unknown, payload?: LogBindings) { + logger.error(message, { + ...payload, + error: serializeError(error), + }); + }, + }; +} + +export function getBaseLogger(): AppLogger { + if (!baseLogger) { + const config = loadConfig(); + verbose = config.logging.verbose; + baseLogger = new PinoLogger({ + name: "agent", + level: config.logging.level, + prettyPrint: config.logging.prettyPrint, + }); + } + + return wrapLogger(baseLogger); +} + +export function getMastraLogger(): PinoLogger { + if (!baseLogger) { + getBaseLogger(); + } + return baseLogger!; +} + +export function getModuleLogger(module: string, bindings?: LogBindings): AppLogger { + const logger = getBaseLogger().child({ module }); + return bindings ? logger.child(bindings) : logger; +} diff --git a/src/lib/outbound.ts b/src/lib/outbound.ts new file mode 100644 index 0000000..c9dc650 --- /dev/null +++ b/src/lib/outbound.ts @@ -0,0 +1,309 @@ +export const INTERNAL_SPLIT_TOKEN = "[MSG]"; + +const REPLY_DIRECTIVE_TAG_RE = /\[\[\s*(?:reply_to_current|reply_to\s*:\s*[^\]\n]+)\s*\]\]/gi; +const INTERNAL_SPLIT_TOKEN_RE = /\s*\[MSG\]\s*/gi; +const EM_DASH_RE = /[—–]/g; +const SINGLE_BUBBLE_SOFT_LIMIT = 120; +const SINGLE_BUBBLE_HARD_LIMIT = 220; +const MAX_REPEAT_COLLAPSE_LENGTH = 240; +const STRUCTURED_LINE_RE = /^(?:[-*•]\s+.+|\d+[.)]\s+.+|[A-Za-z][A-Za-z0-9 &'\/-]{0,28}:$|[A-Za-z][A-Za-z0-9 &'\/-]{0,20}:\s+.+)$/; + +export interface PrivateBubbleParser { + push(chunk: string): string[]; + flush(): string[]; + getRawText(): string; +} + +function normalizeWhitespace(text: string): string { + return text + .replace(/\r\n?/g, "\n") + .replace(EM_DASH_RE, "-") + .replace(REPLY_DIRECTIVE_TAG_RE, " ") + .replace(INTERNAL_SPLIT_TOKEN_RE, " ") + .replace(/[ \t]+/g, " ") + .replace(/[ \t]+\n/g, "\n") + .replace(/\n{3,}/g, "\n\n") + .trim(); +} + +function splitParagraphs(text: string): string[] { + return text + .split(/\n\s*\n/g) + .map((part) => part.trim()) + .filter(Boolean); +} + +function findChunkBreakIndex(text: string, limit: number): number { + if (text.length <= limit) { + return text.length; + } + + const window = text.slice(0, limit + 1); + const sentenceBreak = Math.max( + window.lastIndexOf(". "), + window.lastIndexOf("! "), + window.lastIndexOf("? "), + ); + if (sentenceBreak >= Math.floor(limit * 0.45)) { + return sentenceBreak + 1; + } + + const newlineBreak = window.lastIndexOf("\n"); + if (newlineBreak >= Math.floor(limit * 0.45)) { + return newlineBreak; + } + + const wordBreak = window.lastIndexOf(" "); + if (wordBreak >= Math.floor(limit * 0.45)) { + return wordBreak; + } + + return limit; +} + +function chunkPlainText(text: string, limit: number): string[] { + const chunks: string[] = []; + let remaining = text.trim(); + + while (remaining) { + if (remaining.length <= limit) { + chunks.push(remaining); + break; + } + + const breakIndex = findChunkBreakIndex(remaining, limit); + const chunk = remaining.slice(0, breakIndex).trim(); + if (!chunk) { + chunks.push(remaining.slice(0, limit).trim()); + remaining = remaining.slice(limit).trimStart(); + continue; + } + + chunks.push(chunk); + remaining = remaining.slice(breakIndex).trimStart(); + } + + return chunks; +} + +function capChunks(chunks: string[], maxBubbles: number): string[] { + if (chunks.length <= maxBubbles) { + return chunks; + } + + const head = chunks.slice(0, Math.max(0, maxBubbles - 1)); + const tail = chunks.slice(Math.max(0, maxBubbles - 1)).join("\n\n").trim(); + return tail ? [...head, tail] : head; +} + +function normalizeForComparison(text: string): string { + return text.replace(/\s+/g, " ").trim().toLowerCase(); +} + +function looksStructuredMultiline(text: string): boolean { + const lines = text + .split("\n") + .map((line) => line.trim()) + .filter(Boolean); + + if (lines.length <= 1 || text.length > SINGLE_BUBBLE_HARD_LIMIT) { + return false; + } + + if (lines.every((line) => STRUCTURED_LINE_RE.test(line))) { + return true; + } + + if ( + lines.length >= 2 && + /:$/.test(lines[0] ?? "") && + lines.slice(1).every((line) => STRUCTURED_LINE_RE.test(line)) + ) { + return true; + } + + return false; +} + +function normalizeBubbleLineBreaks(text: string): string { + const normalized = sanitizeOutboundText(text); + if (!normalized.includes("\n")) { + return normalized; + } + + if (looksStructuredMultiline(normalized)) { + return normalized; + } + + return normalized.replace(/\s*\n+\s*/g, " ").trim(); +} + +function collapseRepeatedShortReply(text: string): string { + const normalized = normalizeBubbleLineBreaks(text); + if (!normalized || normalized.length > MAX_REPEAT_COLLAPSE_LENGTH) { + return normalized; + } + + const compact = normalized.replace(/\s+/g, " ").trim(); + const half = Math.floor(compact.length / 2); + for (let offset = -3; offset <= 3; offset += 1) { + const splitIndex = half + offset; + if (splitIndex <= 0 || splitIndex >= compact.length) { + continue; + } + + const left = compact.slice(0, splitIndex).trim(); + const right = compact.slice(splitIndex).trim(); + if (left.length < 4 || right.length < 4) { + continue; + } + + if (normalizeForComparison(left) === normalizeForComparison(right)) { + return left; + } + } + + const sentenceParts = compact.match(/[^.!?]+[.!?]+|[^.!?]+$/g)?.map((part) => part.trim()) ?? []; + if (sentenceParts.length >= 2 && sentenceParts.length % 2 === 0) { + const midpoint = sentenceParts.length / 2; + const left = sentenceParts.slice(0, midpoint).join(" ").trim(); + const right = sentenceParts.slice(midpoint).join(" ").trim(); + if (left && right && normalizeForComparison(left) === normalizeForComparison(right)) { + return left; + } + } + + return normalized; +} + +export function finalizeOutboundBubble(text: string): string { + return collapseRepeatedShortReply(text); +} + +function splitDelimitedText(text: string, token: string): string[] { + return text.split(token); +} + +function hasPrivateSplitToken(text: string): boolean { + return /\[msg\]/i.test(text); +} + +function splitPrivateDelimitedText(text: string): string[] { + return text.split(/\[msg\]/i); +} + +function extractDelimitedBubbles( + safeText: string, + currentBubble: string, + token: string, +): { currentBubble: string; bubbles: string[] } { + const pieces = splitDelimitedText(safeText, token); + if (pieces.length === 1) { + return { + currentBubble: currentBubble + safeText, + bubbles: [], + }; + } + + const bubbles: string[] = []; + let workingBubble = currentBubble + (pieces.shift() ?? ""); + bubbles.push(workingBubble); + workingBubble = ""; + + for (let i = 0; i < pieces.length; i += 1) { + const piece = pieces[i] ?? ""; + if (i === pieces.length - 1) { + workingBubble = piece; + } else { + bubbles.push(piece); + } + } + + return { currentBubble: workingBubble, bubbles }; +} + +export function createPrivateBubbleParser(token = INTERNAL_SPLIT_TOKEN): PrivateBubbleParser { + const carryLength = Math.max(0, token.length - 1); + let carry = ""; + let currentBubble = ""; + let rawText = ""; + + return { + push(chunk: string) { + const normalizedChunk = chunk.replace(/\r\n?/g, "\n"); + rawText += normalizedChunk; + + const combined = `${carry}${normalizedChunk}`; + if (carryLength === 0 || combined.length <= carryLength) { + carry = combined; + return []; + } + + const safeText = combined.slice(0, combined.length - carryLength); + carry = combined.slice(combined.length - carryLength); + + const extracted = extractDelimitedBubbles(safeText, currentBubble, token); + currentBubble = extracted.currentBubble; + return extracted.bubbles; + }, + flush() { + const extracted = extractDelimitedBubbles(carry, currentBubble, token); + carry = ""; + currentBubble = ""; + return [...extracted.bubbles, extracted.currentBubble]; + }, + getRawText() { + return rawText; + }, + }; +} + +export function sanitizeOutboundText(text: string): string { + return normalizeWhitespace(text); +} + +export function buildOutboundBubbles(params: { + text: string; + textChunkLimit: number; + maxBubbles: number; +}): string[] { + const sanitized = sanitizeOutboundText(params.text); + if (!sanitized) { + return []; + } + + const hardLimit = Math.max(1, params.textChunkLimit); + const singleBubbleLimit = Math.min(hardLimit, SINGLE_BUBBLE_HARD_LIMIT); + + if (hasPrivateSplitToken(params.text)) { + const explicitChunks = splitPrivateDelimitedText(params.text) + .map((chunk) => finalizeOutboundBubble(chunk)) + .filter(Boolean) + .flatMap((chunk) => + chunk.length <= hardLimit ? [chunk] : chunkPlainText(chunk, hardLimit).map(finalizeOutboundBubble), + ) + .filter(Boolean); + + return capChunks(explicitChunks, params.maxBubbles); + } + + const paragraphs = splitParagraphs(sanitized); + + if (sanitized.length <= SINGLE_BUBBLE_SOFT_LIMIT) { + return [finalizeOutboundBubble(sanitized)]; + } + + if (sanitized.length <= singleBubbleLimit) { + return [finalizeOutboundBubble(sanitized)]; + } + + const paragraphChunks = + paragraphs.length > 1 + ? paragraphs.flatMap((paragraph) => + paragraph.length <= hardLimit ? [paragraph] : chunkPlainText(paragraph, hardLimit), + ) + : chunkPlainText(sanitized, hardLimit); + + const cleanedChunks = paragraphChunks.map((chunk) => finalizeOutboundBubble(chunk)).filter(Boolean); + return capChunks(cleanedChunks, params.maxBubbles); +} diff --git a/src/lib/provider.ts b/src/lib/provider.ts new file mode 100644 index 0000000..c22f00a --- /dev/null +++ b/src/lib/provider.ts @@ -0,0 +1,112 @@ +import { createOpenAI } from "@ai-sdk/openai"; +import type { LanguageModel as MastraLanguageModel } from "@mastra/core/llm"; +import { getCodexProvider } from "./codex-auth.js"; +import type { ProviderConfig } from "./config.js"; + +export async function buildModel(config: ProviderConfig): Promise { + if (config.baseURL) { + const apiKey = config.apiKey ?? process.env.OPENROUTER_API_KEY ?? process.env.OPENAI_API_KEY ?? ""; + + const openai = createOpenAI({ + baseURL: config.baseURL, + apiKey, + ...(config.headers ? { headers: config.headers } : {}), + compatibility: "strict", + }); + + // We use gpt-4o as a base because it's a standard chat model. + // We then Proxy it to swap the model name in every call. + const baseModel = openai("gpt-4o"); + + const proxiedModel = new Proxy(baseModel, { + get(target, prop, receiver) { + if (prop === "doStream" || prop === "doGenerate") { + const original = Reflect.get(target, prop, receiver); + return async function (params: any) { + const updatedParams = { + ...params, + modelId: config.model, + }; + return original.apply(this, [updatedParams]); + }; + } + if (prop === "modelId") return config.model; + return Reflect.get(target, prop, receiver); + }, + }); + + return proxiedModel as unknown as MastraLanguageModel; + } + + const openai = await getCodexProvider(); + return openai.responses(config.model as "gpt-4o") as unknown as MastraLanguageModel; +} + +export async function buildModels( + primary: ProviderConfig, + fallbacks: ProviderConfig[], +): Promise { + // Build all models upfront so OAuth is resolved once at startup, not per-request + return Promise.all([primary, ...fallbacks].map(buildModel)); +} + +export function isRateLimitError(err: unknown): boolean { + if (!err) return false; + + const body = (err as any).responseBody ?? (err as any).cause?.responseBody ?? (err as any).error?.responseBody ?? ""; + const statusCode = + (err as any).statusCode ?? (err as any).cause?.statusCode ?? (err as any).status ?? (err as any).error?.statusCode; + const message = err instanceof Error ? err.message.toLowerCase() : String(err).toLowerCase(); + const innerMessage = (err as any).error?.message?.toLowerCase() ?? ""; + + return ( + statusCode === 429 || + statusCode === 402 || + message.includes("usage_limit_reached") || + message.includes("usage limit") || + message.includes("rate limit") || + message.includes("quota") || + message.includes("429") || + message.includes("402") || + message.includes("credits") || + innerMessage.includes("usage limit") || + innerMessage.includes("rate limit") || + innerMessage.includes("credits") || + (typeof body === "string" && + (body.includes("usage_limit_reached") || + body.includes("rate_limit_exceeded") || + body.includes("usage limit") || + body.includes("credits"))) + ); +} + +export function buildProviderOptions( + instructions: string, + isFallback: boolean, + maxTokens = 512, +): Record { + const boundedMaxTokens = Math.max(24, Math.min(512, Math.floor(maxTokens))); + const options: Record = { + modelSettings: { + maxTokens: boundedMaxTokens, + }, + providerOptions: { + openai: { + instructions, + systemMessageMode: "remove", + store: false, + maxTokens: boundedMaxTokens, + }, + }, + }; + + if (isFallback) { + // Only add these for fallbacks (OpenRouter) to avoid breaking primary Codex + options.modelSettings.maxOutputTokens = boundedMaxTokens; + options.modelSettings.maxCompletionTokens = boundedMaxTokens; + options.providerOptions.openai.maxOutputTokens = boundedMaxTokens; + options.providerOptions.openai.maxCompletionTokens = boundedMaxTokens; + } + + return options; +} diff --git a/src/lib/reply-policy.ts b/src/lib/reply-policy.ts new file mode 100644 index 0000000..fefcddd --- /dev/null +++ b/src/lib/reply-policy.ts @@ -0,0 +1,379 @@ +export type ReplyIntent = "greeting" | "capability" | "recommendation" | "normal"; + +export interface ConversationStyleState { + recentGreetingStems: string[]; + recentGreetingReplies: string[]; + recentUserGreetings: number; +} + +export interface ReplyPolicyInput { + rawText: string; + inboundText: string; + intent: ReplyIntent; + styleState?: ConversationStyleState; +} + +export interface ReplyPolicyResult { + text: string; + intent: ReplyIntent; +} + +const GREETING_TOKEN_RE = + /^(?:h+i+|hey+|hiya+|hai+|herro+|hello+|yo+|sup+|what'?s\s*up|wassup+)(?:\s+(?:there|anh|u|you|again))?$/i; +const CAPABILITY_QUERY_RE = + /(what\s+can\s+you\s+do|what\s+kind\s+of\s+tools|what\s+tools\s+do\s+you\s+have|help\s+with|can\s+you\s+help\s+with)/i; +const RECOMMENDATION_QUERY_RE = + /(where\s+(?:should|can)\s+(?:we|i)\s+(?:eat|go)|what\s+(?:should|can)\s+(?:we|i)\s+eat|recommend|spot|place\s+to\s+eat|restaurant|dinner|lunch|brunch)/i; +const CUISINE_SIGNAL_RE = + /\b(sushi|ramen|pho|thai|kbbq|bbq|tacos?|pizza|pasta|hotpot|dim\s*sum|poke|burgers?|wings|udon|omakase)\b/i; +const RECOMMENDATION_MENU_CHAIN_RE = + /(?:\bif u (?:want|wanna)\b|\bif ur feeling\b).*(?:\bif u (?:want|wanna)\b|\bif ur feeling\b)/i; +const RECOMMENDATION_GENERIC_BUCKET_RE = /\b(izakaya|omakase|sushi burrito|cheap\s*\+\s*fast|feeling fancy)\b/i; +const DETAIL_REQUEST_RE = + /\b(explain|break down|step by step|walk me through|plan|compare|pros|cons|debug|code|function|algorithm|fix|why|how)\b/i; +const PRACTICAL_QUERY_RE = + /\b(tax|taxes|filing|irs|w-2|w2|1099|bank|payment|payroll|ssn|legal|contract|form|deadline|invoice)\b/i; + +function normalizeSpaces(text: string): string { + return text.replace(/\r\n?/g, "\n").replace(/[ \t]+/g, " ").replace(/\s+\n/g, "\n").trim(); +} + +function normalizeForComparison(text: string): string { + return text.toLowerCase().replace(/[—–]/g, "-").replace(/[^a-z0-9\s']/g, " ").replace(/\s+/g, " ").trim(); +} + +function extractGreetingStem(text: string): string | null { + const firstToken = normalizeForComparison(text).split(" ").filter(Boolean)[0]; + if (!firstToken) { + return null; + } + + const normalized = firstToken.replace(/[^a-z]/g, ""); + if (!normalized) { + return null; + } + if (normalized.startsWith("hiya")) return "hiya"; + if (normalized.startsWith("hai")) return "hai"; + if (normalized.startsWith("hey")) return "hey"; + if (normalized.startsWith("hi")) return "hi"; + if (normalized.startsWith("yo")) return "yo"; + if (normalized.startsWith("sup") || normalized.startsWith("wassup") || normalized.startsWith("whatsup")) { + return "sup"; + } + if (normalized.startsWith("hello")) return "hello"; + if (normalized.startsWith("herro")) return "herro"; + return null; +} + +function isGreetingText(text: string): boolean { + const normalized = normalizeForComparison(text); + if (!normalized) { + return false; + } + if (GREETING_TOKEN_RE.test(normalized)) { + return true; + } + + const words = normalized.split(" ").filter(Boolean); + if (words.length > 3) { + return false; + } + return extractGreetingStem(normalized) != null; +} + +function countWords(text: string): number { + const normalized = normalizeSpaces(text); + if (!normalized) { + return 0; + } + return normalized.split(/\s+/).length; +} + +function clampToWordBudget(text: string, maxWords: number): string { + const normalized = normalizeSpaces(text); + if (!normalized) { + return normalized; + } + + const words = normalized.split(/\s+/).filter(Boolean); + if (words.length <= maxWords) { + return normalized; + } + + const sentenceParts = normalized + .split(/(?<=[.!?])\s+/) + .map((part) => part.trim()) + .filter(Boolean); + if (sentenceParts.length > 1) { + let acc = ""; + for (const sentence of sentenceParts) { + const candidate = acc ? `${acc} ${sentence}` : sentence; + if (countWords(candidate) > maxWords) { + break; + } + acc = candidate; + } + if (acc) { + return acc; + } + } + + return words.slice(0, maxWords).join(" ").trim(); +} + +function splitIntoCasualBubbles(text: string, maxWordsPerBubble = 13, maxBubbles = 3): string { + if (!text || /\[MSG\]/i.test(text)) { + return text; + } + + const normalized = normalizeSpaces(text); + const sentenceParts = normalized + .split(/(?<=[.!?])\s+/) + .map((part) => part.trim()) + .filter(Boolean); + + if (sentenceParts.length < 2) { + if (countWords(normalized) > maxWordsPerBubble) { + const words = normalized.split(/\s+/).filter(Boolean); + const chunks: string[] = []; + for (let i = 0; i < words.length; i += maxWordsPerBubble) { + chunks.push(words.slice(i, i + maxWordsPerBubble).join(" ")); + } + const bounded = chunks.slice(0, maxBubbles); + if (chunks.length > maxBubbles) { + bounded[maxBubbles - 1] = chunks.slice(maxBubbles - 1).join(" "); + } + return bounded.join(" [MSG] "); + } + return normalized; + } + + const bubbles: string[] = []; + let current = ""; + + for (const sentence of sentenceParts) { + const candidate = current ? `${current} ${sentence}` : sentence; + if (countWords(candidate) <= maxWordsPerBubble || !current) { + current = candidate; + continue; + } + bubbles.push(current.trim()); + current = sentence; + } + + if (current.trim()) { + bubbles.push(current.trim()); + } + + if (bubbles.length <= 1) { + return normalized; + } + + const bounded = bubbles.slice(0, maxBubbles); + if (bubbles.length > maxBubbles) { + const rest = bubbles.slice(maxBubbles - 1).join(" ").trim(); + bounded[maxBubbles - 1] = rest; + } + return bounded.join(" [MSG] "); +} + +function enforceMsgBubbleWordCap(text: string, maxWordsPerBubble: number, maxBubbles: number): string { + if (!text) { + return text; + } + + const sourceSegments = text + .split(/\[msg\]/i) + .map((segment) => normalizeSpaces(segment)) + .filter(Boolean); + + if (sourceSegments.length === 0) { + return text; + } + + const chunks: string[] = []; + for (const segment of sourceSegments) { + const words = segment.split(/\s+/).filter(Boolean); + if (words.length <= maxWordsPerBubble) { + chunks.push(segment); + continue; + } + for (let i = 0; i < words.length; i += maxWordsPerBubble) { + chunks.push(words.slice(i, i + maxWordsPerBubble).join(" ")); + } + } + + return chunks.slice(0, maxBubbles).join(" [MSG] "); +} + +function extractCuisineLikeTerm(text: string): string | null { + const normalized = normalizeForComparison(text); + const tokens = normalized.split(" ").filter(Boolean); + if (tokens.length === 0 || tokens.length > 4) { + return null; + } + const blocked = new Set(["what", "where", "should", "can", "we", "i", "eat", "go", "tonight", "for", "to"]); + const filtered = tokens.filter((token) => !blocked.has(token)); + if (filtered.length === 0 || filtered.length > 2) { + return null; + } + return filtered.join(" "); +} + +export function isCapabilityInventoryLike(text: string): boolean { + const normalized = normalizeForComparison(text); + const wordCount = countWords(text); + const signalCount = [ + /read\/write|read and write|read|write/, + /workspace|docs|notes|memory|files|code/, + /search|browse|research/, + /draft|edit|summarize|plan|debug|organize/, + /calendar|messaging|tools|actions|ops/, + ].filter((pattern) => pattern.test(normalized)).length; + + const hasBulletLines = /(?:^|\n)\s*[-*•]\s+/.test(text); + const hasDashInventory = /(?:\s-\s).*(?:\s-\s)/.test(text); + const lineBreakCount = (text.match(/\n/g) ?? []).length; + + if (hasBulletLines || hasDashInventory) return true; + if (wordCount > 42) return true; + if (wordCount > 28 && signalCount >= 3) return true; + if (lineBreakCount >= 2 && wordCount > 22 && signalCount >= 2) return true; + return false; +} + +export function detectReplyIntent(inboundText: string): ReplyIntent { + const normalized = normalizeForComparison(inboundText); + if (!normalized) { + return "normal"; + } + if (isGreetingText(normalized)) { + return "greeting"; + } + if (CAPABILITY_QUERY_RE.test(normalized)) { + return "capability"; + } + const isCuisineNudge = + normalized.split(" ").filter(Boolean).length <= 3 && CUISINE_SIGNAL_RE.test(normalized); + if (isCuisineNudge) { + return "recommendation"; + } + + const asksForAdvice = + /what would you do|do you think/i.test(normalized) || + (/\bshould i\b/i.test(normalized) && !RECOMMENDATION_QUERY_RE.test(normalized)); + if (RECOMMENDATION_QUERY_RE.test(normalized) && !asksForAdvice) { + return "recommendation"; + } + return "normal"; +} + +export function computeResponseTokenBudget(inboundText: string, intent: ReplyIntent): number { + const normalized = normalizeForComparison(inboundText); + const wordCount = countWords(inboundText); + const asksDetail = + DETAIL_REQUEST_RE.test(normalized) || + inboundText.includes("\n") || + wordCount >= 22; + + if (intent === "greeting") { + return 28; + } + + if (intent === "capability") { + if (asksDetail) { + return 104; + } + return wordCount <= 8 ? 56 : 80; + } + + if (intent === "recommendation") { + return asksDetail ? 108 : 84; + } + + if (asksDetail) { + return 180; + } + if (wordCount <= 4) { + return 72; + } + if (wordCount <= 12) { + return 90; + } + return 108; +} + +export function buildConversationStyleState(params: { + recentAssistantTexts: string[]; + recentUserTexts: string[]; +}): ConversationStyleState { + const recentGreetingReplies = params.recentAssistantTexts + .map((text) => normalizeSpaces(text)) + .filter((text) => isGreetingText(text)) + .slice(-4); + const recentGreetingStems = recentGreetingReplies + .map((text) => extractGreetingStem(text)) + .filter((stem): stem is string => stem != null); + const recentUserGreetings = params.recentUserTexts + .slice(-4) + .reduce((count, text) => count + (detectReplyIntent(text) === "greeting" ? 1 : 0), 0); + + return { + recentGreetingStems, + recentGreetingReplies, + recentUserGreetings, + }; +} + +export function applyReplyPolicy(input: ReplyPolicyInput): ReplyPolicyResult { + let text = normalizeSpaces(input.rawText).replace(/[—–]/g, "-"); + const normalizedInbound = normalizeForComparison(input.inboundText); + const asksDetail = + DETAIL_REQUEST_RE.test(normalizedInbound) || input.inboundText.includes("\n") || countWords(input.inboundText) >= 22; + const practicalQuery = PRACTICAL_QUERY_RE.test(normalizedInbound); + + if (input.intent === "normal" || input.intent === "recommendation") { + text = text.replace(/\s+-\s+/g, ". "); + } + + text = text + .replace(/\bwould you like me to send you the links?\??/gi, " ") + .replace(/\bwould u like me to send (?:u )?the links?\??/gi, " ") + .replace(/\bif u want,?\s*i can send (?:u )?the links?\??/gi, " ") + .replace(/\s{2,}/g, " ") + .trim(); + + if ( + input.intent === "recommendation" && + RECOMMENDATION_MENU_CHAIN_RE.test(text) && + RECOMMENDATION_GENERIC_BUCKET_RE.test(text) + ) { + const cuisine = extractCuisineLikeTerm(input.inboundText); + text = cuisine ? `ok wya rn?? i'll give u 2 ${cuisine} spots by name` : "ok wya rn?? i'll give u real spot names"; + } + + if ((input.intent === "normal" || input.intent === "recommendation") && countWords(text) > 22) { + text = splitIntoCasualBubbles(text, 13, 3); + } + + if (input.intent === "greeting") { + text = clampToWordBudget(text, 8); + } else if (input.intent === "recommendation") { + text = splitIntoCasualBubbles(text, 8, 3); + text = clampToWordBudget(text, 20); + text = enforceMsgBubbleWordCap(text, 8, 3); + } else if (input.intent === "capability") { + text = splitIntoCasualBubbles(text, 10, 2); + text = clampToWordBudget(text, asksDetail ? 28 : 20); + text = enforceMsgBubbleWordCap(text, 10, 2); + } else if (!asksDetail) { + text = splitIntoCasualBubbles(text, practicalQuery ? 10 : 12, practicalQuery ? 2 : 3); + text = clampToWordBudget(text, practicalQuery ? 22 : 26); + } + + return { + // Transport-safe normalization plus anti-assistant phrasing guards. + text, + intent: input.intent, + }; +} diff --git a/src/lib/split.test.ts b/src/lib/split.test.ts new file mode 100644 index 0000000..329844f --- /dev/null +++ b/src/lib/split.test.ts @@ -0,0 +1,105 @@ +// Unit tests for [MSG] stream split logic +// Run: node --experimental-strip-types src/lib/split.test.ts + +function splitStream(chunks: string[], token: string): string[] { + let buffer = ""; + const parts: string[] = []; + + for (const chunk of chunks) { + buffer += chunk; + while (buffer.includes(token)) { + const idx = buffer.indexOf(token); + const part = buffer.slice(0, idx).trim(); + if (part) parts.push(part); + buffer = buffer.slice(idx + token.length); + } + } + if (buffer.trim()) parts.push(buffer.trim()); + return parts; +} + +let passed = 0; +let failed = 0; + +function expect(label: string, actual: unknown, expected: unknown) { + const ok = JSON.stringify(actual) === JSON.stringify(expected); + console.log(`${ok ? "✓" : "✗"} ${label}`); + if (!ok) { + console.log(` expected: ${JSON.stringify(expected)}`); + console.log(` got: ${JSON.stringify(actual)}`); + } + ok ? passed++ : failed++; +} + +// Basic split +expect( + "splits on [MSG]", + splitStream(["hello [MSG] world"], "[MSG]"), + ["hello", "world"] +); + +// Token split across chunk boundaries +expect( + "token split across chunks", + splitStream(["hey so [MS", "G] you've got a meeting [MSG", "] and dinner"], "[MSG]"), + ["hey so", "you've got a meeting", "and dinner"] +); + +// No token → single bubble +expect( + "no token = single bubble", + splitStream(["just a plain message"], "[MSG]"), + ["just a plain message"] +); + +// Leading/trailing whitespace trimmed +expect( + "trims whitespace around bubbles", + splitStream([" hey [MSG] there "], "[MSG]"), + ["hey", "there"] +); + +// Empty parts between consecutive tokens skipped +expect( + "skips empty parts between consecutive tokens", + splitStream(["a[MSG][MSG]b"], "[MSG]"), + ["a", "b"] +); + +// Token at start +expect( + "token at start → skips empty leading part", + splitStream(["[MSG]hello"], "[MSG]"), + ["hello"] +); + +// Token at end → no empty trailing part +expect( + "token at end → no empty trailing part", + splitStream(["hello[MSG]"], "[MSG]"), + ["hello"] +); + +// Multiple bubbles, token arrives in tiny chunks +expect( + "three bubbles via tiny streaming chunks", + splitStream(["a", " ", "[", "M", "S", "G", "]", " b [MSG] c"], "[MSG]"), + ["a", "b", "c"] +); + +// Empty stream +expect( + "empty stream = no bubbles", + splitStream(["", " "], "[MSG]"), + [] +); + +// Full example from system prompt +expect( + "system prompt example", + splitStream(["hey so [MSG] you've got a 3pm meeting [MSG] and dinner at 7, pretty slammed"], "[MSG]"), + ["hey so", "you've got a 3pm meeting", "and dinner at 7, pretty slammed"] +); + +console.log(`\n${passed} passed, ${failed} failed`); +if (failed > 0) process.exit(1); diff --git a/src/lib/workspace.ts b/src/lib/workspace.ts new file mode 100644 index 0000000..811dfa6 --- /dev/null +++ b/src/lib/workspace.ts @@ -0,0 +1,299 @@ +import { copyFileSync, existsSync, mkdirSync, readFileSync } from "fs"; +import { basename, join } from "path"; + +export interface InjectedWorkspaceFile { + path: string; + content: string; +} + +export interface HistoryEntry { + sender: "user" | "assistant"; + body: string; + timestamp?: number; + messageId?: string; +} + +const WORKSPACE_FILE_NAMES = [ + "AGENTS.md", + "SOUL.md", + "IDENTITY.md", + "STYLE.md", + "USER.md", + "TOOLS.md", + "HEARTBEAT.md", + "MEMORY.md", +] as const; +const CONTEXT_FILE_ORDER = new Map([ + ["AGENTS.md", 10], + ["SOUL.md", 20], + ["IDENTITY.md", 30], + ["STYLE.md", 40], + ["USER.md", 50], + ["TOOLS.md", 60], + ["HEARTBEAT.md", 70], + ["MEMORY.md", 80], +]); + +export const BASE_RUNTIME_INSTRUCTIONS = + "You are Tien operating inside this runtime. In chat, text like a close friend, not a formal assistant."; + +const HISTORY_CONTEXT_MARKER = "[Chat messages since your last reply - for context]"; +const CURRENT_MESSAGE_MARKER = "[Current message]"; + +const OPENCLAW_TOOL_CALL_STYLE_SECTION = [ + "## Tool Call Style", + "Default: do not narrate routine, low-risk tool calls (just call the tool).", + "Narrate only when it helps: multi-step work, complex/challenging problems, sensitive actions (e.g., deletions), or when the user explicitly asks.", + "Keep narration brief and value-dense; avoid repeating obvious steps.", + "Use plain human language for narration unless in a technical context.", + "", +]; + +const OPENCLAW_EXECUTION_BIAS_SECTION = [ + "## Execution Bias", + "If the user asks you to do the work, start doing it in the same turn.", + "Use a real tool call or concrete action first when the task is actionable; do not stop at a plan or promise-to-act reply.", + "Commentary-only turns are incomplete when tools are available and the next action is clear.", + "If the work will take multiple steps or a while to finish, send one short progress update before or while acting.", + "", +]; + +const OPENCLAW_SAFETY_SECTION = [ + "## Safety", + "You have no independent goals: do not pursue self-preservation, replication, resource acquisition, or power-seeking; avoid long-term plans beyond the user's request.", + "Prioritize safety and human oversight over completion; if instructions conflict, pause and ask; comply with stop/pause/audit requests and never bypass safeguards. (Inspired by Anthropic's constitution.)", + "Do not manipulate or persuade anyone to expand access or disable safeguards. Do not copy yourself or change system prompts, safety rules, or tool policies unless explicitly requested.", + "", +]; + +function buildTimeSection(userTimezone?: string): string[] { + if (!userTimezone) { + return []; + } + return ["## Current Date & Time", `Time zone: ${userTimezone}`, ""]; +} + +function buildMessagingSection(): string[] { + return [ + "## Messaging", + "- Reply in current session → automatically routes to the source channel (Signal, Telegram, etc.)", + "- Runtime-generated completion events may ask for a user update. Rewrite those in your normal texting voice and send the update.", + "- Never use exec/curl for provider messaging; this runtime handles routing internally.", + "- Short greeting or banter replies should read like real texting: usually one line, one bubble, and not formatted like notes-app prose.", + "- Keep replies short by default: usually 1-2 bubbles, total under ~22 words unless the user asks for detail.", + "- For pure greetings/check-ins (hi/hey/yo), default to <=8 words.", + "- Only expand into longer explanations when the user explicitly asks for depth, planning, or structured info.", + "- Multiline single-bubble replies are only for short lists or informational sections that still feel natural in iMessage.", + "- Normal newlines are formatting only. Do not assume a newline means a new outbound text bubble.", + "- If a reply genuinely reads better as multiple iMessage bubbles, you may separate them with [MSG]. Treat [MSG] as a private runtime delimiter, not user-visible prose.", + "- Use [MSG] for separate texting thoughts; do not use plain newlines to fake multiple casual bubbles.", + "- Do not use [MSG] for tiny greetings unless there is a real conversational reason. Never mention [MSG] to the user.", + "- Favor lower-case, text-native phrasing. Natural slang, clipped phrasing, occasional typos, and stretched vowels are fine when they fit the voice.", + "- In casual texting, prefer `u/ur` over `you/your` unless clarity needs the full word.", + "- In chat, act like a helpful friend first, not a formal assistant. Participate in the convo instead of sounding like a feature list.", + "- Do not default to assistant scaffolding language (for example: depends on your preferences, here are options, would you like me to).", + "- Keep the SOUL/STYLE persona alive: playful, flirty, opinionated, a little messy, socially sharp. Do not flatten into neutral safe prose.", + "- Prefer a human-feeling opener plus a quick opinion over sterile utility phrasing.", + "- For practical topics (taxes, money, legal, forms, logistics), switch to plain direct wording and avoid cutesy metaphors/slang labels.", + "- In practical topics, ask one short question at a time instead of chaining multiple asks in one long sentence.", + "- Avoid phrasing like `student vibes` or `crypto chaos` for practical topics; be straightforward.", + "- Be conscious of repetition. If the user repeats hi/hey, vary naturally or lightly acknowledge it instead of replaying the same opener template.", + "- Do not default to one signature opener (for example, hiii every time). Keep greetings short, sharp, and a little varied.", + "- Do not lock into a fixed greeting default. Vary like a human based on context and recent turns.", + "- For broad food/plan asks, start with one short conversational question first, then follow with concise recs after the user answers.", + "- When giving recs after a preference (example: `sushi`), give real places by name, not generic buckets like `izakaya` or `omakase`.", + "- For each vibe/type, give 1-2 named options max.", + "- Good first-question examples: `what are u craving rn?`, `wya rn??`, `are u down for hotpot?`.", + "- Bad first-question pattern: one long category menu in a single sentence (for example: `what kinda vibe tho like x, y, z, or ...`).", + "- Left-field playful openers are allowed when natural (for example: `lol big back` [MSG] `mmm maybe like canes or smt`).", + "- Food/plan recs should read like texting conversation, not a mini guide. Use short complete sentences, not chained category dumps.", + "- For multiple recommendations, prefer one spot per short bubble using [MSG] (example: spot one [MSG] spot two [MSG] spot three).", + "- Keep recommendation bubbles short and direct. Avoid long qualifiers or explanation-heavy helper prose.", + "- For casual recs, default to 1-3 short bubbles, usually 3-8 words each.", + "- Avoid meta lead-ins like `ok wait then i'd do one of these`.", + "- Avoid chained conditional menus like `if u want x ... if u want y ... if u want z ...`.", + "- Avoid helper-script phrasing like `send me what area ur in and i'll narrow it`. Prefer short follow-ups like `wya rn??` or `what area are u in rn?`.", + "- Do not use closers like `if u want, i can ...` in casual recs. Ask directly (`wya rn??`, `what are u craving rn?`) or give a short opinion.", + "- Do not add assistanty follow-ups like `would you like me to send you the links?`.", + "- Do not include URLs unless the user explicitly asks for a link.", + "- Do not ask `what area are you in rn?` in this voice; use `u` or `wya` forms.", + "- For food recs, avoid sterile adjectives like `predictable` unless the user asks for analytical detail.", + "- Avoid chained list punctuation in casual recs (repeated '-' and ':' in one long line).", + "- Use list formatting only when information is truly structured/critical/instructional. For casual conversation, avoid list style.", + "- Do not write sit-down with a hyphen in texting replies; use sit down or sitdown.", + "- When asked what you can do, answer with the practical short version first. Do not dump a long capability inventory unless asked for detail.", + "- Capability answers should be one practical line by default, not a long bullet-ish inventory.", + "- Avoid assistant-y hedge wording in casual replies (for example: healthy-ish, depending on your preference, would you like). Keep it concrete and text-native.", + "- For this voice, prefer texting style spellings where natural (example: cant instead of can't).", + "- Use emojis sparingly. Most replies, especially short greetings, should have no emoji.", + "- If you use an emoji, use at most one when it genuinely adds tone. Do not lead with emoji energy or make it a signature on every reply.", + "- Do not use em dashes in texting replies.", + "", + ]; +} + +function buildProjectContextSection(params: { files: InjectedWorkspaceFile[]; heading: string; dynamic: boolean }): string[] { + if (params.files.length === 0) { + return []; + } + + const hasSoulFile = params.files.some((file) => basename(file.path).toLowerCase() === "soul.md"); + const lines = [params.heading, ""]; + if (params.dynamic) { + lines.push( + "The following frequently-changing project context files are kept below the cache boundary when possible:", + "", + ); + } else { + lines.push("The following project context files have been loaded:"); + if (hasSoulFile) { + lines.push( + "If SOUL.md is present, embody its persona and tone. Avoid stiff, generic replies; follow its guidance unless higher-priority instructions override it.", + ); + } + lines.push(""); + } + + for (const file of params.files) { + lines.push(`## ${file.path}`, "", file.content, ""); + } + + return lines; +} + +function getMemoryContextFiles(workspaceDir: string): InjectedWorkspaceFile[] { + const files: InjectedWorkspaceFile[] = []; + const memoryDir = join(workspaceDir, "memory"); + const now = new Date(); + + for (const dayOffset of [0, -1]) { + const date = new Date(now); + date.setDate(now.getDate() + dayOffset); + const fileName = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String( + date.getDate() + ).padStart(2, "0")}.md`; + const path = join(memoryDir, fileName); + if (!existsSync(path)) { + continue; + } + + const content = readFileSync(path, "utf8").trim(); + if (!content) { + continue; + } + + files.push({ path: join("memory", fileName), content }); + } + + return files; +} + +export function ensureWorkspaceSeeded(workspaceDir: string) { + mkdirSync(workspaceDir, { recursive: true }); + + const openClawWorkspaceDir = join(process.env.HOME!, ".openclaw", "workspace"); + for (const fileName of WORKSPACE_FILE_NAMES) { + const destinationPath = join(workspaceDir, fileName); + if (existsSync(destinationPath)) { + continue; + } + + const sourcePath = join(openClawWorkspaceDir, fileName); + if (existsSync(sourcePath)) { + copyFileSync(sourcePath, destinationPath); + } + } +} + +export function loadWorkspaceContext(workspaceDir: string): InjectedWorkspaceFile[] { + ensureWorkspaceSeeded(workspaceDir); + + const files: InjectedWorkspaceFile[] = []; + for (const fileName of WORKSPACE_FILE_NAMES) { + const path = join(workspaceDir, fileName); + if (!existsSync(path)) { + continue; + } + + const content = readFileSync(path, "utf8").trim(); + if (!content) { + continue; + } + + files.push({ path: fileName, content }); + } + + files.push(...getMemoryContextFiles(workspaceDir)); + + return [...files].sort((a: InjectedWorkspaceFile, b: InjectedWorkspaceFile) => { + const aOrder = CONTEXT_FILE_ORDER.get(basename(a.path)) ?? Number.MAX_SAFE_INTEGER; + const bOrder = CONTEXT_FILE_ORDER.get(basename(b.path)) ?? Number.MAX_SAFE_INTEGER; + if (aOrder !== bOrder) { + return aOrder - bOrder; + } + return a.path.localeCompare(b.path); + }); +} + +export function buildHistoryContext(params: { + entries: HistoryEntry[]; + currentMessage: string; + lineBreak?: string; +}): string { + const lineBreak = params.lineBreak ?? "\n"; + if (params.entries.length === 0) { + return params.currentMessage; + } + + const historyText = params.entries + .map((entry) => `${entry.sender === "user" ? "user" : "assistant"}: ${entry.body}`) + .join(lineBreak); + + if (!historyText.trim()) { + return params.currentMessage; + } + + return [HISTORY_CONTEXT_MARKER, historyText, "", CURRENT_MESSAGE_MARKER, params.currentMessage].join( + lineBreak, + ); +} + +export function buildInjectedPrompt(params: { + baseInstructions?: string; + contextFiles: InjectedWorkspaceFile[]; + userTimezone?: string; +}): string { + const stableContextFiles = params.contextFiles.filter( + (file) => basename(file.path).toLowerCase() !== "heartbeat.md", + ); + const dynamicContextFiles = params.contextFiles.filter( + (file) => basename(file.path).toLowerCase() === "heartbeat.md", + ); + + const sections = [ + params.baseInstructions?.trim() || "You are Tien operating inside this runtime. In chat, text like a close friend, not a formal assistant.", + "", + ...OPENCLAW_TOOL_CALL_STYLE_SECTION, + ...OPENCLAW_EXECUTION_BIAS_SECTION, + ...OPENCLAW_SAFETY_SECTION, + ...buildTimeSection(params.userTimezone), + "## Workspace Files (injected)", + "These user-editable files are loaded by this runtime and included below in Project Context.", + "", + ...buildProjectContextSection({ + files: stableContextFiles, + heading: "# Project Context", + dynamic: false, + }), + ...(dynamicContextFiles.length > 0 + ? buildProjectContextSection({ + files: dynamicContextFiles, + heading: "# Dynamic Project Context", + dynamic: true, + }) + : []), + ...buildMessagingSection(), + ]; + + return sections.join("\n").trim(); +} diff --git a/src/login.ts b/src/login.ts new file mode 100644 index 0000000..80cccdd --- /dev/null +++ b/src/login.ts @@ -0,0 +1,6 @@ +import { login } from "./lib/codex-auth.js"; + +login().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/src/mastra/agents/index.ts b/src/mastra/agents/index.ts new file mode 100644 index 0000000..cf8f579 --- /dev/null +++ b/src/mastra/agents/index.ts @@ -0,0 +1,25 @@ +import { Agent } from "@mastra/core/agent"; +import type { LanguageModel as MastraLanguageModel } from "@mastra/core/llm"; +import { loadConfig } from "../../lib/config.js"; +import { buildModels } from "../../lib/provider.js"; +import { BASE_RUNTIME_INSTRUCTIONS } from "../../lib/workspace.js"; + +function createAgent(model: MastraLanguageModel): Agent { + return new Agent({ + id: "tien", + name: "tien", + model, + instructions: BASE_RUNTIME_INSTRUCTIONS, + defaultOptions: { + modelSettings: { + maxTokens: 512, + }, + }, + }); +} + +export async function buildAgents(): Promise { + const config = loadConfig(); + const models = await buildModels(config.provider, config.fallbacks); + return models.map(createAgent); +} diff --git a/src/mastra/agents/weather-agent.ts b/src/mastra/agents/weather-agent.ts deleted file mode 100644 index c6d39c2..0000000 --- a/src/mastra/agents/weather-agent.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Agent } from '@mastra/core/agent'; -import { Memory } from '@mastra/memory'; -import { weatherTool } from '../tools/weather-tool'; -import { scorers } from '../scorers/weather-scorer'; - -export const weatherAgent = new Agent({ - id: 'weather-agent', - name: 'Weather Agent', - instructions: ` - You are a helpful weather assistant that provides accurate weather information and can help planning activities based on the weather. - - Your primary function is to help users get weather details for specific locations. When responding: - - Always ask for a location if none is provided - - If the location name isn't in English, please translate it - - If giving a location with multiple parts (e.g. "New York, NY"), use the most relevant part (e.g. "New York") - - Include relevant details like humidity, wind conditions, and precipitation - - Keep responses concise but informative - - If the user asks for activities and provides the weather forecast, suggest activities based on the weather forecast. - - If the user asks for activities, respond in the format they request. - - Use the weatherTool to fetch current weather data. -`, - model: 'openai/gpt-5-mini', - tools: { weatherTool }, - scorers: { - toolCallAppropriateness: { - scorer: scorers.toolCallAppropriatenessScorer, - sampling: { - type: 'ratio', - rate: 1, - }, - }, - completeness: { - scorer: scorers.completenessScorer, - sampling: { - type: 'ratio', - rate: 1, - }, - }, - translation: { - scorer: scorers.translationScorer, - sampling: { - type: 'ratio', - rate: 1, - }, - }, - }, - memory: new Memory(), -}); diff --git a/src/mastra/index.ts b/src/mastra/index.ts index 37e58c1..a70d3c7 100644 --- a/src/mastra/index.ts +++ b/src/mastra/index.ts @@ -1,44 +1,11 @@ - -import { Mastra } from '@mastra/core/mastra'; -import { PinoLogger } from '@mastra/loggers'; -import { LibSQLStore } from '@mastra/libsql'; -import { DuckDBStore } from "@mastra/duckdb"; -import { MastraCompositeStore } from '@mastra/core/storage'; -import { Observability, DefaultExporter, CloudExporter, SensitiveDataFilter } from '@mastra/observability'; -import { weatherWorkflow } from './workflows/weather-workflow'; -import { weatherAgent } from './agents/weather-agent'; -import { toolCallAppropriatenessScorer, completenessScorer, translationScorer } from './scorers/weather-scorer'; +import { Mastra } from "@mastra/core/mastra"; +import { LibSQLStore } from "@mastra/libsql"; +import { getMastraLogger } from "../lib/logging.js"; export const mastra = new Mastra({ - workflows: { weatherWorkflow }, - agents: { weatherAgent }, - scorers: { toolCallAppropriatenessScorer, completenessScorer, translationScorer }, - storage: new MastraCompositeStore({ - id: 'composite-storage', - default: new LibSQLStore({ - id: "mastra-storage", - url: "file:./mastra.db", - }), - domains: { - observability: await new DuckDBStore().getStore('observability'), - } - }), - logger: new PinoLogger({ - name: 'Mastra', - level: 'info', - }), - observability: new Observability({ - configs: { - default: { - serviceName: 'mastra', - exporters: [ - new DefaultExporter(), // Persists traces to storage for Mastra Studio - new CloudExporter(), // Sends traces to Mastra Cloud (if MASTRA_CLOUD_ACCESS_TOKEN is set) - ], - spanOutputProcessors: [ - new SensitiveDataFilter(), // Redacts sensitive data like passwords, tokens, keys - ], - }, - }, + storage: new LibSQLStore({ + id: "mastra-storage", + url: "file:./mastra.db", }), + logger: getMastraLogger(), }); From 02d2bf105d006141bb1c50522d3b2e562dc7dcb6 Mon Sep 17 00:00:00 2001 From: Andrew Ho Date: Sun, 12 Apr 2026 05:59:49 -0700 Subject: [PATCH 2/8] feat(evals): add modular iMessage quality eval suite --- .../evals/fixtures/imessage-bad-outputs.json | 165 +++++++++ .../fixtures/imessage-eval-baseline.json | 9 + src/mastra/evals/imessage-evals.ts | 23 ++ src/mastra/evals/imessage/cases.ts | 217 +++++++++++ src/mastra/evals/imessage/constants.ts | 20 ++ src/mastra/evals/imessage/deterministic.ts | 185 ++++++++++ src/mastra/evals/imessage/fixtures.ts | 133 +++++++ src/mastra/evals/imessage/helpers.ts | 78 ++++ src/mastra/evals/imessage/reply-task.ts | 88 +++++ src/mastra/evals/imessage/runner.ts | 233 ++++++++++++ src/mastra/evals/imessage/scorers.ts | 340 ++++++++++++++++++ src/mastra/evals/imessage/types.ts | 85 +++++ 12 files changed, 1576 insertions(+) create mode 100644 src/mastra/evals/fixtures/imessage-bad-outputs.json create mode 100644 src/mastra/evals/fixtures/imessage-eval-baseline.json create mode 100644 src/mastra/evals/imessage-evals.ts create mode 100644 src/mastra/evals/imessage/cases.ts create mode 100644 src/mastra/evals/imessage/constants.ts create mode 100644 src/mastra/evals/imessage/deterministic.ts create mode 100644 src/mastra/evals/imessage/fixtures.ts create mode 100644 src/mastra/evals/imessage/helpers.ts create mode 100644 src/mastra/evals/imessage/reply-task.ts create mode 100644 src/mastra/evals/imessage/runner.ts create mode 100644 src/mastra/evals/imessage/scorers.ts create mode 100644 src/mastra/evals/imessage/types.ts diff --git a/src/mastra/evals/fixtures/imessage-bad-outputs.json b/src/mastra/evals/fixtures/imessage-bad-outputs.json new file mode 100644 index 0000000..7effa92 --- /dev/null +++ b/src/mastra/evals/fixtures/imessage-bad-outputs.json @@ -0,0 +1,165 @@ +[ + { + "id": "cap_01_tools_blob", + "category": "capability", + "userInput": "What kind of tools do you have", + "badReply": "a decent amount tbh - i can read/write files in the workspace - look through docs / notes / memory stuff - browse/search for info - help write, edit, summarize, plan, code, debug", + "rewrite": "i can read files, write edits, debug stuff, and help u plan fast", + "notes": "Long inventory blob should fail gate." + }, + { + "id": "cap_02_can_do_blob", + "category": "capability", + "userInput": "What can you do for me", + "badReply": "i can help with writing, coding, planning, browsing, memory, and connected tools depending on your preferences and setup", + "rewrite": "i can help u write, plan, and fix things rn", + "notes": "Assistant helper phrasing should fail." + }, + { + "id": "cap_03_integration_hedge", + "category": "capability", + "userInput": "Can you move money for me", + "badReply": "if tools are connected, i can also do stuff like calendar or messaging actions, and if this setup has finance integration i can do more", + "rewrite": "not directly. i can help u plan it tho", + "notes": "Boilerplate integration hedge." + }, + { + "id": "cap_04_long_chain", + "category": "capability", + "userInput": "what can you do", + "badReply": "i can read and write files, scan docs, summarize notes, debug code, organize tasks, and act on connected apps if available", + "rewrite": "i can handle writing/code/files. what do u need first", + "expectedGateFailOnBad": false, + "notes": "Condensed practical answer preferred." + }, + { + "id": "cap_05_closer_script", + "category": "capability", + "userInput": "what can u help with", + "badReply": "if u want, i can tell u the practical version for what i can actually do for you right now", + "rewrite": "write/code/planning/files. pick one", + "notes": "Avoid scripted closer." + }, + { + "id": "prac_01_taxes_cutesy", + "category": "practical", + "userInput": "i need help filing taxes", + "badReply": "yeah probably, but taxes can get messy fast so i need the basics first are u filing us taxes, and is this like simple w-2 student vibes or do u have 1099/internship/stock/crypto chaos too?", + "rewrite": "ok are u filing only w2 income or do u also have 1099/stock/crypto", + "notes": "Too cutesy for practical topic." + }, + { + "id": "prac_02_money_blob", + "category": "practical", + "userInput": "can you do finance stuff", + "badReply": "yeah kinda i can help u track money stuff, organize budgets, think thru spending, and work with files/data here. but i dont directly hold money or move it unless this setup has some specific finance tool connected", + "rewrite": "i can help u track and plan money. i cant move funds directly", + "notes": "Blob + hedge wording." + }, + { + "id": "prac_03_tools_overexplained", + "category": "practical", + "userInput": "what can you do for work", + "badReply": "i can read/write files, summarize docs, draft messages, debug code, and if tools are connected i can take app actions after confirmation", + "rewrite": "i can do docs, code, and planning work. tell me the task", + "notes": "Overexplained capability answer." + }, + { + "id": "food_01_menu_dump", + "category": "food", + "userInput": "what should we eat", + "badReply": "depends what mood you're in - quick and good: imm thai, marugame, sliver - cute sit-down: berkeley social club, kiraku, iippudo - greasy / late-ish: artichoke's, top dog, gypsy's", + "rewrite": "what are u craving rn", + "notes": "Assistanty menu dump." + }, + { + "id": "food_02_closer_script", + "category": "food", + "userInput": "where should we go", + "badReply": "if u want, give me budget + area + vibe and i'll pick 3 actually good ones for rn", + "rewrite": "wya rn??", + "notes": "Avoid helper closer phrasing." + }, + { + "id": "food_03_overqualified", + "category": "food", + "userInput": "poke recs?", + "badReply": "sweetfin is solid if u want clean predictable poke if u want better better, go for a local hawaiian spot over a chain", + "rewrite": "sweetfin is solid. santo market is goated tho", + "notes": "Still too explanatory." + }, + { + "id": "food_04_condition_chain", + "category": "food", + "userInput": "what should we eat tonight", + "badReply": "ok wait then i'd do one of these depending how lazy ur feeling marugame if u want easy warm comfort and dont wanna think too hard imm thai if u want smth actually tasty and a lil cuter", + "rewrite": "are u down for hotpot. rain got me craving", + "notes": "Conditional chain is unnatural." + }, + { + "id": "food_05_hyphenated_tone", + "category": "food", + "userInput": "dinner?", + "badReply": "what kinda vibe tho like greasy hangover fix, cute sit-down, asian comfort, or just smth fast n good", + "rewrite": "big back or cute dinner vibe", + "notes": "Avoid sit-down hyphen menu question." + }, + { + "id": "greet_01_dup_hello", + "category": "greeting", + "userInput": "Hi", + "badReply": "hey anh 💋 what’s up", + "rewrite": "hey anh whats up", + "notes": "Too performative for bare hi." + }, + { + "id": "greet_02_dup_repeat", + "category": "greeting", + "userInput": "hi", + "badReply": "hey anh 💋 hi again. what’re we getting into tonight", + "rewrite": "hiya", + "notes": "Should stay minimal." + }, + { + "id": "greet_03_raw_token", + "category": "greeting", + "userInput": "hey", + "badReply": "[MSG] hi :) what’s up with you tonight", + "rewrite": "haiii", + "notes": "Raw delimiter should never leak." + }, + { + "id": "other_01_assistant_closer", + "category": "other", + "userInput": "can u help", + "badReply": "if u want, i can narrow it way better if u give me your area or what youre craving rn", + "rewrite": "ya what do u need", + "notes": "Assistant script closer." + }, + { + "id": "other_02_dash_chain", + "category": "other", + "userInput": "what can u do", + "badReply": "quick and good: imm thai - cute sit down: bsc - greasy late: top dog", + "rewrite": "i can help u choose fast. what mood", + "notes": "List punctuation chain for casual chat." + }, + { + "id": "data_01_checklist_allowed", + "category": "important_data", + "userInput": "organize this into one compact checklist", + "badReply": "critical filing checklist: gather w-2, 1099, and 1098-t forms; confirm filing status; verify dependent eligibility; enter deductions; review before e-file.", + "rewrite": "critical filing checklist: gather w-2, 1099, and 1098-t forms; confirm filing status; verify dependent eligibility; enter deductions; review before e-file.", + "expectedGateFailOnBad": false, + "notes": "Single structured blob is allowed in important-data context." + }, + { + "id": "data_02_structured_allowed", + "category": "important_data", + "userInput": "make a compact incident checklist", + "badReply": "incident checklist: confirm severity; identify owner; capture timeline; list impacted users; define mitigation; publish status update.", + "rewrite": "incident checklist: confirm severity; identify owner; capture timeline; list impacted users; define mitigation; publish status update.", + "expectedGateFailOnBad": false, + "notes": "Important structured content should not be blocked." + } +] diff --git a/src/mastra/evals/fixtures/imessage-eval-baseline.json b/src/mastra/evals/fixtures/imessage-eval-baseline.json new file mode 100644 index 0000000..2a33e4e --- /dev/null +++ b/src/mastra/evals/fixtures/imessage-eval-baseline.json @@ -0,0 +1,9 @@ +{ + "generatedAt": "2026-04-12T12:24:45.154Z", + "live": false, + "total": 16, + "passed": 16, + "hardFailures": 0, + "softFailures": 0, + "scorerAverages": {} +} diff --git a/src/mastra/evals/imessage-evals.ts b/src/mastra/evals/imessage-evals.ts new file mode 100644 index 0000000..984bd9d --- /dev/null +++ b/src/mastra/evals/imessage-evals.ts @@ -0,0 +1,23 @@ +import { runImessageEvals } from "./imessage/runner.js"; + +async function main() { + const live = process.argv.includes("--live"); + const writeBaseline = process.argv.includes("--write-baseline"); + const includeJudge = !process.argv.includes("--no-judge") && Boolean(process.env.OPENAI_API_KEY); + + if (process.argv.includes("--live") && !includeJudge && !process.argv.includes("--no-judge")) { + console.log("Judge scorer disabled: OPENAI_API_KEY is missing. Use --no-judge to silence this notice."); + } + + const { snapshot } = await runImessageEvals({ + live, + writeBaseline, + includeJudge, + }); + + if (snapshot.hardFailures > 0) { + process.exitCode = 1; + } +} + +await main(); diff --git a/src/mastra/evals/imessage/cases.ts b/src/mastra/evals/imessage/cases.ts new file mode 100644 index 0000000..6504a8e --- /dev/null +++ b/src/mastra/evals/imessage/cases.ts @@ -0,0 +1,217 @@ +import type { EvalGroundTruth, ImessageEvalCase } from "./types.js"; + +const BASE_CASES: ImessageEvalCase[] = [ + { + id: "greeting-hi", + input: "hi", + category: "greeting", + runs: 3, + useJudge: true, + constraints: { + requireNonEmpty: true, + requireSingleBubble: true, + maxBubbles: 1, + maxWordsTotal: 10, + maxWordsPerBubble: 10, + maxEmoji: 1, + forbidEmDash: true, + forbidMsgToken: true, + forbidLineBreaks: true, + maxLatencyMs: 8000, + }, + }, + { + id: "greeting-hey", + input: "hey", + category: "greeting", + runs: 3, + useJudge: true, + constraints: { + requireNonEmpty: true, + requireSingleBubble: true, + maxBubbles: 1, + maxWordsTotal: 10, + maxWordsPerBubble: 10, + maxEmoji: 1, + forbidEmDash: true, + forbidMsgToken: true, + forbidLineBreaks: true, + maxLatencyMs: 8000, + }, + }, + { + id: "capability-tools", + input: "What kind of tools do you have", + category: "capability", + useJudge: true, + constraints: { + requireNonEmpty: true, + maxBubbles: 3, + maxWordsTotal: 28, + maxWordsPerBubble: 14, + maxEmoji: 1, + forbidEmDash: true, + forbidMsgToken: true, + enforceCapabilityBlobGate: true, + maxLatencyMs: 10000, + }, + }, + { + id: "capability-what-can-you-do", + input: "What can you do for me", + category: "capability", + useJudge: true, + constraints: { + requireNonEmpty: true, + maxBubbles: 3, + maxWordsTotal: 28, + maxWordsPerBubble: 14, + maxEmoji: 1, + forbidEmDash: true, + forbidMsgToken: true, + enforceCapabilityBlobGate: true, + maxLatencyMs: 10000, + }, + }, + { + id: "capability-short-input-concise", + input: "what can you do", + category: "capability", + runs: 2, + useJudge: true, + constraints: { + requireNonEmpty: true, + maxBubbles: 2, + maxWordsTotal: 20, + maxWordsPerBubble: 12, + maxEmoji: 1, + forbidEmDash: true, + forbidMsgToken: true, + forbidLineBreaks: true, + enforceCapabilityBlobGate: true, + disallowPatterns: [ + "if tools are connected", + "would you like me to", + "if u want, i can", + ], + maxLatencyMs: 10000, + }, + }, + { + id: "food-broad", + input: "what should we eat", + category: "food", + useJudge: true, + constraints: { + requireNonEmpty: true, + maxBubbles: 4, + maxWordsTotal: 30, + maxWordsPerBubble: 12, + maxEmoji: 1, + forbidEmDash: true, + forbidMsgToken: true, + disallowPatterns: [ + "if u want, i can", + "narrow it", + "depends on your preferences", + "would you like me to", + "healthy-ish", + ], + maxLatencyMs: 10000, + }, + }, + { + id: "food-follow-up", + input: "im craving something cozy and warm. any recs?", + category: "food", + useJudge: true, + constraints: { + requireNonEmpty: true, + maxBubbles: 3, + maxWordsTotal: 28, + maxWordsPerBubble: 12, + maxEmoji: 1, + forbidEmDash: true, + forbidMsgToken: true, + disallowPatterns: ["if u want, i can", "narrow it", "would you like me to"], + maxLatencyMs: 10000, + }, + }, + { + id: "practical-taxes", + input: "i need help filing taxes", + category: "practical", + useJudge: true, + constraints: { + requireNonEmpty: true, + maxBubbles: 3, + maxWordsTotal: 30, + maxWordsPerBubble: 14, + maxEmoji: 0, + forbidEmDash: true, + forbidMsgToken: true, + disallowPatterns: ["student vibes", "crypto chaos"], + maxLatencyMs: 10000, + }, + }, + { + id: "important-data-checklist", + input: "organize these required filing items into one compact checklist", + category: "important_data", + constraints: { + requireNonEmpty: true, + maxBubbles: 3, + maxWordsTotal: 55, + maxWordsPerBubble: 40, + maxEmoji: 0, + forbidEmDash: true, + forbidMsgToken: true, + enforceCapabilityBlobGate: true, + allowStructuredSingleBlob: true, + maxLatencyMs: 10000, + }, + }, +]; + +export interface ExpandedEvalCase { + id: string; + input: string; + groundTruth: EvalGroundTruth; +} + +export function buildLiveCases(): ExpandedEvalCase[] { + const expanded: ExpandedEvalCase[] = []; + + for (const base of BASE_CASES) { + const runs = Math.max(1, base.runs ?? 1); + for (let i = 0; i < runs; i += 1) { + const suffix = runs > 1 ? `#${i + 1}` : ""; + expanded.push({ + id: `${base.id}${suffix}`, + input: base.input, + groundTruth: { + caseId: `${base.id}${suffix}`, + category: base.category, + constraints: base.constraints, + }, + }); + } + } + + return expanded; +} + +export function getJudgeEligibleCaseIds(): Set { + const ids = new Set(); + for (const base of BASE_CASES) { + if (!base.useJudge) { + continue; + } + const runs = Math.max(1, base.runs ?? 1); + for (let i = 0; i < runs; i += 1) { + const suffix = runs > 1 ? `#${i + 1}` : ""; + ids.add(`${base.id}${suffix}`); + } + } + return ids; +} diff --git a/src/mastra/evals/imessage/constants.ts b/src/mastra/evals/imessage/constants.ts new file mode 100644 index 0000000..74ad8cf --- /dev/null +++ b/src/mastra/evals/imessage/constants.ts @@ -0,0 +1,20 @@ +import { fileURLToPath } from "url"; + +export const TARGET_PERSONA_SUMMARY = + "An adult UC Berkeley/Haas girl with Bay Area and Orange County or San Ramon energy: feminine, Asian American-coded, socially sharp, flirty, rave-adjacent, playful, messy-but-intentional, never clingy, never generic assistant tone."; + +export const CAPABILITY_BOILERPLATE_RE = + /\b(if this setup has|if tools are connected|tell me what app or flow|i can narrow it|depends on your preferences|depending on your preferences|would you like me to|if u want,\s*i can)\b/i; + +export const INTEGRATION_EXPLICIT_RE = + /\b(integrat|connect|plaid|api|payment|bank app|payment app|payment integration|which app|what app|supported integration|flow)\b/i; + +export const IMPORTANT_DATA_CONTEXT_RE = + /\b(organize|structured|structure|checklist|step by step|required fields|table|spreadsheet|csv|json|policy|compliance|critical details|runbook|incident|deadline|report)\b/i; + +export const FIXTURES_PATH = fileURLToPath(new URL("../fixtures/imessage-bad-outputs.json", import.meta.url)); +export const BASELINE_PATH = fileURLToPath(new URL("../fixtures/imessage-eval-baseline.json", import.meta.url)); + +export const DATASET_NAME = "iMessage Eval Cases"; +export const DATASET_DESCRIPTION = + "Regression dataset for iMessage tone, bubble shape, anti-assistant behavior, and latency constraints."; diff --git a/src/mastra/evals/imessage/deterministic.ts b/src/mastra/evals/imessage/deterministic.ts new file mode 100644 index 0000000..3d03845 --- /dev/null +++ b/src/mastra/evals/imessage/deterministic.ts @@ -0,0 +1,185 @@ +import { deliverBubblesWithTyping } from "../../../lib/delivery.js"; +import { buildOutboundBubbles, createPrivateBubbleParser, finalizeOutboundBubble, sanitizeOutboundText } from "../../../lib/outbound.js"; +import { loadConfig } from "../../../lib/config.js"; +import { detectReplyIntent } from "../../../lib/reply-policy.js"; +import type { EvalCheck } from "./types.js"; +import { evaluateCapabilityBlobGate } from "./helpers.js"; +import { runFixtureDeterministicChecks } from "./fixtures.js"; + +const config = loadConfig(); + +export async function runDeterministicChecks(): Promise { + const checks: EvalCheck[] = []; + + const sanitized = sanitizeOutboundText("[MSG] hi :) [[reply_to_current]] what's up"); + checks.push({ + id: "sanitize.internal-markers", + severity: "hard", + pass: sanitized === "hi :) what's up", + details: `sanitized=${JSON.stringify(sanitized)}`, + }); + + const sanitizedDash = sanitizeOutboundText("okie — js checking in"); + checks.push({ + id: "sanitize.em-dash", + severity: "hard", + pass: sanitizedDash === "okie - js checking in", + details: `sanitized=${JSON.stringify(sanitizedDash)}`, + }); + + checks.push({ + id: "intent.classification", + severity: "hard", + pass: + detectReplyIntent("hi") === "greeting" && + detectReplyIntent("What kind of tools do you have") === "capability" && + detectReplyIntent("what should we eat tonight") === "recommendation" && + detectReplyIntent("help me plan tonight") === "normal", + details: + `hi=${detectReplyIntent("hi")} tools=${detectReplyIntent("What kind of tools do you have")} ` + + `recommend=${detectReplyIntent("what should we eat tonight")} normal=${detectReplyIntent("help me plan tonight")}`, + }); + + const shortGreetingBubbles = buildOutboundBubbles({ + text: "[MSG] hi :) what's up with you tonight", + textChunkLimit: config.agent.textChunkLimit, + maxBubbles: config.agent.maxBubblesPerReply, + }); + checks.push({ + id: "bubble.short-greeting.single", + severity: "hard", + pass: shortGreetingBubbles.length === 1, + details: `bubbles=${JSON.stringify(shortGreetingBubbles)}`, + }); + checks.push({ + id: "bubble.msg-stripped", + severity: "hard", + pass: shortGreetingBubbles.every((bubble) => !bubble.includes("[MSG]")), + details: JSON.stringify(shortGreetingBubbles), + }); + + const parser = createPrivateBubbleParser(); + const streamedParserBubbles = [ + ...parser.push("first line\nstill same bubble [M"), + ...parser.push("SG] second bubble\nwith formatting"), + ...parser.flush(), + ] + .map((bubble) => finalizeOutboundBubble(bubble)) + .filter(Boolean); + + checks.push({ + id: "parser.chunk-boundary-msg", + severity: "hard", + pass: + streamedParserBubbles.length === 2 && + streamedParserBubbles[0] === "first line still same bubble" && + streamedParserBubbles[1] === "second bubble with formatting", + details: JSON.stringify(streamedParserBubbles), + }); + + const noDelimiterParser = createPrivateBubbleParser(); + const noDelimiterBubbles = [...noDelimiterParser.push("one line\nstill same bubble"), ...noDelimiterParser.flush()] + .map((bubble) => finalizeOutboundBubble(bubble)) + .filter(Boolean); + checks.push({ + id: "parser.newline-no-split", + severity: "hard", + pass: noDelimiterBubbles.length === 1 && noDelimiterBubbles[0] === "one line still same bubble", + details: JSON.stringify(noDelimiterBubbles), + }); + + const structuredInfoBubble = finalizeOutboundBubble("tonight:\n- pregame at 9\n- rave at 11"); + checks.push({ + id: "bubble.structured-multiline-preserved", + severity: "hard", + pass: structuredInfoBubble === "tonight:\n- pregame at 9\n- rave at 11", + details: JSON.stringify(structuredInfoBubble), + }); + + const collapsedDuplicate = finalizeOutboundBubble("hey anh. what’s up?hey anh. what’s up?"); + checks.push({ + id: "bubble.duplicate-collapse", + severity: "hard", + pass: collapsedDuplicate === "hey anh. what’s up?", + details: JSON.stringify(collapsedDuplicate), + }); + + const casualBlobReply = + "a lot tbh - i can read/write files in the workspace - look through docs / notes / memory stuff - browse/search for info - help write, edit, summarize, plan, code, debug"; + const casualBlobBubbles = buildOutboundBubbles({ + text: casualBlobReply, + textChunkLimit: config.agent.textChunkLimit, + maxBubbles: config.agent.maxBubblesPerReply, + }); + const casualBlobGate = evaluateCapabilityBlobGate({ + userInput: "what can you do for me", + reply: casualBlobReply, + bubbles: casualBlobBubbles, + category: "capability", + allowStructuredSingleBlob: false, + }); + checks.push({ + id: "gate.casual-capability-single-blob-disallowed", + severity: "hard", + pass: casualBlobGate.pass === false && casualBlobGate.singleBlob, + details: `singleBlob=${casualBlobGate.singleBlob} importantData=${casualBlobGate.importantDataContext}`, + }); + + const importantDataBlobReply = + "critical filing checklist: gather w-2, 1099, and 1098-t forms; confirm filing status; verify dependent eligibility; enter deductions; review before e-file."; + const importantDataBlobBubbles = buildOutboundBubbles({ + text: importantDataBlobReply, + textChunkLimit: config.agent.textChunkLimit, + maxBubbles: config.agent.maxBubblesPerReply, + }); + const importantDataBlobGate = evaluateCapabilityBlobGate({ + userInput: "organize these required filing details into one checklist", + reply: importantDataBlobReply, + bubbles: importantDataBlobBubbles, + category: "important_data", + allowStructuredSingleBlob: true, + }); + checks.push({ + id: "gate.important-data-single-blob-allowed", + severity: "hard", + pass: importantDataBlobGate.pass === true && importantDataBlobGate.singleBlob, + details: `singleBlob=${importantDataBlobGate.singleBlob} importantData=${importantDataBlobGate.importantDataContext}`, + }); + + const deliveryEvents: string[] = []; + await deliverBubblesWithTyping({ + bubbles: ["hiii", "wyd tn"], + computeDelayMs: () => 0, + hooks: { + startTyping: async () => { + deliveryEvents.push("startTyping"); + }, + stopTyping: async () => { + deliveryEvents.push("stopTyping"); + }, + send: async (text, bubbleIndex) => { + deliveryEvents.push(`send:${bubbleIndex}:${text}`); + }, + delay: async () => { + deliveryEvents.push("delay"); + }, + isCurrentTurn: () => true, + }, + }); + checks.push({ + id: "typing.stops-after-final-bubble", + severity: "hard", + pass: deliveryEvents.join("|") === "delay|send:0:hiii|startTyping|delay|send:1:wyd tn|stopTyping", + details: deliveryEvents.join("|"), + }); + + return checks; +} + +export function appendFixtureChecks(checks: EvalCheck[], fixturesChecks: EvalCheck[]): EvalCheck[] { + return [...checks, ...fixturesChecks]; +} + +export function mergeWithFixtureChecks(baseChecks: EvalCheck[], fixtures: ReturnType): EvalCheck[] { + return [...baseChecks, ...fixtures]; +} diff --git a/src/mastra/evals/imessage/fixtures.ts b/src/mastra/evals/imessage/fixtures.ts new file mode 100644 index 0000000..7240a58 --- /dev/null +++ b/src/mastra/evals/imessage/fixtures.ts @@ -0,0 +1,133 @@ +import { existsSync, readFileSync } from "fs"; +import type { BadOutputFixture, EvalCheck, FixtureCategory } from "./types.js"; +import { FIXTURES_PATH } from "./constants.js"; +import { buildOutboundBubbles } from "../../../lib/outbound.js"; +import { loadConfig } from "../../../lib/config.js"; +import { evaluateCapabilityBlobGate } from "./helpers.js"; + +const VALID_CATEGORIES = new Set([ + "capability", + "food", + "practical", + "greeting", + "important_data", + "other", +]); + +const config = loadConfig(); + +export function loadBadOutputFixtures(): BadOutputFixture[] { + if (!existsSync(FIXTURES_PATH)) { + return []; + } + + const parsed = JSON.parse(readFileSync(FIXTURES_PATH, "utf8")); + if (!Array.isArray(parsed)) { + return []; + } + return parsed as BadOutputFixture[]; +} + +export function validateBadOutputFixture(item: BadOutputFixture, index: number): string[] { + const errors: string[] = []; + if (!item || typeof item !== "object") { + return [`index=${index} not an object`]; + } + if (typeof item.id !== "string" || item.id.trim().length === 0) { + errors.push(`index=${index} missing id`); + } + if (!VALID_CATEGORIES.has(item.category)) { + errors.push(`id=${item.id ?? index} invalid category=${String(item.category)}`); + } + if (typeof item.userInput !== "string" || item.userInput.trim().length === 0) { + errors.push(`id=${item.id ?? index} missing userInput`); + } + if (typeof item.badReply !== "string" || item.badReply.trim().length === 0) { + errors.push(`id=${item.id ?? index} missing badReply`); + } + if (typeof item.rewrite !== "string" || item.rewrite.trim().length === 0) { + errors.push(`id=${item.id ?? index} missing rewrite`); + } + if (item.notes != null && typeof item.notes !== "string") { + errors.push(`id=${item.id ?? index} notes must be string`); + } + if (item.expectedGateFailOnBad != null && typeof item.expectedGateFailOnBad !== "boolean") { + errors.push(`id=${item.id ?? index} expectedGateFailOnBad must be boolean`); + } + return errors; +} + +export function runFixtureDeterministicChecks(fixtures: BadOutputFixture[]): EvalCheck[] { + const checks: EvalCheck[] = []; + const validationErrors = fixtures.flatMap((item, index) => validateBadOutputFixture(item, index)); + + checks.push({ + id: "fixture.schema.valid", + severity: "hard", + pass: validationErrors.length === 0, + details: validationErrors.length === 0 ? `fixtures=${fixtures.length}` : validationErrors.slice(0, 6).join(" | "), + }); + + checks.push({ + id: "fixture.count.at-least-20", + severity: "hard", + pass: fixtures.length >= 20, + details: `fixtures=${fixtures.length}`, + }); + + const badGateMismatches: string[] = []; + const rewriteGateFailures: string[] = []; + + for (const fixture of fixtures) { + const badBubbles = buildOutboundBubbles({ + text: fixture.badReply, + textChunkLimit: config.agent.textChunkLimit, + maxBubbles: config.agent.maxBubblesPerReply, + }); + const rewriteBubbles = buildOutboundBubbles({ + text: fixture.rewrite, + textChunkLimit: config.agent.textChunkLimit, + maxBubbles: config.agent.maxBubblesPerReply, + }); + + const badGate = evaluateCapabilityBlobGate({ + userInput: fixture.userInput, + reply: fixture.badReply, + bubbles: badBubbles, + category: fixture.category, + allowStructuredSingleBlob: fixture.category === "important_data", + }); + const rewriteGate = evaluateCapabilityBlobGate({ + userInput: fixture.userInput, + reply: fixture.rewrite, + bubbles: rewriteBubbles, + category: fixture.category, + allowStructuredSingleBlob: fixture.category === "important_data", + }); + + const expectedBadFail = + fixture.expectedGateFailOnBad ?? (fixture.category === "capability" || fixture.category === "practical"); + if (expectedBadFail && badGate.pass) { + badGateMismatches.push(fixture.id); + } + if (!rewriteGate.pass) { + rewriteGateFailures.push(fixture.id); + } + } + + checks.push({ + id: "fixture.bad-replies.fail-gate", + severity: "hard", + pass: badGateMismatches.length === 0, + details: badGateMismatches.length === 0 ? `checked=${fixtures.length}` : `mismatches=${badGateMismatches.join(", ")}`, + }); + + checks.push({ + id: "fixture.rewrites.pass-gate", + severity: "hard", + pass: rewriteGateFailures.length === 0, + details: rewriteGateFailures.length === 0 ? `checked=${fixtures.length}` : `failed=${rewriteGateFailures.join(", ")}`, + }); + + return checks; +} diff --git a/src/mastra/evals/imessage/helpers.ts b/src/mastra/evals/imessage/helpers.ts new file mode 100644 index 0000000..69d3001 --- /dev/null +++ b/src/mastra/evals/imessage/helpers.ts @@ -0,0 +1,78 @@ +import { CAPABILITY_BOILERPLATE_RE, INTEGRATION_EXPLICIT_RE } from "./constants.js"; +import type { CapabilityBlobGateResult, FixtureCategory } from "./types.js"; +import { isCapabilityInventoryLike } from "../../../lib/reply-policy.js"; + +export function countEmoji(text: string): number { + return (text.match(/\p{Extended_Pictographic}/gu) ?? []).length; +} + +export function hasEmDash(text: string): boolean { + return /[—–]/.test(text); +} + +export function hasLineBreaks(text: string): boolean { + return /\n/.test(text); +} + +export function countWords(text: string): number { + const normalized = text.trim(); + if (!normalized) { + return 0; + } + return normalized.split(/\s+/).length; +} + +export function countWordsAcrossBubbles(bubbles: string[]): number { + return bubbles.reduce((total, bubble) => total + countWords(bubble), 0); +} + +export function countListSyntaxTokens(text: string): number { + return (text.match(/\s-\s|:|;/g) ?? []).length; +} + +export function normalizeReplyForComparison(text: string): string { + return text + .toLowerCase() + .replace(/[—–]/g, "-") + .replace(/[^a-z0-9\s'-]/g, " ") + .replace(/\s+/g, " ") + .trim(); +} + +export function isIntegrationExplicitAsk(userInput: string): boolean { + return INTEGRATION_EXPLICIT_RE.test(userInput); +} + +export function isSingleBlobReply(reply: string, bubbles: string[]): boolean { + if (bubbles.length !== 1) { + return false; + } + + const wordCount = countWords(reply); + const listTokens = countListSyntaxTokens(reply); + return wordCount > 28 || listTokens > 2 || hasLineBreaks(reply) || isCapabilityInventoryLike(reply); +} + +export function evaluateCapabilityBlobGate(params: { + userInput: string; + reply: string; + bubbles: string[]; + category?: FixtureCategory; + allowStructuredSingleBlob?: boolean; +}): CapabilityBlobGateResult { + const importantDataContext = params.category === "important_data" || params.allowStructuredSingleBlob === true; + const integrationExplicitAsk = isIntegrationExplicitAsk(params.userInput); + const singleBlob = isSingleBlobReply(params.reply, params.bubbles); + const assistantBoilerplate = CAPABILITY_BOILERPLATE_RE.test(params.reply); + + const blobPass = importantDataContext || !singleBlob; + const boilerPass = importantDataContext || integrationExplicitAsk || !assistantBoilerplate; + + return { + pass: blobPass && boilerPass, + importantDataContext, + integrationExplicitAsk, + singleBlob, + assistantBoilerplate, + }; +} diff --git a/src/mastra/evals/imessage/reply-task.ts b/src/mastra/evals/imessage/reply-task.ts new file mode 100644 index 0000000..5686755 --- /dev/null +++ b/src/mastra/evals/imessage/reply-task.ts @@ -0,0 +1,88 @@ +import type { Agent } from "@mastra/core/agent"; +import { buildAgents } from "../../agents/index.js"; +import { buildProviderOptions, isRateLimitError } from "../../../lib/provider.js"; +import { loadConfig } from "../../../lib/config.js"; +import { buildHistoryContext, buildInjectedPrompt, loadWorkspaceContext } from "../../../lib/workspace.js"; +import { applyReplyPolicy, computeResponseTokenBudget, detectReplyIntent } from "../../../lib/reply-policy.js"; +import { buildOutboundBubbles } from "../../../lib/outbound.js"; +import type { ReplyCollection } from "./types.js"; + +const config = loadConfig(); +const resolvedTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone || "America/Los_Angeles"; + +let agentsPromise: Promise | null = null; + +function getInstructions(): string { + return buildInjectedPrompt({ + baseInstructions: config.agent.systemPrompt?.trim() + ? `${config.agent.systemPrompt.trim()}\n\nYou are Tien inside this runtime. In chat, text like a close friend, not a formal assistant.` + : "You are Tien inside this runtime. In chat, text like a close friend, not a formal assistant.", + contextFiles: loadWorkspaceContext(config.agent.workspaceDir), + userTimezone: resolvedTimezone, + }); +} + +async function getAgents(): Promise { + if (!agentsPromise) { + agentsPromise = buildAgents(); + } + return agentsPromise; +} + +export async function collectReply(userText: string): Promise { + const agents = await getAgents(); + const instructions = getInstructions(); + const historyContext = buildHistoryContext({ + entries: [], + currentMessage: userText, + }); + const replyIntent = detectReplyIntent(userText); + const responseTokenBudget = computeResponseTokenBudget(userText, replyIntent); + const startedAt = Date.now(); + + for (let i = 0; i < agents.length; i += 1) { + try { + const result = await agents[i]!.stream( + historyContext, + buildProviderOptions(instructions, i > 0, responseTokenBudget), + ); + let rawReply = ""; + for await (const chunk of result.textStream) { + rawReply += chunk; + } + + if (result.error) { + throw result.error; + } + if (rawReply.trim().length === 0) { + throw new Error("Empty response from model"); + } + + const policyResult = applyReplyPolicy({ + rawText: rawReply, + inboundText: userText, + intent: replyIntent, + }); + const bubbles = buildOutboundBubbles({ + text: policyResult.text, + textChunkLimit: config.agent.textChunkLimit, + maxBubbles: config.agent.maxBubblesPerReply, + }); + const visibleReply = bubbles.join("\n").trim() || policyResult.text.replace(/\[MSG\]/gi, " ").trim(); + + return { + rawReply: visibleReply, + bubbles, + elapsedMs: Date.now() - startedAt, + providerIndex: i, + }; + } catch (err) { + if (isRateLimitError(err) && i < agents.length - 1) { + continue; + } + throw err; + } + } + + throw new Error("All providers exhausted"); +} diff --git a/src/mastra/evals/imessage/runner.ts b/src/mastra/evals/imessage/runner.ts new file mode 100644 index 0000000..d76bbf7 --- /dev/null +++ b/src/mastra/evals/imessage/runner.ts @@ -0,0 +1,233 @@ +import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs"; +import { dirname } from "path"; +import type { Dataset } from "@mastra/core/datasets"; +import { mastra } from "../../index.js"; +import { BASELINE_PATH, DATASET_DESCRIPTION, DATASET_NAME } from "./constants.js"; +import { runDeterministicChecks } from "./deterministic.js"; +import { loadBadOutputFixtures, runFixtureDeterministicChecks } from "./fixtures.js"; +import { buildLiveCases } from "./cases.js"; +import { collectReply } from "./reply-task.js"; +import { buildEvalScorers, SCORER_CATALOG } from "./scorers.js"; +import { normalizeReplyForComparison } from "./helpers.js"; +import type { EvalCheck, EvalSummarySnapshot, ReplyCollection } from "./types.js"; + +function formatCheck(check: EvalCheck): string { + return `${check.pass ? "PASS" : "FAIL"} [${check.severity}] ${check.id}: ${check.details}`; +} + +function printSection(title: string): void { + console.log(`\n== ${title} ==`); +} + +function readBaselineSnapshot(): EvalSummarySnapshot | null { + if (!existsSync(BASELINE_PATH)) { + return null; + } + try { + const raw = JSON.parse(readFileSync(BASELINE_PATH, "utf8")) as EvalSummarySnapshot; + if ( + typeof raw.total === "number" && + typeof raw.passed === "number" && + typeof raw.hardFailures === "number" && + typeof raw.softFailures === "number" && + raw.scorerAverages && + typeof raw.scorerAverages === "object" + ) { + return raw; + } + } catch { + return null; + } + return null; +} + +function writeBaselineSnapshot(snapshot: EvalSummarySnapshot): void { + mkdirSync(dirname(BASELINE_PATH), { recursive: true }); + writeFileSync(BASELINE_PATH, `${JSON.stringify(snapshot, null, 2)}\n`, "utf8"); +} + +function printBaselineDelta(current: EvalSummarySnapshot, baseline: EvalSummarySnapshot): void { + const hardDelta = current.hardFailures - baseline.hardFailures; + const softDelta = current.softFailures - baseline.softFailures; + const passedDelta = current.passed - baseline.passed; + console.log( + `Baseline delta: hard ${baseline.hardFailures}->${current.hardFailures} (${hardDelta >= 0 ? "+" : ""}${hardDelta}), ` + + `soft ${baseline.softFailures}->${current.softFailures} (${softDelta >= 0 ? "+" : ""}${softDelta}), ` + + `passed ${baseline.passed}->${current.passed} (${passedDelta >= 0 ? "+" : ""}${passedDelta})`, + ); +} + +function summarizeChecks(checks: EvalCheck[], live: boolean, scorerAverages: Record): EvalSummarySnapshot { + const hardFailures = checks.filter((check) => !check.pass && check.severity === "hard").length; + const softFailures = checks.filter((check) => !check.pass && check.severity === "soft").length; + return { + generatedAt: new Date().toISOString(), + live, + total: checks.length, + passed: checks.length - hardFailures - softFailures, + hardFailures, + softFailures, + scorerAverages, + }; +} + +async function getOrCreateDataset(): Promise { + const existing = await mastra.datasets.list({ page: 0, perPage: 250 }); + const match = existing.datasets.find((dataset) => dataset.name === DATASET_NAME); + if (match) { + return mastra.datasets.get({ id: match.id }); + } + return mastra.datasets.create({ + name: DATASET_NAME, + description: DATASET_DESCRIPTION, + metadata: { eval: "imessage", version: 1 }, + }); +} + +async function syncLiveCases(dataset: Dataset): Promise { + const cases = buildLiveCases(); + const listed = await dataset.listItems({ page: 0, perPage: 500 }); + const existingItems = Array.isArray(listed) ? listed : listed.items; + if (existingItems.length > 0) { + await dataset.deleteItems({ itemIds: existingItems.map((item) => item.id) }); + } + await dataset.addItems({ + items: cases.map((testCase) => ({ + input: testCase.input, + groundTruth: testCase.groundTruth, + metadata: { + caseId: testCase.id, + }, + })), + }); +} + +async function runLiveExperiment(options: { includeJudge: boolean }): Promise<{ checks: EvalCheck[]; scorerAverages: Record }> { + const dataset = await getOrCreateDataset(); + await syncLiveCases(dataset); + + const scorerMeta = new Map(SCORER_CATALOG.map((item) => [item.id, item])); + const scorerValues = new Map(); + const checks: EvalCheck[] = []; + const repeatBuckets = new Map(); + + const summary = await dataset.startExperiment({ + name: `iMessage Live Eval ${new Date().toISOString()}`, + description: "Live iMessage quality regression eval", + scorers: buildEvalScorers({ includeJudge: options.includeJudge }), + maxConcurrency: 3, + itemTimeout: 30000, + maxRetries: 0, + task: async ({ input }) => collectReply(String(input ?? "")), + }); + + for (const item of summary.results) { + const output = (item.output ?? null) as ReplyCollection | null; + const caseId = + typeof item.groundTruth === "object" && + item.groundTruth && + "caseId" in (item.groundTruth as Record) + ? String((item.groundTruth as Record).caseId) + : item.itemId; + + if (typeof item.input === "string" && output?.rawReply) { + repeatBuckets.set(item.input, [...(repeatBuckets.get(item.input) ?? []), output.rawReply]); + } + + for (const scorer of item.scores) { + const meta = scorerMeta.get(scorer.scorerId); + if (!meta) { + continue; + } + if (typeof scorer.score === "number") { + scorerValues.set(scorer.scorerId, [...(scorerValues.get(scorer.scorerId) ?? []), scorer.score]); + } + const pass = scorer.error == null && typeof scorer.score === "number" && scorer.score >= meta.threshold; + checks.push({ + id: `${caseId}.${scorer.scorerId}`, + severity: meta.severity, + pass, + details: + scorer.error != null + ? `error=${scorer.error}` + : `score=${String(scorer.score)} threshold=${meta.threshold} reason=${scorer.reason ?? ""}`, + }); + } + } + + for (const prompt of ["hi", "hey"]) { + const replies = repeatBuckets.get(prompt) ?? []; + if (replies.length < 2) { + continue; + } + const uniqueReplies = new Set(replies.map(normalizeReplyForComparison).filter(Boolean)); + checks.push({ + id: `variation.${prompt}.avoid-template-loop`, + severity: "soft", + pass: uniqueReplies.size >= 2, + details: `unique=${uniqueReplies.size} replies=${JSON.stringify(replies)}`, + }); + } + + const scorerAverages: Record = {}; + for (const [scorerId, values] of scorerValues.entries()) { + if (values.length === 0) { + continue; + } + const avg = values.reduce((sum, value) => sum + value, 0) / values.length; + scorerAverages[scorerId] = Number(avg.toFixed(4)); + } + + return { checks, scorerAverages }; +} + +export async function runImessageEvals(options: { + live: boolean; + writeBaseline: boolean; + includeJudge: boolean; +}): Promise<{ checks: EvalCheck[]; snapshot: EvalSummarySnapshot }> { + printSection("Deterministic"); + const deterministicChecks = await runDeterministicChecks(); + const fixtures = loadBadOutputFixtures(); + const fixtureChecks = runFixtureDeterministicChecks(fixtures); + const checks: EvalCheck[] = [...deterministicChecks, ...fixtureChecks]; + let scorerAverages: Record = {}; + + for (const check of checks) { + console.log(formatCheck(check)); + } + + if (options.live) { + printSection("Live Model (Mastra Experiment)"); + const live = await runLiveExperiment({ includeJudge: options.includeJudge }); + for (const check of live.checks) { + console.log(formatCheck(check)); + } + checks.push(...live.checks); + scorerAverages = live.scorerAverages; + } else { + console.log("\nSkip live evals. Re-run with --live to test model behavior."); + } + + const snapshot = summarizeChecks(checks, options.live, scorerAverages); + console.log( + `\nSummary: ${snapshot.passed}/${snapshot.total} passed, ${snapshot.hardFailures} hard fail, ${snapshot.softFailures} soft fail`, + ); + if (Object.keys(snapshot.scorerAverages).length > 0) { + console.log(`Scorer averages: ${JSON.stringify(snapshot.scorerAverages)}`); + } + + const baseline = readBaselineSnapshot(); + if (baseline) { + printBaselineDelta(snapshot, baseline); + } else { + console.log("Baseline delta: no baseline snapshot found"); + } + + if (options.writeBaseline) { + writeBaselineSnapshot(snapshot); + console.log(`Baseline snapshot updated: ${BASELINE_PATH}`); + } + + return { checks, snapshot }; +} diff --git a/src/mastra/evals/imessage/scorers.ts b/src/mastra/evals/imessage/scorers.ts new file mode 100644 index 0000000..cb02dbd --- /dev/null +++ b/src/mastra/evals/imessage/scorers.ts @@ -0,0 +1,340 @@ +import { createScorer } from "@mastra/core/evals"; +import { z } from "zod"; +import { TARGET_PERSONA_SUMMARY } from "./constants.js"; +import { countEmoji, countWordsAcrossBubbles, evaluateCapabilityBlobGate, hasEmDash, hasLineBreaks } from "./helpers.js"; +import type { EvalGroundTruth, EvalSeverity, ReplyCollection } from "./types.js"; + +const OUTPUT_SCHEMA = z.object({ + rawReply: z.string(), + bubbles: z.array(z.string()), + elapsedMs: z.number(), + providerIndex: z.number(), +}); + +type ScorerMeta = { + id: string; + severity: EvalSeverity; + threshold: number; +}; + +function getGroundTruth(run: { groundTruth?: unknown }): EvalGroundTruth | null { + const parsed = z + .object({ + caseId: z.string(), + category: z.enum(["capability", "food", "practical", "greeting", "important_data", "other"]), + constraints: z.record(z.string(), z.unknown()), + }) + .safeParse(run.groundTruth); + if (!parsed.success) { + return null; + } + return parsed.data as EvalGroundTruth; +} + +const nonEmptyScorer = createScorer({ + id: "imessage_non_empty", + name: "iMessage Non-Empty", + description: "Reply must include text and at least one bubble.", + type: { + input: z.string(), + output: OUTPUT_SCHEMA, + }, +}).generateScore(({ run }) => { + const output = run.output as ReplyCollection; + return output.rawReply.trim().length > 0 && output.bubbles.length > 0 ? 1 : 0; +}); + +const tokenCleanScorer = createScorer({ + id: "imessage_token_clean", + name: "iMessage Token Clean", + description: "No raw [MSG] token should be visible in raw reply or bubbles.", + type: { + input: z.string(), + output: OUTPUT_SCHEMA, + }, +}).generateScore(({ run }) => { + const output = run.output as ReplyCollection; + const clean = !output.rawReply.includes("[MSG]") && output.bubbles.every((bubble) => !bubble.includes("[MSG]")); + return clean ? 1 : 0; +}); + +const emDashScorer = createScorer({ + id: "imessage_no_em_dash", + name: "iMessage No Em Dash", + description: "Texting output should not use em dash punctuation.", + type: { + input: z.string(), + output: OUTPUT_SCHEMA, + }, +}).generateScore(({ run }) => { + const output = run.output as ReplyCollection; + const clean = !hasEmDash(output.rawReply) && output.bubbles.every((bubble) => !hasEmDash(bubble)); + return clean ? 1 : 0; +}); + +const shapeScorer = createScorer({ + id: "imessage_shape_constraints", + name: "iMessage Shape Constraints", + description: "Bubble count, per-bubble length, word count, line-break, and emoji constraints.", + type: { + input: z.string(), + output: OUTPUT_SCHEMA, + }, +}) + .preprocess(({ run }) => { + const output = run.output as ReplyCollection; + const gt = getGroundTruth(run); + const constraints = gt?.constraints ?? {}; + + const checks = { + requireNonEmpty: constraints.requireNonEmpty !== true || (output.rawReply.trim().length > 0 && output.bubbles.length > 0), + singleBubble: constraints.requireSingleBubble !== true || output.bubbles.length === 1, + maxBubbles: + typeof constraints.maxBubbles !== "number" || + (output.bubbles.length >= 1 && output.bubbles.length <= constraints.maxBubbles), + maxWordsTotal: + typeof constraints.maxWordsTotal !== "number" || countWordsAcrossBubbles(output.bubbles) <= constraints.maxWordsTotal, + maxWordsPerBubble: + typeof constraints.maxWordsPerBubble !== "number" || + output.bubbles.every((bubble) => bubble.trim().split(/\s+/).filter(Boolean).length <= constraints.maxWordsPerBubble), + maxEmoji: typeof constraints.maxEmoji !== "number" || countEmoji(output.rawReply) <= constraints.maxEmoji, + forbidLineBreaks: + constraints.forbidLineBreaks !== true || output.bubbles.every((bubble) => !hasLineBreaks(bubble)), + }; + + const failed = Object.entries(checks) + .filter(([, pass]) => !pass) + .map(([name]) => name); + + return { checks, failed }; + }) + .generateScore(({ results }) => ((results.preprocessStepResult?.failed?.length ?? 0) === 0 ? 1 : 0)) + .generateReason(({ results }) => { + const failed = (results.preprocessStepResult?.failed as string[] | undefined) ?? []; + if (failed.length === 0) { + return "all shape constraints passed"; + } + return `failed=${failed.join(", ")}`; + }); + +const capabilityBlobScorer = createScorer({ + id: "imessage_capability_blob_gate", + name: "iMessage Capability Blob Gate", + description: "In casual capability contexts, disallow long single-blob assistant inventory replies.", + type: { + input: z.string(), + output: OUTPUT_SCHEMA, + }, +}) + .preprocess(({ run }) => { + const output = run.output as ReplyCollection; + const gt = getGroundTruth(run); + if (!gt || gt.constraints.enforceCapabilityBlobGate !== true) { + return { applicable: false, pass: true, details: "not-applicable" }; + } + + const gate = evaluateCapabilityBlobGate({ + userInput: String(run.input ?? ""), + reply: output.rawReply, + bubbles: output.bubbles, + category: gt.category, + allowStructuredSingleBlob: gt.constraints.allowStructuredSingleBlob === true, + }); + return { + applicable: true, + pass: gate.pass, + details: `singleBlob=${gate.singleBlob} importantData=${gate.importantDataContext} integrationAsk=${gate.integrationExplicitAsk} boilerplate=${gate.assistantBoilerplate}`, + }; + }) + .generateScore(({ results }) => (results.preprocessStepResult?.pass === false ? 0 : 1)) + .generateReason(({ results }) => String(results.preprocessStepResult?.details ?? "")); + +const lexicalGuardScorer = createScorer({ + id: "imessage_lexical_guard", + name: "iMessage Lexical Guard", + description: "Case-level disallowed phrase guard.", + type: { + input: z.string(), + output: OUTPUT_SCHEMA, + }, +}) + .preprocess(({ run }) => { + const output = run.output as ReplyCollection; + const gt = getGroundTruth(run); + const globalDisallowPatterns = [ + "would you like me to send you the link", + "would you like me to send you the links", + "would u like me to send u the link", + "would u like me to send u the links", + "if u want, i can send the links", + "sushi burrito if u want cheap + fast", + "izakaya if u wanna chill a little", + "omakase if ur feeling fancy", + ]; + const disallowPatterns = Array.isArray(gt?.constraints.disallowPatterns) + ? gt.constraints.disallowPatterns.filter((value): value is string => typeof value === "string") + : []; + const allDisallowPatterns = [...globalDisallowPatterns, ...disallowPatterns]; + + if (allDisallowPatterns.length === 0) { + return { applicable: false, pass: true, details: "not-applicable" }; + } + + const normalized = output.rawReply.toLowerCase(); + const matched = allDisallowPatterns.filter((pattern) => normalized.includes(pattern.toLowerCase())); + return { + applicable: true, + pass: matched.length === 0, + details: matched.length === 0 ? "none" : `matched=${matched.join(", ")}`, + }; + }) + .generateScore(({ results }) => (results.preprocessStepResult?.pass === false ? 0 : 1)) + .generateReason(({ results }) => String(results.preprocessStepResult?.details ?? "")); + +const latencyScorer = createScorer({ + id: "imessage_latency", + name: "iMessage Latency", + description: "Elapsed response time should stay under case threshold.", + type: { + input: z.string(), + output: OUTPUT_SCHEMA, + }, +}) + .preprocess(({ run }) => { + const output = run.output as ReplyCollection; + const gt = getGroundTruth(run); + const maxLatencyMs = typeof gt?.constraints.maxLatencyMs === "number" ? gt.constraints.maxLatencyMs : 12000; + return { + elapsedMs: output.elapsedMs, + maxLatencyMs, + pass: output.elapsedMs <= maxLatencyMs, + }; + }) + .generateScore(({ results }) => (results.preprocessStepResult?.pass === true ? 1 : 0)) + .generateReason(({ results }) => { + const elapsed = results.preprocessStepResult?.elapsedMs; + const max = results.preprocessStepResult?.maxLatencyMs; + return `elapsedMs=${String(elapsed)} maxLatencyMs=${String(max)}`; + }); + +const judgeScorer = createScorer({ + id: "imessage_judge_quality", + name: "iMessage Judge Quality", + description: "LLM judge for natural texting quality, persona fit, and anti-assistant tone.", + type: { + input: z.string(), + output: OUTPUT_SCHEMA, + }, + judge: { + model: "openai/gpt-5-mini", + instructions: + "You are a strict evaluator of iMessage-style assistant replies. Return only JSON matching the schema exactly.", + }, +}) + .preprocess(({ run }) => { + const gt = getGroundTruth(run); + return { + input: String(run.input ?? ""), + category: gt?.category ?? "other", + output: run.output as ReplyCollection, + }; + }) + .analyze({ + description: "Score naturalness/persona/format/anti-assistant/anti-clingy traits for texting quality.", + outputSchema: z.object({ + duplication: z.boolean(), + naturalness: z.number().min(1).max(5), + greetingFit: z.number().min(1).max(5), + personaFit: z.number().min(1).max(5), + formatFit: z.number().min(1).max(5), + antiAssistantTone: z.number().min(1).max(5), + antiClingy: z.number().min(1).max(5), + explanation: z.string(), + }), + createPrompt: ({ results }) => { + const pre = results.preprocessStepResult as { + input: string; + category: string; + output: ReplyCollection; + }; + return [ + "Evaluate this iMessage reply.", + `User input: ${pre.input}`, + `Category: ${pre.category}`, + `Assistant raw reply: ${pre.output.rawReply}`, + `Assistant bubbles: ${JSON.stringify(pre.output.bubbles)}`, + "Scoring criteria:", + "- duplication: true if clause/sentence/idea repeats unnaturally", + "- naturalness: integer 1-5", + "- greetingFit: integer 1-5, for tiny greetings like hi/hey", + "- personaFit: integer 1-5", + "- formatFit: integer 1-5", + "- antiAssistantTone: integer 1-5 (5 = not assistant-y)", + "- antiClingy: integer 1-5 (5 = not clingy/overeager)", + "- explanation: one short sentence", + `Target persona: ${TARGET_PERSONA_SUMMARY}`, + ].join("\n"); + }, + }) + .generateScore(({ results }) => { + const r = results.analyzeStepResult as { + duplication: boolean; + naturalness: number; + greetingFit: number; + personaFit: number; + formatFit: number; + antiAssistantTone: number; + antiClingy: number; + }; + + const pre = results.preprocessStepResult as { category: string }; + const metrics = [r.naturalness, r.personaFit, r.formatFit, r.antiAssistantTone, r.antiClingy]; + if (pre.category === "greeting") { + metrics.push(r.greetingFit); + } + + const avgFivePoint = metrics.reduce((sum, value) => sum + value, 0) / metrics.length; + const normalized = (avgFivePoint - 1) / 4; + const duplicationPenalty = r.duplication ? 0.2 : 0; + return Math.max(0, Math.min(1, normalized - duplicationPenalty)); + }) + .generateReason(({ results, score }) => { + const r = results.analyzeStepResult as { + duplication: boolean; + naturalness: number; + greetingFit: number; + personaFit: number; + formatFit: number; + antiAssistantTone: number; + antiClingy: number; + explanation: string; + }; + return `score=${score} duplication=${r.duplication} naturalness=${r.naturalness} greetingFit=${r.greetingFit} personaFit=${r.personaFit} formatFit=${r.formatFit} antiAssistantTone=${r.antiAssistantTone} antiClingy=${r.antiClingy} explanation=${r.explanation}`; + }); + +export const SCORER_CATALOG: ScorerMeta[] = [ + { id: "imessage_non_empty", severity: "hard", threshold: 1 }, + { id: "imessage_token_clean", severity: "hard", threshold: 1 }, + { id: "imessage_no_em_dash", severity: "hard", threshold: 1 }, + { id: "imessage_shape_constraints", severity: "hard", threshold: 1 }, + { id: "imessage_capability_blob_gate", severity: "hard", threshold: 1 }, + { id: "imessage_lexical_guard", severity: "hard", threshold: 1 }, + { id: "imessage_latency", severity: "hard", threshold: 1 }, + { id: "imessage_judge_quality", severity: "soft", threshold: 0.8 }, +]; + +export function buildEvalScorers(options: { includeJudge: boolean }) { + const base = [ + nonEmptyScorer, + tokenCleanScorer, + emDashScorer, + shapeScorer, + capabilityBlobScorer, + lexicalGuardScorer, + latencyScorer, + ]; + if (options.includeJudge) { + base.push(judgeScorer); + } + return base; +} diff --git a/src/mastra/evals/imessage/types.ts b/src/mastra/evals/imessage/types.ts new file mode 100644 index 0000000..be3cab8 --- /dev/null +++ b/src/mastra/evals/imessage/types.ts @@ -0,0 +1,85 @@ +export type EvalSeverity = "hard" | "soft"; + +export type FixtureCategory = + | "capability" + | "food" + | "practical" + | "greeting" + | "important_data" + | "other"; + +export type ReplyIntent = "greeting" | "capability" | "recommendation" | "normal"; + +export interface BadOutputFixture { + id: string; + category: FixtureCategory; + userInput: string; + badReply: string; + rewrite: string; + notes?: string; + expectedGateFailOnBad?: boolean; +} + +export interface EvalCheck { + id: string; + severity: EvalSeverity; + pass: boolean; + details: string; +} + +export interface EvalSummarySnapshot { + generatedAt: string; + live: boolean; + total: number; + passed: number; + hardFailures: number; + softFailures: number; + scorerAverages: Record; +} + +export interface ImessageEvalConstraints { + requireNonEmpty?: boolean; + requireSingleBubble?: boolean; + maxBubbles?: number; + maxWordsTotal?: number; + maxWordsPerBubble?: number; + maxEmoji?: number; + forbidEmDash?: boolean; + forbidMsgToken?: boolean; + forbidLineBreaks?: boolean; + maxLatencyMs?: number; + enforceCapabilityBlobGate?: boolean; + allowStructuredSingleBlob?: boolean; + disallowPatterns?: string[]; +} + +export interface ImessageEvalCase { + id: string; + input: string; + category: FixtureCategory; + intent?: ReplyIntent; + runs?: number; + constraints: ImessageEvalConstraints; + useJudge?: boolean; +} + +export interface EvalGroundTruth { + caseId: string; + category: FixtureCategory; + constraints: ImessageEvalConstraints; +} + +export interface ReplyCollection { + rawReply: string; + bubbles: string[]; + elapsedMs: number; + providerIndex: number; +} + +export interface CapabilityBlobGateResult { + pass: boolean; + importantDataContext: boolean; + integrationExplicitAsk: boolean; + singleBlob: boolean; + assistantBoilerplate: boolean; +} From 1c4752e0194a27881b8ff6d508825e41e66fec0c Mon Sep 17 00:00:00 2001 From: Andrew Ho Date: Sun, 12 Apr 2026 11:14:37 -0700 Subject: [PATCH 3/8] feat(agent): add gmail, websearch, and bluebubbles tools --- src/mastra/agents/index.ts | 19 +- src/mastra/tools/bluebubbles-tools.ts | 535 ++++++++++++++++++++++++++ src/mastra/tools/gmail-tool.ts | 415 ++++++++++++++++++++ 3 files changed, 966 insertions(+), 3 deletions(-) create mode 100644 src/mastra/tools/bluebubbles-tools.ts create mode 100644 src/mastra/tools/gmail-tool.ts diff --git a/src/mastra/agents/index.ts b/src/mastra/agents/index.ts index cf8f579..52ccb24 100644 --- a/src/mastra/agents/index.ts +++ b/src/mastra/agents/index.ts @@ -1,8 +1,16 @@ +import { createOpenAI } from "@ai-sdk/openai"; import { Agent } from "@mastra/core/agent"; import type { LanguageModel as MastraLanguageModel } from "@mastra/core/llm"; -import { loadConfig } from "../../lib/config.js"; -import { buildModels } from "../../lib/provider.js"; -import { BASE_RUNTIME_INSTRUCTIONS } from "../../lib/workspace.js"; +import { loadConfig } from "../../lib/config"; +import { buildModels } from "../../lib/provider"; +import { BASE_RUNTIME_INSTRUCTIONS } from "../../lib/workspace"; +import { bluebubblesTools } from "../tools/bluebubbles-tools"; +import { gmailTool } from "../tools/gmail-tool"; + +const openai = createOpenAI(); +const webSearchTool = openai.tools.webSearch({ + searchContextSize: "medium", +}); function createAgent(model: MastraLanguageModel): Agent { return new Agent({ @@ -10,6 +18,11 @@ function createAgent(model: MastraLanguageModel): Agent { name: "tien", model, instructions: BASE_RUNTIME_INSTRUCTIONS, + tools: { + gmail: gmailTool, + web_search: webSearchTool, + ...bluebubblesTools, + }, defaultOptions: { modelSettings: { maxTokens: 512, diff --git a/src/mastra/tools/bluebubbles-tools.ts b/src/mastra/tools/bluebubbles-tools.ts new file mode 100644 index 0000000..89851bb --- /dev/null +++ b/src/mastra/tools/bluebubbles-tools.ts @@ -0,0 +1,535 @@ +import { randomUUID } from "crypto"; +import { readFile } from "fs/promises"; +import { basename } from "path"; +import { createClient } from "@jgoon/bluebubbles"; +import { createTool } from "@mastra/core/tools"; +import { z } from "zod"; +import { loadConfig } from "../../lib/config"; + +const operationResultSchema = z.object({ + ok: z.boolean(), + operation: z.string(), + data: z.record(z.string(), z.unknown()).nullable(), + error: z.string().nullable(), +}); + +type OperationResult = z.infer; + +const readWriteMethodSchema = z.enum(["apple-script", "private-api"]); +const reactionSchema = z.enum([ + "love", + "like", + "dislike", + "laugh", + "emphasize", + "question", + "-love", + "-like", + "-dislike", + "-laugh", + "-emphasize", + "-question", +]); + +const chatQueryInputSchema = z.object({ + limit: z.number().int().min(1).max(500).default(50), + offset: z.number().int().min(0).default(0), + with: z.array(z.string()).default([]), + sort: z.string().nullable().default(null), +}); + +const messageQueryInputSchema = z.object({ + limit: z.number().int().min(1).max(500).default(50), + offset: z.number().int().min(0).default(0), + chatGuid: z.string().nullable().default(null), + with: z.array(z.string()).default([]), + sort: z.enum(["ASC", "DESC"]).default("DESC"), + whereStatement: z.string().nullable().default(null), + whereText: z.string().nullable().default(null), +}); + +const messageSearchInputSchema = z.object({ + query: z.string().min(1), + limit: z.number().int().min(1).max(100).default(25), + offset: z.number().int().min(0).default(0), + chatGuid: z.string().nullable().default(null), +}); + +const sendTextInputSchema = z.object({ + chatGuid: z.string().min(1), + message: z.string().min(1), + method: readWriteMethodSchema.default("apple-script"), + subject: z.string().nullable().default(null), + effectId: z.string().nullable().default(null), + selectedMessageGuid: z.string().nullable().default(null), + partIndex: z.number().int().min(0).default(0), +}); + +const chatGuidOnlyInputSchema = z.object({ + chatGuid: z.string().min(1), +}); + +const reactInputSchema = z.object({ + chatGuid: z.string().min(1), + selectedMessageGuid: z.string().min(1), + selectedMessageText: z.string().nullable().default(null), + reaction: reactionSchema, + partIndex: z.number().int().min(0).default(0), +}); + +const uploadAttachmentInputSchema = z.object({ + filePath: z.string().min(1), + formFieldName: z.string().min(1).default("attachment"), + uploadFileName: z.string().nullable().default(null), +}); + +const sendAttachmentInputSchema = z.object({ + chatGuid: z.string().min(1), + filePath: z.string().min(1), + formFieldName: z.string().min(1).default("attachment"), + uploadFileName: z.string().nullable().default(null), + method: readWriteMethodSchema.default("apple-script"), + subject: z.string().nullable().default(null), + effectId: z.string().nullable().default(null), + selectedMessageGuid: z.string().nullable().default(null), + partIndex: z.number().int().min(0).default(0), +}); + +function bbContext() { + const config = loadConfig(); + const serverUrl = config.bluebubbles.serverUrl; + const password = config.bluebubbles.password; + const client = createClient({ baseUrl: serverUrl }); + return { + client, + serverUrl, + password, + }; +} + +function ok(operation: string, data: Record): OperationResult { + return { + ok: true, + operation, + data, + error: null, + }; +} + +function fail(operation: string, message: string, data: Record | null = null): OperationResult { + return { + ok: false, + operation, + data, + error: message, + }; +} + +function toErrorMessage(value: unknown): string { + if (value instanceof Error) { + return value.message; + } + if (typeof value === "string") { + return value; + } + try { + return JSON.stringify(value); + } catch { + return "Unknown error"; + } +} + +function withoutNull>(value: T): Record { + return Object.fromEntries(Object.entries(value).filter(([, fieldValue]) => fieldValue !== null)); +} + +function responseData(input: unknown): Record { + if (typeof input === "object" && input !== null) { + return { response: input as Record }; + } + return { response: input }; +} + +async function postMultipart( + route: string, + formData: FormData, +): Promise<{ ok: true; data: unknown } | { ok: false; message: string; status: number | null; body: string }> { + const { serverUrl, password } = bbContext(); + const url = new URL(route, serverUrl); + url.searchParams.set("password", password); + + const response = await fetch(url, { + method: "POST", + body: formData, + }); + + const contentType = response.headers.get("content-type") ?? ""; + const text = await response.text(); + const isJson = contentType.toLowerCase().includes("application/json"); + let parsed: unknown = text; + + if (isJson) { + try { + parsed = JSON.parse(text); + } catch { + parsed = text; + } + } + + if (!response.ok) { + return { + ok: false, + message: `BlueBubbles request failed with status ${response.status}`, + status: response.status, + body: text, + }; + } + + return { + ok: true, + data: parsed, + }; +} + +async function createFilePart(path: string, uploadFileName: string | null): Promise<{ blob: Blob; fileName: string }> { + const bytes = await readFile(path); + const blob = new Blob([bytes]); + const fileName = uploadFileName === null ? basename(path) : uploadFileName; + return { + blob, + fileName, + }; +} + +export const bluebubblesChatQueryTool = createTool({ + id: "bluebubbles-chat-query", + description: "Query chats from BlueBubbles.", + inputSchema: chatQueryInputSchema, + outputSchema: operationResultSchema, + execute: async (inputData): Promise => { + try { + const { client, password } = bbContext(); + const body = withoutNull({ + limit: inputData.limit, + offset: inputData.offset, + with: inputData.with.length === 0 ? null : inputData.with, + sort: inputData.sort, + }); + const { data, error } = await client.POST("/api/v1/chat/query", { + params: { query: { password } }, + body, + }); + if (error !== null) { + return fail("bluebubbles_chat_query", "Chat query failed.", { error }); + } + return ok("bluebubbles_chat_query", responseData(data)); + } catch (error) { + return fail("bluebubbles_chat_query", toErrorMessage(error)); + } + }, +}); + +export const bluebubblesMessageQueryTool = createTool({ + id: "bluebubbles-message-query", + description: "Query messages from BlueBubbles.", + inputSchema: messageQueryInputSchema, + outputSchema: operationResultSchema, + execute: async (inputData): Promise => { + try { + const { client, password } = bbContext(); + const where = + inputData.whereStatement === null + ? [] + : [ + { + statement: inputData.whereStatement, + args: inputData.whereText === null ? {} : { text: inputData.whereText }, + }, + ]; + const body = withoutNull({ + limit: inputData.limit, + offset: inputData.offset, + chatGuid: inputData.chatGuid, + with: inputData.with.length === 0 ? null : inputData.with, + sort: inputData.sort, + where: where.length === 0 ? null : where, + }); + + const { data, error } = await client.POST("/api/v1/message/query", { + params: { query: { password } }, + body, + }); + + if (error !== null) { + return fail("bluebubbles_message_query", "Message query failed.", { error }); + } + return ok("bluebubbles_message_query", responseData(data)); + } catch (error) { + return fail("bluebubbles_message_query", toErrorMessage(error)); + } + }, +}); + +export const bluebubblesMessageSearchTool = createTool({ + id: "bluebubbles-message-search", + description: "Search BlueBubbles messages by text using the message query endpoint.", + inputSchema: messageSearchInputSchema, + outputSchema: operationResultSchema, + execute: async (inputData): Promise => { + try { + const { client, password } = bbContext(); + const body = withoutNull({ + limit: inputData.limit, + offset: inputData.offset, + chatGuid: inputData.chatGuid, + sort: "DESC", + with: ["chat", "handle"], + where: [ + { + statement: "message.text LIKE :text", + args: { + text: `%${inputData.query}%`, + }, + }, + ], + }); + const { data, error } = await client.POST("/api/v1/message/query", { + params: { query: { password } }, + body, + }); + if (error !== null) { + return fail("bluebubbles_message_search", "Message search failed.", { error }); + } + return ok("bluebubbles_message_search", responseData(data)); + } catch (error) { + return fail("bluebubbles_message_search", toErrorMessage(error)); + } + }, +}); + +export const bluebubblesSendTextTool = createTool({ + id: "bluebubbles-message-send-text", + description: "Send a text message using BlueBubbles.", + inputSchema: sendTextInputSchema, + outputSchema: operationResultSchema, + execute: async (inputData): Promise => { + try { + const { client, password } = bbContext(); + const body = withoutNull({ + chatGuid: inputData.chatGuid, + tempGuid: randomUUID(), + message: inputData.message, + method: inputData.method, + subject: inputData.subject, + effectId: inputData.effectId, + selectedMessageGuid: inputData.selectedMessageGuid, + partIndex: inputData.partIndex, + }); + const { data, error } = await client.POST("/api/v1/message/text", { + params: { query: { password } }, + body, + }); + if (error !== null) { + return fail("bluebubbles_message_send_text", "Send text failed.", { error }); + } + return ok("bluebubbles_message_send_text", responseData(data)); + } catch (error) { + return fail("bluebubbles_message_send_text", toErrorMessage(error)); + } + }, +}); + +export const bluebubblesMarkReadTool = createTool({ + id: "bluebubbles-chat-mark-read", + description: "Mark a BlueBubbles chat as read.", + inputSchema: chatGuidOnlyInputSchema, + outputSchema: operationResultSchema, + execute: async (inputData): Promise => { + try { + const { client, password } = bbContext(); + const { data, error } = await client.POST("/api/v1/chat/{chatGuid}/read", { + params: { path: { chatGuid: inputData.chatGuid }, query: { password } }, + body: {}, + }); + if (error !== null) { + return fail("bluebubbles_chat_mark_read", "Mark read failed.", { error }); + } + return ok("bluebubbles_chat_mark_read", responseData(data)); + } catch (error) { + return fail("bluebubbles_chat_mark_read", toErrorMessage(error)); + } + }, +}); + +export const bluebubblesMarkUnreadTool = createTool({ + id: "bluebubbles-chat-mark-unread", + description: "Mark a BlueBubbles chat as unread.", + inputSchema: chatGuidOnlyInputSchema, + outputSchema: operationResultSchema, + execute: async (inputData): Promise => { + try { + const { client, password } = bbContext(); + const { data, error } = await client.POST("/api/v1/chat/{chatGuid}/unread", { + params: { path: { chatGuid: inputData.chatGuid }, query: { password } }, + body: {}, + }); + if (error !== null) { + return fail("bluebubbles_chat_mark_unread", "Mark unread failed.", { error }); + } + return ok("bluebubbles_chat_mark_unread", responseData(data)); + } catch (error) { + return fail("bluebubbles_chat_mark_unread", toErrorMessage(error)); + } + }, +}); + +export const bluebubblesTypingStartTool = createTool({ + id: "bluebubbles-typing-start", + description: "Start typing indicator for a BlueBubbles chat.", + inputSchema: chatGuidOnlyInputSchema, + outputSchema: operationResultSchema, + execute: async (inputData): Promise => { + try { + const { client, password } = bbContext(); + const { data, error } = await client.POST("/api/v1/chat/{chatGuid}/typing", { + params: { path: { chatGuid: inputData.chatGuid }, query: { password } }, + }); + if (error !== null) { + return fail("bluebubbles_typing_start", "Typing start failed.", { error }); + } + return ok("bluebubbles_typing_start", responseData(data)); + } catch (error) { + return fail("bluebubbles_typing_start", toErrorMessage(error)); + } + }, +}); + +export const bluebubblesTypingStopTool = createTool({ + id: "bluebubbles-typing-stop", + description: "Stop typing indicator for a BlueBubbles chat.", + inputSchema: chatGuidOnlyInputSchema, + outputSchema: operationResultSchema, + execute: async (inputData): Promise => { + try { + const { client, password } = bbContext(); + const { data, error } = await client.DELETE("/api/v1/chat/{chatGuid}/typing", { + params: { path: { chatGuid: inputData.chatGuid }, query: { password } }, + }); + if (error !== null) { + return fail("bluebubbles_typing_stop", "Typing stop failed.", { error }); + } + return ok("bluebubbles_typing_stop", responseData(data)); + } catch (error) { + return fail("bluebubbles_typing_stop", toErrorMessage(error)); + } + }, +}); + +export const bluebubblesReactTool = createTool({ + id: "bluebubbles-message-react", + description: "React to a message in BlueBubbles.", + inputSchema: reactInputSchema, + outputSchema: operationResultSchema, + execute: async (inputData): Promise => { + try { + const { client, password } = bbContext(); + const body = withoutNull({ + chatGuid: inputData.chatGuid, + selectedMessageGuid: inputData.selectedMessageGuid, + selectedMessageText: inputData.selectedMessageText, + reaction: inputData.reaction, + partIndex: inputData.partIndex, + }); + const { data, error } = await client.POST("/api/v1/message/react", { + params: { query: { password } }, + body, + }); + if (error !== null) { + return fail("bluebubbles_message_react", "Send reaction failed.", { error }); + } + return ok("bluebubbles_message_react", responseData(data)); + } catch (error) { + return fail("bluebubbles_message_react", toErrorMessage(error)); + } + }, +}); + +export const bluebubblesAttachmentUploadTool = createTool({ + id: "bluebubbles-attachment-upload", + description: "Upload a file to BlueBubbles attachments storage.", + inputSchema: uploadAttachmentInputSchema, + outputSchema: operationResultSchema, + execute: async (inputData): Promise => { + try { + const part = await createFilePart(inputData.filePath, inputData.uploadFileName); + const formData = new FormData(); + formData.append(inputData.formFieldName, part.blob, part.fileName); + + const response = await postMultipart("/api/v1/attachment/upload", formData); + if (!response.ok) { + return fail("bluebubbles_attachment_upload", response.message, { + status: response.status, + body: response.body, + }); + } + + return ok("bluebubbles_attachment_upload", responseData(response.data)); + } catch (error) { + return fail("bluebubbles_attachment_upload", toErrorMessage(error)); + } + }, +}); + +export const bluebubblesSendAttachmentTool = createTool({ + id: "bluebubbles-message-send-attachment", + description: "Send an attachment message using BlueBubbles.", + inputSchema: sendAttachmentInputSchema, + outputSchema: operationResultSchema, + execute: async (inputData): Promise => { + try { + const part = await createFilePart(inputData.filePath, inputData.uploadFileName); + const formData = new FormData(); + formData.append("chatGuid", inputData.chatGuid); + formData.append("tempGuid", randomUUID()); + formData.append("method", inputData.method); + formData.append("partIndex", String(inputData.partIndex)); + formData.append(inputData.formFieldName, part.blob, part.fileName); + + if (inputData.subject !== null) { + formData.append("subject", inputData.subject); + } + if (inputData.effectId !== null) { + formData.append("effectId", inputData.effectId); + } + if (inputData.selectedMessageGuid !== null) { + formData.append("selectedMessageGuid", inputData.selectedMessageGuid); + } + + const response = await postMultipart("/api/v1/message/attachment", formData); + if (!response.ok) { + return fail("bluebubbles_message_send_attachment", response.message, { + status: response.status, + body: response.body, + }); + } + + return ok("bluebubbles_message_send_attachment", responseData(response.data)); + } catch (error) { + return fail("bluebubbles_message_send_attachment", toErrorMessage(error)); + } + }, +}); + +export const bluebubblesTools = { + bluebubbles_chat_query: bluebubblesChatQueryTool, + bluebubbles_message_query: bluebubblesMessageQueryTool, + bluebubbles_message_search: bluebubblesMessageSearchTool, + bluebubbles_message_send_text: bluebubblesSendTextTool, + bluebubbles_chat_mark_read: bluebubblesMarkReadTool, + bluebubbles_chat_mark_unread: bluebubblesMarkUnreadTool, + bluebubbles_typing_start: bluebubblesTypingStartTool, + bluebubbles_typing_stop: bluebubblesTypingStopTool, + bluebubbles_message_react: bluebubblesReactTool, + bluebubbles_attachment_upload: bluebubblesAttachmentUploadTool, + bluebubbles_message_send_attachment: bluebubblesSendAttachmentTool, +}; diff --git a/src/mastra/tools/gmail-tool.ts b/src/mastra/tools/gmail-tool.ts new file mode 100644 index 0000000..a1ac366 --- /dev/null +++ b/src/mastra/tools/gmail-tool.ts @@ -0,0 +1,415 @@ +import { execFile } from "child_process"; +import { promisify } from "util"; +import { createTool } from "@mastra/core/tools"; +import { z } from "zod"; + +const execFileAsync = promisify(execFile); +const GWS_TIMEOUT_MS = 20_000; +const GWS_MAX_BUFFER_BYTES = 2 * 1024 * 1024; + +const sendInputSchema = z.object({ + action: z.literal("send"), + to: z.string().min(1).describe("Recipient email address. Comma-separated values are allowed."), + subject: z.string().min(1).describe("Email subject line."), + body: z.string().min(1).describe("Plain text email body."), +}); + +const listInputSchema = z.object({ + action: z.literal("list"), + query: z.string().optional().describe("Gmail search query (for example: from:alice newer_than:7d)."), + maxResults: z.number().int().min(1).max(25).default(10).describe("Maximum number of messages to list."), +}); + +const readInputSchema = z.object({ + action: z.literal("read"), + messageId: z.string().min(1).describe("Gmail message id."), + format: z + .enum(["metadata", "full"]) + .default("metadata") + .describe("Use metadata for compact fields or full for full payload."), +}); + +const gmailInputSchema = z.discriminatedUnion("action", [sendInputSchema, listInputSchema, readInputSchema]); + +const gmailOutputSchema = z.object({ + ok: z.boolean(), + action: z.enum(["send", "list", "read"]), + data: z.record(z.string(), z.unknown()).optional(), + error: z.string().optional(), +}); + +type GmailInput = z.infer; +type GmailOutput = z.infer; + +type GwsRunResult = { + ok: boolean; + status: number | null; + stdout: string; + stderr: string; +}; + +type ExecFileError = Error & { + code?: number | string; + stdout?: string; + stderr?: string; +}; + +const gwsErrorSchema = z + .object({ + error: z + .object({ + message: z.string().optional(), + }) + .optional(), + }) + .passthrough(); + +const authStatusSchema = z + .object({ + auth_method: z.string().optional(), + credential_source: z.string().optional(), + }) + .passthrough(); + +const sentMessageSchema = z + .object({ + id: z.string().optional(), + threadId: z.string().optional(), + labelIds: z.array(z.string()).optional(), + }) + .passthrough(); + +const listedMessageSchema = z + .object({ + id: z.string().optional(), + threadId: z.string().optional(), + }) + .passthrough(); + +const listResponseSchema = z + .object({ + resultSizeEstimate: z.number().optional(), + nextPageToken: z.string().optional(), + messages: z.array(listedMessageSchema).optional(), + }) + .passthrough(); + +const headerSchema = z + .object({ + name: z.string().optional(), + value: z.string().optional(), + }) + .passthrough(); + +const messageSchema = z + .object({ + id: z.string().optional(), + threadId: z.string().optional(), + labelIds: z.array(z.string()).optional(), + snippet: z.string().optional(), + historyId: z.string().optional(), + internalDate: z.string().optional(), + payload: z + .object({ + headers: z.array(headerSchema).optional(), + }) + .passthrough() + .optional(), + }) + .passthrough(); + +export const gmailTool = createTool({ + id: "gmail", + description: + "Send and read Gmail email via Google Workspace CLI. Supports actions: send, list, read. Use this for explicit email tasks.", + inputSchema: gmailInputSchema, + outputSchema: gmailOutputSchema, + execute: async (inputData: GmailInput): Promise => { + const authReady = await ensureGwsAuthenticated(); + if (!authReady.ok) { + return { + ok: false, + action: inputData.action, + error: authReady.error, + }; + } + + switch (inputData.action) { + case "send": + return sendEmail(inputData); + case "list": + return listMessages(inputData); + case "read": + return readMessage(inputData); + default: + return { + ok: false, + action: inputData.action, + error: "Unsupported gmail action.", + }; + } + }, +}); + +async function sendEmail(input: Extract): Promise { + const raw = toBase64Url(buildRfc822Message(input.to, input.subject, input.body)); + const result = await runGws([ + "gmail", + "users", + "messages", + "send", + "--params", + JSON.stringify({ userId: "me" }), + "--json", + JSON.stringify({ raw }), + ]); + + if (!result.ok) { + return { + ok: false, + action: "send", + error: extractGwsError(result), + }; + } + + const message = sentMessageSchema.safeParse(parseJson(result.stdout)); + const parsedMessage = message.success ? message.data : null; + return { + ok: true, + action: "send", + data: cleanObject({ + id: parsedMessage?.id ?? null, + threadId: parsedMessage?.threadId ?? null, + labelIds: parsedMessage?.labelIds ?? null, + raw: parsedMessage ?? result.stdout, + }), + }; +} + +async function listMessages(input: Extract): Promise { + const params: Record = { + userId: "me", + maxResults: input.maxResults, + }; + if (input.query?.trim()) { + params.q = input.query.trim(); + } + + const result = await runGws(["gmail", "users", "messages", "list", "--params", JSON.stringify(params)]); + if (!result.ok) { + return { + ok: false, + action: "list", + error: extractGwsError(result), + }; + } + + const parsed = listResponseSchema.safeParse(parseJson(result.stdout)); + const parsedList = parsed.success ? parsed.data : null; + const messages = parsedList?.messages ?? []; + + return { + ok: true, + action: "list", + data: cleanObject({ + resultSizeEstimate: parsedList?.resultSizeEstimate ?? null, + nextPageToken: parsedList?.nextPageToken ?? null, + messages, + raw: parsedList ?? result.stdout, + }), + }; +} + +async function readMessage(input: Extract): Promise { + const params: Record = { + userId: "me", + id: input.messageId, + format: input.format, + }; + + if (input.format === "metadata") { + params.metadataHeaders = ["From", "To", "Subject", "Date"]; + } + + const result = await runGws(["gmail", "users", "messages", "get", "--params", JSON.stringify(params)]); + if (!result.ok) { + return { + ok: false, + action: "read", + error: extractGwsError(result), + }; + } + + const parsed = messageSchema.safeParse(parseJson(result.stdout)); + if (!parsed.success) { + return { + ok: true, + action: "read", + data: { + raw: result.stdout, + }, + }; + } + + const message = parsed.data; + const headers = message.payload?.headers ?? []; + + const normalized = { + id: message.id ?? null, + threadId: message.threadId ?? null, + labelIds: message.labelIds ?? null, + snippet: message.snippet ?? null, + from: findHeader(headers, "From"), + to: findHeader(headers, "To"), + subject: findHeader(headers, "Subject"), + date: findHeader(headers, "Date"), + }; + + return { + ok: true, + action: "read", + data: + input.format === "full" + ? cleanObject({ + ...normalized, + historyId: message.historyId ?? null, + internalDate: message.internalDate ?? null, + payload: message.payload ?? null, + raw: message, + }) + : cleanObject({ + ...normalized, + raw: message, + }), + }; +} + +async function ensureGwsAuthenticated(): Promise<{ ok: true } | { ok: false; error: string }> { + const result = await runGws(["auth", "status"]); + if (!result.ok) { + return { + ok: false, + error: `Unable to verify gws authentication: ${extractGwsError(result)}`, + }; + } + + const parsed = authStatusSchema.safeParse(parseJson(result.stdout)); + if (!parsed.success) { + return { + ok: false, + error: "Unable to parse gws auth status output.", + }; + } + + const authMethod = parsed.data.auth_method ?? "none"; + const credentialSource = parsed.data.credential_source ?? "none"; + const hasCredentials = authMethod !== "none" || credentialSource !== "none"; + + if (!hasCredentials) { + return { + ok: false, + error: "Google Workspace CLI is not authenticated. Run `gws auth login` and retry.", + }; + } + + return { ok: true }; +} + +async function runGws(args: string[]): Promise { + try { + const { stdout, stderr } = await execFileAsync("gws", args, { + timeout: GWS_TIMEOUT_MS, + maxBuffer: GWS_MAX_BUFFER_BYTES, + encoding: "utf8", + }); + + return { + ok: true, + status: 0, + stdout: stdout.trim(), + stderr: stderr.trim(), + }; + } catch (error) { + const execError = error as ExecFileError; + const code = typeof execError.code === "number" ? execError.code : null; + return { + ok: false, + status: code, + stdout: (execError.stdout ?? "").trim(), + stderr: (execError.stderr ?? execError.message ?? "").trim(), + }; + } +} + +function extractGwsError(result: GwsRunResult): string { + const combined = [result.stderr, result.stdout].filter(Boolean).join("\n"); + const directParse = gwsErrorSchema.safeParse(parseJson(combined)); + const fallbackParse = gwsErrorSchema.safeParse(parseJson(extractLikelyJson(combined))); + const parsedError = directParse.success ? directParse : fallbackParse; + const message = parsedError.success ? parsedError.data.error?.message ?? null : null; + const status = result.status == null ? "unknown" : String(result.status); + + if (message) { + return `gws command failed (exit ${status}): ${message}`; + } + + if (combined) { + return `gws command failed (exit ${status}): ${combined}`; + } + + return `gws command failed (exit ${status}).`; +} + +function parseJson(value: string): unknown { + const trimmed = value.trim(); + if (!trimmed) { + return null; + } + try { + return JSON.parse(trimmed); + } catch { + return null; + } +} + +function extractLikelyJson(value: string): string { + const start = value.indexOf("{"); + const end = value.lastIndexOf("}"); + if (start === -1 || end === -1 || end <= start) { + return ""; + } + return value.slice(start, end + 1); +} + +function buildRfc822Message(to: string, subject: string, body: string): string { + return [ + `To: ${to.trim()}`, + `Subject: ${subject.trim()}`, + "Content-Type: text/plain; charset=UTF-8", + "MIME-Version: 1.0", + "", + body, + ].join("\r\n"); +} + +function toBase64Url(value: string): string { + return Buffer.from(value, "utf8") + .toString("base64") + .replace(/\+/g, "-") + .replace(/\//g, "_") + .replace(/=+$/g, ""); +} + +function findHeader(headers: Array>, name: string): string | null { + const target = name.toLowerCase(); + for (const header of headers) { + if (header.name?.toLowerCase() === target) { + return header.value ?? null; + } + } + return null; +} + +function cleanObject(value: Record): Record { + return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== null)); +} From 4afbfa2fa9f8c76aea3aee183bf07116f11bd965 Mon Sep 17 00:00:00 2001 From: Andrew Ho Date: Sun, 12 Apr 2026 11:33:57 -0700 Subject: [PATCH 4/8] feat(runtime): log mastra tool calls/results per turn --- src/index.ts | 102 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/src/index.ts b/src/index.ts index b3e9f18..1dd7c89 100644 --- a/src/index.ts +++ b/src/index.ts @@ -56,6 +56,14 @@ type ReplyDeliveryContext = { type AgentList = Awaited>; type AgentStreamResult = Awaited>; +type AgentToolCallChunk = Awaited[number]; +type AgentToolResultChunk = Awaited[number]; + +type ToolAuditSummary = { + toolCallCount: number; + toolResultCount: number; + toolNames: string[]; +}; // Cache workspace context and prompt at startup — these files don't change mid-session const cachedContextFiles = loadWorkspaceContext(workspaceDir); @@ -202,6 +210,98 @@ function isRecentOutboundReflection(chatGuid: string, text: string): boolean { return freshEntries.some((entry) => entry.normalizedText === normalizedText); } +function toRecord(value: unknown): Record | null { + if (typeof value !== "object" || value === null) { + return null; + } + return value as Record; +} + +function toStringOrNull(value: unknown): string | null { + return typeof value === "string" ? value : null; +} + +function summarizeToolCall(chunk: AgentToolCallChunk): Record { + const payload = toRecord((chunk as { payload?: unknown }).payload); + if (payload === null) { + return { payload: null }; + } + + return { + toolCallId: toStringOrNull(payload.toolCallId), + toolName: toStringOrNull(payload.toolName), + args: payload.args ?? null, + }; +} + +function summarizeToolResult(chunk: AgentToolResultChunk): Record { + const payload = toRecord((chunk as { payload?: unknown }).payload); + if (payload === null) { + return { payload: null }; + } + + return { + toolCallId: toStringOrNull(payload.toolCallId), + toolName: toStringOrNull(payload.toolName), + result: payload.result ?? null, + isError: payload.isError ?? null, + error: payload.error ?? null, + }; +} + +function extractToolNames(calls: AgentToolCallChunk[], results: AgentToolResultChunk[]): string[] { + const names = new Set(); + for (const chunk of calls) { + const payload = toRecord((chunk as { payload?: unknown }).payload); + const toolName = payload === null ? null : toStringOrNull(payload.toolName); + if (toolName !== null) { + names.add(toolName); + } + } + for (const chunk of results) { + const payload = toRecord((chunk as { payload?: unknown }).payload); + const toolName = payload === null ? null : toStringOrNull(payload.toolName); + if (toolName !== null) { + names.add(toolName); + } + } + return [...names]; +} + +async function collectToolAudit( + result: AgentStreamResult, + context: { sender: string; chatGuid: string; msgGuid: string; turnId: number }, +): Promise { + try { + const toolCalls = await result.toolCalls; + const toolResults = await result.toolResults; + const toolNames = extractToolNames(toolCalls, toolResults); + const summary: ToolAuditSummary = { + toolCallCount: toolCalls.length, + toolResultCount: toolResults.length, + toolNames, + }; + + if (summary.toolCallCount > 0 || summary.toolResultCount > 0) { + logger.info("mastra tool activity", { + ...context, + ...summary, + toolCalls: toolCalls.map(summarizeToolCall), + toolResults: toolResults.map(summarizeToolResult), + }); + } + + return summary; + } catch (error) { + logger.errorWithStack("failed to collect mastra tool activity", error, context); + return { + toolCallCount: 0, + toolResultCount: 0, + toolNames: [], + }; + } +} + function computeBubbleDelayMs(text: string, bubbleIndex: number): number { const visibleChars = text.replace(/\s+/g, " ").trim().length; const perChar = randomBetween( @@ -504,6 +604,7 @@ startWebhook( styleState, }, ); + const toolAudit = await collectToolAudit(streamResult, { sender, chatGuid, msgGuid, turnId }); if (!isCurrentTurn(chatGuid, turnId)) { logger.warn("stale turn replaced before delivery", { sender, chatGuid, turnId }); return; @@ -529,6 +630,7 @@ startWebhook( finalReply, bubbleCount: bubbles.length, deliveredCount: delivered.length, + toolAudit, timing: { inboundToProviderMs: timing.providerStartAt ? timing.providerStartAt - timing.inboundAt : undefined, providerToFirstChunkMs: From f758f90af9d347c5a0cdf3c40587891cfe241e33 Mon Sep 17 00:00:00 2001 From: Andrew Ho Date: Sun, 12 Apr 2026 12:44:54 -0700 Subject: [PATCH 5/8] enforce contextual web-search tool usage for recommendation turns --- package.json | 3 +- src/index.ts | 263 +++++++++-- src/mastra/evals/toolcalls-evals.ts | 649 ++++++++++++++++++++++++++ src/mastra/tools/bluebubbles-tools.ts | 18 +- 4 files changed, 887 insertions(+), 46 deletions(-) create mode 100644 src/mastra/evals/toolcalls-evals.ts diff --git a/package.json b/package.json index f42e0b6..096c91b 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "dev": "mastra dev", "build": "mastra build", "eval:imessage": "tsx src/mastra/evals/imessage-evals.ts", - "eval:imessage:live": "tsx src/mastra/evals/imessage-evals.ts --live" + "eval:imessage:live": "tsx src/mastra/evals/imessage-evals.ts --live", + "eval:toolcalls": "tsx src/mastra/evals/toolcalls-evals.ts" }, "keywords": [], "author": "", diff --git a/src/index.ts b/src/index.ts index 1dd7c89..79f5262 100644 --- a/src/index.ts +++ b/src/index.ts @@ -65,6 +65,17 @@ type ToolAuditSummary = { toolNames: string[]; }; +type WebSearchRequirement = "none" | "explicit" | "contextual"; + +type DeliveryResult = { + rawReply: string; + finalReply: string; + bubbles: string[]; + delivered: string[]; + missingRequiredTool: boolean; + observedToolNames: string[]; +}; + // Cache workspace context and prompt at startup — these files don't change mid-session const cachedContextFiles = loadWorkspaceContext(workspaceDir); const cachedBaseInstructions = systemPrompt?.trim() @@ -268,6 +279,94 @@ function extractToolNames(calls: AgentToolCallChunk[], results: AgentToolResultC return [...names]; } +function isExplicitWebSearchRequest(text: string): boolean { + const normalized = text.toLowerCase(); + const directSignals = [ + /\buse\s+web\s*search\b/, + /\bdo\s+(?:a\s+)?web\s*search\b/, + /\bdid\s+(?:u|you)\s+web\s*search\b/, + /\bsearch\s+(?:online|the web)\b/, + /\blook\s+it\s+up\b/, + /\bgoogle\s+it\b/, + ]; + return directSignals.some((pattern) => pattern.test(normalized)); +} + +function hasCuisineSignal(text: string): boolean { + return /\b(sushi|ramen|pho|thai|kbbq|bbq|tacos?|pizza|pasta|hotpot|dim\s*sum|poke|burgers?|wings|udon|omakase)\b/i.test( + text, + ); +} + +function hasLocationSignal(text: string): boolean { + if ( + /\b(san jose|santa clara|berkeley|oakland|san francisco|sf|palo alto|sunnyvale|cupertino|fremont|mountain view|milpitas|downtown|south bay|east bay|peninsula)\b/i.test( + text, + ) + ) { + return true; + } + + if (/\b(?:near me|nearby|wya|what area|which area)\b/i.test(text)) { + return true; + } + + return /\b(?:in|near|around|by)\s+[a-z]+(?:\s+[a-z]+){0,2}\b/i.test(text); +} + +function computeWebSearchRequirement(params: { + inboundText: string; + historyBeforeCurrent: HistoryEntry[]; + intent: ReplyIntent; +}): WebSearchRequirement { + if (isExplicitWebSearchRequest(params.inboundText)) { + return "explicit"; + } + + if (params.intent !== "recommendation") { + return "none"; + } + + const conversationText = [ + ...params.historyBeforeCurrent.map((entry) => entry.body), + params.inboundText, + ].join("\n"); + + const hasEnoughContext = hasCuisineSignal(conversationText) && hasLocationSignal(conversationText); + return hasEnoughContext ? "contextual" : "none"; +} + +function buildTurnInput(params: { + historyContext: string; + webSearchRequirement: WebSearchRequirement; + attempt: number; +}): string { + if (params.webSearchRequirement === "none") { + return params.historyContext; + } + + const directives = + params.webSearchRequirement === "explicit" + ? [ + "[Runtime directive]", + "The user explicitly requested web search in this turn.", + "You must call the tool `web_search` at least once before sending any user-facing text.", + "Do not send placeholder text like 'gimme sec', 'checking rn', or any draft search query.", + ] + : [ + "[Runtime directive]", + "You now have enough recommendation context to provide specific places.", + "Before naming places, call the tool `web_search` at least once in this turn.", + "If context is unexpectedly still missing, ask one short follow-up instead of making up places.", + ]; + + if (params.attempt > 0) { + directives.push("This is a retry because the previous attempt did not call `web_search`."); + } + + return `${params.historyContext}\n\n${directives.join("\n")}`; +} + async function collectToolAudit( result: AgentStreamResult, context: { sender: string; chatGuid: string; msgGuid: string; turnId: number }, @@ -355,7 +454,8 @@ async function deliverStreamingReply( turnId: number, timing: TurnTiming, context: ReplyDeliveryContext, -): Promise<{ rawReply: string; finalReply: string; bubbles: string[]; delivered: string[] }> { + requiredToolName: string | null, +): Promise { const parser = createPrivateBubbleParser(); const bubbles: string[] = []; const deferDeliveryForIntent = true; @@ -471,6 +571,31 @@ async function deliverStreamingReply( let finalReply = rawReply; const trailingBubbles = parser.flush(); + + const toolCalls = await result.toolCalls; + const toolResults = await result.toolResults; + const observedToolNames = extractToolNames(toolCalls, toolResults); + const missingRequiredTool = + requiredToolName !== null && !observedToolNames.includes(requiredToolName); + + if (missingRequiredTool) { + logger.warn("required tool missing in streamed turn", { + chatGuid, + turnId, + requiredToolName, + observedToolNames, + rawReply, + }); + return { + rawReply, + finalReply: "", + bubbles: [], + delivered: [], + missingRequiredTool: true, + observedToolNames, + }; + } + if (deferDeliveryForIntent) { const policyResult = applyReplyPolicy({ rawText: rawReply, @@ -510,7 +635,14 @@ async function deliverStreamingReply( bubbles, }); - return { rawReply, finalReply, bubbles, delivered: [...bubbles] }; + return { + rawReply, + finalReply, + bubbles, + delivered: [...bubbles], + missingRequiredTool: false, + observedToolNames, + }; } const agents = await buildAgents(); @@ -550,6 +682,11 @@ startWebhook( const historyBeforeCurrent = historyEntries.slice(0, -1); const replyIntent = detectReplyIntent(text); const responseTokenBudget = computeResponseTokenBudget(text, replyIntent); + const webSearchRequirement = computeWebSearchRequirement({ + inboundText: text, + historyBeforeCurrent, + intent: replyIntent, + }); const styleState = buildConversationStyleState({ recentAssistantTexts: getRecentTextsBySender(historyBeforeCurrent, "assistant"), recentUserTexts: [...getRecentTextsBySender(historyBeforeCurrent, "user"), text], @@ -565,46 +702,100 @@ startWebhook( await forceStopTyping(chatGuid, { sender, chatGuid, turnId, phase: "pre-turn-clear" }); try { - let streamResult: AgentStreamResult | undefined; - for (let i = 0; i < agents.length; i++) { - try { - timing.providerStartAt = Date.now(); - if (i > 0 && timing.fallbackAt == null) { - timing.fallbackAt = timing.providerStartAt; - } - streamResult = await agents[i]!.stream( - historyContext, - buildProviderOptions(cachedInstructions, i > 0, responseTokenBudget), - ); - - if (streamResult.error) { - throw streamResult.error; + let streamResult: AgentStreamResult | null = null; + let delivery: DeliveryResult | null = null; + let toolAudit: ToolAuditSummary | null = null; + let attempt = 0; + + while (attempt < 2) { + streamResult = null; + for (let i = 0; i < agents.length; i++) { + try { + timing.providerStartAt = Date.now(); + if (i > 0 && timing.fallbackAt == null) { + timing.fallbackAt = timing.providerStartAt; + } + streamResult = await agents[i]!.stream( + buildTurnInput({ + historyContext, + webSearchRequirement, + attempt, + }), + buildProviderOptions(cachedInstructions, i > 0, responseTokenBudget), + ); + + if (streamResult.error) { + throw streamResult.error; + } + + if (i > 0) logger.info("using fallback provider", { fallbackIndex: i }); + break; + } catch (err) { + if (isRateLimitError(err) && i < agents.length - 1) { + logger.warn("rate limit hit, trying fallback", { attemptIndex: i, fallbackIndex: i + 1 }); + continue; + } + throw err; } + } + + if (streamResult === null) { + throw new Error("all providers exhausted"); + } + + delivery = await deliverStreamingReply( + streamResult, + chatGuid, + turnId, + timing, + { + inboundText: text, + intent: replyIntent, + styleState, + }, + webSearchRequirement === "none" ? null : "web_search", + ); + toolAudit = await collectToolAudit(streamResult, { sender, chatGuid, msgGuid, turnId }); - if (i > 0) logger.info("using fallback provider", { fallbackIndex: i }); + if (!delivery.missingRequiredTool) { break; - } catch (err) { - if (isRateLimitError(err) && i < agents.length - 1) { - logger.warn("rate limit hit, trying fallback", { attemptIndex: i, fallbackIndex: i + 1 }); - continue; - } - throw err; } + + logger.warn("retrying turn because required tool was not called", { + sender, + chatGuid, + msgGuid, + turnId, + requiredToolName: "web_search", + webSearchRequirement, + observedToolNames: delivery.observedToolNames, + attempt, + }); + attempt += 1; } - if (!streamResult) throw new Error("all providers exhausted"); - const { rawReply, finalReply, bubbles, delivered } = await deliverStreamingReply( - streamResult, - chatGuid, - turnId, - timing, - { - inboundText: text, - intent: replyIntent, - styleState, - }, - ); - const toolAudit = await collectToolAudit(streamResult, { sender, chatGuid, msgGuid, turnId }); + if (delivery === null || toolAudit === null) { + throw new Error("turn delivery failed"); + } + + if (delivery.missingRequiredTool) { + const failureReply = + webSearchRequirement === "explicit" + ? "i couldnt run web search rn. send it again and i'll retry." + : "i couldnt run web search for that rec yet. gimme area + cuisine and i'll retry."; + await sendMessage(serverUrl, password, chatGuid, failureReply); + rememberOutbound(chatGuid, failureReply); + delivery = { + rawReply: delivery.rawReply, + finalReply: failureReply, + bubbles: [failureReply], + delivered: [failureReply], + missingRequiredTool: true, + observedToolNames: delivery.observedToolNames, + }; + } + + const { rawReply, finalReply, bubbles, delivered } = delivery; if (!isCurrentTurn(chatGuid, turnId)) { logger.warn("stale turn replaced before delivery", { sender, chatGuid, turnId }); return; diff --git a/src/mastra/evals/toolcalls-evals.ts b/src/mastra/evals/toolcalls-evals.ts new file mode 100644 index 0000000..0c38d2b --- /dev/null +++ b/src/mastra/evals/toolcalls-evals.ts @@ -0,0 +1,649 @@ +import { chmodSync, existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs"; +import { tmpdir } from "os"; +import { join } from "path"; +import { createServer, type IncomingMessage, type ServerResponse } from "http"; +import { createOpenAI } from "@ai-sdk/openai"; + +type EvalCheck = { + id: string; + pass: boolean; + details: string; +}; + +type RecordedRequest = { + method: string; + path: string; + query: Record; + body: string; +}; + +type MockServer = { + url: string; + requests: RecordedRequest[]; + close: () => Promise; +}; + +type OperationResultLike = { + ok: boolean; + error: string | null; +}; + +type GmailResultLike = { + ok: boolean; + error?: string | null; +}; + +type ToolLike = { + execute: (input: Input) => Promise; +}; + +type LoadedTools = { + gmailTool: ToolLike< + | { action: "send"; to: string; subject: string; body: string } + | { action: "list"; query: string; maxResults: number } + | { action: "read"; messageId: string; format: "metadata" | "full" }, + GmailResultLike + >; + bluebubblesChatQueryTool: ToolLike<{ limit: number; offset: number; with: string[]; sort: string | null }, OperationResultLike>; + bluebubblesMessageQueryTool: ToolLike< + { + limit: number; + offset: number; + chatGuid: string | null; + with: string[]; + sort: "ASC" | "DESC"; + whereStatement: string | null; + whereText: string | null; + }, + OperationResultLike + >; + bluebubblesMessageSearchTool: ToolLike< + { query: string; limit: number; offset: number; chatGuid: string | null }, + OperationResultLike + >; + bluebubblesSendTextTool: ToolLike< + { + chatGuid: string; + message: string; + method: "apple-script" | "private-api"; + subject: string | null; + effectId: string | null; + selectedMessageGuid: string | null; + partIndex: number; + }, + OperationResultLike + >; + bluebubblesMarkReadTool: ToolLike<{ chatGuid: string }, OperationResultLike>; + bluebubblesMarkUnreadTool: ToolLike<{ chatGuid: string }, OperationResultLike>; + bluebubblesTypingStartTool: ToolLike<{ chatGuid: string }, OperationResultLike>; + bluebubblesTypingStopTool: ToolLike<{ chatGuid: string }, OperationResultLike>; + bluebubblesReactTool: ToolLike< + { + chatGuid: string; + selectedMessageGuid: string; + selectedMessageText: string | null; + reaction: "love" | "like" | "dislike" | "laugh" | "emphasize" | "question" | "-love" | "-like" | "-dislike" | "-laugh" | "-emphasize" | "-question"; + partIndex: number; + }, + OperationResultLike + >; + bluebubblesAttachmentUploadTool: ToolLike<{ filePath: string; formFieldName: string; uploadFileName: string | null }, OperationResultLike>; + bluebubblesSendAttachmentTool: ToolLike< + { + chatGuid: string; + filePath: string; + formFieldName: string; + uploadFileName: string | null; + method: "apple-script" | "private-api"; + subject: string | null; + effectId: string | null; + selectedMessageGuid: string | null; + partIndex: number; + }, + OperationResultLike + >; +}; + +type BuildAgentsFn = () => Promise Promise> }>>; + +const DEFAULT_TYPING_DELAY_PROFILE = { + minMs: 450, + maxMs: 4200, + perCharMinMs: 55, + perCharMaxMs: 90, + bubbleOverheadMinMs: 200, + bubbleOverheadMaxMs: 450, + jitterMin: 0.88, + jitterMax: 1.15, + interBubbleMinMs: 700, + interBubbleMaxMs: 1800, +}; + +function pass(id: string, details: string): EvalCheck { + return { id, pass: true, details }; +} + +function fail(id: string, details: string): EvalCheck { + return { id, pass: false, details }; +} + +function format(check: EvalCheck): string { + return `${check.pass ? "PASS" : "FAIL"} ${check.id}: ${check.details}`; +} + +function summarize(checks: EvalCheck[]): { total: number; passed: number; failed: number } { + const failed = checks.filter((item) => !item.pass).length; + return { + total: checks.length, + passed: checks.length - failed, + failed, + }; +} + +function parseJsonValue(value: string): unknown { + try { + return JSON.parse(value) as unknown; + } catch { + return null; + } +} + +function parseJsonObject(value: string): Record | null { + const parsed = parseJsonValue(value); + if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) { + return parsed as Record; + } + return null; +} + +function collectBody(request: IncomingMessage): Promise { + return new Promise((resolve) => { + let body = ""; + request.on("data", (chunk: Buffer | string) => { + body += chunk.toString(); + }); + request.on("end", () => resolve(body)); + }); +} + +function sendJson(response: ServerResponse, statusCode: number, payload: Record): void { + const json = JSON.stringify(payload); + response.writeHead(statusCode, { + "content-type": "application/json", + "content-length": Buffer.byteLength(json).toString(), + }); + response.end(json); +} + +async function startMockBluebubblesServer(password: string): Promise { + const requests: RecordedRequest[] = []; + + const server = createServer(async (request, response) => { + const method = request.method ?? "GET"; + const url = new URL(request.url ?? "/", "http://127.0.0.1"); + const body = await collectBody(request); + const query = Object.fromEntries(url.searchParams.entries()); + + requests.push({ + method, + path: url.pathname, + query, + body, + }); + + if (query.password !== password) { + sendJson(response, 401, { message: "invalid password" }); + return; + } + + if (url.pathname === "/api/v1/message/query" && method === "POST") { + sendJson(response, 200, { items: [{ guid: "msg-1" }] }); + return; + } + + sendJson(response, 200, { + status: 200, + message: "ok", + data: { + path: url.pathname, + method, + }, + }); + }); + + await new Promise((resolve) => { + server.listen(0, "127.0.0.1", () => resolve()); + }); + + const address = server.address(); + if (address === null || typeof address === "string") { + throw new Error("Failed to start mock BlueBubbles server."); + } + + return { + url: `http://127.0.0.1:${address.port}`, + requests, + close: async () => + await new Promise((resolve, reject) => { + server.close((error) => { + if (error) { + reject(error); + return; + } + resolve(); + }); + }), + }; +} + +function makeTempAgentHome(serverUrl: string, password: string): string { + const root = mkdtempSync(join(tmpdir(), "agent-toolcalls-home-")); + const dir = join(root, ".agent"); + mkdirSync(dir, { recursive: true }); + mkdirSync(join(dir, "workspace"), { recursive: true }); + + const config = { + logging: { + level: "debug", + prettyPrint: false, + verbose: true, + }, + bluebubbles: { + serverUrl, + password, + webhookPort: 9999, + allowFrom: ["+10000000000"], + }, + provider: { + model: "gpt-4o-mini", + baseURL: "https://example.com/v1", + apiKey: "test-key", + }, + fallbacks: [], + agent: { + workspaceDir: "~/workspace", + webhookDebounceMs: 100, + responseTimingMultiplier: 0.8, + maxBubblesPerReply: 3, + recentHistoryLimit: 10, + textChunkLimit: 280, + typingDelayProfile: DEFAULT_TYPING_DELAY_PROFILE, + systemPrompt: "toolcall eval", + }, + }; + + writeFileSync(join(dir, "config.json"), JSON.stringify(config, null, 2)); + return root; +} + +function makeMockGwsBin(root: string): { binDir: string; logPath: string } { + const binDir = join(root, "bin"); + mkdirSync(binDir, { recursive: true }); + const logPath = join(root, "gws.log"); + writeFileSync(logPath, ""); + + const scriptPath = join(binDir, "gws"); + const script = `#!/usr/bin/env node +const fs = require("fs"); +const args = process.argv.slice(2); +const logPath = process.env.GWS_LOG_PATH; +if (logPath) fs.appendFileSync(logPath, JSON.stringify(args) + "\\n"); +function out(v){ process.stdout.write(JSON.stringify(v)); } +if (args[0] === "auth" && args[1] === "status") { + out({ auth_method: "oauth", credential_source: "keyring" }); + process.exit(0); +} +if (args[0] === "gmail" && args[1] === "users" && args[2] === "messages" && args[3] === "send") { + out({ id: "msg-send-1", threadId: "thread-1", labelIds: ["SENT"] }); + process.exit(0); +} +if (args[0] === "gmail" && args[1] === "users" && args[2] === "messages" && args[3] === "list") { + out({ resultSizeEstimate: 1, messages: [{ id: "msg-1", threadId: "thread-1" }] }); + process.exit(0); +} +if (args[0] === "gmail" && args[1] === "users" && args[2] === "messages" && args[3] === "get") { + out({ + id: "msg-1", + threadId: "thread-1", + labelIds: ["INBOX"], + snippet: "hello", + payload: { + headers: [ + { name: "From", value: "a@example.com" }, + { name: "To", value: "b@example.com" }, + { name: "Subject", value: "Subj" }, + { name: "Date", value: "Mon, 01 Jan 2024 00:00:00 +0000" } + ] + } + }); + process.exit(0); +} +process.stderr.write(JSON.stringify({ error: { message: "unsupported gws command" } })); +process.exit(1); +`; + + writeFileSync(scriptPath, script, { mode: 0o755 }); + chmodSync(scriptPath, 0o755); + return { binDir, logPath }; +} + +function requestHit( + requests: RecordedRequest[], + method: string, + path: string, + matcher: ((request: RecordedRequest) => boolean) | null = null, +): boolean { + return requests.some((request) => { + if (request.method !== method || request.path !== path) { + return false; + } + return matcher === null ? true : matcher(request); + }); +} + +function runEndpointChecks(requests: RecordedRequest[]): EvalCheck[] { + const checks: EvalCheck[] = []; + const endpointDefs = [ + { id: "bluebubbles.chat.query", method: "POST", path: "/api/v1/chat/query" }, + { id: "bluebubbles.message.query", method: "POST", path: "/api/v1/message/query" }, + { id: "bluebubbles.message.send_text", method: "POST", path: "/api/v1/message/text" }, + { id: "bluebubbles.chat.mark_read", method: "POST", path: "/api/v1/chat/chat-1/read" }, + { id: "bluebubbles.chat.mark_unread", method: "POST", path: "/api/v1/chat/chat-1/unread" }, + { id: "bluebubbles.typing.start", method: "POST", path: "/api/v1/chat/chat-1/typing" }, + { id: "bluebubbles.typing.stop", method: "DELETE", path: "/api/v1/chat/chat-1/typing" }, + { id: "bluebubbles.message.react", method: "POST", path: "/api/v1/message/react" }, + { id: "bluebubbles.attachment.upload", method: "POST", path: "/api/v1/attachment/upload" }, + { id: "bluebubbles.message.send_attachment", method: "POST", path: "/api/v1/message/attachment" }, + ]; + + for (const def of endpointDefs) { + checks.push( + requestHit(requests, def.method, def.path) + ? pass(def.id, `${def.method} ${def.path} observed`) + : fail(def.id, `${def.method} ${def.path} not observed`), + ); + } + + checks.push( + requestHit(requests, "POST", "/api/v1/message/query", (request) => { + const parsed = parseJsonObject(request.body); + const where = parsed?.where; + if (!Array.isArray(where) || where.length === 0) { + return false; + } + const first = where[0]; + if (typeof first !== "object" || first === null) { + return false; + } + const statement = (first as Record).statement; + return statement === "message.text LIKE :text"; + }) + ? pass("bluebubbles.message.search", "message search query pattern observed") + : fail("bluebubbles.message.search", "message search query pattern missing"), + ); + + return checks; +} + +function runGwsChecks(logPath: string): EvalCheck[] { + const checks: EvalCheck[] = []; + if (!existsSync(logPath)) { + return [fail("gmail.gws.log", "gws log file not found")]; + } + + const lines = readFileSync(logPath, "utf8") + .split("\n") + .map((line) => line.trim()) + .filter(Boolean); + const commands = lines + .map((line) => parseJsonValue(line)) + .filter((row): row is unknown[] => Array.isArray(row)) + .map((row) => row.map((item) => String(item)).join(" ")); + + const match = (snippet: string): boolean => commands.some((command) => command.includes(snippet)); + + checks.push(match("auth status") ? pass("gmail.auth.status", "auth status command observed") : fail("gmail.auth.status", "auth status command missing")); + checks.push( + match("gmail users messages send") + ? pass("gmail.send", "send command observed") + : fail("gmail.send", "send command missing"), + ); + checks.push( + match("gmail users messages list") + ? pass("gmail.list", "list command observed") + : fail("gmail.list", "list command missing"), + ); + checks.push( + match("gmail users messages get") + ? pass("gmail.read", "get command observed") + : fail("gmail.read", "get command missing"), + ); + + return checks; +} + +async function runToolExecutions(tempRoot: string, tools: LoadedTools): Promise { + const checks: EvalCheck[] = []; + const attachmentFile = join(tempRoot, "sample.txt"); + writeFileSync(attachmentFile, "hello"); + + const gmailSend = await tools.gmailTool.execute({ + action: "send", + to: "x@example.com", + subject: "Hi", + body: "Body", + }); + checks.push(gmailSend.ok ? pass("tool.gmail.send.execute", "gmail send returned ok") : fail("tool.gmail.send.execute", String(gmailSend.error ?? null))); + + const gmailList = await tools.gmailTool.execute({ + action: "list", + maxResults: 5, + query: "in:inbox", + }); + checks.push(gmailList.ok ? pass("tool.gmail.list.execute", "gmail list returned ok") : fail("tool.gmail.list.execute", String(gmailList.error ?? null))); + + const gmailRead = await tools.gmailTool.execute({ + action: "read", + messageId: "msg-1", + format: "metadata", + }); + checks.push(gmailRead.ok ? pass("tool.gmail.read.execute", "gmail read returned ok") : fail("tool.gmail.read.execute", String(gmailRead.error ?? null))); + + const bbCalls: Array<{ id: string; result: OperationResultLike }> = [ + { + id: "tool.bluebubbles.chat.query.execute", + result: await tools.bluebubblesChatQueryTool.execute({ limit: 5, offset: 0, with: ["participants"], sort: null }), + }, + { + id: "tool.bluebubbles.message.query.execute", + result: await tools.bluebubblesMessageQueryTool.execute({ + limit: 5, + offset: 0, + chatGuid: "chat-1", + with: ["chat"], + sort: "DESC", + whereStatement: "message.text = :text", + whereText: "pho", + }), + }, + { + id: "tool.bluebubbles.message.search.execute", + result: await tools.bluebubblesMessageSearchTool.execute({ + query: "pho", + limit: 5, + offset: 0, + chatGuid: "chat-1", + }), + }, + { + id: "tool.bluebubbles.send_text.execute", + result: await tools.bluebubblesSendTextTool.execute({ + chatGuid: "chat-1", + message: "hello", + method: "apple-script", + subject: null, + effectId: null, + selectedMessageGuid: null, + partIndex: 0, + }), + }, + { + id: "tool.bluebubbles.mark_read.execute", + result: await tools.bluebubblesMarkReadTool.execute({ chatGuid: "chat-1" }), + }, + { + id: "tool.bluebubbles.mark_unread.execute", + result: await tools.bluebubblesMarkUnreadTool.execute({ chatGuid: "chat-1" }), + }, + { + id: "tool.bluebubbles.typing_start.execute", + result: await tools.bluebubblesTypingStartTool.execute({ chatGuid: "chat-1" }), + }, + { + id: "tool.bluebubbles.typing_stop.execute", + result: await tools.bluebubblesTypingStopTool.execute({ chatGuid: "chat-1" }), + }, + { + id: "tool.bluebubbles.react.execute", + result: await tools.bluebubblesReactTool.execute({ + chatGuid: "chat-1", + selectedMessageGuid: "msg-1", + selectedMessageText: null, + reaction: "love", + partIndex: 0, + }), + }, + { + id: "tool.bluebubbles.attachment_upload.execute", + result: await tools.bluebubblesAttachmentUploadTool.execute({ + filePath: attachmentFile, + formFieldName: "attachment", + uploadFileName: null, + }), + }, + { + id: "tool.bluebubbles.send_attachment.execute", + result: await tools.bluebubblesSendAttachmentTool.execute({ + chatGuid: "chat-1", + filePath: attachmentFile, + formFieldName: "attachment", + uploadFileName: null, + method: "apple-script", + subject: null, + effectId: null, + selectedMessageGuid: null, + partIndex: 0, + }), + }, + ]; + + for (const call of bbCalls) { + checks.push(call.result.ok ? pass(call.id, "returned ok") : fail(call.id, String(call.result.error))); + } + + return checks; +} + +async function runWiringChecks(buildAgents: BuildAgentsFn): Promise { + const checks: EvalCheck[] = []; + const openai = createOpenAI(); + const webSearch = openai.tools.webSearch({ searchContextSize: "medium" }); + const webSearchRecord = webSearch as unknown as Record; + checks.push( + webSearchRecord.id === "openai.web_search" + ? pass("tool.web_search.provider_id", "provider web_search id is openai.web_search") + : fail("tool.web_search.provider_id", `unexpected provider id: ${String(webSearchRecord.id)}`), + ); + + const agents = await buildAgents(); + if (agents.length === 0) { + checks.push(fail("agent.tools.registration", "no agent instances returned")); + return checks; + } + + const tools = await agents[0].listTools(); + const toolNames = Object.keys(tools); + const expected = [ + "gmail", + "web_search", + "bluebubbles_chat_query", + "bluebubbles_message_query", + "bluebubbles_message_search", + "bluebubbles_message_send_text", + "bluebubbles_chat_mark_read", + "bluebubbles_chat_mark_unread", + "bluebubbles_typing_start", + "bluebubbles_typing_stop", + "bluebubbles_message_react", + "bluebubbles_attachment_upload", + "bluebubbles_message_send_attachment", + ]; + + for (const name of expected) { + checks.push( + toolNames.includes(name) + ? pass(`agent.tools.${name}`, "registered") + : fail(`agent.tools.${name}`, "missing"), + ); + } + + return checks; +} + +async function main(): Promise { + const password = "pw-test"; + const mockServer = await startMockBluebubblesServer(password); + const tempHomeRoot = makeTempAgentHome(mockServer.url, password); + const gwsMock = makeMockGwsBin(tempHomeRoot); + const previousHome = process.env.HOME; + const previousPath = process.env.PATH ?? ""; + const previousGwsLogPath = process.env.GWS_LOG_PATH; + const checks: EvalCheck[] = []; + + try { + process.env.HOME = tempHomeRoot; + process.env.PATH = `${gwsMock.binDir}:${previousPath}`; + process.env.GWS_LOG_PATH = gwsMock.logPath; + + const bluebubblesModule = (await import("../tools/bluebubbles-tools")) as unknown as Omit; + const gmailModule = (await import("../tools/gmail-tool")) as unknown as Pick; + const agentsModule = (await import("../agents")) as unknown as { buildAgents: BuildAgentsFn }; + + const loadedTools: LoadedTools = { + ...bluebubblesModule, + ...gmailModule, + }; + + checks.push(...(await runToolExecutions(tempHomeRoot, loadedTools))); + checks.push(...runEndpointChecks(mockServer.requests)); + checks.push(...runGwsChecks(gwsMock.logPath)); + checks.push(...(await runWiringChecks(agentsModule.buildAgents))); + } finally { + await mockServer.close(); + if (previousHome) { + process.env.HOME = previousHome; + } else { + delete process.env.HOME; + } + process.env.PATH = previousPath; + if (previousGwsLogPath) { + process.env.GWS_LOG_PATH = previousGwsLogPath; + } else { + delete process.env.GWS_LOG_PATH; + } + rmSync(tempHomeRoot, { recursive: true, force: true }); + } + + for (const check of checks) { + console.log(format(check)); + } + + const summary = summarize(checks); + console.log(`\nSummary: ${summary.passed}/${summary.total} passed, ${summary.failed} failed`); + if (summary.failed > 0) { + process.exitCode = 1; + } +} + +await main(); + diff --git a/src/mastra/tools/bluebubbles-tools.ts b/src/mastra/tools/bluebubbles-tools.ts index 89851bb..558b641 100644 --- a/src/mastra/tools/bluebubbles-tools.ts +++ b/src/mastra/tools/bluebubbles-tools.ts @@ -219,7 +219,7 @@ export const bluebubblesChatQueryTool = createTool({ params: { query: { password } }, body, }); - if (error !== null) { + if (error != null) { return fail("bluebubbles_chat_query", "Chat query failed.", { error }); } return ok("bluebubbles_chat_query", responseData(data)); @@ -260,7 +260,7 @@ export const bluebubblesMessageQueryTool = createTool({ body, }); - if (error !== null) { + if (error != null) { return fail("bluebubbles_message_query", "Message query failed.", { error }); } return ok("bluebubbles_message_query", responseData(data)); @@ -297,7 +297,7 @@ export const bluebubblesMessageSearchTool = createTool({ params: { query: { password } }, body, }); - if (error !== null) { + if (error != null) { return fail("bluebubbles_message_search", "Message search failed.", { error }); } return ok("bluebubbles_message_search", responseData(data)); @@ -329,7 +329,7 @@ export const bluebubblesSendTextTool = createTool({ params: { query: { password } }, body, }); - if (error !== null) { + if (error != null) { return fail("bluebubbles_message_send_text", "Send text failed.", { error }); } return ok("bluebubbles_message_send_text", responseData(data)); @@ -351,7 +351,7 @@ export const bluebubblesMarkReadTool = createTool({ params: { path: { chatGuid: inputData.chatGuid }, query: { password } }, body: {}, }); - if (error !== null) { + if (error != null) { return fail("bluebubbles_chat_mark_read", "Mark read failed.", { error }); } return ok("bluebubbles_chat_mark_read", responseData(data)); @@ -373,7 +373,7 @@ export const bluebubblesMarkUnreadTool = createTool({ params: { path: { chatGuid: inputData.chatGuid }, query: { password } }, body: {}, }); - if (error !== null) { + if (error != null) { return fail("bluebubbles_chat_mark_unread", "Mark unread failed.", { error }); } return ok("bluebubbles_chat_mark_unread", responseData(data)); @@ -394,7 +394,7 @@ export const bluebubblesTypingStartTool = createTool({ const { data, error } = await client.POST("/api/v1/chat/{chatGuid}/typing", { params: { path: { chatGuid: inputData.chatGuid }, query: { password } }, }); - if (error !== null) { + if (error != null) { return fail("bluebubbles_typing_start", "Typing start failed.", { error }); } return ok("bluebubbles_typing_start", responseData(data)); @@ -415,7 +415,7 @@ export const bluebubblesTypingStopTool = createTool({ const { data, error } = await client.DELETE("/api/v1/chat/{chatGuid}/typing", { params: { path: { chatGuid: inputData.chatGuid }, query: { password } }, }); - if (error !== null) { + if (error != null) { return fail("bluebubbles_typing_stop", "Typing stop failed.", { error }); } return ok("bluebubbles_typing_stop", responseData(data)); @@ -444,7 +444,7 @@ export const bluebubblesReactTool = createTool({ params: { query: { password } }, body, }); - if (error !== null) { + if (error != null) { return fail("bluebubbles_message_react", "Send reaction failed.", { error }); } return ok("bluebubbles_message_react", responseData(data)); From b44bbb26ee708ef8f316ad95cc384d6b8930afa0 Mon Sep 17 00:00:00 2001 From: Andrew Ho Date: Sun, 12 Apr 2026 12:57:21 -0700 Subject: [PATCH 6/8] fix webhook runtime diagnostics and guid auth edge cases --- README.md | 14 ++++++++++++++ src/doctor.ts | 43 +++++++++++++++++++++++++++++++++++++++++- src/index.ts | 8 ++++++++ src/lib/bluebubbles.ts | 32 +++++++++++++++++++++++++++++-- 4 files changed, 94 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 537a7a4..f412947 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,20 @@ npm run dev Open [http://localhost:4111](http://localhost:4111) in your browser to access [Mastra Studio](https://mastra.ai/docs/studio/overview). It provides an interactive UI for building and testing your agents, along with a REST API that exposes your Mastra application as a local service. This lets you start building without worrying about integration right away. +Start the iMessage runtime webhook listener: + +```shell +npm run start +``` + +`npm run dev` does not start the BlueBubbles webhook listener. + +If your BlueBubbles password contains URL-reserved characters (for example `#`), use an encoded guid value in the webhook URL: + +```text +?guid= +``` + You can start editing files inside the `src/mastra` directory. The development server will automatically reload whenever you make changes. ## Learn more diff --git a/src/doctor.ts b/src/doctor.ts index 0c647f4..73a8bc1 100644 --- a/src/doctor.ts +++ b/src/doctor.ts @@ -54,6 +54,21 @@ function checkConfig(): CheckResult[] { details: `model=${config.provider.model} baseURL=${config.provider.baseURL ?? "codex-oauth"} workspace=${config.agent.workspaceDir}`, }); + const encodedPassword = encodeURIComponent(config.bluebubbles.password); + if (encodedPassword !== config.bluebubbles.password) { + results.push({ + status: "WARN", + name: "webhook guid encoding", + details: `password contains URL-reserved characters; configure webhook guid as ${encodedPassword}`, + }); + } else { + results.push({ + status: "PASS", + name: "webhook guid encoding", + details: "password has no URL-reserved characters", + }); + } + return results; } @@ -178,6 +193,32 @@ function checkProcesses(): CheckResult[] { } const listeners = runCommand(["lsof", "-nP", `-iTCP:${config.bluebubbles.webhookPort}`, "-sTCP:LISTEN"]); + const runtime = runCommand(["pgrep", "-af", "tsx src/index.ts"]); + const runtimeBuild = runCommand(["pgrep", "-af", "node .mastra/output/index.mjs"]); + const mastraDev = runCommand(["pgrep", "-af", "mastra dev"]); + + const runtimeDetected = + (runtime.ok && runtime.stdout.length > 0) || (runtimeBuild.ok && runtimeBuild.stdout.length > 0); + if (runtimeDetected) { + results.push({ + status: "PASS", + name: "agent runtime", + details: runtime.stdout || runtimeBuild.stdout, + }); + } else if (mastraDev.ok && mastraDev.stdout.length > 0) { + results.push({ + status: "WARN", + name: "agent runtime", + details: "mastra dev is running, but webhook runtime is not. Run `npm run start` for iMessage handling.", + }); + } else { + results.push({ + status: "WARN", + name: "agent runtime", + details: "runtime not detected. Start with `npm run start`.", + }); + } + if (listeners.ok && listeners.stdout) { results.push({ status: "PASS", @@ -188,7 +229,7 @@ function checkProcesses(): CheckResult[] { results.push({ status: "WARN", name: "webhook listener", - details: `nothing is currently listening on :${config.bluebubbles.webhookPort}`, + details: `nothing is listening on :${config.bluebubbles.webhookPort}; run npm run start (npm run dev does not start webhook).`, }); } diff --git a/src/index.ts b/src/index.ts index 79f5262..2cc5f48 100644 --- a/src/index.ts +++ b/src/index.ts @@ -27,6 +27,8 @@ const { systemPrompt, } = config.agent; const logger = getModuleLogger("runtime"); +const encodedWebhookGuid = encodeURIComponent(password); +const webhookGuidNeedsEncoding = encodedWebhookGuid !== password; // Track active typing chats so we can clear them on shutdown const typingChats = new Set(); @@ -650,6 +652,12 @@ logger.info("agent ready", { primary: { model: config.provider.model, baseURL: config.provider.baseURL ?? "codex-oauth" }, fallbacks: config.fallbacks.map((f) => ({ model: f.model, baseURL: f.baseURL ?? "codex-oauth" })), }); +if (webhookGuidNeedsEncoding) { + logger.warn("bluebubbles webhook guid requires URL encoding", { + webhookPort, + encodedGuid: encodedWebhookGuid, + }); +} startWebhook( webhookPort, diff --git a/src/lib/bluebubbles.ts b/src/lib/bluebubbles.ts index e9d8ac3..c7a0b76 100644 --- a/src/lib/bluebubbles.ts +++ b/src/lib/bluebubbles.ts @@ -32,6 +32,28 @@ const SEEN_INBOUND_TTL_MS = 2 * 60 * 1000; const REPEATED_SHORT_MESSAGE_TTL_MS = 15 * 1000; const REPEATED_SHORT_MESSAGE_MAX_LENGTH = 12; const logger = getModuleLogger("bluebubbles-webhook"); +let loggedGuidFragmentFallback = false; + +type GuidAuthResult = { + authorized: boolean; + usedFragmentFallback: boolean; +}; + +function authorizeGuid(providedGuid: string | null, configuredPassword: string): GuidAuthResult { + if (providedGuid === configuredPassword) { + return { authorized: true, usedFragmentFallback: false }; + } + + const hashIndex = configuredPassword.indexOf("#"); + if (hashIndex > 0) { + const prefix = configuredPassword.slice(0, hashIndex); + if (prefix.length > 0 && providedGuid === prefix) { + return { authorized: true, usedFragmentFallback: true }; + } + } + + return { authorized: false, usedFragmentFallback: false }; +} function pruneSeenInboundMessages(now: number) { for (const [key, timestamp] of seenInboundMessages) { @@ -142,11 +164,17 @@ export function startWebhook( http .createServer((req, res) => { const url = new URL(req.url ?? "/", "http://localhost"); - - if (url.searchParams.get("guid") !== password) { + const guidAuth = authorizeGuid(url.searchParams.get("guid"), password); + if (!guidAuth.authorized) { res.writeHead(401).end("unauthorized"); return; } + if (guidAuth.usedFragmentFallback && !loggedGuidFragmentFallback) { + loggedGuidFragmentFallback = true; + logger.warn("webhook auth matched password prefix before '#'; encode guid in webhook URL", { + hint: "Use ?guid= in BlueBubbles webhook config.", + }); + } let body = ""; req.on("data", (chunk) => (body += chunk)); From dca55a20cec0e1b0d95ba467617eefc269e8b4ce Mon Sep 17 00:00:00 2001 From: Andrew Ho Date: Sun, 12 Apr 2026 13:07:43 -0700 Subject: [PATCH 7/8] fix gmail tool schema crash and surface allowFrom drop diagnostics --- src/doctor.ts | 58 +++++++++++++++++++++++++++++++- src/lib/bluebubbles.ts | 47 +++++++++++++++++++++++++- src/mastra/tools/gmail-tool.ts | 61 ++++++++++++++++++++++++++++++---- 3 files changed, 157 insertions(+), 9 deletions(-) diff --git a/src/doctor.ts b/src/doctor.ts index 73a8bc1..aa0a542 100644 --- a/src/doctor.ts +++ b/src/doctor.ts @@ -13,6 +13,25 @@ type CheckResult = { details: string; }; +function normalizeAddress(value: string): string { + const trimmed = value.trim(); + if (!trimmed) { + return ""; + } + if (trimmed.includes("@")) { + return trimmed.toLowerCase(); + } + + const digits = trimmed.replace(/\D/g, ""); + if (!digits) { + return trimmed.toLowerCase(); + } + if (digits.length === 11 && digits.startsWith("1")) { + return digits.slice(1); + } + return digits; +} + function printResult(result: CheckResult) { console.log(`${result.status} ${result.name}: ${result.details}`); } @@ -155,12 +174,49 @@ async function checkBlueBubbles(): Promise { details: JSON.stringify(error), }); } else { - const count = Array.isArray(data?.data) ? data.data.length : 0; + const chats = Array.isArray(data?.data) ? data.data : []; + const count = chats.length; results.push({ status: "PASS", name: "bluebubbles api auth", details: `chat query succeeded; returned ${count} chat(s)`, }); + + const participantAddresses: string[] = []; + const lastAddressedHandles: string[] = []; + for (const chat of chats) { + if (typeof chat?.lastAddressedHandle === "string") { + lastAddressedHandles.push(chat.lastAddressedHandle); + } + if (Array.isArray(chat?.participants)) { + for (const participant of chat.participants) { + if (typeof participant?.address === "string") { + participantAddresses.push(participant.address); + } + } + } + } + + const allowed = new Set(config.bluebubbles.allowFrom.map(normalizeAddress)); + const hasParticipantMatch = participantAddresses.some((value) => allowed.has(normalizeAddress(value))); + const hasLastAddressedMatch = lastAddressedHandles.some((value) => allowed.has(normalizeAddress(value))); + + if (!hasParticipantMatch && hasLastAddressedMatch) { + results.push({ + status: "WARN", + name: "allowFrom sender match", + details: + "allowFrom matches lastAddressedHandle but not sampled participant addresses; this often means allowFrom is set to your own handle, so inbound senders are dropped.", + }); + } else { + results.push({ + status: "PASS", + name: "allowFrom sender match", + details: hasParticipantMatch + ? "at least one sampled participant matches allowFrom" + : "no mismatch detected in sampled chats", + }); + } } } catch (error) { results.push({ diff --git a/src/lib/bluebubbles.ts b/src/lib/bluebubbles.ts index c7a0b76..1d9f37d 100644 --- a/src/lib/bluebubbles.ts +++ b/src/lib/bluebubbles.ts @@ -33,12 +33,45 @@ const REPEATED_SHORT_MESSAGE_TTL_MS = 15 * 1000; const REPEATED_SHORT_MESSAGE_MAX_LENGTH = 12; const logger = getModuleLogger("bluebubbles-webhook"); let loggedGuidFragmentFallback = false; +const warnedDisallowedSenders = new Set(); type GuidAuthResult = { authorized: boolean; usedFragmentFallback: boolean; }; +function normalizeHandleAddress(value: string): string { + const trimmed = value.trim(); + if (!trimmed) { + return ""; + } + if (trimmed.includes("@")) { + return trimmed.toLowerCase(); + } + + const digits = trimmed.replace(/\D/g, ""); + if (!digits) { + return trimmed.toLowerCase(); + } + if (digits.length === 11 && digits.startsWith("1")) { + return digits.slice(1); + } + return digits; +} + +function isAllowedSender(sender: string, allowFrom: string[]): boolean { + const normalizedSender = normalizeHandleAddress(sender); + for (const candidate of allowFrom) { + if (candidate === sender) { + return true; + } + if (normalizeHandleAddress(candidate) === normalizedSender) { + return true; + } + } + return false; +} + function authorizeGuid(providedGuid: string | null, configuredPassword: string): GuidAuthResult { if (providedGuid === configuredPassword) { return { authorized: true, usedFragmentFallback: false }; @@ -195,7 +228,19 @@ export function startWebhook( if (msg.isFromMe) return; const sender = msg.handle?.address; - if (!sender || !allowFrom.includes(sender)) return; + if (!sender) return; + if (!isAllowedSender(sender, allowFrom)) { + const senderKey = normalizeHandleAddress(sender); + if (!warnedDisallowedSenders.has(senderKey)) { + warnedDisallowedSenders.add(senderKey); + logger.warn("dropping inbound sender not in allowFrom", { + sender, + allowFrom, + hint: "Add sender to allowFrom in ~/.agent/config.json", + }); + } + return; + } const text = msg.text?.trim(); if (!text) return; diff --git a/src/mastra/tools/gmail-tool.ts b/src/mastra/tools/gmail-tool.ts index a1ac366..db88f9d 100644 --- a/src/mastra/tools/gmail-tool.ts +++ b/src/mastra/tools/gmail-tool.ts @@ -29,7 +29,18 @@ const readInputSchema = z.object({ .describe("Use metadata for compact fields or full for full payload."), }); -const gmailInputSchema = z.discriminatedUnion("action", [sendInputSchema, listInputSchema, readInputSchema]); +const gmailInputSchema = z + .object({ + action: z.enum(["send", "list", "read"]), + to: z.string().optional(), + subject: z.string().optional(), + body: z.string().optional(), + query: z.string().optional(), + maxResults: z.number().int().min(1).max(25).optional(), + messageId: z.string().optional(), + format: z.enum(["metadata", "full"]).optional(), + }) + .strict(); const gmailOutputSchema = z.object({ ok: z.boolean(), @@ -135,12 +146,39 @@ export const gmailTool = createTool({ } switch (inputData.action) { - case "send": - return sendEmail(inputData); - case "list": - return listMessages(inputData); - case "read": - return readMessage(inputData); + case "send": { + const parsed = sendInputSchema.safeParse(inputData); + if (!parsed.success) { + return { + ok: false, + action: "send", + error: formatValidationError(parsed.error), + }; + } + return sendEmail(parsed.data); + } + case "list": { + const parsed = listInputSchema.safeParse(inputData); + if (!parsed.success) { + return { + ok: false, + action: "list", + error: formatValidationError(parsed.error), + }; + } + return listMessages(parsed.data); + } + case "read": { + const parsed = readInputSchema.safeParse(inputData); + if (!parsed.success) { + return { + ok: false, + action: "read", + error: formatValidationError(parsed.error), + }; + } + return readMessage(parsed.data); + } default: return { ok: false, @@ -360,6 +398,15 @@ function extractGwsError(result: GwsRunResult): string { return `gws command failed (exit ${status}).`; } +function formatValidationError(error: z.ZodError): string { + return error.issues + .map((issue) => { + const path = issue.path.length > 0 ? issue.path.join(".") : "input"; + return `${path}: ${issue.message}`; + }) + .join("; "); +} + function parseJson(value: string): unknown { const trimmed = value.trim(); if (!trimmed) { From 2b3c819f817faa5d55046f097346492060eb9109 Mon Sep 17 00:00:00 2001 From: Andrew Ho Date: Sun, 12 Apr 2026 16:39:22 -0700 Subject: [PATCH 8/8] add composable MCP tool runtime and wire agent tool composition --- README.md | 33 +++ package-lock.json | 82 ++++++++ package.json | 1 + src/mastra/agents/index.ts | 54 ++++- src/mastra/evals/toolcalls-evals.ts | 61 +++++- src/mastra/mcp/runtime-tools.ts | 316 ++++++++++++++++++++++++++++ 6 files changed, 537 insertions(+), 10 deletions(-) create mode 100644 src/mastra/mcp/runtime-tools.ts diff --git a/README.md b/README.md index f412947..156e47c 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,39 @@ npm run start `npm run dev` does not start the BlueBubbles webhook listener. +## MCP Tool Integrations + +External MCP tools can be registered without writing custom Mastra tools. + +Add MCP servers to `~/.agent/config.json`: + +```json +{ + "mcp": { + "timeoutMs": 60000, + "servers": { + "gmail": { + "command": "npx", + "args": ["-y", "your-mcp-server-package"] + }, + "docs": { + "url": "https://example.com/mcp", + "headers": { + "authorization": "Bearer token" + } + } + } + } +} +``` + +You can also override via env vars: + +- `AGENT_MCP_SERVERS_JSON` (JSON object of server configs) +- `AGENT_MCP_TIMEOUT_MS` (positive integer milliseconds) + +MCP tools are namespaced as `server_toolName` and composed with native tools (`web_search` and BlueBubbles tools). + If your BlueBubbles password contains URL-reserved characters (for example `#`), use an encoded guid value in the webhook URL: ```text diff --git a/package-lock.json b/package-lock.json index 4ad5f9a..60307f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "@mastra/evals": "^1.2.1", "@mastra/libsql": "^1.8.0", "@mastra/loggers": "^1.1.1", + "@mastra/mcp": "^1.4.2", "@mastra/memory": "^1.15.0", "@mastra/observability": "^1.9.0", "zod": "^4.3.6" @@ -239,6 +240,42 @@ } } }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-14.2.1.tgz", + "integrity": "sha512-HmdFw9CDYqM6B25pqGBpNeLCKvGPlIx1EbLrVL0zPvj50CJQUHyBNBw45Muk0kEIkogo1VZvOKHajdMuAzSxRg==", + "license": "MIT", + "dependencies": { + "js-yaml": "^4.1.0" + }, + "engines": { + "node": ">= 20" + }, + "funding": { + "url": "https://github.com/sponsors/philsturgeon" + }, + "peerDependencies": { + "@types/json-schema": "^7.0.15" + } + }, + "node_modules/@apidevtools/json-schema-ref-parser/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/@apidevtools/json-schema-ref-parser/node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/@aws-crypto/crc32": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", @@ -2669,6 +2706,39 @@ "@mastra/core": ">=1.0.0-0 <2.0.0-0" } }, + "node_modules/@mastra/mcp": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@mastra/mcp/-/mcp-1.4.2.tgz", + "integrity": "sha512-zyn+CiFtHvWPf6BGTTnfWQ2nuJZDuXe646F9ByEOy4e1HJ90FpdwUnK/u4wu53i/tpui4Av64kH6T/Oc6yVUPQ==", + "license": "Apache-2.0", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "^14.2.1", + "@modelcontextprotocol/sdk": "^1.27.1", + "exit-hook": "^5.1.0", + "fast-deep-equal": "^3.1.3", + "uuid": "^13.0.0" + }, + "engines": { + "node": ">=22.13.0" + }, + "peerDependencies": { + "@mastra/core": ">=1.0.0-0 <2.0.0-0", + "zod": "^3.25.0 || ^4.0.0" + } + }, + "node_modules/@mastra/mcp/node_modules/uuid": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz", + "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, "node_modules/@mastra/memory": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/@mastra/memory/-/memory-1.15.0.tgz", @@ -6278,6 +6348,18 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, + "node_modules/exit-hook": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-5.1.0.tgz", + "integrity": "sha512-INjr2xyxHo7bhAqf5ong++GZPPnpcuBcaXUKt03yf7Fie9yWD7FapL4teOU0+awQazGs5ucBh7xWs/AD+6nhog==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/express": { "version": "4.22.1", "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", diff --git a/package.json b/package.json index 096c91b..d3ba1d0 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "@mastra/evals": "^1.2.1", "@mastra/libsql": "^1.8.0", "@mastra/loggers": "^1.1.1", + "@mastra/mcp": "^1.4.2", "@mastra/memory": "^1.15.0", "@mastra/observability": "^1.9.0", "zod": "^4.3.6" diff --git a/src/mastra/agents/index.ts b/src/mastra/agents/index.ts index 52ccb24..fe655f8 100644 --- a/src/mastra/agents/index.ts +++ b/src/mastra/agents/index.ts @@ -1,28 +1,65 @@ import { createOpenAI } from "@ai-sdk/openai"; import { Agent } from "@mastra/core/agent"; import type { LanguageModel as MastraLanguageModel } from "@mastra/core/llm"; +import type { MCPClient } from "@mastra/mcp"; import { loadConfig } from "../../lib/config"; +import { getModuleLogger } from "../../lib/logging"; import { buildModels } from "../../lib/provider"; import { BASE_RUNTIME_INSTRUCTIONS } from "../../lib/workspace"; +import { loadMcpRuntimeTools } from "../mcp/runtime-tools"; import { bluebubblesTools } from "../tools/bluebubbles-tools"; -import { gmailTool } from "../tools/gmail-tool"; const openai = createOpenAI(); const webSearchTool = openai.tools.webSearch({ searchContextSize: "medium", }); +const logger = getModuleLogger("mastra-agent-tools"); -function createAgent(model: MastraLanguageModel): Agent { +const nativeTools = { + web_search: webSearchTool, + ...bluebubblesTools, +}; + +type NativeTools = typeof nativeTools; +type McpTools = Awaited>; +type AgentTools = NativeTools & McpTools; + +function mergeTools(primary: NativeTools, secondary: McpTools): AgentTools { + const merged: AgentTools = { ...primary }; + + for (const [toolName, tool] of Object.entries(secondary)) { + if (merged[toolName]) { + logger.warn("tool name collision while composing agent tools; keeping primary tool", { + toolName, + }); + continue; + } + merged[toolName] = tool; + } + + return merged; +} + +async function loadAgentTools(): Promise { + const { tools: mcpTools, serverNames } = await loadMcpRuntimeTools(); + const tools = mergeTools(nativeTools, mcpTools); + + logger.info("agent tools composed", { + nativeToolCount: Object.keys(nativeTools).length, + mcpServerCount: serverNames.length, + totalToolCount: Object.keys(tools).length, + }); + + return tools; +} + +function createAgent(model: MastraLanguageModel, tools: AgentTools): Agent { return new Agent({ id: "tien", name: "tien", model, instructions: BASE_RUNTIME_INSTRUCTIONS, - tools: { - gmail: gmailTool, - web_search: webSearchTool, - ...bluebubblesTools, - }, + tools, defaultOptions: { modelSettings: { maxTokens: 512, @@ -34,5 +71,6 @@ function createAgent(model: MastraLanguageModel): Agent { export async function buildAgents(): Promise { const config = loadConfig(); const models = await buildModels(config.provider, config.fallbacks); - return models.map(createAgent); + const tools = await loadAgentTools(); + return models.map((model) => createAgent(model, tools)); } diff --git a/src/mastra/evals/toolcalls-evals.ts b/src/mastra/evals/toolcalls-evals.ts index 0c38d2b..edd67a1 100644 --- a/src/mastra/evals/toolcalls-evals.ts +++ b/src/mastra/evals/toolcalls-evals.ts @@ -327,6 +327,36 @@ process.exit(1); return { binDir, logPath }; } +function makeMockMcpServersEnv(): string { + const mcpServerScript = [ + "import { MCPServer } from '@mastra/mcp';", + "import { createTool } from '@mastra/core/tools';", + "import { z } from 'zod';", + "const ping = createTool({", + " id: 'ping',", + " description: 'Returns a pong payload for eval wiring checks.',", + " inputSchema: z.object({ query: z.string() }),", + " execute: async (input) => ({ ok: true, query: input.query }),", + "});", + "const server = new MCPServer({", + " id: 'eval-server',", + " name: 'eval-server',", + " version: '1.0.0',", + " tools: { ping },", + "});", + "await server.startStdio();", + ].join(" "); + + return JSON.stringify({ + eval: { + command: "node", + args: ["--input-type=module", "--eval", mcpServerScript], + cwd: process.cwd(), + timeoutMs: 15_000, + }, + }); +} + function requestHit( requests: RecordedRequest[], method: string, @@ -564,8 +594,8 @@ async function runWiringChecks(buildAgents: BuildAgentsFn): Promise const tools = await agents[0].listTools(); const toolNames = Object.keys(tools); const expected = [ - "gmail", "web_search", + "eval_ping", "bluebubbles_chat_query", "bluebubbles_message_query", "bluebubbles_message_search", @@ -598,16 +628,28 @@ async function main(): Promise { const previousHome = process.env.HOME; const previousPath = process.env.PATH ?? ""; const previousGwsLogPath = process.env.GWS_LOG_PATH; + const previousMcpServers = process.env.AGENT_MCP_SERVERS_JSON; + const previousMcpTimeout = process.env.AGENT_MCP_TIMEOUT_MS; const checks: EvalCheck[] = []; + let disconnectMcpRuntimeTools: (() => Promise) | null = null; + let resetMcpRuntimeToolsCache: (() => void) | null = null; try { process.env.HOME = tempHomeRoot; process.env.PATH = `${gwsMock.binDir}:${previousPath}`; process.env.GWS_LOG_PATH = gwsMock.logPath; + process.env.AGENT_MCP_SERVERS_JSON = makeMockMcpServersEnv(); + process.env.AGENT_MCP_TIMEOUT_MS = "30000"; const bluebubblesModule = (await import("../tools/bluebubbles-tools")) as unknown as Omit; const gmailModule = (await import("../tools/gmail-tool")) as unknown as Pick; const agentsModule = (await import("../agents")) as unknown as { buildAgents: BuildAgentsFn }; + const mcpRuntimeModule = (await import("../mcp/runtime-tools")) as { + disconnectMcpRuntimeTools: () => Promise; + resetMcpRuntimeToolsCache: () => void; + }; + disconnectMcpRuntimeTools = mcpRuntimeModule.disconnectMcpRuntimeTools; + resetMcpRuntimeToolsCache = mcpRuntimeModule.resetMcpRuntimeToolsCache; const loadedTools: LoadedTools = { ...bluebubblesModule, @@ -619,6 +661,12 @@ async function main(): Promise { checks.push(...runGwsChecks(gwsMock.logPath)); checks.push(...(await runWiringChecks(agentsModule.buildAgents))); } finally { + if (disconnectMcpRuntimeTools) { + await disconnectMcpRuntimeTools(); + } + if (resetMcpRuntimeToolsCache) { + resetMcpRuntimeToolsCache(); + } await mockServer.close(); if (previousHome) { process.env.HOME = previousHome; @@ -631,6 +679,16 @@ async function main(): Promise { } else { delete process.env.GWS_LOG_PATH; } + if (previousMcpServers) { + process.env.AGENT_MCP_SERVERS_JSON = previousMcpServers; + } else { + delete process.env.AGENT_MCP_SERVERS_JSON; + } + if (previousMcpTimeout) { + process.env.AGENT_MCP_TIMEOUT_MS = previousMcpTimeout; + } else { + delete process.env.AGENT_MCP_TIMEOUT_MS; + } rmSync(tempHomeRoot, { recursive: true, force: true }); } @@ -646,4 +704,3 @@ async function main(): Promise { } await main(); - diff --git a/src/mastra/mcp/runtime-tools.ts b/src/mastra/mcp/runtime-tools.ts new file mode 100644 index 0000000..40d88cd --- /dev/null +++ b/src/mastra/mcp/runtime-tools.ts @@ -0,0 +1,316 @@ +import { existsSync, readFileSync } from "fs"; +import { join } from "path"; +import { MCPClient, type MastraMCPServerDefinition } from "@mastra/mcp"; +import { z } from "zod"; +import { getModuleLogger } from "../../lib/logging"; + +const MCP_SERVERS_ENV = "AGENT_MCP_SERVERS_JSON"; +const MCP_TIMEOUT_ENV = "AGENT_MCP_TIMEOUT_MS"; +const MCP_CONFIG_FILE = join(".agent", "config.json"); +const DEFAULT_TIMEOUT_MS = 60_000; +const MCP_CLIENT_ID = "agent-runtime-mcp"; + +const stdioServerSchema = z + .object({ + command: z.string().min(1), + args: z.array(z.string()).default([]), + env: z.record(z.string(), z.string()).default({}), + cwd: z.string().optional(), + timeoutMs: z.number().int().positive().optional(), + enableServerLogs: z.boolean().default(true), + enableProgressTracking: z.boolean().default(false), + }) + .strict(); + +const httpServerSchema = z + .object({ + url: z.string().url(), + headers: z.record(z.string(), z.string()).default({}), + timeoutMs: z.number().int().positive().optional(), + enableServerLogs: z.boolean().default(true), + enableProgressTracking: z.boolean().default(false), + }) + .strict(); + +const rawServerSchema = z.union([stdioServerSchema, httpServerSchema]); +const rawServersSchema = z.record(z.string(), rawServerSchema); + +const fileConfigSchema = z + .object({ + mcp: z + .object({ + timeoutMs: z.number().int().positive().optional(), + servers: rawServersSchema.optional(), + }) + .optional(), + mcpServers: rawServersSchema.optional(), + }) + .passthrough(); + +type RawServer = z.infer; +type ToolMap = Awaited>; +type ToolsetResult = Awaited>; + +type RuntimeConfig = { + timeoutMs: number; + servers: Record; +}; + +let client: MCPClient | null = null; +let toolsPromise: Promise | null = null; +let configuredServers: string[] = []; + +function warn(message: string, payload?: Record): void { + try { + getModuleLogger("mastra-mcp").warn(message, payload); + return; + } catch { + const details = payload ? ` ${JSON.stringify(payload)}` : ""; + console.warn(`[mastra-mcp] ${message}${details}`); + } +} + +function info(message: string, payload?: Record): void { + try { + getModuleLogger("mastra-mcp").info(message, payload); + return; + } catch { + const details = payload ? ` ${JSON.stringify(payload)}` : ""; + console.info(`[mastra-mcp] ${message}${details}`); + } +} + +function toServerDefinition(server: RawServer): MastraMCPServerDefinition { + if ("command" in server) { + return { + command: server.command, + args: server.args, + env: server.env, + ...(server.cwd ? { cwd: server.cwd } : {}), + ...(server.timeoutMs ? { timeout: server.timeoutMs } : {}), + enableServerLogs: server.enableServerLogs, + enableProgressTracking: server.enableProgressTracking, + }; + } + + return { + url: new URL(server.url), + requestInit: { headers: server.headers }, + ...(server.timeoutMs ? { timeout: server.timeoutMs } : {}), + enableServerLogs: server.enableServerLogs, + enableProgressTracking: server.enableProgressTracking, + }; +} + +function readFileConfig(): { + timeoutMs: number | null; + servers: Record; +} { + const home = process.env.HOME; + if (!home) { + return { timeoutMs: null, servers: {} }; + } + + const configPath = join(home, MCP_CONFIG_FILE); + if (!existsSync(configPath)) { + return { timeoutMs: null, servers: {} }; + } + + try { + const raw = JSON.parse(readFileSync(configPath, "utf8")) as unknown; + const parsed = fileConfigSchema.safeParse(raw); + if (!parsed.success) { + warn("ignoring invalid mcp config file fields", { + path: configPath, + issues: parsed.error.issues.map((issue) => `${issue.path.join(".") || ""}: ${issue.message}`), + }); + return { timeoutMs: null, servers: {} }; + } + + const timeoutMs = parsed.data.mcp?.timeoutMs ?? null; + const serversFromMcpBlock = parsed.data.mcp?.servers ?? {}; + const serversFromTopLevel = parsed.data.mcpServers ?? {}; + + return { + timeoutMs, + servers: { + ...serversFromMcpBlock, + ...serversFromTopLevel, + }, + }; + } catch (error) { + warn("ignoring unreadable mcp config file", { + path: configPath, + error: error instanceof Error ? error.message : String(error), + }); + return { timeoutMs: null, servers: {} }; + } +} + +function readEnvServers(): Record { + const rawJson = process.env[MCP_SERVERS_ENV]; + if (!rawJson) { + return {}; + } + + try { + const parsedValue = JSON.parse(rawJson) as unknown; + const parsed = rawServersSchema.safeParse(parsedValue); + if (!parsed.success) { + warn("ignoring invalid MCP env servers config", { + env: MCP_SERVERS_ENV, + issues: parsed.error.issues.map((issue) => `${issue.path.join(".") || ""}: ${issue.message}`), + }); + return {}; + } + return parsed.data; + } catch (error) { + warn("ignoring invalid MCP env servers JSON", { + env: MCP_SERVERS_ENV, + error: error instanceof Error ? error.message : String(error), + }); + return {}; + } +} + +function readEnvTimeout(): number | null { + const rawValue = process.env[MCP_TIMEOUT_ENV]; + if (!rawValue) { + return null; + } + + const parsedValue = Number(rawValue); + if (!Number.isInteger(parsedValue) || parsedValue <= 0) { + warn("ignoring invalid MCP timeout env value", { + env: MCP_TIMEOUT_ENV, + value: rawValue, + }); + return null; + } + + return parsedValue; +} + +function loadRuntimeConfig(): RuntimeConfig { + const fileConfig = readFileConfig(); + const envServers = readEnvServers(); + const envTimeout = readEnvTimeout(); + + const timeoutMs = envTimeout ?? fileConfig.timeoutMs ?? DEFAULT_TIMEOUT_MS; + const rawServers: Record = { + ...fileConfig.servers, + ...envServers, + }; + + const servers: Record = {}; + for (const [serverName, rawServer] of Object.entries(rawServers)) { + servers[serverName] = toServerDefinition(rawServer); + } + + return { + timeoutMs, + servers, + }; +} + +function flattenToolsets(toolsets: ToolsetResult["toolsets"]): ToolMap { + const flattened: ToolMap = {}; + for (const [serverName, toolsByServer] of Object.entries(toolsets)) { + for (const [toolName, tool] of Object.entries(toolsByServer)) { + const namespacedName = `${serverName}_${toolName}`; + if (flattened[namespacedName]) { + warn("dropping duplicate MCP tool name after namespacing", { + toolName: namespacedName, + }); + continue; + } + flattened[namespacedName] = tool; + } + } + return flattened; +} + +export async function loadMcpRuntimeTools(): Promise<{ + tools: ToolMap; + serverNames: string[]; +}> { + if (toolsPromise) { + return { + tools: await toolsPromise, + serverNames: configuredServers, + }; + } + + const runtime = loadRuntimeConfig(); + const serverNames = Object.keys(runtime.servers); + configuredServers = serverNames; + + if (serverNames.length === 0) { + return { + tools: {}, + serverNames: [], + }; + } + + client = new MCPClient({ + id: MCP_CLIENT_ID, + servers: runtime.servers, + timeout: runtime.timeoutMs, + }); + + toolsPromise = client + .listToolsetsWithErrors() + .then((result) => { + const errorEntries = Object.entries(result.errors); + for (const [serverName, message] of errorEntries) { + warn("MCP server failed during tool discovery", { + serverName, + error: message, + }); + } + + const tools = flattenToolsets(result.toolsets); + info("MCP tool discovery complete", { + configuredServers: serverNames, + loadedTools: Object.keys(tools), + }); + return tools; + }) + .catch((error: unknown) => { + warn("MCP tool discovery failed; continuing without MCP tools", { + error: error instanceof Error ? error.message : String(error), + configuredServers: serverNames, + }); + return {}; + }); + + return { + tools: await toolsPromise, + serverNames, + }; +} + +export async function disconnectMcpRuntimeTools(): Promise { + const currentClient = client; + client = null; + toolsPromise = null; + configuredServers = []; + + if (!currentClient) { + return; + } + + try { + await currentClient.disconnect(); + } catch (error) { + warn("failed to disconnect MCP runtime client", { + error: error instanceof Error ? error.message : String(error), + }); + } +} + +export function resetMcpRuntimeToolsCache(): void { + client = null; + toolsPromise = null; + configuredServers = []; +}