diff --git a/README.md b/README.md index 42d87c398..c063795d3 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,7 @@ The structure of this mono repository: - `packages/frontend` - Reusable code for web frontends including WebRTC peer connections - `packages/keyvalue` - Key-value storage service client - `packages/opencode` - Opencode agent plugins for Agentuity +- `packages/pi` - Pi coding agent provider plugin for Agentuity AI Gateway models - `packages/postgres` - Resilient PostgreSQL client with automatic reconnection - `packages/queue` - Queue service client for publishing messages to queues - `packages/react` - React package for the Browser including WebRTC hooks diff --git a/bun.lock b/bun.lock index 2624bb576..9de0e0b6b 100644 --- a/bun.lock +++ b/bun.lock @@ -535,8 +535,7 @@ "name": "@agentuity/coder-tui", "version": "2.0.15", "dependencies": { - "@agentuity/core": "workspace:*", - "@agentuity/server": "workspace:*", + "@agentuity/pi": "workspace:*", "@mariozechner/pi-coding-agent": "^0.72.1", "@mariozechner/pi-tui": "^0.72.1", "@sinclair/typebox": "^0.34.49", @@ -685,6 +684,23 @@ "typescript": "^5.9.0", }, }, + "packages/pi": { + "name": "@agentuity/pi", + "version": "2.0.15", + "dependencies": { + "@agentuity/core": "workspace:*", + "@agentuity/server": "workspace:*", + }, + "devDependencies": { + "@mariozechner/pi-coding-agent": "^0.72.1", + "@types/bun": "^1.3.9", + "bun-types": "^1.3.9", + "typescript": "^5.9.0", + }, + "peerDependencies": { + "@mariozechner/pi-coding-agent": "^0.72.1", + }, + }, "packages/postgres": { "name": "@agentuity/postgres", "version": "2.0.15", @@ -1014,6 +1030,8 @@ "@agentuity/opencode": ["@agentuity/opencode@workspace:packages/opencode"], + "@agentuity/pi": ["@agentuity/pi@workspace:packages/pi"], + "@agentuity/postgres": ["@agentuity/postgres@workspace:packages/postgres"], "@agentuity/queue": ["@agentuity/queue@workspace:packages/queue"], @@ -3020,7 +3038,7 @@ "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], - "gaxios": ["gaxios@7.1.4", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2" } }, "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA=="], + "gaxios": ["gaxios@6.7.1", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "is-stream": "^2.0.0", "node-fetch": "^2.6.9", "uuid": "^9.0.1" } }, "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ=="], "gcp-metadata": ["gcp-metadata@6.1.1", "", { "dependencies": { "gaxios": "^6.1.1", "google-logging-utils": "^0.0.2", "json-bigint": "^1.0.0" } }, "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A=="], @@ -3066,7 +3084,7 @@ "google-auth-library": ["google-auth-library@10.6.2", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^7.1.4", "gcp-metadata": "8.1.2", "google-logging-utils": "1.1.3", "jws": "^4.0.0" } }, "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw=="], - "google-logging-utils": ["google-logging-utils@1.1.3", "", {}, "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA=="], + "google-logging-utils": ["google-logging-utils@0.0.2", "", {}, "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ=="], "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], @@ -3148,7 +3166,7 @@ "husky": ["husky@9.1.7", "", { "bin": { "husky": "bin.js" } }, "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA=="], - "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + "iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], @@ -3572,7 +3590,7 @@ "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], - "node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], + "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], "node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.2.2", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw=="], @@ -4564,6 +4582,8 @@ "cytoscape-fcose/cose-base": ["cose-base@2.2.0", "", { "dependencies": { "layout-base": "^2.0.0" } }, "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g=="], + "d3-dsv/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + "d3-sankey/d3-array": ["d3-array@2.12.1", "", { "dependencies": { "internmap": "^1.0.0" } }, "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ=="], "d3-sankey/d3-shape": ["d3-shape@1.3.7", "", { "dependencies": { "d3-path": "1" } }, "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw=="], @@ -4588,6 +4608,8 @@ "effect/uuid": ["uuid@13.0.2", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-vzi9uRZ926x4XV73S/4qQaTwPXM2JBj6/6lI/byHH1jOpCzb0zDbfytgA9LcN/hzb2l7WQSQnxITOVx5un/wGw=="], + "encoding-sniffer/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + "escodegen/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], "extract-zip/yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="], @@ -4596,12 +4618,14 @@ "form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], - "gcp-metadata/gaxios": ["gaxios@6.7.1", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "is-stream": "^2.0.0", "node-fetch": "^2.6.9", "uuid": "^9.0.1" } }, "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ=="], + "gaxios/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], - "gcp-metadata/google-logging-utils": ["google-logging-utils@0.0.2", "", {}, "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ=="], + "google-auth-library/gaxios": ["gaxios@7.1.4", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2" } }, "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA=="], "google-auth-library/gcp-metadata": ["gcp-metadata@8.1.2", "", { "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", "json-bigint": "^1.0.0" } }, "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg=="], + "google-auth-library/google-logging-utils": ["google-logging-utils@1.1.3", "", {}, "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA=="], + "happy-dom/whatwg-mimetype": ["whatwg-mimetype@3.0.0", "", {}, "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="], "hast-util-from-html/parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], @@ -4636,13 +4660,11 @@ "mongodb-connection-string-url/whatwg-url": ["whatwg-url@14.2.0", "", { "dependencies": { "tr46": "^5.1.0", "webidl-conversions": "^7.0.0" } }, "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw=="], - "mysql2/iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], - "next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], "nextjs-app-agentuity/@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="], - "node-fetch/data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="], + "node-fetch/whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], "normalize-package-data/hosted-git-info": ["hosted-git-info@7.0.2", "", { "dependencies": { "lru-cache": "^10.0.1" } }, "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w=="], @@ -4726,6 +4748,8 @@ "webrtc-test/@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="], + "whatwg-encoding/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + "wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], @@ -4886,9 +4910,7 @@ "form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], - "gcp-metadata/gaxios/node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], - - "gcp-metadata/gaxios/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], + "google-auth-library/gaxios/node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], "hast-util-from-html/parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], @@ -4912,6 +4934,10 @@ "nextjs-app-agentuity/@vitejs/plugin-react/react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="], + "node-fetch/whatwg-url/tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], + + "node-fetch/whatwg-url/webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], + "normalize-package-data/hosted-git-info/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], "oauth/@vitejs/plugin-react/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="], @@ -5124,7 +5150,7 @@ "docs/ai/@ai-sdk/gateway/@vercel/oidc": ["@vercel/oidc@3.1.0", "", {}, "sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w=="], - "gcp-metadata/gaxios/node-fetch/whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], + "google-auth-library/gaxios/node-fetch/data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="], "prebuild-install/tar-fs/tar-stream/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], @@ -5142,10 +5168,6 @@ "create-agentuity/@agentuity/cli/@agentuity/auth/@agentuity/drizzle/drizzle-orm": ["drizzle-orm@0.45.2", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q=="], - "gcp-metadata/gaxios/node-fetch/whatwg-url/tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], - - "gcp-metadata/gaxios/node-fetch/whatwg-url/webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], - "archiver-utils/glob/jackspeak/@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], "archiver-utils/glob/jackspeak/@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], diff --git a/package.json b/package.json index e94c7a6cf..458aadb95 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,8 @@ ], "scripts": { "prepare": "husky", - "build": "bunx tsc --build && bun run --filter='./packages/core' build && bun run --filter='./packages/schema' build && bun run --filter='./packages/frontend' build && bun run --filter='./packages/server' build && bun run --filter='./packages/react' build && bun run --filter='./packages/postgres' build && bun run --filter='./packages/drizzle' build && bun run --filter='./packages/auth' build && bun run --filter='./packages/aigateway' build && bun run --filter='./packages/evals' build && bun run --filter='./packages/workbench' build && bun run --filter='./packages/runtime' build && bun packages/frontend/scripts/build-beacon.ts && bun run --filter='./packages/cli' build && bun run --filter='./packages/opencode' build && bun run --filter='./apps/testing/integration-suite' build && bun run --filter='./apps/testing/cloud-deployment' build && bun run --filter='./apps/testing/e2e-web' build && bun run --filter='./apps/testing/svelte-web' build", - "build:packages": "bunx tsc --build && bun run --filter='./packages/core' build && bun run --filter='./packages/schema' build && bun run --filter='./packages/frontend' build && bun run --filter='./packages/server' build && bun run --filter='./packages/react' build && bun run --filter='./packages/postgres' build && bun run --filter='./packages/drizzle' build && bun run --filter='./packages/auth' build && bun run --filter='./packages/aigateway' build && bun run --filter='./packages/evals' build && bun run --filter='./packages/workbench' build && bun run --filter='./packages/runtime' build && bun packages/frontend/scripts/build-beacon.ts && bun run --filter='./packages/cli' build && bun run --filter='./packages/opencode' build", + "build": "bunx tsc --build && bun run --filter='./packages/core' build && bun run --filter='./packages/schema' build && bun run --filter='./packages/frontend' build && bun run --filter='./packages/server' build && bun run --filter='./packages/pi' build && bun run --filter='./packages/react' build && bun run --filter='./packages/postgres' build && bun run --filter='./packages/drizzle' build && bun run --filter='./packages/auth' build && bun run --filter='./packages/aigateway' build && bun run --filter='./packages/evals' build && bun run --filter='./packages/workbench' build && bun run --filter='./packages/runtime' build && bun packages/frontend/scripts/build-beacon.ts && bun run --filter='./packages/cli' build && bun run --filter='./packages/opencode' build && bun run --filter='./apps/testing/integration-suite' build && bun run --filter='./apps/testing/cloud-deployment' build && bun run --filter='./apps/testing/e2e-web' build && bun run --filter='./apps/testing/svelte-web' build", + "build:packages": "bunx tsc --build && bun run --filter='./packages/core' build && bun run --filter='./packages/schema' build && bun run --filter='./packages/frontend' build && bun run --filter='./packages/server' build && bun run --filter='./packages/pi' build && bun run --filter='./packages/react' build && bun run --filter='./packages/postgres' build && bun run --filter='./packages/drizzle' build && bun run --filter='./packages/auth' build && bun run --filter='./packages/aigateway' build && bun run --filter='./packages/evals' build && bun run --filter='./packages/workbench' build && bun run --filter='./packages/runtime' build && bun packages/frontend/scripts/build-beacon.ts && bun run --filter='./packages/cli' build && bun run --filter='./packages/opencode' build", "dev:workbench": "concurrently \"cd packages/workbench && bun run dev:app\" \"cd apps/testing/integration-suite && bun run dev\" --names \"workbench,integration-suite\" --prefix-colors \"blue,green\"", "test": "bun test:packages && cd packages/cli && bun run test && cd ../.. && bun test:templates && bun test:pkginstall && bun test:create", "test:packages": "cd packages/core && bun test && cd ../schema && bun test && cd ../frontend && bun test && cd ../server && bun test && cd ../react && bun test && cd ../postgres && bun test && cd ../drizzle && bun test && cd ../auth && bun test && cd ../runtime && bun test --max-concurrency=1 && cd ../opencode && bun test", diff --git a/packages/coder-tui/package.json b/packages/coder-tui/package.json index 742a62f79..e779f6322 100644 --- a/packages/coder-tui/package.json +++ b/packages/coder-tui/package.json @@ -25,8 +25,7 @@ "prepublishOnly": "bun run clean && bun run build" }, "dependencies": { - "@agentuity/core": "workspace:*", - "@agentuity/server": "workspace:*", + "@agentuity/pi": "workspace:*", "@mariozechner/pi-coding-agent": "^0.72.1", "@mariozechner/pi-tui": "^0.72.1", "@sinclair/typebox": "^0.34.49" diff --git a/packages/coder-tui/src/aigateway.ts b/packages/coder-tui/src/aigateway.ts deleted file mode 100644 index 22a8f5fff..000000000 --- a/packages/coder-tui/src/aigateway.ts +++ /dev/null @@ -1,227 +0,0 @@ -/** - * Agentuity AI Gateway Custom Provider Extension - * - * Registers models from the Agentuity AI Gateway using the appropriate API type - * based on model ID patterns. Models are loaded dynamically from the gateway's /models endpoint. - * - * Usage: - * Use /model to switch to aigateway models - */ -import { delimiter, join } from 'node:path'; -import { existsSync } from 'node:fs'; -import { execFileSync } from 'node:child_process'; -import { createMinimalLogger, StructuredError } from '@agentuity/core'; -import { - AIGatewayService, - type AIGatewayModel, - type AIGatewayModels, -} from '@agentuity/core/aigateway'; -import { createServerFetchAdapter } from '@agentuity/server'; -import type { ExtensionAPI, ProviderModelConfig } from '@mariozechner/pi-coding-agent'; - -export type KnownApi = - | 'openai-completions' - | 'mistral-conversations' - | 'openai-responses' - | 'azure-openai-responses' - | 'openai-codex-responses' - | 'anthropic-messages' - | 'bedrock-converse-stream' - | 'google-generative-ai' - | 'google-gemini-cli' - | 'google-vertex'; - -const KNOWN_APIS = new Set([ - 'openai-completions', - 'mistral-conversations', - 'openai-responses', - 'azure-openai-responses', - 'openai-codex-responses', - 'anthropic-messages', - 'bedrock-converse-stream', - 'google-generative-ai', - 'google-gemini-cli', - 'google-vertex', -] satisfies KnownApi[]); - -const AIGatewayModelFetchError = StructuredError('AIGatewayModelFetchError')<{ - cause?: unknown; -}>(); - -function getEnv(...keys: string[]): string | undefined { - for (const key of keys) { - if (process.env[key]) { - return process.env[key]; - } - } -} - -function normalizeCredential(value: unknown): string | undefined { - if (value === undefined || value === null) { - return undefined; - } - const normalized = String(value).trim(); - return normalized.length > 0 ? normalized : undefined; -} - -function isKnownApi(api: unknown): api is KnownApi { - return typeof api === 'string' && KNOWN_APIS.has(api); -} - -function getRegion(): string { - return getEnv('AGENTUITY_REGION') ?? 'usc'; -} - -function getBaseUrl(): string { - const region = getRegion(); - return `https://aigateway-${region}.agentuity.cloud`; -} - -async function fetchModels(): Promise { - const baseUrl = getBaseUrl(); - let apiKey = normalizeCredential( - getEnv( - 'AGENTUITY_CODER_API_KEY', - 'AGENTUITY_SDK_KEY', - 'AGENTUITY_CLI_API_KEY', - 'AGENTUITY_CLI_KEY' - ) - ); - let orgId = normalizeCredential( - getEnv('AGENTUITY_ORGID', 'AGENTUITY_CLOUD_ORG_ID', 'AGENTUITY_ORG_ID') - ); - - if (!apiKey) { - let found = false; - const path = process.env.PATH?.split(delimiter) ?? []; - for (const dir of path) { - const fn = join(dir, 'agentuity'); - if (existsSync(fn)) { - try { - const res = execFileSync(fn, ['auth', 'apikey', '--json']); - const apiKeyResult = JSON.parse(res.toString()) as { apiKey: string }; - apiKey = normalizeCredential(apiKeyResult.apiKey); - found = true; - if (!orgId) { - const ores = execFileSync(fn, ['auth', 'org', 'current']); - orgId = normalizeCredential(ores); - if (!orgId) { - console.warn( - 'Cannot determine the org id. Use `agentuity auth org select` to select a default organization' - ); - return {}; - } - } - break; - } catch (error) { - throw new AIGatewayModelFetchError({ - message: 'Failed to fetch models from AI Gateway', - cause: error, - }); - } - } - } - if (!found) { - console.warn( - 'AGENTUITY_SDK_KEY, AGENTUITY_CLI_API_KEY or AGENTUITY_CLI_KEY not set and cannot find the agentuit cli, cannot fetch models from AI Gateway' - ); - return {}; - } - } - - if (!apiKey) { - console.warn('Cannot determine the API key, cannot fetch models from AI Gateway'); - return {}; - } - - process.env.AGENTUITY_AIGATEWAY_KEY = apiKey; - if (orgId) { - process.env.AGENTUITY_AIGATEWAY_ORGID = orgId; - } - - try { - const service = new AIGatewayService( - baseUrl, - createServerFetchAdapter({ headers: {} }, createMinimalLogger()) - ); - return await service.listModels(); - } catch (error) { - throw new AIGatewayModelFetchError({ - message: 'Failed to fetch models from AI Gateway', - cause: error, - }); - } -} - -function sanitizeModalities(modalities: string[] | undefined): ('text' | 'image')[] { - const sanitized = (modalities ?? []).filter( - (modality): modality is 'text' | 'image' => modality === 'text' || modality === 'image' - ); - return sanitized.length > 0 ? sanitized : ['text']; -} - -function toPiModel(m: AIGatewayModel): ProviderModelConfig { - return { - id: m.id, - name: m.name, - reasoning: m.reasoning ?? false, - input: sanitizeModalities(m.input_modalities), - contextWindow: m.context_window ?? 40000, - maxTokens: m.max_output_tokens ?? 64000, - cost: { - input: m.pricing?.input ?? 0, - output: m.pricing?.output ?? 0, - cacheRead: m.pricing?.cached_input ?? 0, - cacheWrite: 0, - }, - compat: { - supportsDeveloperRole: false, - }, - }; -} - -export async function setupAIGateway(pi: ExtensionAPI) { - const models = await fetchModels(); - const baseUrl = getBaseUrl(); - - const allModels: AIGatewayModel[] = []; - for (const providerModels of Object.values(models)) { - if (providerModels) { - allModels.push(...providerModels); - } - } - if (allModels.length === 0) { - return; - } - - const modelsByApi = new Map(); - - for (const m of allModels) { - const apiType = m.api; - if (!isKnownApi(apiType)) { - continue; // THIS SHOULD NEVER HAPPEN BUT JUST IN CASE - } - const existing = modelsByApi.get(apiType) ?? []; - existing.push(toPiModel(m)); - modelsByApi.set(apiType, existing); - } - - const headers: Record = {}; - if (process.env.AGENTUITY_AIGATEWAY_ORGID) { - headers['x-agentuity-orgid'] = process.env.AGENTUITY_AIGATEWAY_ORGID; - } - - for (const [apiType, providerModels] of modelsByApi) { - const apitok = apiType.split('-'); - const name = apitok.length >= 2 ? apitok.slice(0, 2).join('-') : apitok[0]; - const providerName = `agentuity/${name}`; - pi.registerProvider(providerName, { - baseUrl, - apiKey: 'AGENTUITY_AIGATEWAY_KEY', - headers, - authHeader: true, - api: apiType, - models: providerModels, - }); - } -} diff --git a/packages/coder-tui/src/index.ts b/packages/coder-tui/src/index.ts index 384116df9..804301a78 100644 --- a/packages/coder-tui/src/index.ts +++ b/packages/coder-tui/src/index.ts @@ -25,7 +25,7 @@ import { handleRemoteUiRequest } from './remote-ui-handler.ts'; import { buildInboundRpcPromptText, getInboundRpcDeliverAs } from './inbound-rpc.ts'; import { applyCoderAuthHeaders, getCoderAuthCurlArgs } from './auth.ts'; import { formatToolDisplay } from './agentuity-cli.ts'; -import { setupAIGateway } from './aigateway.ts'; +import { setupAIGateway } from '@agentuity/pi'; import { adaptInitMessageForLocalTui } from './local-init-filter.ts'; import { selectSubAgentToolNames } from './subagent-tool-selection.ts'; import type { diff --git a/packages/coder-tui/tsconfig.json b/packages/coder-tui/tsconfig.json index 5156d1abb..2511e52c5 100644 --- a/packages/coder-tui/tsconfig.json +++ b/packages/coder-tui/tsconfig.json @@ -6,12 +6,10 @@ "rootDir": "./src", "rewriteRelativeImportExtensions": true, "paths": { - "@agentuity/core": ["../core/src"], - "@agentuity/core/*": ["../core/src/services/*"], - "@agentuity/server": ["../server/src"] + "@agentuity/pi": ["../pi/src"] } }, "include": ["src/**/*"], "exclude": ["test/**/*"], - "references": [{ "path": "../core" }, { "path": "../server" }] + "references": [{ "path": "../pi" }] } diff --git a/packages/pi/README.md b/packages/pi/README.md new file mode 100644 index 000000000..c4b2da640 --- /dev/null +++ b/packages/pi/README.md @@ -0,0 +1,3 @@ +# @agentuity/pi + +Agentuity AI Gateway provider plugin for the Pi coding agent. diff --git a/packages/pi/package.json b/packages/pi/package.json new file mode 100644 index 000000000..e6e3241b4 --- /dev/null +++ b/packages/pi/package.json @@ -0,0 +1,44 @@ +{ + "name": "@agentuity/pi", + "version": "2.0.15", + "description": "Agentuity AI Gateway provider plugin for Pi coding agent", + "license": "Apache-2.0", + "author": "Agentuity employees and contributors", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "files": [ + "README.md", + "src", + "dist" + ], + "exports": { + ".": { + "import": "./dist/index.js", + "types": "./dist/index.d.ts" + } + }, + "scripts": { + "clean": "rm -rf dist tsconfig.tsbuildinfo", + "build": "bunx tsc --build", + "typecheck": "bunx tsc --noEmit", + "prepublishOnly": "bun run clean && bun run build" + }, + "dependencies": { + "@agentuity/core": "workspace:*", + "@agentuity/server": "workspace:*" + }, + "peerDependencies": { + "@mariozechner/pi-coding-agent": "^0.72.1" + }, + "devDependencies": { + "@mariozechner/pi-coding-agent": "^0.72.1", + "@types/bun": "^1.3.9", + "bun-types": "^1.3.9", + "typescript": "^5.9.0" + }, + "publishConfig": { + "access": "public" + }, + "sideEffects": false +} diff --git a/packages/pi/src/index.ts b/packages/pi/src/index.ts new file mode 100644 index 000000000..dbf807a6b --- /dev/null +++ b/packages/pi/src/index.ts @@ -0,0 +1,465 @@ +/** + * Agentuity AI Gateway Custom Provider Extension + * + * Registers models from the Agentuity AI Gateway using the appropriate API type + * based on model ID patterns. Models are loaded dynamically from the gateway's /models endpoint. + * + * Usage: + * Use /model to switch to aigateway models + */ +import { execFileSync } from 'node:child_process'; +import { existsSync } from 'node:fs'; +import { delimiter, join } from 'node:path'; +import { createMinimalLogger, StructuredError } from '@agentuity/core'; +import { + AIGatewayService, + type AIGatewayModel, + type AIGatewayModels, +} from '@agentuity/core/aigateway'; +import { createServerFetchAdapter } from '@agentuity/server'; +import type { + ExtensionAPI, + ExtensionCommandContext, + ProviderModelConfig, +} from '@mariozechner/pi-coding-agent'; + +export type KnownApi = + | 'openai-completions' + | 'mistral-conversations' + | 'openai-responses' + | 'azure-openai-responses' + | 'openai-codex-responses' + | 'anthropic-messages' + | 'bedrock-converse-stream' + | 'google-generative-ai' + | 'google-gemini-cli' + | 'google-vertex'; + +const KNOWN_APIS = new Set([ + 'openai-completions', + 'mistral-conversations', + 'openai-responses', + 'azure-openai-responses', + 'openai-codex-responses', + 'anthropic-messages', + 'bedrock-converse-stream', + 'google-generative-ai', + 'google-gemini-cli', + 'google-vertex', +] satisfies KnownApi[]); + +const AIGatewayModelFetchError = StructuredError('AIGatewayModelFetchError')<{ + cause?: unknown; +}>(); + +type AgentuityOrganization = { + id: string; + name: string; +}; + +type AgentuityWhoami = { + organizations?: AgentuityOrganization[]; +}; + +type AgentuityRegion = { + region: string; + description: string; + default?: boolean; +}; + +function parseFirstJsonObject(value: string): unknown { + const start = value.indexOf('{'); + if (start === -1) { + throw new SyntaxError('No JSON object found'); + } + + let depth = 0; + let inString = false; + let escaped = false; + for (let i = start; i < value.length; i++) { + const char = value[i]; + if (escaped) { + escaped = false; + continue; + } + if (char === '\\') { + escaped = true; + continue; + } + if (char === '"') { + inString = !inString; + continue; + } + if (inString) { + continue; + } + if (char === '{') { + depth++; + } else if (char === '}') { + depth--; + if (depth === 0) { + return JSON.parse(value.slice(start, i + 1)); + } + } + } + + throw new SyntaxError('Unterminated JSON object'); +} + +function parseJson(value: string): unknown { + const trimmed = value.trim(); + if (trimmed.startsWith('[')) { + return JSON.parse(trimmed); + } + return parseFirstJsonObject(trimmed); +} + +function getEnv(...keys: string[]): string | undefined { + for (const key of keys) { + if (process.env[key]) { + return process.env[key]; + } + } +} + +function normalizeCredential(value: unknown): string | undefined { + if (value === undefined || value === null) { + return undefined; + } + const normalized = String(value).trim(); + return normalized.length > 0 ? normalized : undefined; +} + +function isKnownApi(api: unknown): api is KnownApi { + return typeof api === 'string' && KNOWN_APIS.has(api); +} + +function getRegion(): string { + return getEnv('AGENTUITY_REGION') ?? 'usc'; +} + +function getBaseUrl(): string { + const region = getRegion(); + return `https://aigateway-${region}.agentuity.cloud`; +} + +function getAgentuityCliPath(): string | undefined { + const path = process.env.PATH?.split(delimiter) ?? []; + for (const dir of path) { + const fn = join(dir, 'agentuity'); + if (existsSync(fn)) { + return fn; + } + } +} + +function fetchOrganizations(): AgentuityOrganization[] { + const agentuity = getAgentuityCliPath(); + if (!agentuity) { + return []; + } + + const res = execFileSync(agentuity, ['auth', 'whoami', '--json']); + const whoami = parseJson(res.toString()) as AgentuityWhoami; + return (whoami.organizations ?? []).filter( + (org): org is AgentuityOrganization => + typeof org?.id === 'string' && + org.id.length > 0 && + typeof org?.name === 'string' && + org.name.length > 0 + ); +} + +function fetchRegions(): AgentuityRegion[] { + const agentuity = getAgentuityCliPath(); + if (!agentuity) { + return []; + } + + const res = execFileSync(agentuity, ['cloud', 'region', 'list', '--json']); + const regions = parseJson(res.toString()) as AgentuityRegion[]; + return (Array.isArray(regions) ? regions : []).filter( + (region): region is AgentuityRegion => + typeof region?.region === 'string' && + region.region.length > 0 && + typeof region?.description === 'string' && + region.description.length > 0 + ); +} + +function getCurrentOrgId(): string | undefined { + return normalizeCredential( + getEnv( + 'AGENTUITY_AIGATEWAY_ORGID', + 'AGENTUITY_ORGID', + 'AGENTUITY_CLOUD_ORG_ID', + 'AGENTUITY_ORG_ID' + ) + ); +} + +async function fetchModels(): Promise { + const baseUrl = getBaseUrl(); + let apiKey = normalizeCredential( + getEnv( + 'AGENTUITY_CODER_API_KEY', + 'AGENTUITY_SDK_KEY', + 'AGENTUITY_CLI_API_KEY', + 'AGENTUITY_CLI_KEY' + ) + ); + let orgId = normalizeCredential( + getEnv('AGENTUITY_ORGID', 'AGENTUITY_CLOUD_ORG_ID', 'AGENTUITY_ORG_ID') + ); + + if (!apiKey) { + let found = false; + const fn = getAgentuityCliPath(); + if (fn) { + try { + const res = execFileSync(fn, ['auth', 'apikey', '--json']); + const apiKeyResult = parseJson(res.toString()) as { apiKey: string }; + apiKey = normalizeCredential(apiKeyResult.apiKey); + found = true; + if (!orgId) { + const ores = execFileSync(fn, ['auth', 'org', 'current']); + orgId = normalizeCredential(ores); + if (!orgId) { + return {}; + } + } + } catch (error) { + throw new AIGatewayModelFetchError({ + message: 'Failed to fetch models from AI Gateway', + cause: error, + }); + } + } + if (!found) { + console.warn( + 'AGENTUITY_SDK_KEY, AGENTUITY_CLI_API_KEY or AGENTUITY_CLI_KEY not set and cannot find the agentuity cli, cannot fetch models from AI Gateway' + ); + return {}; + } + } + + if (!apiKey) { + console.warn('Cannot determine the API key, cannot fetch models from AI Gateway'); + return {}; + } + + process.env.AGENTUITY_AIGATEWAY_KEY = apiKey; + if (orgId) { + process.env.AGENTUITY_AIGATEWAY_ORGID = orgId; + } + + try { + const service = new AIGatewayService( + baseUrl, + createServerFetchAdapter({ headers: {} }, createMinimalLogger()) + ); + return await service.listModels(); + } catch (error) { + throw new AIGatewayModelFetchError({ + message: 'Failed to fetch models from AI Gateway', + cause: error, + }); + } +} + +function sanitizeModalities(modalities: string[] | undefined): ('text' | 'image')[] { + const sanitized = (modalities ?? []).filter( + (modality): modality is 'text' | 'image' => modality === 'text' || modality === 'image' + ); + return sanitized.length > 0 ? sanitized : ['text']; +} + +function toPiModel(m: AIGatewayModel): ProviderModelConfig { + return { + id: m.id, + name: m.name, + reasoning: m.reasoning ?? false, + input: sanitizeModalities(m.input_modalities), + contextWindow: m.context_window ?? 40000, + maxTokens: m.max_output_tokens ?? 64000, + cost: { + input: m.pricing?.input ?? 0, + output: m.pricing?.output ?? 0, + cacheRead: m.pricing?.cached_input ?? 0, + cacheWrite: 0, + }, + compat: { + supportsDeveloperRole: false, + }, + }; +} + +async function registerAIGatewayProviders(pi: ExtensionAPI) { + const models = await fetchModels(); + const baseUrl = getBaseUrl(); + + const allModels: AIGatewayModel[] = []; + for (const providerModels of Object.values(models)) { + if (providerModels) { + allModels.push(...providerModels); + } + } + if (allModels.length === 0) { + return; + } + + const modelsByApi = new Map(); + + for (const m of allModels) { + const apiType = m.api; + if (!isKnownApi(apiType)) { + continue; + } + const existing = modelsByApi.get(apiType) ?? []; + existing.push(toPiModel(m)); + modelsByApi.set(apiType, existing); + } + + const headers: Record = {}; + if (process.env.AGENTUITY_AIGATEWAY_ORGID) { + headers['x-agentuity-orgid'] = process.env.AGENTUITY_AIGATEWAY_ORGID; + } + + for (const [apiType, providerModels] of modelsByApi) { + const apitok = apiType.split('-'); + const name = apitok.length >= 2 ? apitok.slice(0, 2).join('-') : apitok[0]; + const providerName = `agentuity/${name}`; + pi.registerProvider(providerName, { + baseUrl, + apiKey: 'AGENTUITY_AIGATEWAY_KEY', + headers, + authHeader: true, + api: apiType, + models: providerModels, + }); + } +} + +function registerRegionCommand(pi: ExtensionAPI): void { + pi.registerCommand('region', { + description: 'Select active Agentuity region', + handler: async (_args: string, ctx: ExtensionCommandContext) => { + if (!ctx.hasUI) { + return; + } + + let regions: AgentuityRegion[]; + try { + regions = fetchRegions(); + } catch (error) { + ctx.ui.notify(`Failed to load Agentuity regions: ${String(error)}`, 'error'); + return; + } + + if (regions.length === 0) { + ctx.ui.notify('No Agentuity regions found.', 'warning'); + return; + } + + const currentRegion = getRegion(); + const labels = regions.map((region) => { + const marker = region.region === currentRegion ? '* ' : ''; + const defaultLabel = region.default ? ' default' : ''; + return `${marker}${region.description} (${region.region})${defaultLabel}`; + }); + const selected = await ctx.ui.select('Select Agentuity Region', labels); + if (!selected) { + return; + } + + const selectedIndex = labels.indexOf(selected); + const region = regions[selectedIndex]; + if (!region) { + return; + } + + process.env.AGENTUITY_REGION = region.region; + ctx.ui.notify(`Using Agentuity region: ${region.description}`, 'info'); + + try { + await registerAIGatewayProviders(pi); + ctx.ui.notify('Agentuity AI Gateway models refreshed.', 'info'); + } catch (error) { + ctx.ui.notify(`Failed to refresh AI Gateway models: ${String(error)}`, 'error'); + } + }, + }); +} + +function registerOrganizationCommand(pi: ExtensionAPI): void { + pi.registerCommand('organization', { + description: 'Select active Agentuity organization', + handler: async (_args: string, ctx: ExtensionCommandContext) => { + if (!ctx.hasUI) { + return; + } + + let organizations: AgentuityOrganization[]; + try { + organizations = fetchOrganizations(); + } catch (error) { + ctx.ui.notify(`Failed to load Agentuity organizations: ${String(error)}`, 'error'); + return; + } + + if (organizations.length === 0) { + ctx.ui.notify('No Agentuity organizations found for the current CLI login.', 'warning'); + return; + } + + const currentOrgId = getCurrentOrgId(); + const labels = organizations.map((org) => { + const marker = org.id === currentOrgId ? '* ' : ''; + return `${marker}${org.name} (${org.id})`; + }); + const selected = await ctx.ui.select('Select Agentuity Organization', labels); + if (!selected) { + return; + } + + const selectedIndex = labels.indexOf(selected); + const organization = organizations[selectedIndex]; + if (!organization) { + return; + } + + process.env.AGENTUITY_AIGATEWAY_ORGID = organization.id; + process.env.AGENTUITY_ORGID = organization.id; + process.env.AGENTUITY_CLOUD_ORG_ID = organization.id; + process.env.AGENTUITY_ORG_ID = organization.id; + + ctx.ui.notify(`Using Agentuity organization: ${organization.name}`, 'info'); + + try { + await registerAIGatewayProviders(pi); + ctx.ui.notify('Agentuity AI Gateway models refreshed.', 'info'); + } catch (error) { + ctx.ui.notify(`Failed to refresh AI Gateway models: ${String(error)}`, 'error'); + } + }, + }); +} + +export async function setupAIGateway(pi: ExtensionAPI) { + registerOrganizationCommand(pi); + registerRegionCommand(pi); + let showedOrganizationPrompt = false; + pi.on('session_start', (_event, ctx) => { + if (showedOrganizationPrompt || !ctx.hasUI || getCurrentOrgId()) { + return; + } + showedOrganizationPrompt = true; + ctx.ui.notify( + 'Use /organization to select an Agentuity organization for AI Gateway models.', + 'info' + ); + }); + await registerAIGatewayProviders(pi); +} + +export default setupAIGateway; diff --git a/packages/pi/tsconfig.json b/packages/pi/tsconfig.json new file mode 100644 index 000000000..192f7101c --- /dev/null +++ b/packages/pi/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./dist", + "rootDir": "./src", + "paths": { + "@agentuity/core": ["../core/src"], + "@agentuity/core/*": ["../core/src/services/*"], + "@agentuity/server": ["../server/src"] + } + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"], + "references": [{ "path": "../core" }, { "path": "../server" }] +} diff --git a/tsconfig.json b/tsconfig.json index abec9854f..5e81d9de1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -17,6 +17,7 @@ { "path": "./packages/migrate" }, { "path": "./packages/opencode" }, { "path": "./packages/postgres" }, + { "path": "./packages/pi" }, { "path": "./packages/queue" }, { "path": "./packages/react" }, { "path": "./packages/runtime" },