Unified · Split
| @@ -59,6 +59,7 @@ const mcpSources: McpToolSource[] = [ · 58 unmodified lines | ||
| 59 | 59 | const POSTHOG_API_KEY = process.env["POSTHOG_PERSONAL_API_KEY"]; |
| 60 | 60 | const POSTHOG_PROJECT_ID = process.env["POSTHOG_PROJECT_ID"]; |
| 61 | 61 | const GITHUB_TOKEN = process.env["OPENASSISTANT_GITHUB_TOKEN"]; |
| 62 | +const VERCEL_TOKEN = process.env["VERCEL_TOKEN"]; | |
| 62 | 63 | |
| 63 | 64 | const openApiSources: OpenApiToolSource[] = [ |
| 64 | 65 | ...(POSTHOG_API_KEY ? [{ |
| @@ -76,6 +77,13 @@ const openApiSources: OpenApiToolSource[] = [ · 11 unmodified lines | ||
| 76 | 77 | defaultReadApproval: "auto" as const, |
| 77 | 78 | defaultWriteApproval: "required" as const, |
| 78 | 79 | }] : []), |
| 80 | + ...(VERCEL_TOKEN ? [{ | |
| 81 | + name: "vercel", | |
| 82 | + spec: "https://openapi.vercel.sh/", | |
| 83 | + auth: { type: "bearer" as const, token: VERCEL_TOKEN }, | |
| 84 | + defaultReadApproval: "auto" as const, | |
| 85 | + defaultWriteApproval: "required" as const, | |
| 86 | + }] : []), | |
| 79 | 87 | ]; |
| 80 | 88 | |
| 81 | 89 | // --------------------------------------------------------------------------- |
| @@ -79,6 +79,7 @@ · 78 unmodified lines | ||
| 79 | 79 | "@modelcontextprotocol/sdk": "^1.26.0", |
| 80 | 80 | "@openassistant/core": "workspace:*", |
| 81 | 81 | "@sinclair/typebox": "^0.34.48", |
| 82 | + "openapi-typescript": "^7.10.1", | |
| 82 | 83 | "yaml": "^2.8.2", |
| 83 | 84 | "zod": "^4.3.6", |
| 84 | 85 | }, |
| @@ -179,6 +180,10 @@ · 94 unmodified lines | ||
| 179 | 180 | |
| 180 | 181 | "@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.3", "", {}, "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw=="], |
| 181 | 182 | |
| 183 | + "@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="], | |
| 184 | + | |
| 185 | + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], | |
| 186 | + | |
| 182 | 187 | "@babel/runtime": ["@babel/runtime@7.28.6", "", {}, "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA=="], |
| 183 | 188 | |
| 184 | 189 | "@borewit/text-codec": ["@borewit/text-codec@0.2.1", "", {}, "sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw=="], |
| @@ -321,6 +326,12 @@ · 136 unmodified lines | ||
| 321 | 326 | |
| 322 | 327 | "@protobufjs/utf8": ["@protobufjs/utf8@1.1.0", "", {}, "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="], |
| 323 | 328 | |
| 329 | + "@redocly/ajv": ["@redocly/ajv@8.17.3", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-NQsbJbB/GV7JVO88ebFkMndrnuGp/dTm5/2NISeg+JGcLzTfGBJZ01+V5zD8nKBOpi/dLLNFT+Ql6IcUk8ehng=="], | |
| 330 | + | |
| 331 | + "@redocly/config": ["@redocly/config@0.22.2", "", {}, "sha512-roRDai8/zr2S9YfmzUfNhKjOF0NdcOIqF7bhf4MVC5UxpjIysDjyudvlAiVbpPHp3eDRWbdzUgtkK1a7YiDNyQ=="], | |
| 332 | + | |
| 333 | + "@redocly/openapi-core": ["@redocly/openapi-core@1.34.6", "", { "dependencies": { "@redocly/ajv": "^8.11.2", "@redocly/config": "^0.22.0", "colorette": "^1.2.0", "https-proxy-agent": "^7.0.5", "js-levenshtein": "^1.1.6", "js-yaml": "^4.1.0", "minimatch": "^5.0.1", "pluralize": "^8.0.0", "yaml-ast-parser": "0.0.43" } }, "sha512-2+O+riuIUgVSuLl3Lyh5AplWZyVMNuG2F98/o6NrutKJfW4/GTZdPpZlIphS0HGgcOHgmWcCSHj+dWFlZaGSHw=="], | |
| 334 | + | |
| 324 | 335 | "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.57.1", "", { "os": "android", "cpu": "arm" }, "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg=="], |
| 325 | 336 | |
| 326 | 337 | "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.57.1", "", { "os": "android", "cpu": "arm64" }, "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w=="], |
| @@ -537,6 +548,8 @@ · 210 unmodified lines | ||
| 537 | 548 | |
| 538 | 549 | "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="], |
| 539 | 550 | |
| 551 | + "ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="], | |
| 552 | + | |
| 540 | 553 | "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], |
| 541 | 554 | |
| 542 | 555 | "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], |
| @@ -579,12 +592,16 @@ · 36 unmodified lines | ||
| 579 | 592 | |
| 580 | 593 | "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], |
| 581 | 594 | |
| 595 | + "change-case": ["change-case@5.4.4", "", {}, "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w=="], | |
| 596 | + | |
| 582 | 597 | "check-error": ["check-error@2.1.3", "", {}, "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA=="], |
| 583 | 598 | |
| 584 | 599 | "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], |
| 585 | 600 | |
| 586 | 601 | "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], |
| 587 | 602 | |
| 603 | + "colorette": ["colorette@1.4.0", "", {}, "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g=="], | |
| 604 | + | |
| 588 | 605 | "content-disposition": ["content-disposition@1.0.1", "", {}, "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q=="], |
| 589 | 606 | |
| 590 | 607 | "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], |
| @@ -737,6 +754,8 @@ · 146 unmodified lines | ||
| 737 | 754 | |
| 738 | 755 | "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], |
| 739 | 756 | |
| 757 | + "index-to-position": ["index-to-position@1.2.0", "", {}, "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw=="], | |
| 758 | + | |
| 740 | 759 | "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], |
| 741 | 760 | |
| 742 | 761 | "ip-address": ["ip-address@10.0.1", "", {}, "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA=="], |
| @@ -753,6 +772,8 @@ · 10 unmodified lines | ||
| 753 | 772 | |
| 754 | 773 | "jose": ["jose@6.1.3", "", {}, "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ=="], |
| 755 | 774 | |
| 775 | + "js-levenshtein": ["js-levenshtein@1.1.6", "", {}, "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g=="], | |
| 776 | + | |
| 756 | 777 | "js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="], |
| 757 | 778 | |
| 758 | 779 | "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], |
| @@ -795,7 +816,7 @@ · 36 unmodified lines | ||
| 795 | 816 | |
| 796 | 817 | "mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], |
| 797 | 818 | |
| 798 | − "minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], | |
| 819 | + "minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="], | |
| 799 | 820 | |
| 800 | 821 | "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], |
| 801 | 822 | |
| @@ -831,12 +852,16 @@ · 29 unmodified lines | ||
| 831 | 852 | |
| 832 | 853 | "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="], |
| 833 | 854 | |
| 855 | + "openapi-typescript": ["openapi-typescript@7.10.1", "", { "dependencies": { "@redocly/openapi-core": "^1.34.5", "ansi-colors": "^4.1.3", "change-case": "^5.4.4", "parse-json": "^8.3.0", "supports-color": "^10.2.2", "yargs-parser": "^21.1.1" }, "peerDependencies": { "typescript": "^5.x" }, "bin": { "openapi-typescript": "bin/cli.js" } }, "sha512-rBcU8bjKGGZQT4K2ekSTY2Q5veOQbVG/lTKZ49DeCyT9z62hM2Vj/LLHjDHC9W7LJG8YMHcdXpRZDqC1ojB/lw=="], | |
| 856 | + | |
| 834 | 857 | "pac-proxy-agent": ["pac-proxy-agent@7.2.0", "", { "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" } }, "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA=="], |
| 835 | 858 | |
| 836 | 859 | "pac-resolver": ["pac-resolver@7.0.1", "", { "dependencies": { "degenerator": "^5.0.0", "netmask": "^2.0.2" } }, "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg=="], |
| 837 | 860 | |
| 838 | 861 | "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], |
| 839 | 862 | |
| 863 | + "parse-json": ["parse-json@8.3.0", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "index-to-position": "^1.1.0", "type-fest": "^4.39.1" } }, "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ=="], | |
| 864 | + | |
| 840 | 865 | "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], |
| 841 | 866 | |
| 842 | 867 | "partial-json": ["partial-json@0.1.7", "", {}, "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA=="], |
| @@ -857,6 +882,8 @@ · 14 unmodified lines | ||
| 857 | 882 | |
| 858 | 883 | "pkce-challenge": ["pkce-challenge@5.0.1", "", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="], |
| 859 | 884 | |
| 885 | + "pluralize": ["pluralize@8.0.0", "", {}, "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA=="], | |
| 886 | + | |
| 860 | 887 | "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], |
| 861 | 888 | |
| 862 | 889 | "protobufjs": ["protobufjs@7.5.4", "", { "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" } }, "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg=="], |
| @@ -945,6 +972,8 @@ · 82 unmodified lines | ||
| 945 | 972 | |
| 946 | 973 | "strtok3": ["strtok3@10.3.4", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg=="], |
| 947 | 974 | |
| 975 | + "supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="], | |
| 976 | + | |
| 948 | 977 | "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], |
| 949 | 978 | |
| 950 | 979 | "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], |
| @@ -967,6 +996,8 @@ · 16 unmodified lines | ||
| 967 | 996 | |
| 968 | 997 | "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], |
| 969 | 998 | |
| 999 | + "type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], | |
| 1000 | + | |
| 970 | 1001 | "type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="], |
| 971 | 1002 | |
| 972 | 1003 | "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], |
| @@ -1005,6 +1036,10 @@ · 32 unmodified lines | ||
| 1005 | 1036 | |
| 1006 | 1037 | "yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="], |
| 1007 | 1038 | |
| 1039 | + "yaml-ast-parser": ["yaml-ast-parser@0.0.43", "", {}, "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A=="], | |
| 1040 | + | |
| 1041 | + "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], | |
| 1042 | + | |
| 1008 | 1043 | "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], |
| 1009 | 1044 | |
| 1010 | 1045 | "zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="], |
| @@ -1025,6 +1060,8 @@ · 14 unmodified lines | ||
| 1025 | 1060 | |
| 1026 | 1061 | "@aws-sdk/middleware-user-agent/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.982.0", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" } }, "sha512-M27u8FJP7O0Of9hMWX5dipp//8iglmV9jr7R8SR8RveU+Z50/8TqH68Tu6wUWBGMfXjzbVwn1INIAO5lZrlxXQ=="], |
| 1027 | 1062 | |
| 1063 | + "@babel/code-frame/js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], | |
| 1064 | + | |
| 1028 | 1065 | "@discordjs/rest/@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="], |
| 1029 | 1066 | |
| 1030 | 1067 | "@discordjs/ws/@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="], |
| @@ -1033,6 +1070,8 @@ · 2 unmodified lines | ||
| 1033 | 1070 | |
| 1034 | 1071 | "express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], |
| 1035 | 1072 | |
| 1073 | + "glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], | |
| 1074 | + | |
| 1036 | 1075 | "node-fetch/data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="], |
| 1037 | 1076 | |
| 1038 | 1077 | "path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], |
| @@ -187,19 +187,32 @@ function buildSystemPrompt(guidance: string, discoveryMode: boolean): string { · 186 unmodified lines | ||
| 187 | 187 | ## Tool Discovery |
| 188 | 188 | |
| 189 | 189 | Some tool namespaces are too large to list here. Use \`tools.discover({ query: "..." })\` to search for tools by keyword. |
| 190 | −The discover tool returns an array of matching tools with their paths, descriptions, and TypeScript signatures. | |
| 190 | +It returns matching tool paths, descriptions, and TypeScript signatures. | |
| 191 | 191 | |
| 192 | −**Workflow for large APIs:** | |
| 193 | −1. Call \`tools.discover({ query: "relevant keywords" })\` to find relevant tools | |
| 194 | −2. Read the returned signatures to understand the input/output types | |
| 195 | −3. Call the discovered tools using the path from the results, e.g. \`tools.posthog.feature_flags.feature_flags_list({ project_id: "123" })\` | |
| 196 | −4. You can call discover multiple times with different queries | |
| 192 | +**Workflow:** First call discover to learn what tools are available. Then write a SINGLE self-contained script that does the entire task — fetching data, looping, transforming, and writing back. Don't split the actual work across multiple run_code calls. | |
| 197 | 193 | |
| 198 | −Example: | |
| 194 | +Example — "close all open issues on acme/myapp": | |
| 195 | + | |
| 196 | +Call 1 (discovery): | |
| 197 | +\`\`\`ts | |
| 198 | +return await tools.discover({ query: "issues list update repo" }); | |
| 199 | +\`\`\` | |
| 200 | + | |
| 201 | +Call 2 (do the work — everything in one script): | |
| 199 | 202 | \`\`\`ts |
| 200 | −const found = await tools.discover({ query: "feature flags" }); | |
| 201 | −// found.results = [{ path: "posthog.feature_flags.feature_flags_list", signature: "...", ... }] | |
| 202 | −const flags = await tools.posthog.feature_flags.feature_flags_list({ project_id: "123" }); | |
| 203 | +const issues = await tools.github.issues.issues_list_for_repo({ | |
| 204 | + owner: "acme", repo: "myapp", state: "open", per_page: 100 | |
| 205 | +}); | |
| 206 | + | |
| 207 | +const results = []; | |
| 208 | +for (const issue of issues) { | |
| 209 | + const closed = await tools.github.issues.issues_update({ | |
| 210 | + owner: "acme", repo: "myapp", issue_number: issue.number, state: "closed" | |
| 211 | + }); | |
| 212 | + results.push({ number: issue.number, title: issue.title, state: closed.state }); | |
| 213 | +} | |
| 214 | + | |
| 215 | +return { closed: results.length, issues: results }; | |
| 203 | 216 | \`\`\` |
| 204 | 217 | ` |
| 205 | 218 | : ""; |
| @@ -215,11 +228,12 @@ ${discoveryInstructions} · 9 unmodified lines | ||
| 215 | 228 | ## Instructions |
| 216 | 229 | |
| 217 | 230 | - Use the \`run_code\` tool to execute TypeScript code |
| 231 | +- **Write complete, self-contained scripts.** Once you know what tools are available, do all the work in a single run_code call — fetch data, loop, transform, write back, and return results. Don't do reads in one call and writes in another. | |
| 218 | 232 | - The code runs in a sandboxed environment — only \`tools.*\` calls are available |
| 219 | 233 | - No \`fetch\`, \`process\`, \`require\`, or \`import\` — use tools for all external interactions |
| 220 | −- Write clean, straightforward TypeScript | |
| 221 | −- Handle errors gracefully — if a tool call is denied, continue with remaining work | |
| 222 | −- After execution, summarize what happened based on the tool call receipts | |
| 234 | +- Write clean, straightforward TypeScript — use for loops, map, filter, Promise.all as needed | |
| 235 | +- Handle errors gracefully with try/catch — if a tool call fails or is denied, continue with remaining work | |
| 236 | +- Return a structured result from your script, then summarize what happened | |
| 223 | 237 | - Be concise and accurate — base your response on actual tool results, not assumptions`; |
| 224 | 238 | } |
| 225 | 239 | |
| @@ -236,7 +250,7 @@ export function createAgent(options: AgentOptions): { · 10 unmodified lines | ||
| 236 | 250 | requestApproval, |
| 237 | 251 | onEvent, |
| 238 | 252 | maxTypecheckRetries = 3, |
| 239 | − maxCodeRuns = 10, | |
| 253 | + maxCodeRuns = 100, | |
| 240 | 254 | timeoutMs = 30_000, |
| 241 | 255 | discoveryThreshold = DISCOVERY_THRESHOLD, |
| 242 | 256 | } = options; |
| @@ -12,7 +12,7 @@ · 11 unmodified lines | ||
| 12 | 12 | */ |
| 13 | 13 | |
| 14 | 14 | import { defineTool, isToolDefinition, type ToolDefinition, type ToolTree } from "./tools.js"; |
| 15 | −import { zodToTypeString } from "./typechecker.js"; | |
| 15 | +import { getArgsTypeString, getReturnsTypeString } from "./typechecker.js"; | |
| 16 | 16 | import { z } from "zod"; |
| 17 | 17 | |
| 18 | 18 | // --------------------------------------------------------------------------- |
| @@ -44,8 +44,8 @@ export function buildToolIndex(tree: ToolTree): ToolIndexEntry[] { · 25 unmodified lines | ||
| 44 | 44 | const path = prefix ? `${prefix}.${key}` : key; |
| 45 | 45 | if (isToolDefinition(value)) { |
| 46 | 46 | const tool = value as ToolDefinition; |
| 47 | − const argsType = zodToTypeString(tool.args); | |
| 48 | − const returnsType = zodToTypeString(tool.returns); | |
| 47 | + const argsType = getArgsTypeString(tool); | |
| 48 | + const returnsType = getReturnsTypeString(tool); | |
| 49 | 49 | entries.push({ |
| 50 | 50 | path, |
| 51 | 51 | description: tool.description, |
| @@ -68,8 +68,10 @@ export function buildToolIndex(tree: ToolTree): ToolIndexEntry[] { · 16 unmodified lines | ||
| 68 | 68 | // --------------------------------------------------------------------------- |
| 69 | 69 | |
| 70 | 70 | /** |
| 71 | − * Simple keyword search. Matches entries where ALL query terms | |
| 72 | − * appear somewhere in the path + description. | |
| 71 | + * Keyword search with fuzzy matching. Scores entries by how many | |
| 72 | + * query terms match, with bonuses for path matches. An entry only | |
| 73 | + * needs to match at least half the terms (rounded up) to be included, | |
| 74 | + * so "github issues list update" finds tools matching any 2+ of those words. | |
| 73 | 75 | */ |
| 74 | 76 | function searchIndex( |
| 75 | 77 | index: ToolIndexEntry[], |
| @@ -79,17 +81,31 @@ function searchIndex( · 3 unmodified lines | ||
| 79 | 81 | const terms = query.toLowerCase().split(/\s+/).filter(Boolean); |
| 80 | 82 | if (terms.length === 0) return index.slice(0, limit); |
| 81 | 83 | |
| 84 | + // Minimum terms that must match: at least 1, or half rounded up for 3+ terms | |
| 85 | + const minMatches = terms.length <= 2 ? 1 : Math.ceil(terms.length / 2); | |
| 86 | + | |
| 82 | 87 | const scored = index |
| 83 | 88 | .map((entry) => { |
| 84 | 89 | let score = 0; |
| 90 | + let matched = 0; | |
| 91 | + | |
| 85 | 92 | for (const term of terms) { |
| 86 | − if (!entry.searchText.includes(term)) return { entry, score: -1 }; | |
| 93 | + const inText = entry.searchText.includes(term); | |
| 94 | + const inPath = entry.path.toLowerCase().includes(term); | |
| 95 | + | |
| 96 | + if (!inText && !inPath) continue; | |
| 97 | + | |
| 98 | + matched++; | |
| 99 | + score += 1; | |
| 87 | 100 | // Bonus for path match (more specific) |
| 88 | − if (entry.path.toLowerCase().includes(term)) score += 2; | |
| 101 | + if (inPath) score += 2; | |
| 89 | 102 | // Bonus for exact word boundary match |
| 90 | 103 | if (entry.searchText.includes(` ${term}`) || entry.searchText.startsWith(term)) score += 1; |
| 91 | − score += 1; | |
| 92 | 104 | } |
| 105 | + | |
| 106 | + if (matched < minMatches) return { entry, score: -1 }; | |
| 107 | + // Bonus for matching more terms | |
| 108 | + score += matched * 2; | |
| 93 | 109 | return { entry, score }; |
| 94 | 110 | }) |
| 95 | 111 | .filter((s) => s.score > 0) |
| @@ -0,0 +1,219 @@ | ||
| 1 | +/** | |
| 2 | + * Test: sandbox loop behavior — list items, then mutate each in a for loop. | |
| 3 | + * Reproduces the "works for 1, breaks for many" issue. | |
| 4 | + */ | |
| 5 | + | |
| 6 | +import { test, expect, describe } from "bun:test"; | |
| 7 | +import { createRunner } from "./runner.js"; | |
| 8 | +import { defineTool, type ToolTree } from "./tools.js"; | |
| 9 | +import { z } from "zod"; | |
| 10 | + | |
| 11 | +// Simulate an API: list items, then update each | |
| 12 | +const items = [ | |
| 13 | + { id: 1, title: "Issue 1", state: "closed" }, | |
| 14 | + { id: 2, title: "Issue 2", state: "closed" }, | |
| 15 | + { id: 3, title: "Issue 3", state: "closed" }, | |
| 16 | + { id: 4, title: "Issue 4", state: "closed" }, | |
| 17 | + { id: 5, title: "Issue 5", state: "closed" }, | |
| 18 | +]; | |
| 19 | + | |
| 20 | +let updateCallCount = 0; | |
| 21 | + | |
| 22 | +const tools: ToolTree = { | |
| 23 | + api: { | |
| 24 | + list_items: defineTool({ | |
| 25 | + description: "List items", | |
| 26 | + approval: "auto", | |
| 27 | + args: z.object({ state: z.string().optional() }), | |
| 28 | + returns: z.array(z.object({ id: z.number(), title: z.string(), state: z.string() })), | |
| 29 | + run: async (input) => { | |
| 30 | + const filtered = input.state | |
| 31 | + ? items.filter((i) => i.state === input.state) | |
| 32 | + : items; | |
| 33 | + return filtered; | |
| 34 | + }, | |
| 35 | + }), | |
| 36 | + update_item: defineTool({ | |
| 37 | + description: "Update an item", | |
| 38 | + approval: "auto", | |
| 39 | + args: z.object({ id: z.number(), state: z.string() }), | |
| 40 | + returns: z.object({ id: z.number(), state: z.string() }), | |
| 41 | + run: async (input) => { | |
| 42 | + updateCallCount++; | |
| 43 | + // Simulate a small network delay | |
| 44 | + await new Promise((r) => setTimeout(r, 10)); | |
| 45 | + return { id: input.id, state: input.state }; | |
| 46 | + }, | |
| 47 | + }), | |
| 48 | + }, | |
| 49 | +}; | |
| 50 | + | |
| 51 | +describe("sandbox loop — list then mutate each", () => { | |
| 52 | + test("for loop with await works for multiple items", async () => { | |
| 53 | + updateCallCount = 0; | |
| 54 | + | |
| 55 | + const runner = createRunner({ | |
| 56 | + tools, | |
| 57 | + requestApproval: async () => "approved", | |
| 58 | + timeoutMs: 30_000, | |
| 59 | + }); | |
| 60 | + | |
| 61 | + const code = ` | |
| 62 | +const items = await tools.api.list_items({ state: "closed" }); | |
| 63 | + | |
| 64 | +const results = []; | |
| 65 | +for (const item of items) { | |
| 66 | + const updated = await tools.api.update_item({ id: item.id, state: "open" }); | |
| 67 | + results.push(updated); | |
| 68 | +} | |
| 69 | + | |
| 70 | +return { total: results.length, results }; | |
| 71 | +`; | |
| 72 | + | |
| 73 | + const result = await runner.run(code); | |
| 74 | + | |
| 75 | + console.log("Result:", JSON.stringify(result.value)); | |
| 76 | + console.log("OK:", result.ok); | |
| 77 | + console.log("Error:", result.error); | |
| 78 | + console.log("Receipts:", result.receipts.length); | |
| 79 | + console.log("Update calls:", updateCallCount); | |
| 80 | + | |
| 81 | + expect(result.ok).toBe(true); | |
| 82 | + expect(result.receipts.length).toBe(6); // 1 list + 5 updates | |
| 83 | + expect(updateCallCount).toBe(5); | |
| 84 | + expect((result.value as any).total).toBe(5); | |
| 85 | + }); | |
| 86 | + | |
| 87 | + test("Promise.all with map works for multiple items", async () => { | |
| 88 | + updateCallCount = 0; | |
| 89 | + | |
| 90 | + const runner = createRunner({ | |
| 91 | + tools, | |
| 92 | + requestApproval: async () => "approved", | |
| 93 | + timeoutMs: 30_000, | |
| 94 | + }); | |
| 95 | + | |
| 96 | + const code = ` | |
| 97 | +const items = await tools.api.list_items({ state: "closed" }); | |
| 98 | + | |
| 99 | +const results = await Promise.all( | |
| 100 | + items.map((item) => tools.api.update_item({ id: item.id, state: "open" })) | |
| 101 | +); | |
| 102 | + | |
| 103 | +return { total: results.length, results }; | |
| 104 | +`; | |
| 105 | + | |
| 106 | + const result = await runner.run(code); | |
| 107 | + | |
| 108 | + console.log("Result:", JSON.stringify(result.value)); | |
| 109 | + console.log("OK:", result.ok); | |
| 110 | + console.log("Error:", result.error); | |
| 111 | + | |
| 112 | + expect(result.ok).toBe(true); | |
| 113 | + expect(result.receipts.length).toBe(6); | |
| 114 | + expect(updateCallCount).toBe(5); | |
| 115 | + }); | |
| 116 | + | |
| 117 | + test("for loop with approval-required tools", async () => { | |
| 118 | + const approvalTools: ToolTree = { | |
| 119 | + api: { | |
| 120 | + list_items: defineTool({ | |
| 121 | + description: "List items", | |
| 122 | + approval: "auto", | |
| 123 | + args: z.object({}), | |
| 124 | + returns: z.array(z.object({ id: z.number() })), | |
| 125 | + run: async () => [{ id: 1 }, { id: 2 }, { id: 3 }], | |
| 126 | + }), | |
| 127 | + update_item: defineTool({ | |
| 128 | + description: "Update item", | |
| 129 | + approval: "required", | |
| 130 | + args: z.object({ id: z.number(), state: z.string() }), | |
| 131 | + returns: z.object({ id: z.number(), state: z.string() }), | |
| 132 | + run: async (input) => ({ id: input.id, state: input.state }), | |
| 133 | + }), | |
| 134 | + }, | |
| 135 | + }; | |
| 136 | + | |
| 137 | + const runner = createRunner({ | |
| 138 | + tools: approvalTools, | |
| 139 | + requestApproval: async () => "approved", | |
| 140 | + timeoutMs: 30_000, | |
| 141 | + }); | |
| 142 | + | |
| 143 | + const code = ` | |
| 144 | +const items = await tools.api.list_items({}); | |
| 145 | +const results = []; | |
| 146 | +for (const item of items) { | |
| 147 | + const updated = await tools.api.update_item({ id: item.id, state: "open" }); | |
| 148 | + results.push(updated); | |
| 149 | +} | |
| 150 | +return { total: results.length, results }; | |
| 151 | +`; | |
| 152 | + | |
| 153 | + const result = await runner.run(code); | |
| 154 | + | |
| 155 | + console.log("Approval loop result:", JSON.stringify(result.value)); | |
| 156 | + console.log("OK:", result.ok); | |
| 157 | + console.log("Error:", result.error); | |
| 158 | + console.log("Receipts:", result.receipts.length); | |
| 159 | + | |
| 160 | + expect(result.ok).toBe(true); | |
| 161 | + expect(result.receipts.length).toBe(4); // 1 list + 3 updates | |
| 162 | + expect((result.value as any).total).toBe(3); | |
| 163 | + }); | |
| 164 | + | |
| 165 | + test("large loop — 20 items", async () => { | |
| 166 | + const manyItems = Array.from({ length: 20 }, (_, i) => ({ | |
| 167 | + id: i + 1, | |
| 168 | + title: `Issue ${i + 1}`, | |
| 169 | + state: "closed", | |
| 170 | + })); | |
| 171 | + | |
| 172 | + const manyTools: ToolTree = { | |
| 173 | + api: { | |
| 174 | + list_items: defineTool({ | |
| 175 | + description: "List items", | |
| 176 | + approval: "auto", | |
| 177 | + args: z.object({}), | |
| 178 | + returns: z.any(), | |
| 179 | + run: async () => manyItems, | |
| 180 | + }), | |
| 181 | + update_item: defineTool({ | |
| 182 | + description: "Update item", | |
| 183 | + approval: "auto", | |
| 184 | + args: z.object({ id: z.number(), state: z.string() }), | |
| 185 | + returns: z.object({ id: z.number(), state: z.string() }), | |
| 186 | + run: async (input) => { | |
| 187 | + await new Promise((r) => setTimeout(r, 5)); | |
| 188 | + return { id: input.id, state: input.state }; | |
| 189 | + }, | |
| 190 | + }), | |
| 191 | + }, | |
| 192 | + }; | |
| 193 | + | |
| 194 | + const runner = createRunner({ | |
| 195 | + tools: manyTools, | |
| 196 | + requestApproval: async () => "approved", | |
| 197 | + timeoutMs: 30_000, | |
| 198 | + }); | |
| 199 | + | |
| 200 | + const code = ` | |
| 201 | +const items = await tools.api.list_items({}); | |
| 202 | +const results = []; | |
| 203 | +for (const item of items) { | |
| 204 | + const updated = await tools.api.update_item({ id: item.id, state: "open" }); | |
| 205 | + results.push(updated); | |
| 206 | +} | |
| 207 | +return { total: results.length }; | |
| 208 | +`; | |
| 209 | + | |
| 210 | + const result = await runner.run(code); | |
| 211 | + | |
| 212 | + console.log("20 items result:", JSON.stringify(result.value)); | |
| 213 | + console.log("OK:", result.ok); | |
| 214 | + console.log("Error:", result.error); | |
| 215 | + | |
| 216 | + expect(result.ok).toBe(true); | |
| 217 | + expect((result.value as any).total).toBe(20); | |
| 218 | + }); | |
| 219 | +}); | |
| @@ -66,10 +66,26 @@ function preview(value: unknown): string { · 65 unmodified lines | ||
| 66 | 66 | } |
| 67 | 67 | |
| 68 | 68 | function describeError(error: unknown): string { |
| 69 | − if (error instanceof Error) return error.message; | |
| 69 | + if (error instanceof Error) { | |
| 70 | + // Include cause if present (e.g. nested API errors) | |
| 71 | + const msg = error.message; | |
| 72 | + if (error.cause) { | |
| 73 | + return `${msg} (cause: ${describeError(error.cause)})`; | |
| 74 | + } | |
| 75 | + return msg; | |
| 76 | + } | |
| 70 | 77 | if (typeof error === "string") return error; |
| 78 | + if (error === null || error === undefined) return String(error); | |
| 79 | + // Try to get a meaningful representation | |
| 71 | 80 | try { |
| 72 | − return JSON.stringify(error); | |
| 81 | + const json = JSON.stringify(error, null, 0); | |
| 82 | + // If it's just "{}", try to find something useful | |
| 83 | + if (json === "{}" || json === "[]") { | |
| 84 | + const proto = Object.getPrototypeOf(error); | |
| 85 | + const name = proto?.constructor?.name; | |
| 86 | + return name && name !== "Object" ? `[${name}]` : String(error); | |
| 87 | + } | |
| 88 | + return json; | |
| 73 | 89 | } catch { |
| 74 | 90 | return String(error); |
| 75 | 91 | } |
| @@ -62,6 +62,12 @@ export interface ApprovalRequest { · 61 unmodified lines | ||
| 62 | 62 | * - TypeScript declaration generation for the typechecker |
| 63 | 63 | * - LLM prompt guidance generation |
| 64 | 64 | * |
| 65 | + * - `metadata.argsType` and `metadata.returnsType` are optional pre-computed | |
| 66 | + * TypeScript type strings. When present, the typechecker and discovery index | |
| 67 | + * use these instead of deriving types from Zod schemas. This is critical for | |
| 68 | + * OpenAPI tools where `returns` is `z.any()` but the spec defines a rich | |
| 69 | + * response schema. | |
| 70 | + * | |
| 65 | 71 | * - `run` is a plain async function (Effect is an internal detail) |
| 66 | 72 | * |
| 67 | 73 | * - `formatApproval` provides rich presentation for approval UIs |
| @@ -79,6 +85,13 @@ export interface ToolDefinition< · 11 unmodified lines | ||
| 79 | 85 | readonly formatApproval?: (( |
| 80 | 86 | input: z.output<TArgs>, |
| 81 | 87 | ) => ApprovalPresentation) | undefined; |
| 88 | + /** Pre-computed type metadata. Preferred over Zod-derived types when present. */ | |
| 89 | + readonly metadata?: { | |
| 90 | + /** TypeScript type string for the input args (e.g. "{ owner: string; repo: string }") */ | |
| 91 | + readonly argsType?: string | undefined; | |
| 92 | + /** TypeScript type string for the return value (e.g. "{ id: number; title: string }") */ | |
| 93 | + readonly returnsType?: string | undefined; | |
| 94 | + } | undefined; | |
| 82 | 95 | } |
| 83 | 96 | |
| 84 | 97 | /** |
| @@ -102,6 +115,11 @@ export interface DefineToolInput< · 17 unmodified lines | ||
| 102 | 115 | returns: TReturns; |
| 103 | 116 | run: (input: z.output<TArgs>) => Promise<z.output<TReturns>>; |
| 104 | 117 | formatApproval?: (input: z.output<TArgs>) => ApprovalPresentation; |
| 118 | + /** Pre-computed type metadata. Preferred over Zod-derived types when present. */ | |
| 119 | + metadata?: { | |
| 120 | + argsType?: string; | |
| 121 | + returnsType?: string; | |
| 122 | + }; | |
| 105 | 123 | } |
| 106 | 124 | |
| 107 | 125 | /** |
| @@ -131,6 +149,7 @@ export function defineTool< · 23 unmodified lines | ||
| 131 | 149 | returns: input.returns, |
| 132 | 150 | run: input.run, |
| 133 | 151 | formatApproval: input.formatApproval, |
| 152 | + metadata: input.metadata, | |
| 134 | 153 | }; |
| 135 | 154 | } |
| 136 | 155 | |
| @@ -125,6 +125,26 @@ export function zodToTypeString(schema: z.ZodType): string { · 124 unmodified lines | ||
| 125 | 125 | } |
| 126 | 126 | } |
| 127 | 127 | |
| 128 | +// --------------------------------------------------------------------------- | |
| 129 | +// Type string resolution | |
| 130 | +// --------------------------------------------------------------------------- | |
| 131 | + | |
| 132 | +/** | |
| 133 | + * Get the TypeScript type string for a tool's args. | |
| 134 | + * Prefers metadata.argsType when available, falls back to Zod-derived type. | |
| 135 | + */ | |
| 136 | +export function getArgsTypeString(tool: ToolDefinition): string { | |
| 137 | + return tool.metadata?.argsType ?? zodToTypeString(tool.args); | |
| 138 | +} | |
| 139 | + | |
| 140 | +/** | |
| 141 | + * Get the TypeScript type string for a tool's return value. | |
| 142 | + * Prefers metadata.returnsType when available, falls back to Zod-derived type. | |
| 143 | + */ | |
| 144 | +export function getReturnsTypeString(tool: ToolDefinition): string { | |
| 145 | + return tool.metadata?.returnsType ?? zodToTypeString(tool.returns); | |
| 146 | +} | |
| 147 | + | |
| 128 | 148 | // --------------------------------------------------------------------------- |
| 129 | 149 | // ToolTree → TypeScript declaration |
| 130 | 150 | // --------------------------------------------------------------------------- |
| @@ -144,8 +164,8 @@ function generateTreeType(tree: ToolTree, indent: number): string { · 13 unmodified lines | ||
| 144 | 164 | for (const [key, value] of Object.entries(tree)) { |
| 145 | 165 | if (isToolDefinition(value)) { |
| 146 | 166 | const tool = value as ToolDefinition; |
| 147 | − const argsType = zodToTypeString(tool.args); | |
| 148 | − const returnsType = zodToTypeString(tool.returns); | |
| 167 | + const argsType = getArgsTypeString(tool); | |
| 168 | + const returnsType = getReturnsTypeString(tool); | |
| 149 | 169 | lines.push(`${pad}${key}(input: ${argsType}): Promise<${returnsType}>;`); |
| 150 | 170 | } else { |
| 151 | 171 | lines.push(`${pad}${key}: {`); |
| @@ -173,8 +193,8 @@ export function generatePromptGuidance(tree: ToolTree): string { · 21 unmodified lines | ||
| 173 | 193 | const path = prefix ? `${prefix}.${key}` : key; |
| 174 | 194 | if (isToolDefinition(value)) { |
| 175 | 195 | const tool = value as ToolDefinition; |
| 176 | − const argsType = zodToTypeString(tool.args); | |
| 177 | − const returnsType = zodToTypeString(tool.returns); | |
| 196 | + const argsType = getArgsTypeString(tool); | |
| 197 | + const returnsType = getReturnsTypeString(tool); | |
| 178 | 198 | const approvalNote = |
| 179 | 199 | tool.approval === "required" ? " (approval required)" : " (auto-approved)"; |
| 180 | 200 | lines.push( |
| @@ -17,6 +17,7 @@ · 16 unmodified lines | ||
| 17 | 17 | "@modelcontextprotocol/sdk": "^1.26.0", |
| 18 | 18 | "@openassistant/core": "workspace:*", |
| 19 | 19 | "@sinclair/typebox": "^0.34.48", |
| 20 | + "openapi-typescript": "^7.10.1", | |
| 20 | 21 | "yaml": "^2.8.2", |
| 21 | 22 | "zod": "^4.3.6" |
| 22 | 23 | } |
| @@ -26,7 +26,8 @@ describe("jsonSchemaToTypeString", () => { · 25 unmodified lines | ||
| 26 | 26 | |
| 27 | 27 | test("object with no properties", () => { |
| 28 | 28 | const result = jsonSchemaToTypeString({ type: "object" }); |
| 29 | − expect(result).toBe("Record<string, unknown>"); | |
| 29 | + // openapi-typescript produces Record<string, never> for empty objects | |
| 30 | + expect(result).toBe("Record<string, never>"); | |
| 30 | 31 | }); |
| 31 | 32 | |
| 32 | 33 | test("array", () => { |
| @@ -34,7 +35,8 @@ describe("jsonSchemaToTypeString", () => { · 1 unmodified line | ||
| 34 | 35 | type: "array", |
| 35 | 36 | items: { type: "string" }, |
| 36 | 37 | }; |
| 37 | − expect(jsonSchemaToTypeString(schema)).toBe("Array<string>"); | |
| 38 | + // openapi-typescript uses T[] syntax instead of Array<T> | |
| 39 | + expect(jsonSchemaToTypeString(schema)).toBe("string[]"); | |
| 38 | 40 | }); |
| 39 | 41 | |
| 40 | 42 | test("enum", () => { |
| @@ -4,10 +4,42 @@ · 3 unmodified lines | ||
| 4 | 4 | * Both MCP tools and OpenAPI specs use JSON Schema for their type definitions. |
| 5 | 5 | * This module converts them to TypeScript type strings for the typechecker, |
| 6 | 6 | * and also creates Zod schemas for runtime validation. |
| 7 | + * | |
| 8 | + * Type string generation uses openapi-typescript's `transformSchemaObject` for | |
| 9 | + * high-fidelity conversion (handles allOf, oneOf, enums, nullable, etc.). | |
| 10 | + * Falls back to our simpler hand-rolled converter for edge cases. | |
| 7 | 11 | */ |
| 8 | 12 | |
| 13 | +import { astToString, transformSchemaObject } from "openapi-typescript"; | |
| 9 | 14 | import { z } from "zod"; |
| 10 | 15 | |
| 16 | +// --------------------------------------------------------------------------- | |
| 17 | +// openapi-typescript context (for individual schema conversion) | |
| 18 | +// --------------------------------------------------------------------------- | |
| 19 | + | |
| 20 | +function makeTransformCtx() { | |
| 21 | + return { | |
| 22 | + additionalProperties: false, | |
| 23 | + alphabetize: false, | |
| 24 | + arrayLength: false, | |
| 25 | + defaultNonNullable: true, | |
| 26 | + discriminators: { refsHandled: [] as string[], objects: {} as Record<string, unknown> }, | |
| 27 | + emptyObjectsUnknown: false, | |
| 28 | + enum: false, | |
| 29 | + enumValues: false, | |
| 30 | + excludeDeprecated: false, | |
| 31 | + exportType: false, | |
| 32 | + immutable: false, | |
| 33 | + indentLv: 0, | |
| 34 | + pathParamsAsTypes: false, | |
| 35 | + postTransform: undefined, | |
| 36 | + propertiesRequiredByDefault: false, | |
| 37 | + redoc: undefined, | |
| 38 | + silent: true, | |
| 39 | + resolve(_ref: string) { return undefined as unknown; }, | |
| 40 | + }; | |
| 41 | +} | |
| 42 | + | |
| 11 | 43 | // --------------------------------------------------------------------------- |
| 12 | 44 | // JSON Schema types (minimal subset we need) |
| 13 | 45 | // --------------------------------------------------------------------------- |
| @@ -29,81 +61,29 @@ export interface JsonSchema { · 15 unmodified lines | ||
| 29 | 61 | } |
| 30 | 62 | |
| 31 | 63 | // --------------------------------------------------------------------------- |
| 32 | −// JSON Schema → TypeScript string | |
| 64 | +// JSON Schema → TypeScript string (via openapi-typescript) | |
| 33 | 65 | // --------------------------------------------------------------------------- |
| 34 | 66 | |
| 67 | +/** | |
| 68 | + * Convert a JSON Schema to a TypeScript type string. | |
| 69 | + * | |
| 70 | + * Uses openapi-typescript's `transformSchemaObject` for high-fidelity conversion | |
| 71 | + * that handles allOf, oneOf, enums, nullable, additionalProperties, etc. | |
| 72 | + * Falls back to "any" for schemas that can't be converted (e.g. circular refs). | |
| 73 | + */ | |
| 35 | 74 | export function jsonSchemaToTypeString(schema: JsonSchema): string { |
| 36 | − if (!schema.type && schema.properties) { | |
| 37 | − // Implicit object type | |
| 38 | − return objectToTypeString(schema); | |
| 39 | − } | |
| 40 | − | |
| 41 | − if (schema.enum) { | |
| 42 | − return schema.enum | |
| 43 | − .map((v) => (typeof v === "string" ? `"${v}"` : String(v))) | |
| 44 | − .join(" | "); | |
| 45 | − } | |
| 46 | − | |
| 47 | − if (schema.const !== undefined) { | |
| 48 | − return typeof schema.const === "string" | |
| 49 | − ? `"${schema.const}"` | |
| 50 | − : String(schema.const); | |
| 51 | − } | |
| 52 | − | |
| 53 | − if (schema.oneOf) { | |
| 54 | − return schema.oneOf.map(jsonSchemaToTypeString).join(" | "); | |
| 55 | − } | |
| 56 | − | |
| 57 | − if (schema.anyOf) { | |
| 58 | − return schema.anyOf.map(jsonSchemaToTypeString).join(" | "); | |
| 59 | − } | |
| 60 | − | |
| 61 | − const type = Array.isArray(schema.type) ? schema.type[0] : schema.type; | |
| 62 | − | |
| 63 | − switch (type) { | |
| 64 | − case "string": | |
| 65 | − return "string"; | |
| 66 | − case "number": | |
| 67 | − case "integer": | |
| 68 | − return "number"; | |
| 69 | − case "boolean": | |
| 70 | − return "boolean"; | |
| 71 | − case "null": | |
| 72 | − return "null"; | |
| 73 | − case "array": { | |
| 74 | − if (schema.items) { | |
| 75 | − return `Array<${jsonSchemaToTypeString(schema.items)}>`; | |
| 76 | − } | |
| 77 | − return "Array<unknown>"; | |
| 78 | − } | |
| 79 | − case "object": | |
| 80 | − return objectToTypeString(schema); | |
| 81 | − default: | |
| 82 | − return "unknown"; | |
| 75 | + if (!schema) return "any"; | |
| 76 | + try { | |
| 77 | + const node = transformSchemaObject(schema as never, { | |
| 78 | + path: "#", | |
| 79 | + ctx: makeTransformCtx() as never, | |
| 80 | + }); | |
| 81 | + return astToString(node).trim(); | |
| 82 | + } catch { | |
| 83 | + return "any"; | |
| 83 | 84 | } |
| 84 | 85 | } |
| 85 | 86 | |
| 86 | −function objectToTypeString(schema: JsonSchema): string { | |
| 87 | − const props = schema.properties; | |
| 88 | − if (!props || Object.keys(props).length === 0) { | |
| 89 | − if (schema.additionalProperties) { | |
| 90 | − const valueType = | |
| 91 | − typeof schema.additionalProperties === "object" | |
| 92 | − ? jsonSchemaToTypeString(schema.additionalProperties) | |
| 93 | − : "unknown"; | |
| 94 | − return `Record<string, ${valueType}>`; | |
| 95 | − } | |
| 96 | − return "Record<string, unknown>"; | |
| 97 | − } | |
| 98 | − | |
| 99 | − const required = new Set(schema.required ?? []); | |
| 100 | − const entries = Object.entries(props).map(([key, propSchema]) => { | |
| 101 | − const opt = required.has(key) ? "" : "?"; | |
| 102 | − return `${key}${opt}: ${jsonSchemaToTypeString(propSchema)}`; | |
| 103 | − }); | |
| 104 | − return `{ ${entries.join("; ")} }`; | |
| 105 | −} | |
| 106 | − | |
| 107 | 87 | // --------------------------------------------------------------------------- |
| 108 | 88 | // JSON Schema → Zod schema |
| 109 | 89 | // --------------------------------------------------------------------------- |
| @@ -116,7 +116,7 @@ export async function generateMcpTools( · 115 unmodified lines | ||
| 116 | 116 | // Generate Zod schema from JSON Schema |
| 117 | 117 | const argsZod = jsonSchemaToZod(inputSchema); |
| 118 | 118 | |
| 119 | − // Generate TypeScript type string for declarations | |
| 119 | + // Generate TypeScript type string using openapi-typescript | |
| 120 | 120 | const argsTypeString = jsonSchemaToTypeString(inputSchema); |
| 121 | 121 | |
| 122 | 122 | // Create the tool definition |
| @@ -125,6 +125,10 @@ export async function generateMcpTools( · 2 unmodified lines | ||
| 125 | 125 | approval, |
| 126 | 126 | args: argsZod, |
| 127 | 127 | returns: z.any(), // MCP tools return opaque content |
| 128 | + metadata: { | |
| 129 | + argsType: argsTypeString, | |
| 130 | + // MCP tools don't declare output schemas, so no returnsType | |
| 131 | + }, | |
| 128 | 132 | run: async (input: unknown) => { |
| 129 | 133 | const result = await client.callTool({ |
| 130 | 134 | name: toolName, |
| @@ -82,6 +82,7 @@ interface ParsedParameter { · 81 unmodified lines | ||
| 82 | 82 | |
| 83 | 83 | const READ_METHODS = new Set(["get", "head", "options"]); |
| 84 | 84 | |
| 85 | + | |
| 85 | 86 | function buildAuthHeaders(auth: OpenApiAuth | undefined): Record<string, string> { |
| 86 | 87 | if (!auth) return {}; |
| 87 | 88 | switch (auth.type) { |
| @@ -305,25 +306,19 @@ export async function generateOpenApiTools( · 217 unmodified lines | ||
| 305 | 306 | }; |
| 306 | 307 | |
| 307 | 308 | let argsZod: z.ZodType; |
| 308 | − let argsTypeString: string; | |
| 309 | − let returnsTypeString: string; | |
| 310 | 309 | |
| 311 | 310 | try { |
| 312 | 311 | argsZod = jsonSchemaToZod(combinedInputSchema); |
| 313 | − argsTypeString = jsonSchemaToTypeString(combinedInputSchema); | |
| 314 | 312 | } catch { |
| 315 | 313 | // Recursive/circular schemas — fall back to z.any() |
| 316 | 314 | argsZod = z.any(); |
| 317 | − argsTypeString = "any"; | |
| 318 | 315 | } |
| 319 | 316 | |
| 320 | − try { | |
| 321 | − returnsTypeString = op.responseSchema | |
| 322 | − ? jsonSchemaToTypeString(op.responseSchema) | |
| 323 | − : "any"; | |
| 324 | − } catch { | |
| 325 | − returnsTypeString = "any"; | |
| 326 | − } | |
| 317 | + // Use openapi-typescript for type strings (handles allOf, oneOf, enums, etc.) | |
| 318 | + const argsTypeString = jsonSchemaToTypeString(combinedInputSchema); | |
| 319 | + const returnsTypeString = op.responseSchema | |
| 320 | + ? jsonSchemaToTypeString(op.responseSchema) | |
| 321 | + : "any"; | |
| 327 | 322 | |
| 328 | 323 | const description = op.summary || op.description || `${op.method.toUpperCase()} ${op.path}`; |
| 329 | 324 | |
| @@ -332,6 +327,10 @@ export async function generateOpenApiTools( · 2 unmodified lines | ||
| 332 | 327 | approval, |
| 333 | 328 | args: argsZod, |
| 334 | 329 | returns: z.any(), // OpenAPI responses are complex; use any for runtime |
| 330 | + metadata: { | |
| 331 | + argsType: argsTypeString, | |
| 332 | + returnsType: returnsTypeString, | |
| 333 | + }, | |
| 335 | 334 | run: async (input: unknown) => { |
| 336 | 335 | const { url, remainingInput } = buildUrl( |
| 337 | 336 | baseUrl, |
| @@ -358,7 +357,7 @@ export async function generateOpenApiTools( · 20 unmodified lines | ||
| 358 | 357 | |
| 359 | 358 | if (!response.ok) { |
| 360 | 359 | const text = await response.text().catch(() => ""); |
| 361 | − throw new Error(`${response.status} ${response.statusText}: ${text.slice(0, 500)}`); | |
| 360 | + throw new Error(`HTTP ${response.status} ${response.statusText} (${op.method.toUpperCase()} ${url}): ${text.slice(0, 500)}`); | |
| 362 | 361 | } |
| 363 | 362 | |
| 364 | 363 | const contentType = response.headers.get("content-type") ?? ""; |