Unified · Split
| @@ -59,6 +59,7 @@ const mcpSources: McpToolSource[] = [ · 58 unmodified lines | |||
| 59 | const POSTHOG_API_KEY = process.env["POSTHOG_PERSONAL_API_KEY"]; | 59 | const POSTHOG_API_KEY = process.env["POSTHOG_PERSONAL_API_KEY"]; |
| 60 | const POSTHOG_PROJECT_ID = process.env["POSTHOG_PROJECT_ID"]; | 60 | const POSTHOG_PROJECT_ID = process.env["POSTHOG_PROJECT_ID"]; |
| 61 | const GITHUB_TOKEN = process.env["OPENASSISTANT_GITHUB_TOKEN"]; | 61 | const GITHUB_TOKEN = process.env["OPENASSISTANT_GITHUB_TOKEN"]; |
| 62 | const VERCEL_TOKEN = process.env["VERCEL_TOKEN"]; | ||
| 62 | 63 | ||
| 63 | const openApiSources: OpenApiToolSource[] = [ | 64 | const openApiSources: OpenApiToolSource[] = [ |
| 64 | ...(POSTHOG_API_KEY ? [{ | 65 | ...(POSTHOG_API_KEY ? [{ |
| @@ -76,6 +77,13 @@ const openApiSources: OpenApiToolSource[] = [ · 11 unmodified lines | |||
| 76 | defaultReadApproval: "auto" as const, | 77 | defaultReadApproval: "auto" as const, |
| 77 | defaultWriteApproval: "required" as const, | 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 | "@modelcontextprotocol/sdk": "^1.26.0", | 79 | "@modelcontextprotocol/sdk": "^1.26.0", |
| 80 | "@openassistant/core": "workspace:*", | 80 | "@openassistant/core": "workspace:*", |
| 81 | "@sinclair/typebox": "^0.34.48", | 81 | "@sinclair/typebox": "^0.34.48", |
| 82 | "openapi-typescript": "^7.10.1", | ||
| 82 | "yaml": "^2.8.2", | 83 | "yaml": "^2.8.2", |
| 83 | "zod": "^4.3.6", | 84 | "zod": "^4.3.6", |
| 84 | }, | 85 | }, |
| @@ -179,6 +180,10 @@ · 94 unmodified lines | |||
| 179 | 180 | ||
| 180 | "@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.3", "", {}, "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw=="], | 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 | "@babel/runtime": ["@babel/runtime@7.28.6", "", {}, "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA=="], | 187 | "@babel/runtime": ["@babel/runtime@7.28.6", "", {}, "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA=="], |
| 183 | 188 | ||
| 184 | "@borewit/text-codec": ["@borewit/text-codec@0.2.1", "", {}, "sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw=="], | 189 | "@borewit/text-codec": ["@borewit/text-codec@0.2.1", "", {}, "sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw=="], |
| @@ -321,6 +326,12 @@ · 136 unmodified lines | |||
| 321 | 326 | ||
| 322 | "@protobufjs/utf8": ["@protobufjs/utf8@1.1.0", "", {}, "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="], | 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 | "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.57.1", "", { "os": "android", "cpu": "arm" }, "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg=="], | 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 | "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.57.1", "", { "os": "android", "cpu": "arm64" }, "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w=="], | 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 | "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="], | 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 | "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], | 553 | "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], |
| 541 | 554 | ||
| 542 | "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], | 555 | "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], |
| @@ -579,12 +592,16 @@ · 36 unmodified lines | |||
| 579 | 592 | ||
| 580 | "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], | 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 | "check-error": ["check-error@2.1.3", "", {}, "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA=="], | 597 | "check-error": ["check-error@2.1.3", "", {}, "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA=="], |
| 583 | 598 | ||
| 584 | "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], | 599 | "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], |
| 585 | 600 | ||
| 586 | "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], | 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 | "content-disposition": ["content-disposition@1.0.1", "", {}, "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q=="], | 605 | "content-disposition": ["content-disposition@1.0.1", "", {}, "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q=="], |
| 589 | 606 | ||
| 590 | "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], | 607 | "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], |
| @@ -737,6 +754,8 @@ · 146 unmodified lines | |||
| 737 | 754 | ||
| 738 | "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], | 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 | "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], | 759 | "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], |
| 741 | 760 | ||
| 742 | "ip-address": ["ip-address@10.0.1", "", {}, "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA=="], | 761 | "ip-address": ["ip-address@10.0.1", "", {}, "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA=="], |
| @@ -753,6 +772,8 @@ · 10 unmodified lines | |||
| 753 | 772 | ||
| 754 | "jose": ["jose@6.1.3", "", {}, "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ=="], | 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 | "js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="], | 777 | "js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="], |
| 757 | 778 | ||
| 758 | "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], | 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 | "mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], | 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 | "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], | 821 | "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], |
| 801 | 822 | ||
| @@ -831,12 +852,16 @@ · 29 unmodified lines | |||
| 831 | 852 | ||
| 832 | "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="], | 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 | "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=="], | 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 | "pac-resolver": ["pac-resolver@7.0.1", "", { "dependencies": { "degenerator": "^5.0.0", "netmask": "^2.0.2" } }, "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg=="], | 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 | "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], | 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 | "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], | 865 | "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], |
| 841 | 866 | ||
| 842 | "partial-json": ["partial-json@0.1.7", "", {}, "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA=="], | 867 | "partial-json": ["partial-json@0.1.7", "", {}, "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA=="], |
| @@ -857,6 +882,8 @@ · 14 unmodified lines | |||
| 857 | 882 | ||
| 858 | "pkce-challenge": ["pkce-challenge@5.0.1", "", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="], | 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 | "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=="], | 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 | "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=="], | 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 | "strtok3": ["strtok3@10.3.4", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg=="], | 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 | "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], | 977 | "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], |
| 949 | 978 | ||
| 950 | "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], | 979 | "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], |
| @@ -967,6 +996,8 @@ · 16 unmodified lines | |||
| 967 | 996 | ||
| 968 | "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], | 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 | "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=="], | 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 | "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], | 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 | "yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="], | 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 | "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], | 1043 | "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], |
| 1009 | 1044 | ||
| 1010 | "zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="], | 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 | "@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=="], | 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 | "@discordjs/rest/@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="], | 1065 | "@discordjs/rest/@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="], |
| 1029 | 1066 | ||
| 1030 | "@discordjs/ws/@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="], | 1067 | "@discordjs/ws/@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="], |
| @@ -1033,6 +1070,8 @@ · 2 unmodified lines | |||
| 1033 | 1070 | ||
| 1034 | "express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], | 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 | "node-fetch/data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="], | 1075 | "node-fetch/data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="], |
| 1037 | 1076 | ||
| 1038 | "path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], | 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 | ## Tool Discovery | 187 | ## Tool Discovery |
| 188 | 188 | ||
| 189 | Some tool namespaces are too large to list here. Use \`tools.discover({ query: "..." })\` to search for tools by keyword. | 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:** | 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. |
| 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 | ||
| 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 | \`\`\`ts | 202 | \`\`\`ts |
| 200 | const found = await tools.discover({ query: "feature flags" }); | 203 | const issues = await tools.github.issues.issues_list_for_repo({ |
| 201 | // found.results = [{ path: "posthog.feature_flags.feature_flags_list", signature: "...", ... }] | 204 | owner: "acme", repo: "myapp", state: "open", per_page: 100 |
| 202 | const flags = await tools.posthog.feature_flags.feature_flags_list({ project_id: "123" }); | 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 | ## Instructions | 228 | ## Instructions |
| 216 | 229 | ||
| 217 | - Use the \`run_code\` tool to execute TypeScript code | 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 | - The code runs in a sandboxed environment — only \`tools.*\` calls are available | 232 | - The code runs in a sandboxed environment — only \`tools.*\` calls are available |
| 219 | - No \`fetch\`, \`process\`, \`require\`, or \`import\` — use tools for all external interactions | 233 | - No \`fetch\`, \`process\`, \`require\`, or \`import\` — use tools for all external interactions |
| 220 | - Write clean, straightforward TypeScript | 234 | - Write clean, straightforward TypeScript — use for loops, map, filter, Promise.all as needed |
| 221 | - Handle errors gracefully — if a tool call is denied, continue with remaining work | 235 | - Handle errors gracefully with try/catch — if a tool call fails or is denied, continue with remaining work |
| 222 | - After execution, summarize what happened based on the tool call receipts | 236 | - Return a structured result from your script, then summarize what happened |
| 223 | - Be concise and accurate — base your response on actual tool results, not assumptions`; | 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 | requestApproval, | 250 | requestApproval, |
| 237 | onEvent, | 251 | onEvent, |
| 238 | maxTypecheckRetries = 3, | 252 | maxTypecheckRetries = 3, |
| 239 | maxCodeRuns = 10, | 253 | maxCodeRuns = 100, |
| 240 | timeoutMs = 30_000, | 254 | timeoutMs = 30_000, |
| 241 | discoveryThreshold = DISCOVERY_THRESHOLD, | 255 | discoveryThreshold = DISCOVERY_THRESHOLD, |
| 242 | } = options; | 256 | } = options; |
| @@ -12,7 +12,7 @@ · 11 unmodified lines | |||
| 12 | */ | 12 | */ |
| 13 | 13 | ||
| 14 | import { defineTool, isToolDefinition, type ToolDefinition, type ToolTree } from "./tools.js"; | 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 | import { z } from "zod"; | 16 | import { z } from "zod"; |
| 17 | 17 | ||
| 18 | // --------------------------------------------------------------------------- | 18 | // --------------------------------------------------------------------------- |
| @@ -44,8 +44,8 @@ export function buildToolIndex(tree: ToolTree): ToolIndexEntry[] { · 25 unmodified lines | |||
| 44 | const path = prefix ? `${prefix}.${key}` : key; | 44 | const path = prefix ? `${prefix}.${key}` : key; |
| 45 | if (isToolDefinition(value)) { | 45 | if (isToolDefinition(value)) { |
| 46 | const tool = value as ToolDefinition; | 46 | const tool = value as ToolDefinition; |
| 47 | const argsType = zodToTypeString(tool.args); | 47 | const argsType = getArgsTypeString(tool); |
| 48 | const returnsType = zodToTypeString(tool.returns); | 48 | const returnsType = getReturnsTypeString(tool); |
| 49 | entries.push({ | 49 | entries.push({ |
| 50 | path, | 50 | path, |
| 51 | description: tool.description, | 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 | 71 | * Keyword search with fuzzy matching. Scores entries by how many |
| 72 | * appear somewhere in the path + description. | 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 | function searchIndex( | 76 | function searchIndex( |
| 75 | index: ToolIndexEntry[], | 77 | index: ToolIndexEntry[], |
| @@ -79,17 +81,31 @@ function searchIndex( · 3 unmodified lines | |||
| 79 | const terms = query.toLowerCase().split(/\s+/).filter(Boolean); | 81 | const terms = query.toLowerCase().split(/\s+/).filter(Boolean); |
| 80 | if (terms.length === 0) return index.slice(0, limit); | 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 | const scored = index | 87 | const scored = index |
| 83 | .map((entry) => { | 88 | .map((entry) => { |
| 84 | let score = 0; | 89 | let score = 0; |
| 90 | let matched = 0; | ||
| 91 | |||
| 85 | for (const term of terms) { | 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 | // Bonus for path match (more specific) | 100 | // Bonus for path match (more specific) |
| 88 | if (entry.path.toLowerCase().includes(term)) score += 2; | 101 | if (inPath) score += 2; |
| 89 | // Bonus for exact word boundary match | 102 | // Bonus for exact word boundary match |
| 90 | if (entry.searchText.includes(` ${term}`) || entry.searchText.startsWith(term)) score += 1; | 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 | return { entry, score }; | 109 | return { entry, score }; |
| 94 | }) | 110 | }) |
| 95 | .filter((s) => s.score > 0) | 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 | function describeError(error: unknown): string { | 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 | if (typeof error === "string") return error; | 77 | if (typeof error === "string") return error; |
| 78 | if (error === null || error === undefined) return String(error); | ||
| 79 | // Try to get a meaningful representation | ||
| 71 | try { | 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 | } catch { | 89 | } catch { |
| 74 | return String(error); | 90 | return String(error); |
| 75 | } | 91 | } |
| @@ -62,6 +62,12 @@ export interface ApprovalRequest { · 61 unmodified lines | |||
| 62 | * - TypeScript declaration generation for the typechecker | 62 | * - TypeScript declaration generation for the typechecker |
| 63 | * - LLM prompt guidance generation | 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 | * - `run` is a plain async function (Effect is an internal detail) | 71 | * - `run` is a plain async function (Effect is an internal detail) |
| 66 | * | 72 | * |
| 67 | * - `formatApproval` provides rich presentation for approval UIs | 73 | * - `formatApproval` provides rich presentation for approval UIs |
| @@ -79,6 +85,13 @@ export interface ToolDefinition< · 11 unmodified lines | |||
| 79 | readonly formatApproval?: (( | 85 | readonly formatApproval?: (( |
| 80 | input: z.output<TArgs>, | 86 | input: z.output<TArgs>, |
| 81 | ) => ApprovalPresentation) | undefined; | 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 | returns: TReturns; | 115 | returns: TReturns; |
| 103 | run: (input: z.output<TArgs>) => Promise<z.output<TReturns>>; | 116 | run: (input: z.output<TArgs>) => Promise<z.output<TReturns>>; |
| 104 | formatApproval?: (input: z.output<TArgs>) => ApprovalPresentation; | 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 | returns: input.returns, | 149 | returns: input.returns, |
| 132 | run: input.run, | 150 | run: input.run, |
| 133 | formatApproval: input.formatApproval, | 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 | // ToolTree → TypeScript declaration | 149 | // ToolTree → TypeScript declaration |
| 130 | // --------------------------------------------------------------------------- | 150 | // --------------------------------------------------------------------------- |
| @@ -144,8 +164,8 @@ function generateTreeType(tree: ToolTree, indent: number): string { · 13 unmodified lines | |||
| 144 | for (const [key, value] of Object.entries(tree)) { | 164 | for (const [key, value] of Object.entries(tree)) { |
| 145 | if (isToolDefinition(value)) { | 165 | if (isToolDefinition(value)) { |
| 146 | const tool = value as ToolDefinition; | 166 | const tool = value as ToolDefinition; |
| 147 | const argsType = zodToTypeString(tool.args); | 167 | const argsType = getArgsTypeString(tool); |
| 148 | const returnsType = zodToTypeString(tool.returns); | 168 | const returnsType = getReturnsTypeString(tool); |
| 149 | lines.push(`${pad}${key}(input: ${argsType}): Promise<${returnsType}>;`); | 169 | lines.push(`${pad}${key}(input: ${argsType}): Promise<${returnsType}>;`); |
| 150 | } else { | 170 | } else { |
| 151 | lines.push(`${pad}${key}: {`); | 171 | lines.push(`${pad}${key}: {`); |
| @@ -173,8 +193,8 @@ export function generatePromptGuidance(tree: ToolTree): string { · 21 unmodified lines | |||
| 173 | const path = prefix ? `${prefix}.${key}` : key; | 193 | const path = prefix ? `${prefix}.${key}` : key; |
| 174 | if (isToolDefinition(value)) { | 194 | if (isToolDefinition(value)) { |
| 175 | const tool = value as ToolDefinition; | 195 | const tool = value as ToolDefinition; |
| 176 | const argsType = zodToTypeString(tool.args); | 196 | const argsType = getArgsTypeString(tool); |
| 177 | const returnsType = zodToTypeString(tool.returns); | 197 | const returnsType = getReturnsTypeString(tool); |
| 178 | const approvalNote = | 198 | const approvalNote = |
| 179 | tool.approval === "required" ? " (approval required)" : " (auto-approved)"; | 199 | tool.approval === "required" ? " (approval required)" : " (auto-approved)"; |
| 180 | lines.push( | 200 | lines.push( |
| @@ -17,6 +17,7 @@ · 16 unmodified lines | |||
| 17 | "@modelcontextprotocol/sdk": "^1.26.0", | 17 | "@modelcontextprotocol/sdk": "^1.26.0", |
| 18 | "@openassistant/core": "workspace:*", | 18 | "@openassistant/core": "workspace:*", |
| 19 | "@sinclair/typebox": "^0.34.48", | 19 | "@sinclair/typebox": "^0.34.48", |
| 20 | "openapi-typescript": "^7.10.1", | ||
| 20 | "yaml": "^2.8.2", | 21 | "yaml": "^2.8.2", |
| 21 | "zod": "^4.3.6" | 22 | "zod": "^4.3.6" |
| 22 | } | 23 | } |
| @@ -26,7 +26,8 @@ describe("jsonSchemaToTypeString", () => { · 25 unmodified lines | |||
| 26 | 26 | ||
| 27 | test("object with no properties", () => { | 27 | test("object with no properties", () => { |
| 28 | const result = jsonSchemaToTypeString({ type: "object" }); | 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 | test("array", () => { | 33 | test("array", () => { |
| @@ -34,7 +35,8 @@ describe("jsonSchemaToTypeString", () => { · 1 unmodified line | |||
| 34 | type: "array", | 35 | type: "array", |
| 35 | items: { type: "string" }, | 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 | test("enum", () => { | 42 | test("enum", () => { |
| @@ -4,10 +4,42 @@ · 3 unmodified lines | |||
| 4 | * Both MCP tools and OpenAPI specs use JSON Schema for their type definitions. | 4 | * Both MCP tools and OpenAPI specs use JSON Schema for their type definitions. |
| 5 | * This module converts them to TypeScript type strings for the typechecker, | 5 | * This module converts them to TypeScript type strings for the typechecker, |
| 6 | * and also creates Zod schemas for runtime validation. | 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 | import { z } from "zod"; | 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 | // JSON Schema types (minimal subset we need) | 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 | export function jsonSchemaToTypeString(schema: JsonSchema): string { | 74 | export function jsonSchemaToTypeString(schema: JsonSchema): string { |
| 36 | if (!schema.type && schema.properties) { | 75 | if (!schema) return "any"; |
| 37 | // Implicit object type | 76 | try { |
| 38 | return objectToTypeString(schema); | 77 | const node = transformSchemaObject(schema as never, { |
| 39 | } | 78 | path: "#", |
| 40 | 79 | ctx: makeTransformCtx() as never, | |
| 41 | if (schema.enum) { | 80 | }); |
| 42 | return schema.enum | 81 | return astToString(node).trim(); |
| 43 | .map((v) => (typeof v === "string" ? `"${v}"` : String(v))) | 82 | } catch { |
| 44 | .join(" | "); | 83 | return "any"; |
| 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"; | ||
| 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 | // JSON Schema → Zod schema | 88 | // JSON Schema → Zod schema |
| 109 | // --------------------------------------------------------------------------- | 89 | // --------------------------------------------------------------------------- |
| @@ -116,7 +116,7 @@ export async function generateMcpTools( · 115 unmodified lines | |||
| 116 | // Generate Zod schema from JSON Schema | 116 | // Generate Zod schema from JSON Schema |
| 117 | const argsZod = jsonSchemaToZod(inputSchema); | 117 | const argsZod = jsonSchemaToZod(inputSchema); |
| 118 | 118 | ||
| 119 | // Generate TypeScript type string for declarations | 119 | // Generate TypeScript type string using openapi-typescript |
| 120 | const argsTypeString = jsonSchemaToTypeString(inputSchema); | 120 | const argsTypeString = jsonSchemaToTypeString(inputSchema); |
| 121 | 121 | ||
| 122 | // Create the tool definition | 122 | // Create the tool definition |
| @@ -125,6 +125,10 @@ export async function generateMcpTools( · 2 unmodified lines | |||
| 125 | approval, | 125 | approval, |
| 126 | args: argsZod, | 126 | args: argsZod, |
| 127 | returns: z.any(), // MCP tools return opaque content | 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 | run: async (input: unknown) => { | 132 | run: async (input: unknown) => { |
| 129 | const result = await client.callTool({ | 133 | const result = await client.callTool({ |
| 130 | name: toolName, | 134 | name: toolName, |
| @@ -82,6 +82,7 @@ interface ParsedParameter { · 81 unmodified lines | |||
| 82 | 82 | ||
| 83 | const READ_METHODS = new Set(["get", "head", "options"]); | 83 | const READ_METHODS = new Set(["get", "head", "options"]); |
| 84 | 84 | ||
| 85 | |||
| 85 | function buildAuthHeaders(auth: OpenApiAuth | undefined): Record<string, string> { | 86 | function buildAuthHeaders(auth: OpenApiAuth | undefined): Record<string, string> { |
| 86 | if (!auth) return {}; | 87 | if (!auth) return {}; |
| 87 | switch (auth.type) { | 88 | switch (auth.type) { |
| @@ -305,25 +306,19 @@ export async function generateOpenApiTools( · 217 unmodified lines | |||
| 305 | }; | 306 | }; |
| 306 | 307 | ||
| 307 | let argsZod: z.ZodType; | 308 | let argsZod: z.ZodType; |
| 308 | let argsTypeString: string; | ||
| 309 | let returnsTypeString: string; | ||
| 310 | 309 | ||
| 311 | try { | 310 | try { |
| 312 | argsZod = jsonSchemaToZod(combinedInputSchema); | 311 | argsZod = jsonSchemaToZod(combinedInputSchema); |
| 313 | argsTypeString = jsonSchemaToTypeString(combinedInputSchema); | ||
| 314 | } catch { | 312 | } catch { |
| 315 | // Recursive/circular schemas — fall back to z.any() | 313 | // Recursive/circular schemas — fall back to z.any() |
| 316 | argsZod = z.any(); | 314 | argsZod = z.any(); |
| 317 | argsTypeString = "any"; | ||
| 318 | } | 315 | } |
| 319 | 316 | ||
| 320 | try { | 317 | // Use openapi-typescript for type strings (handles allOf, oneOf, enums, etc.) |
| 321 | returnsTypeString = op.responseSchema | 318 | const argsTypeString = jsonSchemaToTypeString(combinedInputSchema); |
| 322 | ? jsonSchemaToTypeString(op.responseSchema) | 319 | const returnsTypeString = op.responseSchema |
| 323 | : "any"; | 320 | ? jsonSchemaToTypeString(op.responseSchema) |
| 324 | } catch { | 321 | : "any"; |
| 325 | returnsTypeString = "any"; | ||
| 326 | } | ||
| 327 | 322 | ||
| 328 | const description = op.summary || op.description || `${op.method.toUpperCase()} ${op.path}`; | 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 | approval, | 327 | approval, |
| 333 | args: argsZod, | 328 | args: argsZod, |
| 334 | returns: z.any(), // OpenAPI responses are complex; use any for runtime | 329 | returns: z.any(), // OpenAPI responses are complex; use any for runtime |
| 330 | metadata: { | ||
| 331 | argsType: argsTypeString, | ||
| 332 | returnsType: returnsTypeString, | ||
| 333 | }, | ||
| 335 | run: async (input: unknown) => { | 334 | run: async (input: unknown) => { |
| 336 | const { url, remainingInput } = buildUrl( | 335 | const { url, remainingInput } = buildUrl( |
| 337 | baseUrl, | 336 | baseUrl, |
| @@ -358,7 +357,7 @@ export async function generateOpenApiTools( · 20 unmodified lines | |||
| 358 | 357 | ||
| 359 | if (!response.ok) { | 358 | if (!response.ok) { |
| 360 | const text = await response.text().catch(() => ""); | 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 | const contentType = response.headers.get("content-type") ?? ""; | 363 | const contentType = response.headers.get("content-type") ?? ""; |