diff --git a/bun.lock b/bun.lock index a61c220ed..0784241cd 100644 --- a/bun.lock +++ b/bun.lock @@ -6,7 +6,7 @@ "name": "agentcore", "dependencies": { "@aws-sdk/client-bedrock-agentcore": "^3.1092.0", - "@aws-sdk/client-bedrock-agentcore-control": "^3.1079.0", + "@aws-sdk/client-bedrock-agentcore-control": "^3.1102.0", "@aws-sdk/client-iam": "^3.1080.0", "@smithy/core": "3.29.3", "@tanstack/react-query": "^5.101.2", @@ -43,7 +43,7 @@ "@aws-sdk/client-bedrock-agentcore": ["@aws-sdk/client-bedrock-agentcore@3.1094.0", "", { "dependencies": { "@aws-sdk/core": "^3.976.0", "@aws-sdk/credential-provider-node": "^3.972.71", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.29.4", "@smithy/fetch-http-handler": "^5.6.6", "@smithy/node-http-handler": "^4.9.6", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-K8+YaYFyaVJvidwVSnmXlHjpOel/qz4ii2TZY2shXbkMoQxftjHuiXlplKeb5pWhzdS092q6YqPR5CZrMH90Ug=="], - "@aws-sdk/client-bedrock-agentcore-control": ["@aws-sdk/client-bedrock-agentcore-control@3.1087.0", "", { "dependencies": { "@aws-sdk/core": "^3.975.2", "@aws-sdk/credential-provider-node": "^3.972.68", "@aws-sdk/types": "^3.974.1", "@smithy/core": "^3.29.3", "@smithy/fetch-http-handler": "^5.6.5", "@smithy/node-http-handler": "^4.9.5", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-ZCWITtXgDZG1g8NfTZVMTIwH/NR9eifXRW1g5+d4Jb93MmwbJWLWRSd0xqxgywMay0sqhNOKXHE4nQRIf9glag=="], + "@aws-sdk/client-bedrock-agentcore-control": ["@aws-sdk/client-bedrock-agentcore-control@3.1102.0", "", { "dependencies": { "@aws-sdk/core": "^3.977.5", "@aws-sdk/credential-provider-node": "^3.972.77", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/fetch-http-handler": "^5.6.13", "@smithy/node-http-handler": "^4.9.13", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-6HjZkg14iiJr8VinzAKy5XRJe5JLrDg9USBia2UZZAg8leGff2/+cyH0y4ZYQqRe1f1TaMZfecNSQLCy3XInjQ=="], "@aws-sdk/client-iam": ["@aws-sdk/client-iam@3.1087.0", "", { "dependencies": { "@aws-sdk/core": "^3.975.2", "@aws-sdk/credential-provider-node": "^3.972.68", "@aws-sdk/types": "^3.974.1", "@smithy/core": "^3.29.3", "@smithy/fetch-http-handler": "^5.6.5", "@smithy/node-http-handler": "^4.9.5", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-S2UBBQnPMTREF52WPz7yabvags3rPYLOPwq8LOPNJIekyAIqUwtGN46z2tqGNr+NyGz9cpEI+3brJgkWKQGNIQ=="], @@ -345,17 +345,15 @@ "zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], - "@aws-sdk/client-bedrock-agentcore-control/@aws-sdk/core": ["@aws-sdk/core@3.975.2", "", { "dependencies": { "@aws-sdk/types": "^3.974.1", "@aws-sdk/xml-builder": "^3.972.35", "@aws/lambda-invoke-store": "^0.3.0", "@smithy/core": "^3.29.3", "@smithy/signature-v4": "^5.6.3", "@smithy/types": "^4.16.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-iyeXwziyjJpixq5OmhsIyrSWx8vwcI7gDo4yRUC3EP7NQtOo9iAJiIEc3G+/HkhtNXqOhofiCK7Lc34Sq+fJWg=="], + "@aws-sdk/client-bedrock-agentcore-control/@aws-sdk/core": ["@aws-sdk/core@3.977.5", "", { "dependencies": { "@aws-sdk/types": "^3.974.2", "@aws-sdk/xml-builder": "^3.972.37", "@aws/lambda-invoke-store": "^0.3.0", "@smithy/core": "^3.31.1", "@smithy/signature-v4": "^5.6.12", "@smithy/types": "^4.16.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-O5otOc1c6UZh5HsHAaPdYBcUUR9HL6mtnKqvc8nxN/CKDGUBUpsdh0q8K04Uz/dd1i0TaGyIQuQNqoO7+ad2TQ=="], - "@aws-sdk/client-bedrock-agentcore-control/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.68", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.58", "@aws-sdk/credential-provider-http": "^3.972.60", "@aws-sdk/credential-provider-ini": "^3.973.2", "@aws-sdk/credential-provider-process": "^3.972.58", "@aws-sdk/credential-provider-sso": "^3.973.2", "@aws-sdk/credential-provider-web-identity": "^3.972.64", "@aws-sdk/types": "^3.974.1", "@smithy/core": "^3.29.3", "@smithy/credential-provider-imds": "^4.4.7", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-4akjzW9CjorByYfqXBXmYUh/h7Io3U4DtVgGGh9TQraZ7ZlyJqNyHwDRGiUFnHD+BTOeTbCesCa4sJaK7BGZ7A=="], + "@aws-sdk/client-bedrock-agentcore-control/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.77", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.66", "@aws-sdk/credential-provider-http": "^3.972.68", "@aws-sdk/credential-provider-ini": "^3.973.11", "@aws-sdk/credential-provider-process": "^3.972.66", "@aws-sdk/credential-provider-sso": "^3.973.10", "@aws-sdk/credential-provider-web-identity": "^3.972.72", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/credential-provider-imds": "^4.4.16", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-l4nitYCN/Ls57vtUfdextCjTjW41JD7lQiAnuR0RTbdByFc/6OmEAzwGd+lrp6CUtiXGQL1FCaYiamfHASrwBw=="], - "@aws-sdk/client-bedrock-agentcore-control/@aws-sdk/types": ["@aws-sdk/types@3.974.1", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-W0IQZR0eaBqlBFIIofMapaWkw1W0U+Xi4dvW+BqwmCEMd8Ng2U6IhkxuPSjMVnR8klLjfuS9PeZWUl1N6UaZdg=="], + "@aws-sdk/client-bedrock-agentcore-control/@smithy/core": ["@smithy/core@3.31.1", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-CyogUINxvi7C7LDsh8Syo6hVJOT9ckz4rG8dRZfTJ8r91HkMY59PnNooaj7WcHyxEkxPfBAmbgztZU+xTo76lg=="], - "@aws-sdk/client-bedrock-agentcore-control/@smithy/core": ["@smithy/core@3.29.3", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-L+Ys6ecjk5vwPMAKHBpPKlJ3DkqwNcnfEISXBZIsVvWG/XKXfsAP8mwIYlTeLcd2ElHdesPI8OuOmJSFAPhm6A=="], + "@aws-sdk/client-bedrock-agentcore-control/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.6.13", "", { "dependencies": { "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-4fW86pEUOMbrD5nkbyl/tTvPHHWJFbuB2odl6ps9lWfHoXf9HWh3Q/Smh59qH1g7+c/BSZghX6bbUk4gsiMs8A=="], - "@aws-sdk/client-bedrock-agentcore-control/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.6.5", "", { "dependencies": { "@smithy/core": "^3.29.3", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-SuqeisTyPoiIPtIYru/sGxGyXzmZ+8nnFOhC+qRPglt06Ebd1yH//CDltZB2J/3WBNVhwfUaZ0EtHB3cm2X32g=="], - - "@aws-sdk/client-bedrock-agentcore-control/@smithy/node-http-handler": ["@smithy/node-http-handler@4.9.5", "", { "dependencies": { "@smithy/core": "^3.29.3", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-bNqdxTQTxmLbomSmlkZFz8L6B/feQ2HHzw4L2zY7Ecp2XffYAZq2uzdWDdxJHJFbEvqd+SRuluJso0P8+xPdbw=="], + "@aws-sdk/client-bedrock-agentcore-control/@smithy/node-http-handler": ["@smithy/node-http-handler@4.9.13", "", { "dependencies": { "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-Nmd/Nl35zfYrd+a6OO2cDJb3GPh9bgTjIUhcM+JFfjpp8/osCgboDV5nCT1I01Pv6R13eSKDKLSoVa5ZB6Zsfw=="], "@aws-sdk/client-iam/@aws-sdk/core": ["@aws-sdk/core@3.975.2", "", { "dependencies": { "@aws-sdk/types": "^3.974.1", "@aws-sdk/xml-builder": "^3.972.35", "@aws/lambda-invoke-store": "^0.3.0", "@smithy/core": "^3.29.3", "@smithy/signature-v4": "^5.6.3", "@smithy/types": "^4.16.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-iyeXwziyjJpixq5OmhsIyrSWx8vwcI7gDo4yRUC3EP7NQtOo9iAJiIEc3G+/HkhtNXqOhofiCK7Lc34Sq+fJWg=="], @@ -379,23 +377,23 @@ "react-devtools-core/ws": ["ws@7.5.12", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-1xGnbYN3zbog9CwuNDQULNRrTCLIn46/WmpR1f0w6PsCYQHkylZr5vkd6kfMZYV6pRnQkcPNRyiA8LsrNKyhpg=="], - "@aws-sdk/client-bedrock-agentcore-control/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.35", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-pXzaWe3evZhjxDXAlMnqISe/XefTCGwBJG4nFTXaWSgAnMkqPEhxEPqJNhhpGesEvKFhvNpnozJJ4GTL11bRYw=="], + "@aws-sdk/client-bedrock-agentcore-control/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.37", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-zKq4HQum8JwDyEuyfuI4bbiAcU0KxP6qy+9PR/IsR92IyE/DaBAikzAS50tjxip4bqIIANpCcG+Yyj6CVhXupg=="], - "@aws-sdk/client-bedrock-agentcore-control/@aws-sdk/core/@smithy/signature-v4": ["@smithy/signature-v4@5.6.4", "", { "dependencies": { "@smithy/core": "^3.29.3", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-B89bpf2t/y/wia6LZ+4JfHXYQT9PnVftsH05rgJKKIStS7r/4XSs9HOjtPoLtgcA6HCW9jVqX5DBbq7E0PAkiQ=="], + "@aws-sdk/client-bedrock-agentcore-control/@aws-sdk/core/@smithy/signature-v4": ["@smithy/signature-v4@5.6.12", "", { "dependencies": { "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-I6KLtq3H0qqSuV9vLglfi8puHqzygzWHOnI4z/Rdoo+q50vvo18vBRdPAvvEtcaKROz7Zn6qnPa14kRfPH6PcQ=="], - "@aws-sdk/client-bedrock-agentcore-control/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.58", "", { "dependencies": { "@aws-sdk/core": "^3.975.2", "@aws-sdk/types": "^3.974.1", "@smithy/core": "^3.29.3", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-vyGtvK1rY940eq7JT0yIGKuZ+2kpPSJcHibSvGlit5oiMFDamzC7cxBGLl4FLnd6suihMXDI2FSF2dL6TmBqPA=="], + "@aws-sdk/client-bedrock-agentcore-control/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.66", "", { "dependencies": { "@aws-sdk/core": "^3.977.5", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-bOzP2+zdJ0XrghywB4FaJXtGZCx9yS0AGps+VJ5yEgg30wVyHNmVDBwVDXcRypzQY5iLGCS3NSn0nsuISqjFCQ=="], - "@aws-sdk/client-bedrock-agentcore-control/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.60", "", { "dependencies": { "@aws-sdk/core": "^3.975.2", "@aws-sdk/types": "^3.974.1", "@smithy/core": "^3.29.3", "@smithy/fetch-http-handler": "^5.6.5", "@smithy/node-http-handler": "^4.9.5", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-g9b9YzDrD5pcKiPBJfCSXRfFMrA39eR0guUhZ5SRm+7vMAVc43+effxbcamxBjSd5bUhrdKo5te/yQuWurLXLA=="], + "@aws-sdk/client-bedrock-agentcore-control/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.68", "", { "dependencies": { "@aws-sdk/core": "^3.977.5", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/fetch-http-handler": "^5.6.13", "@smithy/node-http-handler": "^4.9.13", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-lkunS8X+H6V76WE+t/uGQm/U8v0JXK5mLfNFTUAMlE1kqaCjwlmqKJrgCVtqjK/vqnlrSWsLK4Lr4NANBWlfTQ=="], - "@aws-sdk/client-bedrock-agentcore-control/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.973.2", "", { "dependencies": { "@aws-sdk/core": "^3.975.2", "@aws-sdk/credential-provider-env": "^3.972.58", "@aws-sdk/credential-provider-http": "^3.972.60", "@aws-sdk/credential-provider-login": "^3.972.64", "@aws-sdk/credential-provider-process": "^3.972.58", "@aws-sdk/credential-provider-sso": "^3.973.2", "@aws-sdk/credential-provider-web-identity": "^3.972.64", "@aws-sdk/nested-clients": "^3.997.32", "@aws-sdk/types": "^3.974.1", "@smithy/core": "^3.29.3", "@smithy/credential-provider-imds": "^4.4.7", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-Yr7yxNyQ8aHt9Ww0RPFUZx+xiem+vl7vuwhP0tniTijoesJNV5jou9HCgVpI0GEPAF+89TkOvilE5uRrZJnjaw=="], + "@aws-sdk/client-bedrock-agentcore-control/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.973.11", "", { "dependencies": { "@aws-sdk/core": "^3.977.5", "@aws-sdk/credential-provider-env": "^3.972.66", "@aws-sdk/credential-provider-http": "^3.972.68", "@aws-sdk/credential-provider-login": "^3.972.73", "@aws-sdk/credential-provider-process": "^3.972.66", "@aws-sdk/credential-provider-sso": "^3.973.10", "@aws-sdk/credential-provider-web-identity": "^3.972.72", "@aws-sdk/nested-clients": "^3.997.40", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/credential-provider-imds": "^4.4.16", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-KoDEolYtLHG/8C+IiZpXbJWyBOMkrHV+j66Kb9PBXmLv5euGb7aELvuCmLenoGAV6gBW2wM7TsG/1e5iulH4kA=="], - "@aws-sdk/client-bedrock-agentcore-control/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.58", "", { "dependencies": { "@aws-sdk/core": "^3.975.2", "@aws-sdk/types": "^3.974.1", "@smithy/core": "^3.29.3", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-1nYitRCaDmXWUrpBJt6WlcGjLx1JVsMY8rlYuHHsTYTSaYikbixYdQSyINN2VYq1F798uTO9qHAzytL25M8g3A=="], + "@aws-sdk/client-bedrock-agentcore-control/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.66", "", { "dependencies": { "@aws-sdk/core": "^3.977.5", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-YOnX6bIhdjx0QfaENu2PB0eFm5MEc9ft8XNGQ+NxMfeLSq9aE+XjWCwDupEnV4UWv5ZFpBLJbTREIx7KNOoqpQ=="], - "@aws-sdk/client-bedrock-agentcore-control/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.973.2", "", { "dependencies": { "@aws-sdk/core": "^3.975.2", "@aws-sdk/nested-clients": "^3.997.32", "@aws-sdk/token-providers": "3.1087.0", "@aws-sdk/types": "^3.974.1", "@smithy/core": "^3.29.3", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-pjMLaLU/JZi5lVfmR14V1OZqRBTuMHf6AwGNZA0K9hK+JKtO3jcLBarfD8iq5oc8cSowvc/9R32sqMVXZPo6xQ=="], + "@aws-sdk/client-bedrock-agentcore-control/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.973.10", "", { "dependencies": { "@aws-sdk/core": "^3.977.5", "@aws-sdk/nested-clients": "^3.997.40", "@aws-sdk/token-providers": "3.1102.0", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-IsXnQ35j5VE+3ZK6aIhT5ypB+Jim3zRwVz0nYuVwyBKZyu/SYx+O2/LQpng8c2EiuwyqceabsDlYrICHDlJPsA=="], - "@aws-sdk/client-bedrock-agentcore-control/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.64", "", { "dependencies": { "@aws-sdk/core": "^3.975.2", "@aws-sdk/nested-clients": "^3.997.32", "@aws-sdk/types": "^3.974.1", "@smithy/core": "^3.29.3", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-7Buc7p0OvDHW7iBsu4b+YdS0WnaFBDGKDfbVQqaac9dkWiSiUtIoarBDsA1RmOVXZijaZJDoHJFIQiicQvWRlQ=="], + "@aws-sdk/client-bedrock-agentcore-control/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.72", "", { "dependencies": { "@aws-sdk/core": "^3.977.5", "@aws-sdk/nested-clients": "^3.997.40", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-nj9Zlsy7ya+fy+jhWTJwgfr7YdtDM4xHyZvgKuftuny0UgROVx9lxwvsWJSLvpKk4lig0m0tHng3k1fEnt0LeA=="], - "@aws-sdk/client-bedrock-agentcore-control/@aws-sdk/credential-provider-node/@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.4.8", "", { "dependencies": { "@smithy/core": "^3.29.3", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-q9J7JTiXrAhB8sDp4px97uEPT7CwKH61Co78grdNQvU8QZAdiuaSRhP0tUVf2ogy36RZTrlMU1rBmDEH+cnkiA=="], + "@aws-sdk/client-bedrock-agentcore-control/@aws-sdk/credential-provider-node/@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.4.16", "", { "dependencies": { "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-QfuLWAkLzptffFW980AFeHZFdqds2B64rpEd3uJ6lgs3xVn9QegGMUgUcj+4d7dRrAsya3r58ZKpku97WcFb4w=="], "@aws-sdk/client-iam/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.35", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-pXzaWe3evZhjxDXAlMnqISe/XefTCGwBJG4nFTXaWSgAnMkqPEhxEPqJNhhpGesEvKFhvNpnozJJ4GTL11bRYw=="], @@ -421,15 +419,15 @@ "log-update/wrap-ansi/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], - "@aws-sdk/client-bedrock-agentcore-control/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.64", "", { "dependencies": { "@aws-sdk/core": "^3.975.2", "@aws-sdk/nested-clients": "^3.997.32", "@aws-sdk/types": "^3.974.1", "@smithy/core": "^3.29.3", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-YQoSI4d6kXvoenoG/0Jv/PqaAuukHzGmGXGyHBQYeEUNsYovlNAn/Sw1wp/WQbhcQ3HsEMGgjEahvD3igz6ecQ=="], + "@aws-sdk/client-bedrock-agentcore-control/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.73", "", { "dependencies": { "@aws-sdk/core": "^3.977.5", "@aws-sdk/nested-clients": "^3.997.40", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-tjsxMkTAFkmiV9ycmymapb9nLECWVOwFs0bZMQ9gB9bnbY8/HwfukHZlWbXZZp7qkPU6EXAfOcMm3DioFFEywA=="], - "@aws-sdk/client-bedrock-agentcore-control/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.32", "", { "dependencies": { "@aws-sdk/core": "^3.975.2", "@aws-sdk/signature-v4-multi-region": "^3.996.40", "@aws-sdk/types": "^3.974.1", "@smithy/core": "^3.29.3", "@smithy/fetch-http-handler": "^5.6.5", "@smithy/node-http-handler": "^4.9.5", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-6Yj2fr9XF67cndITea48rchTdVr3VGx6PN47bIKNinJAjLkmaIlz/4EBPCgJ8UmhVopiXmeAuPLI3+DXDDbMhQ=="], + "@aws-sdk/client-bedrock-agentcore-control/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.40", "", { "dependencies": { "@aws-sdk/core": "^3.977.5", "@aws-sdk/signature-v4-multi-region": "^3.996.43", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/fetch-http-handler": "^5.6.13", "@smithy/node-http-handler": "^4.9.13", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-hEdHT0PBR4fkGxWhwKG5EtEYKnAM7HKkp0vD10ufk4YcXejH4r4q6G/XhPzjUc6Yxo5kBS2vHg7llj4ViR9VTQ=="], - "@aws-sdk/client-bedrock-agentcore-control/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.32", "", { "dependencies": { "@aws-sdk/core": "^3.975.2", "@aws-sdk/signature-v4-multi-region": "^3.996.40", "@aws-sdk/types": "^3.974.1", "@smithy/core": "^3.29.3", "@smithy/fetch-http-handler": "^5.6.5", "@smithy/node-http-handler": "^4.9.5", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-6Yj2fr9XF67cndITea48rchTdVr3VGx6PN47bIKNinJAjLkmaIlz/4EBPCgJ8UmhVopiXmeAuPLI3+DXDDbMhQ=="], + "@aws-sdk/client-bedrock-agentcore-control/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.40", "", { "dependencies": { "@aws-sdk/core": "^3.977.5", "@aws-sdk/signature-v4-multi-region": "^3.996.43", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/fetch-http-handler": "^5.6.13", "@smithy/node-http-handler": "^4.9.13", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-hEdHT0PBR4fkGxWhwKG5EtEYKnAM7HKkp0vD10ufk4YcXejH4r4q6G/XhPzjUc6Yxo5kBS2vHg7llj4ViR9VTQ=="], - "@aws-sdk/client-bedrock-agentcore-control/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1087.0", "", { "dependencies": { "@aws-sdk/core": "^3.975.2", "@aws-sdk/nested-clients": "^3.997.32", "@aws-sdk/types": "^3.974.1", "@smithy/core": "^3.29.3", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-umM+qNq16f2fH+VLM5MqXW4ORNQAjk+TOSto73xbUHcKaU41L48j786r3UWQYlejeJk37NlvRYgxBT+MBkfaYQ=="], + "@aws-sdk/client-bedrock-agentcore-control/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1102.0", "", { "dependencies": { "@aws-sdk/core": "^3.977.5", "@aws-sdk/nested-clients": "^3.997.40", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-Ua700vVvM1q105yABSUQWkCK6FeTrNfU6ORGetJe5BzkZWY7QhkF7SVTOlmDGWRDNd6jbyY0Dv5e+E4bMBEmLg=="], - "@aws-sdk/client-bedrock-agentcore-control/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.32", "", { "dependencies": { "@aws-sdk/core": "^3.975.2", "@aws-sdk/signature-v4-multi-region": "^3.996.40", "@aws-sdk/types": "^3.974.1", "@smithy/core": "^3.29.3", "@smithy/fetch-http-handler": "^5.6.5", "@smithy/node-http-handler": "^4.9.5", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-6Yj2fr9XF67cndITea48rchTdVr3VGx6PN47bIKNinJAjLkmaIlz/4EBPCgJ8UmhVopiXmeAuPLI3+DXDDbMhQ=="], + "@aws-sdk/client-bedrock-agentcore-control/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.40", "", { "dependencies": { "@aws-sdk/core": "^3.977.5", "@aws-sdk/signature-v4-multi-region": "^3.996.43", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/fetch-http-handler": "^5.6.13", "@smithy/node-http-handler": "^4.9.13", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-hEdHT0PBR4fkGxWhwKG5EtEYKnAM7HKkp0vD10ufk4YcXejH4r4q6G/XhPzjUc6Yxo5kBS2vHg7llj4ViR9VTQ=="], "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.64", "", { "dependencies": { "@aws-sdk/core": "^3.975.2", "@aws-sdk/nested-clients": "^3.997.32", "@aws-sdk/types": "^3.974.1", "@smithy/core": "^3.29.3", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-YQoSI4d6kXvoenoG/0Jv/PqaAuukHzGmGXGyHBQYeEUNsYovlNAn/Sw1wp/WQbhcQ3HsEMGgjEahvD3igz6ecQ=="], @@ -445,11 +443,11 @@ "log-update/cli-cursor/restore-cursor/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], - "@aws-sdk/client-bedrock-agentcore-control/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.40", "", { "dependencies": { "@aws-sdk/types": "^3.974.1", "@smithy/signature-v4": "^5.6.3", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-wrGZ/authosokclY1DXsiWT/1WjfCI22FuZGgdcilF+XLTXs5dCjAtiFYSPsEToZkbm3Lj2YP8PoWg0yoMNu0g=="], + "@aws-sdk/client-bedrock-agentcore-control/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.43", "", { "dependencies": { "@aws-sdk/types": "^3.974.2", "@smithy/signature-v4": "^5.6.12", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-lKekx8bLBXSv4O+cslk9Zfnw2XKSkWBs3uWL5QGhH2ZAQfNS7FE0vcSSN2vD/AhxX54ZTywWxR4STThoeOXlBA=="], - "@aws-sdk/client-bedrock-agentcore-control/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.40", "", { "dependencies": { "@aws-sdk/types": "^3.974.1", "@smithy/signature-v4": "^5.6.3", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-wrGZ/authosokclY1DXsiWT/1WjfCI22FuZGgdcilF+XLTXs5dCjAtiFYSPsEToZkbm3Lj2YP8PoWg0yoMNu0g=="], + "@aws-sdk/client-bedrock-agentcore-control/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.43", "", { "dependencies": { "@aws-sdk/types": "^3.974.2", "@smithy/signature-v4": "^5.6.12", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-lKekx8bLBXSv4O+cslk9Zfnw2XKSkWBs3uWL5QGhH2ZAQfNS7FE0vcSSN2vD/AhxX54ZTywWxR4STThoeOXlBA=="], - "@aws-sdk/client-bedrock-agentcore-control/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.40", "", { "dependencies": { "@aws-sdk/types": "^3.974.1", "@smithy/signature-v4": "^5.6.3", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-wrGZ/authosokclY1DXsiWT/1WjfCI22FuZGgdcilF+XLTXs5dCjAtiFYSPsEToZkbm3Lj2YP8PoWg0yoMNu0g=="], + "@aws-sdk/client-bedrock-agentcore-control/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.43", "", { "dependencies": { "@aws-sdk/types": "^3.974.2", "@smithy/signature-v4": "^5.6.12", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-lKekx8bLBXSv4O+cslk9Zfnw2XKSkWBs3uWL5QGhH2ZAQfNS7FE0vcSSN2vD/AhxX54ZTywWxR4STThoeOXlBA=="], "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.40", "", { "dependencies": { "@aws-sdk/types": "^3.974.1", "@smithy/signature-v4": "^5.6.3", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-wrGZ/authosokclY1DXsiWT/1WjfCI22FuZGgdcilF+XLTXs5dCjAtiFYSPsEToZkbm3Lj2YP8PoWg0yoMNu0g=="], @@ -457,11 +455,11 @@ "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.40", "", { "dependencies": { "@aws-sdk/types": "^3.974.1", "@smithy/signature-v4": "^5.6.3", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-wrGZ/authosokclY1DXsiWT/1WjfCI22FuZGgdcilF+XLTXs5dCjAtiFYSPsEToZkbm3Lj2YP8PoWg0yoMNu0g=="], - "@aws-sdk/client-bedrock-agentcore-control/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region/@smithy/signature-v4": ["@smithy/signature-v4@5.6.4", "", { "dependencies": { "@smithy/core": "^3.29.3", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-B89bpf2t/y/wia6LZ+4JfHXYQT9PnVftsH05rgJKKIStS7r/4XSs9HOjtPoLtgcA6HCW9jVqX5DBbq7E0PAkiQ=="], + "@aws-sdk/client-bedrock-agentcore-control/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region/@smithy/signature-v4": ["@smithy/signature-v4@5.6.12", "", { "dependencies": { "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-I6KLtq3H0qqSuV9vLglfi8puHqzygzWHOnI4z/Rdoo+q50vvo18vBRdPAvvEtcaKROz7Zn6qnPa14kRfPH6PcQ=="], - "@aws-sdk/client-bedrock-agentcore-control/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region/@smithy/signature-v4": ["@smithy/signature-v4@5.6.4", "", { "dependencies": { "@smithy/core": "^3.29.3", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-B89bpf2t/y/wia6LZ+4JfHXYQT9PnVftsH05rgJKKIStS7r/4XSs9HOjtPoLtgcA6HCW9jVqX5DBbq7E0PAkiQ=="], + "@aws-sdk/client-bedrock-agentcore-control/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region/@smithy/signature-v4": ["@smithy/signature-v4@5.6.12", "", { "dependencies": { "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-I6KLtq3H0qqSuV9vLglfi8puHqzygzWHOnI4z/Rdoo+q50vvo18vBRdPAvvEtcaKROz7Zn6qnPa14kRfPH6PcQ=="], - "@aws-sdk/client-bedrock-agentcore-control/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region/@smithy/signature-v4": ["@smithy/signature-v4@5.6.4", "", { "dependencies": { "@smithy/core": "^3.29.3", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-B89bpf2t/y/wia6LZ+4JfHXYQT9PnVftsH05rgJKKIStS7r/4XSs9HOjtPoLtgcA6HCW9jVqX5DBbq7E0PAkiQ=="], + "@aws-sdk/client-bedrock-agentcore-control/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region/@smithy/signature-v4": ["@smithy/signature-v4@5.6.12", "", { "dependencies": { "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-I6KLtq3H0qqSuV9vLglfi8puHqzygzWHOnI4z/Rdoo+q50vvo18vBRdPAvvEtcaKROz7Zn6qnPa14kRfPH6PcQ=="], "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region/@smithy/signature-v4": ["@smithy/signature-v4@5.6.4", "", { "dependencies": { "@smithy/core": "^3.29.3", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-B89bpf2t/y/wia6LZ+4JfHXYQT9PnVftsH05rgJKKIStS7r/4XSs9HOjtPoLtgcA6HCW9jVqX5DBbq7E0PAkiQ=="], diff --git a/package.json b/package.json index c2fa1da53..19a1e1e5f 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ }, "dependencies": { "@aws-sdk/client-bedrock-agentcore": "^3.1092.0", - "@aws-sdk/client-bedrock-agentcore-control": "^3.1079.0", + "@aws-sdk/client-bedrock-agentcore-control": "^3.1102.0", "@aws-sdk/client-iam": "^3.1080.0", "@smithy/core": "3.29.3", "@tanstack/react-query": "^5.101.2", diff --git a/src/core/gateway.tsx b/src/core/gateway.tsx index 0e4808176..7f5f4a3d0 100644 --- a/src/core/gateway.tsx +++ b/src/core/gateway.tsx @@ -1,10 +1,16 @@ import { + CreateGatewayCommand, + CreateGatewayRuleCommand, + CreateGatewayTargetCommand, GetGatewayCommand, GetGatewayRuleCommand, GetGatewayTargetCommand, ListGatewayRulesCommand, ListGatewaysCommand, ListGatewayTargetsCommand, + type CreateGatewayResponse, + type CreateGatewayRuleResponse, + type CreateGatewayTargetResponse, type GetGatewayResponse, type GetGatewayRuleResponse, type GetGatewayTargetResponse, @@ -12,13 +18,33 @@ import { type ListGatewaysResponse, type ListGatewayTargetsResponse, } from "@aws-sdk/client-bedrock-agentcore-control"; -import type { CoreGatewayClient } from "../handlers/gateway/types"; +import type { + CoreGatewayClient, + CreateGatewayInput, + CreateGatewayRuleInput, + CreateGatewayTargetInput, +} from "../handlers/gateway/types"; import type { AwsClients, CoreOptions } from "./types"; import { toClientConfig } from "./utils"; export class GatewayClient implements CoreGatewayClient { constructor(private readonly clients: AwsClients) {} + async createGateway( + input: CreateGatewayInput, + options: CoreOptions, + ): Promise { + const control = this.clients.control(toClientConfig(options)); + const { protocol, roleArn, ...request } = input; + return control.send( + new CreateGatewayCommand({ + ...request, + roleArn, + ...(protocol === "mcp" ? { protocolType: "MCP" as const } : {}), + }), + ); + } + async getGateway(id: string, options: CoreOptions): Promise { return this.clients .control(toClientConfig(options)) @@ -63,6 +89,15 @@ export class GatewayClient implements CoreGatewayClient { ); } + async createGatewayTarget( + input: CreateGatewayTargetInput, + options: CoreOptions, + ): Promise { + return this.clients + .control(toClientConfig(options)) + .send(new CreateGatewayTargetCommand(input)); + } + async getGatewayRule( gatewayId: string, ruleId: string, @@ -90,4 +125,11 @@ export class GatewayClient implements CoreGatewayClient { }), ); } + + async createGatewayRule( + input: CreateGatewayRuleInput, + options: CoreOptions, + ): Promise { + return this.clients.control(toClientConfig(options)).send(new CreateGatewayRuleCommand(input)); + } } diff --git a/src/core/index.tsx b/src/core/index.tsx index f99f9347e..b34b816d2 100644 --- a/src/core/index.tsx +++ b/src/core/index.tsx @@ -101,7 +101,7 @@ export class CoreClient implements AwsClients { } // iam returns the IAM client for `config`, creating and caching it on first - // use (used to provision default harness execution roles). + // use (used to provision default execution roles). iam(config: ClientConfig): IAMClient { const key = cacheKey(config); let client = this.iamClients.get(key); diff --git a/src/handlers/gateway/__fixtures__/create/CreateGatewayCommand.7f678b79455bc400.json b/src/handlers/gateway/__fixtures__/create/CreateGatewayCommand.7f678b79455bc400.json new file mode 100644 index 000000000..f64f9cb8f --- /dev/null +++ b/src/handlers/gateway/__fixtures__/create/CreateGatewayCommand.7f678b79455bc400.json @@ -0,0 +1,17 @@ +{ + "gatewayArn": "arn:aws:bedrock-agentcore:us-east-1:685197708687:gateway/agentcore-cli-gateway-create-fixture-bkf6yayfce", + "gatewayId": "agentcore-cli-gateway-create-fixture-bkf6yayfce", + "createdAt": { + "$date": "2026-08-06T17:25:41.425Z" + }, + "updatedAt": { + "$date": "2026-08-06T17:25:41.425Z" + }, + "status": "CREATING", + "name": "agentcore-cli-gateway-create-fixture", + "authorizerType": "NONE", + "gatewayUrl": "https://agentcore-cli-gateway-create-fixture-bkf6yayfce.gateway.bedrock-agentcore.us-east-1.amazonaws.com", + "description": "Disposable Gateway Create fixture", + "roleArn": "arn:aws:iam::685197708687:role/AgentCoreGateway-agentcore-cli-gateway-create-fix-f0aa3f799a4b", + "workloadIdentityDetails": {} +} \ No newline at end of file diff --git a/src/handlers/gateway/__fixtures__/create/CreateGatewayRuleCommand.d87304453bfe9534.json b/src/handlers/gateway/__fixtures__/create/CreateGatewayRuleCommand.d87304453bfe9534.json new file mode 100644 index 000000000..2b0ab7ee5 --- /dev/null +++ b/src/handlers/gateway/__fixtures__/create/CreateGatewayRuleCommand.d87304453bfe9534.json @@ -0,0 +1,19 @@ +{ + "ruleId": "93d59ee8-8a9b-4535-9f11-df7006241fed", + "gatewayArn": "arn:aws:bedrock-agentcore:us-east-1:685197708687:gateway/agentcore-cli-gateway-create-fixture-bkf6yayfce", + "priority": 10, + "actions": [ + { + "routeToTarget": { + "staticRoute": { + "targetName": "http-fixture" + } + } + } + ], + "createdAt": { + "$date": "2026-08-06T17:25:57.672Z" + }, + "status": "CREATING", + "description": "Disposable Gateway Rule Create fixture" +} \ No newline at end of file diff --git a/src/handlers/gateway/__fixtures__/create/CreateGatewayTargetCommand.22512d9b9027f76a.json b/src/handlers/gateway/__fixtures__/create/CreateGatewayTargetCommand.22512d9b9027f76a.json new file mode 100644 index 000000000..f11639c22 --- /dev/null +++ b/src/handlers/gateway/__fixtures__/create/CreateGatewayTargetCommand.22512d9b9027f76a.json @@ -0,0 +1,35 @@ +{ + "gatewayArn": "arn:aws:bedrock-agentcore:us-east-1:685197708687:gateway/agentcore-cli-gateway-create-fixture-bkf6yayfce", + "targetId": "U4ADV7VNSV", + "createdAt": { + "$date": "2026-08-06T17:25:52.273Z" + }, + "updatedAt": { + "$date": "2026-08-06T17:25:52.273Z" + }, + "status": "CREATING", + "name": "web-search-fixture", + "targetConfiguration": { + "mcp": { + "connector": { + "source": { + "connectorId": "web-search" + }, + "configurations": [ + { + "name": "WebSearch", + "parameterValues": { + "maxResults": 10 + } + } + ] + } + } + }, + "credentialProviderConfigurations": [ + { + "credentialProviderType": "GATEWAY_IAM_ROLE" + } + ], + "description": "Disposable Gateway Connector Create fixture" +} \ No newline at end of file diff --git a/src/handlers/gateway/__fixtures__/create/CreateGatewayTargetCommand.93c8d0503a636294.json b/src/handlers/gateway/__fixtures__/create/CreateGatewayTargetCommand.93c8d0503a636294.json new file mode 100644 index 000000000..35aa79c1b --- /dev/null +++ b/src/handlers/gateway/__fixtures__/create/CreateGatewayTargetCommand.93c8d0503a636294.json @@ -0,0 +1,20 @@ +{ + "gatewayArn": "arn:aws:bedrock-agentcore:us-east-1:685197708687:gateway/agentcore-cli-gateway-create-fixture-bkf6yayfce", + "targetId": "VZ7M0WBGJF", + "createdAt": { + "$date": "2026-08-06T17:25:46.743Z" + }, + "updatedAt": { + "$date": "2026-08-06T17:25:46.743Z" + }, + "status": "CREATING", + "name": "http-fixture", + "targetConfiguration": { + "http": { + "passthrough": { + "endpoint": "https://example.com", + "protocolType": "CUSTOM" + } + } + } +} \ No newline at end of file diff --git a/src/handlers/gateway/__fixtures__/create/GetGatewayCommand.5ff84cafc95022b4.json b/src/handlers/gateway/__fixtures__/create/GetGatewayCommand.5ff84cafc95022b4.json new file mode 100644 index 000000000..58e25d0d8 --- /dev/null +++ b/src/handlers/gateway/__fixtures__/create/GetGatewayCommand.5ff84cafc95022b4.json @@ -0,0 +1,19 @@ +{ + "gatewayArn": "arn:aws:bedrock-agentcore:us-east-1:685197708687:gateway/agentcore-cli-gateway-create-fixture-bkf6yayfce", + "gatewayId": "agentcore-cli-gateway-create-fixture-bkf6yayfce", + "createdAt": { + "$date": "2026-08-06T17:25:41.425Z" + }, + "updatedAt": { + "$date": "2026-08-06T17:25:42.396Z" + }, + "status": "READY", + "name": "agentcore-cli-gateway-create-fixture", + "authorizerType": "NONE", + "gatewayUrl": "https://agentcore-cli-gateway-create-fixture-bkf6yayfce.gateway.bedrock-agentcore.us-east-1.amazonaws.com", + "description": "Disposable Gateway Create fixture", + "roleArn": "arn:aws:iam::685197708687:role/AgentCoreGateway-agentcore-cli-gateway-create-fix-f0aa3f799a4b", + "workloadIdentityDetails": { + "workloadIdentityArn": "arn:aws:bedrock-agentcore:us-east-1:685197708687:workload-identity-directory/default/workload-identity/agentcore-cli-gateway-create-fixture-bkf6yayfce" + } +} \ No newline at end of file diff --git a/src/handlers/gateway/__fixtures__/create/GetGatewayRuleCommand.9a6bd6a64eebb5df.json b/src/handlers/gateway/__fixtures__/create/GetGatewayRuleCommand.9a6bd6a64eebb5df.json new file mode 100644 index 000000000..fd163aa51 --- /dev/null +++ b/src/handlers/gateway/__fixtures__/create/GetGatewayRuleCommand.9a6bd6a64eebb5df.json @@ -0,0 +1,22 @@ +{ + "ruleId": "93d59ee8-8a9b-4535-9f11-df7006241fed", + "gatewayArn": "arn:aws:bedrock-agentcore:us-east-1:685197708687:gateway/agentcore-cli-gateway-create-fixture-bkf6yayfce", + "priority": 10, + "actions": [ + { + "routeToTarget": { + "staticRoute": { + "targetName": "http-fixture" + } + } + } + ], + "createdAt": { + "$date": "2026-08-06T17:25:57.672Z" + }, + "status": "ACTIVE", + "description": "Disposable Gateway Rule Create fixture", + "updatedAt": { + "$date": "2026-08-06T17:25:57.672Z" + } +} \ No newline at end of file diff --git a/src/handlers/gateway/__fixtures__/create/GetGatewayTargetCommand.3855b58e8bc470ab.json b/src/handlers/gateway/__fixtures__/create/GetGatewayTargetCommand.3855b58e8bc470ab.json new file mode 100644 index 000000000..41f19859a --- /dev/null +++ b/src/handlers/gateway/__fixtures__/create/GetGatewayTargetCommand.3855b58e8bc470ab.json @@ -0,0 +1,35 @@ +{ + "gatewayArn": "arn:aws:bedrock-agentcore:us-east-1:685197708687:gateway/agentcore-cli-gateway-create-fixture-bkf6yayfce", + "targetId": "U4ADV7VNSV", + "createdAt": { + "$date": "2026-08-06T17:25:52.273Z" + }, + "updatedAt": { + "$date": "2026-08-06T17:25:53.039Z" + }, + "status": "READY", + "name": "web-search-fixture", + "targetConfiguration": { + "mcp": { + "connector": { + "source": { + "connectorId": "web-search" + }, + "configurations": [ + { + "name": "WebSearch", + "parameterValues": { + "maxResults": 10 + } + } + ] + } + } + }, + "credentialProviderConfigurations": [ + { + "credentialProviderType": "GATEWAY_IAM_ROLE" + } + ], + "description": "Disposable Gateway Connector Create fixture" +} \ No newline at end of file diff --git a/src/handlers/gateway/__fixtures__/create/GetGatewayTargetCommand.91cea07923c15ff.json b/src/handlers/gateway/__fixtures__/create/GetGatewayTargetCommand.91cea07923c15ff.json new file mode 100644 index 000000000..4bb1476e1 --- /dev/null +++ b/src/handlers/gateway/__fixtures__/create/GetGatewayTargetCommand.91cea07923c15ff.json @@ -0,0 +1,20 @@ +{ + "gatewayArn": "arn:aws:bedrock-agentcore:us-east-1:685197708687:gateway/agentcore-cli-gateway-create-fixture-bkf6yayfce", + "targetId": "VZ7M0WBGJF", + "createdAt": { + "$date": "2026-08-06T17:25:46.743Z" + }, + "updatedAt": { + "$date": "2026-08-06T17:25:47.390Z" + }, + "status": "READY", + "name": "http-fixture", + "targetConfiguration": { + "http": { + "passthrough": { + "endpoint": "https://example.com", + "protocolType": "CUSTOM" + } + } + } +} \ No newline at end of file diff --git a/src/handlers/gateway/__fixtures__/create/connector-create.golden.json b/src/handlers/gateway/__fixtures__/create/connector-create.golden.json new file mode 100644 index 000000000..86bb9c031 --- /dev/null +++ b/src/handlers/gateway/__fixtures__/create/connector-create.golden.json @@ -0,0 +1,31 @@ +{ + "gatewayArn": "arn:aws:bedrock-agentcore:us-east-1:685197708687:gateway/agentcore-cli-gateway-create-fixture-bkf6yayfce", + "targetId": "U4ADV7VNSV", + "createdAt": "2026-08-06T17:25:52.273Z", + "updatedAt": "2026-08-06T17:25:52.273Z", + "status": "CREATING", + "name": "web-search-fixture", + "targetConfiguration": { + "mcp": { + "connector": { + "source": { + "connectorId": "web-search" + }, + "configurations": [ + { + "name": "WebSearch", + "parameterValues": { + "maxResults": 10 + } + } + ] + } + } + }, + "credentialProviderConfigurations": [ + { + "credentialProviderType": "GATEWAY_IAM_ROLE" + } + ], + "description": "Disposable Gateway Connector Create fixture" +} \ No newline at end of file diff --git a/src/handlers/gateway/__fixtures__/create/gateway-create.golden.json b/src/handlers/gateway/__fixtures__/create/gateway-create.golden.json new file mode 100644 index 000000000..4ec1da7bb --- /dev/null +++ b/src/handlers/gateway/__fixtures__/create/gateway-create.golden.json @@ -0,0 +1,13 @@ +{ + "gatewayArn": "arn:aws:bedrock-agentcore:us-east-1:685197708687:gateway/agentcore-cli-gateway-create-fixture-bkf6yayfce", + "gatewayId": "agentcore-cli-gateway-create-fixture-bkf6yayfce", + "createdAt": "2026-08-06T17:25:41.425Z", + "updatedAt": "2026-08-06T17:25:41.425Z", + "status": "CREATING", + "name": "agentcore-cli-gateway-create-fixture", + "authorizerType": "NONE", + "gatewayUrl": "https://agentcore-cli-gateway-create-fixture-bkf6yayfce.gateway.bedrock-agentcore.us-east-1.amazonaws.com", + "description": "Disposable Gateway Create fixture", + "roleArn": "arn:aws:iam::685197708687:role/AgentCoreGateway-agentcore-cli-gateway-create-fix-f0aa3f799a4b", + "workloadIdentityDetails": {} +} \ No newline at end of file diff --git a/src/handlers/gateway/__fixtures__/create/rule-create.golden.json b/src/handlers/gateway/__fixtures__/create/rule-create.golden.json new file mode 100644 index 000000000..b011a33b3 --- /dev/null +++ b/src/handlers/gateway/__fixtures__/create/rule-create.golden.json @@ -0,0 +1,17 @@ +{ + "ruleId": "93d59ee8-8a9b-4535-9f11-df7006241fed", + "gatewayArn": "arn:aws:bedrock-agentcore:us-east-1:685197708687:gateway/agentcore-cli-gateway-create-fixture-bkf6yayfce", + "priority": 10, + "actions": [ + { + "routeToTarget": { + "staticRoute": { + "targetName": "http-fixture" + } + } + } + ], + "createdAt": "2026-08-06T17:25:57.672Z", + "status": "CREATING", + "description": "Disposable Gateway Rule Create fixture" +} \ No newline at end of file diff --git a/src/handlers/gateway/__fixtures__/create/target-create.golden.json b/src/handlers/gateway/__fixtures__/create/target-create.golden.json new file mode 100644 index 000000000..183dfcdac --- /dev/null +++ b/src/handlers/gateway/__fixtures__/create/target-create.golden.json @@ -0,0 +1,16 @@ +{ + "gatewayArn": "arn:aws:bedrock-agentcore:us-east-1:685197708687:gateway/agentcore-cli-gateway-create-fixture-bkf6yayfce", + "targetId": "VZ7M0WBGJF", + "createdAt": "2026-08-06T17:25:46.743Z", + "updatedAt": "2026-08-06T17:25:46.743Z", + "status": "CREATING", + "name": "http-fixture", + "targetConfiguration": { + "http": { + "passthrough": { + "endpoint": "https://example.com", + "protocolType": "CUSTOM" + } + } + } +} \ No newline at end of file diff --git a/src/handlers/gateway/connector/create/index.tsx b/src/handlers/gateway/connector/create/index.tsx new file mode 100644 index 000000000..584fde697 --- /dev/null +++ b/src/handlers/gateway/connector/create/index.tsx @@ -0,0 +1,128 @@ +import type { + CredentialProviderConfiguration, + MetadataConfiguration, + PrivateEndpoint, + TargetConfiguration, +} from "@aws-sdk/client-bedrock-agentcore-control"; +import z from "zod"; +import { InputValidationError } from "../../../../errors"; +import { type AppIO, SourceResolver } from "../../../../io"; +import { createHandler, flag } from "../../../../router"; +import { JsonRendererKey } from "../../../../tui"; +import type { Core } from "../../../types"; +import { coreOptsFromCtx, parseJsonArrayFlag, parseJsonObjectFlag } from "../../../utils"; +import type { CreateGatewayTargetInput } from "../../types"; +import { GatewayConnectorTarget } from "../gatewayConnectorTarget"; + +export const createCreateGatewayConnectorHandler = (core: Core, io: AppIO) => + createHandler({ + name: "create", + description: "create a connector-backed Gateway Target", + flags: [ + flag("gateway-id", "the parent Gateway ID", z.string().optional()), + flag("name", "Connector Target name", z.string().optional()), + flag("description", "Connector Target description", z.string().optional()), + flag( + "connector-configuration", + "connector-backed Target configuration (JSON; inline, file://, or - for stdin)", + z.string().optional(), + ), + flag( + "connector", + "curated connector", + z.enum(["web-search", "bedrock-knowledge-bases", "bedrock-mantle"]).optional(), + ), + flag( + "knowledge-base-id", + "Knowledge Base ID for the bedrock-knowledge-bases connector", + z.string().optional(), + ), + flag( + "credential-provider-configurations", + "outbound credentials (JSON array; inline, file://, or - for stdin)", + z.string().optional(), + ), + flag( + "metadata-configuration", + "metadata propagation (JSON; inline, file://, or - for stdin)", + z.string().optional(), + ), + flag( + "private-endpoint", + "private endpoint (JSON; inline, file://, or - for stdin)", + z.string().optional(), + ), + ], + handle: async (ctx, flags) => { + if (!flags["gateway-id"]) { + throw new InputValidationError("required option '--gateway-id ' not specified"); + } + if (!flags.name) { + throw new InputValidationError("required option '--name ' not specified"); + } + if ((flags.connector === undefined) === (flags["connector-configuration"] === undefined)) { + throw new InputValidationError( + "specify exactly one of '--connector' or '--connector-configuration'", + ); + } + if ( + flags.connector === "bedrock-knowledge-bases" && + flags["knowledge-base-id"] === undefined + ) { + throw new InputValidationError( + "--connector bedrock-knowledge-bases requires --knowledge-base-id", + ); + } + if ( + flags["knowledge-base-id"] !== undefined && + flags.connector !== "bedrock-knowledge-bases" + ) { + throw new InputValidationError( + "--knowledge-base-id requires --connector bedrock-knowledge-bases", + ); + } + + const source = new SourceResolver({ stdin: io.stdin }); + const exactConfiguration = parseJsonObjectFlag( + "connector-configuration", + await source.resolveText("connector-configuration", flags["connector-configuration"]), + ); + const targetConfiguration = + exactConfiguration ?? + GatewayConnectorTarget.fromShortcut(flags.connector!, flags["knowledge-base-id"]); + if (!GatewayConnectorTarget.is(targetConfiguration)) { + throw new InputValidationError( + "--connector-configuration must contain an MCP or inference connector Target", + ); + } + + const credentialProviderConfigurations = parseJsonArrayFlag( + "credential-provider-configurations", + await source.resolveText( + "credential-provider-configurations", + flags["credential-provider-configurations"], + ), + ); + const metadataConfiguration = parseJsonObjectFlag( + "metadata-configuration", + await source.resolveText("metadata-configuration", flags["metadata-configuration"]), + ); + const privateEndpoint = parseJsonObjectFlag( + "private-endpoint", + await source.resolveText("private-endpoint", flags["private-endpoint"]), + ); + const input: CreateGatewayTargetInput = { + gatewayIdentifier: flags["gateway-id"], + name: flags.name, + targetConfiguration, + ...(flags.description ? { description: flags.description } : {}), + ...(credentialProviderConfigurations ? { credentialProviderConfigurations } : {}), + ...(metadataConfiguration ? { metadataConfiguration } : {}), + ...(privateEndpoint ? { privateEndpoint } : {}), + }; + + ctx + .require(JsonRendererKey) + .renderJson(await core.gateway.createGatewayTarget(input, coreOptsFromCtx(ctx))); + }, + }); diff --git a/src/handlers/gateway/connector/gatewayConnectorTarget.test.ts b/src/handlers/gateway/connector/gatewayConnectorTarget.test.ts new file mode 100644 index 000000000..aa27adc85 --- /dev/null +++ b/src/handlers/gateway/connector/gatewayConnectorTarget.test.ts @@ -0,0 +1,63 @@ +import { describe, expect, test } from "bun:test"; +import type { TargetConfiguration } from "@aws-sdk/client-bedrock-agentcore-control"; +import { GatewayConnectorTarget, type GatewayConnectorShortcut } from "./gatewayConnectorTarget"; + +describe("GatewayConnectorTarget.fromShortcut", () => { + test.each([ + { + shortcut: "web-search", + expected: { + mcp: { + connector: { + source: { connectorId: "web-search" }, + configurations: [ + { + name: "WebSearch", + parameterValues: { maxResults: 10 }, + }, + ], + }, + }, + }, + }, + { + shortcut: "bedrock-knowledge-bases", + knowledgeBaseId: "KB12345678", + expected: { + mcp: { + connector: { + source: { connectorId: "bedrock-knowledge-bases" }, + configurations: [ + { + name: "Retrieve", + parameterValues: { knowledgeBaseId: "KB12345678" }, + }, + ], + }, + }, + }, + }, + { + shortcut: "bedrock-mantle", + expected: { + inference: { + connector: { + source: { connectorId: "bedrock-mantle" }, + }, + }, + }, + }, + ] as { + shortcut: GatewayConnectorShortcut; + knowledgeBaseId?: string; + expected: TargetConfiguration; + }[])("builds the $shortcut configuration", ({ shortcut, knowledgeBaseId, expected }) => { + expect(GatewayConnectorTarget.fromShortcut(shortcut, knowledgeBaseId)).toEqual(expected); + }); + + test("requires a Knowledge Base ID", () => { + expect(() => GatewayConnectorTarget.fromShortcut("bedrock-knowledge-bases")).toThrow( + /Knowledge Base ID/, + ); + }); +}); diff --git a/src/handlers/gateway/connector/gatewayConnectorTarget.ts b/src/handlers/gateway/connector/gatewayConnectorTarget.ts new file mode 100644 index 000000000..bb895e036 --- /dev/null +++ b/src/handlers/gateway/connector/gatewayConnectorTarget.ts @@ -0,0 +1,55 @@ +import type { TargetConfiguration } from "@aws-sdk/client-bedrock-agentcore-control"; + +export type GatewayConnectorShortcut = "web-search" | "bedrock-knowledge-bases" | "bedrock-mantle"; + +export class GatewayConnectorTarget { + static is(configuration: TargetConfiguration | undefined): boolean { + return ( + configuration?.mcp?.connector !== undefined || + configuration?.inference?.connector !== undefined + ); + } + + static fromShortcut( + shortcut: GatewayConnectorShortcut, + knowledgeBaseId?: string, + ): TargetConfiguration { + switch (shortcut) { + case "bedrock-mantle": + return { inference: { connector: { source: { connectorId: shortcut } } } }; + case "bedrock-knowledge-bases": + if (!knowledgeBaseId) { + throw new Error( + "A Knowledge Base ID is required for the bedrock-knowledge-bases connector", + ); + } + return { + mcp: { + connector: { + source: { connectorId: shortcut }, + configurations: [ + { + name: "Retrieve", + parameterValues: { knowledgeBaseId }, + }, + ], + }, + }, + }; + case "web-search": + return { + mcp: { + connector: { + source: { connectorId: shortcut }, + configurations: [ + { + name: "WebSearch", + parameterValues: { maxResults: 10 }, + }, + ], + }, + }, + }; + } + } +} diff --git a/src/handlers/gateway/connector/get/index.tsx b/src/handlers/gateway/connector/get/index.tsx index 5932fcddb..111e9265c 100644 --- a/src/handlers/gateway/connector/get/index.tsx +++ b/src/handlers/gateway/connector/get/index.tsx @@ -1,16 +1,10 @@ -import type { TargetConfiguration } from "@aws-sdk/client-bedrock-agentcore-control"; import z from "zod"; import { InputValidationError } from "../../../../errors"; import { createHandler, flag } from "../../../../router"; import { JsonRendererKey } from "../../../../tui"; import type { Core } from "../../../types"; import { coreOptsFromCtx } from "../../../utils"; - -function isConnectorTarget(configuration: TargetConfiguration | undefined): boolean { - return ( - configuration?.mcp?.connector !== undefined || configuration?.inference?.connector !== undefined - ); -} +import { GatewayConnectorTarget } from "../gatewayConnectorTarget"; export const createGetGatewayConnectorHandler = (core: Core) => createHandler({ @@ -33,7 +27,7 @@ export const createGetGatewayConnectorHandler = (core: Core) => flags.id, coreOptsFromCtx(ctx), ); - if (!isConnectorTarget(target.targetConfiguration)) { + if (!GatewayConnectorTarget.is(target.targetConfiguration)) { throw new InputValidationError(`Gateway Target "${flags.id}" is not connector-backed`); } diff --git a/src/handlers/gateway/connector/index.tsx b/src/handlers/gateway/connector/index.tsx index c4eddc556..4a87991ca 100644 --- a/src/handlers/gateway/connector/index.tsx +++ b/src/handlers/gateway/connector/index.tsx @@ -2,12 +2,14 @@ import type { AppIO } from "../../../io"; import { Router } from "../../../router"; import { createHelpDefault } from "../../help"; import type { Core } from "../../types"; +import { createCreateGatewayConnectorHandler } from "./create"; import { createGetGatewayConnectorHandler } from "./get"; import { createListGatewayConnectorsHandler } from "./list"; export function createGatewayConnectorHandler(core: Core, io: AppIO): Router { return new Router("connector", "inspect connectors configured for an AgentCore Gateway") .default(createHelpDefault(io)) + .handler(createCreateGatewayConnectorHandler(core, io)) .handler(createGetGatewayConnectorHandler(core)) .handler(createListGatewayConnectorsHandler(core)); } diff --git a/src/handlers/gateway/create/index.tsx b/src/handlers/gateway/create/index.tsx new file mode 100644 index 000000000..4a2680a99 --- /dev/null +++ b/src/handlers/gateway/create/index.tsx @@ -0,0 +1,157 @@ +import type { + AuthorizerConfiguration, + GatewayInterceptorConfiguration, + GatewayProtocolConfiguration, +} from "@aws-sdk/client-bedrock-agentcore-control"; +import z from "zod"; +import { InputValidationError } from "../../../errors"; +import { type AppIO, SourceResolver } from "../../../io"; +import { createHandler, flag } from "../../../router"; +import { JsonRendererKey } from "../../../tui"; +import type { Core } from "../../types"; +import { coreOptsFromCtx, parseJsonArrayFlag, parseJsonObjectFlag, parseTags } from "../../utils"; +import type { CreateGatewayInput } from "../types"; + +export const createCreateGatewayHandler = (core: Core, io: AppIO) => + createHandler({ + name: "create", + description: "create an AgentCore Gateway", + flags: [ + flag("name", "the Gateway name", z.string().optional()), + flag("role-arn", "IAM role the Gateway assumes", z.string().optional()), + flag( + "protocol", + "restrict Target protocols to MCP; omitted allows every Target protocol", + z.enum(["mcp"]).optional(), + ), + flag( + "authorizer-type", + "inbound authorizer: AWS_IAM, CUSTOM_JWT, NONE, or AUTHENTICATE_ONLY", + z.enum(["AWS_IAM", "CUSTOM_JWT", "NONE", "AUTHENTICATE_ONLY"]).optional(), + ), + flag("description", "Gateway description", z.string().optional()), + flag( + "protocol-configuration", + "MCP protocol configuration (JSON; inline, file://, or - for stdin)", + z.string().optional(), + ), + flag( + "authorizer-configuration", + "authorizer configuration (JSON; inline, file://, or - for stdin)", + z.string().optional(), + ), + flag("kms-key-arn", "KMS key ARN", z.string().optional()), + flag( + "interceptor-configurations", + "interceptor configurations (JSON array; inline, file://, or - for stdin)", + z.string().optional(), + ), + flag("policy-engine-arn", "Policy Engine ARN", z.string().optional()), + flag( + "policy-engine-mode", + "Policy Engine mode: log-only or enforce", + z.enum(["log-only", "enforce"]).optional(), + ), + flag("exception-level", "exception detail level: debug", z.enum(["debug"]).optional()), + flag( + "tags", + "tags as key=value (repeatable) or a JSON object", + z.array(z.string()).optional(), + ), + flag("client-token", "idempotency token", z.string().optional()), + ], + handle: async (ctx, flags) => { + if (!flags.name) { + throw new InputValidationError("required option '--name ' not specified"); + } + if (!flags["role-arn"]) { + throw new InputValidationError("required option '--role-arn ' not specified"); + } + if (!flags["authorizer-type"]) { + throw new InputValidationError( + "required option '--authorizer-type ' not specified", + ); + } + if ( + flags["authorizer-type"] === "CUSTOM_JWT" && + flags["authorizer-configuration"] === undefined + ) { + throw new InputValidationError("CUSTOM_JWT requires --authorizer-configuration"); + } + if ( + flags["authorizer-type"] !== "CUSTOM_JWT" && + flags["authorizer-configuration"] !== undefined + ) { + throw new InputValidationError("--authorizer-configuration is valid only with CUSTOM_JWT"); + } + if ( + (flags["policy-engine-arn"] === undefined) !== + (flags["policy-engine-mode"] === undefined) + ) { + throw new InputValidationError( + "--policy-engine-arn and --policy-engine-mode must be supplied together", + ); + } + + const source = new SourceResolver({ stdin: io.stdin }); + const protocolConfiguration = parseJsonObjectFlag( + "protocol-configuration", + await source.resolveText("protocol-configuration", flags["protocol-configuration"]), + ); + const authorizerConfiguration = parseJsonObjectFlag( + "authorizer-configuration", + await source.resolveText("authorizer-configuration", flags["authorizer-configuration"]), + ); + const interceptorConfigurations = parseJsonArrayFlag( + "interceptor-configurations", + await source.resolveText("interceptor-configurations", flags["interceptor-configurations"]), + ); + const policyEngineConfiguration = + flags["policy-engine-arn"] && flags["policy-engine-mode"] + ? { + arn: flags["policy-engine-arn"], + mode: + flags["policy-engine-mode"] === "enforce" + ? ("ENFORCE" as const) + : ("LOG_ONLY" as const), + } + : undefined; + const tags = await resolveTags(source, flags.tags); + + const input: CreateGatewayInput = { + name: flags.name, + roleArn: flags["role-arn"], + ...(flags.protocol ? { protocol: flags.protocol } : {}), + authorizerType: flags["authorizer-type"], + ...(flags.description ? { description: flags.description } : {}), + ...(protocolConfiguration ? { protocolConfiguration } : {}), + ...(authorizerConfiguration ? { authorizerConfiguration } : {}), + ...(flags["kms-key-arn"] ? { kmsKeyArn: flags["kms-key-arn"] } : {}), + ...(interceptorConfigurations ? { interceptorConfigurations } : {}), + ...(policyEngineConfiguration ? { policyEngineConfiguration } : {}), + ...(flags["exception-level"] ? { exceptionLevel: "DEBUG" as const } : {}), + ...(tags ? { tags } : {}), + ...(flags["client-token"] ? { clientToken: flags["client-token"] } : {}), + }; + + ctx + .require(JsonRendererKey) + .renderJson(await core.gateway.createGateway(input, coreOptsFromCtx(ctx))); + }, + }); + +async function resolveTags( + source: SourceResolver, + values: string[] | undefined, +): Promise | undefined> { + const value = values?.[0]; + if ( + values?.length === 1 && + value && + (value === "-" || value.startsWith("file://") || value.trimStart().startsWith("{")) + ) { + const resolved = await source.resolveText("tags", value); + return parseTags([resolved!]); + } + return parseTags(values); +} diff --git a/src/handlers/gateway/gateway.create.test.tsx b/src/handlers/gateway/gateway.create.test.tsx new file mode 100644 index 000000000..58940ed2a --- /dev/null +++ b/src/handlers/gateway/gateway.create.test.tsx @@ -0,0 +1,601 @@ +import { describe, expect, test } from "bun:test"; +import { readFileSync } from "node:fs"; +import { join } from "node:path"; +import { + DeleteGatewayCommand, + DeleteGatewayRuleCommand, + DeleteGatewayTargetCommand, + GetGatewayCommand, + GetGatewayTargetCommand, +} from "@aws-sdk/client-bedrock-agentcore-control"; +import { + CreateRoleCommand, + DeleteRoleCommand, + DeleteRolePolicyCommand, + PutRolePolicyCommand, +} from "@aws-sdk/client-iam"; +import { CoreClient } from "../../core"; +import { createControlClient, createIamClient } from "../../core/factories"; +import { + createSilentLogger, + fixtureFactories, + isRecording, + matchGolden, + TestGlobalConfigAccessor, + testIO, +} from "../../testing"; +import { createRootHandler } from "../index"; + +const REGION = "us-east-1"; +const GATEWAY_NAME = "agentcore-cli-gateway-create-fixture"; +const HTTP_TARGET_NAME = "http-fixture"; +const CONNECTOR_TARGET_NAME = "web-search-fixture"; +const ROLE_NAME = "AgentCoreGateway-agentcore-cli-gateway-create-fix-f0aa3f799a4b"; +const WEB_SEARCH_POLICY_NAME = "AgentCoreCliWebSearchFixture"; +const FIXTURES = join(import.meta.dir, "__fixtures__", "create"); +const FLOW_TIMEOUT = 600_000; +const TEST_ROLE_ARN = "arn:aws:iam::123456789012:role/GatewayFixtureRole"; + +// Record with AWS_PROFILE=e2e-test RECORD=1 bun test src/handlers/gateway/gateway.create.test.tsx +type FixtureState = { + gatewayId?: string; + targetIds: string[]; + ruleId?: string; +}; + +function createFixtureCore(): CoreClient { + const { createControlClient, createDataClient, createIamClient } = fixtureFactories(FIXTURES); + return new CoreClient({ + createControlClient, + createDataClient, + createIamClient, + logger: createSilentLogger(), + }); +} + +async function run(args: string[]): Promise { + const io = testIO(); + const root = createRootHandler(createFixtureCore(), { + io: io.io, + logger: createSilentLogger(), + globalConfigAccessor: new TestGlobalConfigAccessor(), + }); + await root.route(["node", "agentcore", ...args, "--region", REGION]); + return io.stdout(); +} + +async function pollUntil( + args: string[], + done: (response: Record) => boolean, +): Promise { + for (let attempt = 0; attempt < 60; attempt += 1) { + const response = JSON.parse(await run(args)) as Record; + if (done(response)) return; + if (!isRecording()) { + throw new Error(`Replayed fixture for \`${args.join(" ")}\` is not settled`); + } + await Bun.sleep(5_000); + } + throw new Error(`Timed out waiting for \`${args.join(" ")}\``); +} + +async function ignoreMissing(operation: () => Promise): Promise { + try { + await operation(); + } catch (error) { + if (!["ResourceNotFoundException", "NoSuchEntityException"].includes((error as Error).name)) { + throw error; + } + } +} + +async function waitUntilMissing(operation: () => Promise): Promise { + for (let attempt = 0; attempt < 60; attempt += 1) { + try { + await operation(); + } catch (error) { + if ((error as Error).name === "ResourceNotFoundException") return; + throw error; + } + await Bun.sleep(2_000); + } + throw new Error("Timed out waiting for fixture resource deletion"); +} + +async function fixtureRoleArn(): Promise { + if (!isRecording()) { + return JSON.parse(readFileSync(join(FIXTURES, "gateway-create.golden.json"), "utf8")).roleArn; + } + + const iam = createIamClient({ region: REGION }); + await ignoreMissing(() => + iam.send( + new DeleteRolePolicyCommand({ RoleName: ROLE_NAME, PolicyName: WEB_SEARCH_POLICY_NAME }), + ), + ); + await ignoreMissing(() => iam.send(new DeleteRoleCommand({ RoleName: ROLE_NAME }))); + const response = await iam.send( + new CreateRoleCommand({ + RoleName: ROLE_NAME, + AssumeRolePolicyDocument: JSON.stringify({ + Version: "2012-10-17", + Statement: [ + { + Effect: "Allow", + Principal: { Service: "bedrock-agentcore.amazonaws.com" }, + Action: "sts:AssumeRole", + }, + ], + }), + }), + ); + if (!response.Role?.Arn) throw new Error("IAM did not return the fixture role ARN"); + await Bun.sleep(10_000); + return response.Role.Arn; +} + +async function deleteTarget( + control: ReturnType, + gatewayId: string, + targetId: string, +): Promise { + for (let attempt = 0; attempt < 60; attempt += 1) { + try { + await control.send( + new DeleteGatewayTargetCommand({ + gatewayIdentifier: gatewayId, + targetId, + }), + ); + await waitUntilMissing(() => + control.send( + new GetGatewayTargetCommand({ + gatewayIdentifier: gatewayId, + targetId, + }), + ), + ); + return; + } catch (error) { + if ((error as Error).name === "ResourceNotFoundException") return; + if ( + (error as Error).name !== "ValidationException" || + !/valid state|Creating state/i.test((error as Error).message) + ) { + throw error; + } + await Bun.sleep(2_000); + } + } + throw new Error(`Timed out deleting fixture Target "${targetId}"`); +} + +async function cleanup(state: FixtureState): Promise { + if (!isRecording()) return; + + const control = createControlClient({ region: REGION }); + if (state.gatewayId && state.ruleId) { + await ignoreMissing(() => + control.send( + new DeleteGatewayRuleCommand({ + gatewayIdentifier: state.gatewayId, + ruleId: state.ruleId, + }), + ), + ); + } + + if (state.gatewayId) { + for (const targetId of state.targetIds) { + await deleteTarget(control, state.gatewayId, targetId); + } + + await ignoreMissing(() => + control.send(new DeleteGatewayCommand({ gatewayIdentifier: state.gatewayId })), + ); + await waitUntilMissing(() => + control.send(new GetGatewayCommand({ gatewayIdentifier: state.gatewayId })), + ); + } + + const iam = createIamClient({ region: REGION }); + await ignoreMissing(() => + iam.send( + new DeleteRolePolicyCommand({ + RoleName: ROLE_NAME, + PolicyName: WEB_SEARCH_POLICY_NAME, + }), + ), + ); + await ignoreMissing(() => + iam.send( + new DeleteRoleCommand({ + RoleName: ROLE_NAME, + }), + ), + ); +} + +describe("Gateway create validation", () => { + test.each([ + ["Gateway name", ["gateway", "create", "--authorizer-type", "NONE"], /--name/], + [ + "Gateway role", + ["gateway", "create", "--name", "orders", "--authorizer-type", "NONE"], + /--role-arn/, + ], + [ + "Gateway authorizer", + ["gateway", "create", "--name", "orders", "--role-arn", TEST_ROLE_ARN], + /--authorizer-type/, + ], + [ + "CUSTOM_JWT configuration", + [ + "gateway", + "create", + "--name", + "orders", + "--role-arn", + TEST_ROLE_ARN, + "--authorizer-type", + "CUSTOM_JWT", + ], + /CUSTOM_JWT requires --authorizer-configuration/, + ], + [ + "Policy Engine pair", + [ + "gateway", + "create", + "--name", + "orders", + "--role-arn", + TEST_ROLE_ARN, + "--authorizer-type", + "AWS_IAM", + "--policy-engine-arn", + "arn:aws:bedrock-agentcore:us-west-2:123456789012:policy-engine/orders", + ], + /must be supplied together/, + ], + ["Target parent", ["gateway", "target", "create", "--name", "target"], /--gateway-id/], + [ + "Target input", + ["gateway", "target", "create", "--gateway-id", "gateway-1", "--name", "target"], + /specify exactly one/, + ], + [ + "Target name", + [ + "gateway", + "target", + "create", + "--gateway-id", + "gateway-1", + "--endpoint", + "https://example.test/mcp", + ], + /Target name/, + ], + [ + "Connector name", + ["gateway", "connector", "create", "--gateway-id", "gateway-1", "--connector", "web-search"], + /--name/, + ], + [ + "Knowledge Base ID", + [ + "gateway", + "connector", + "create", + "--gateway-id", + "gateway-1", + "--name", + "kb", + "--connector", + "bedrock-knowledge-bases", + ], + /--knowledge-base-id/, + ], + [ + "Connector exclusivity", + [ + "gateway", + "connector", + "create", + "--gateway-id", + "gateway-1", + "--name", + "search", + "--connector", + "web-search", + "--connector-configuration", + '{"mcp":{"connector":{"source":{"connectorId":"web-search"}}}}', + ], + /specify exactly one/, + ], + [ + "Connector Knowledge Base ID", + [ + "gateway", + "connector", + "create", + "--gateway-id", + "gateway-1", + "--name", + "search", + "--connector", + "web-search", + "--knowledge-base-id", + "KB12345678", + ], + /--knowledge-base-id requires --connector bedrock-knowledge-bases/, + ], + [ + "Target tool schema", + [ + "gateway", + "target", + "create", + "--gateway-id", + "gateway-1", + "--name", + "target", + "--target-configuration", + "{}", + "--tool-schema", + "{}", + ], + /--tool-schema requires --endpoint/, + ], + ["Rule parent", ["gateway", "rule", "create", "--priority", "10"], /--gateway-id/], + [ + "Rule priority", + ["gateway", "rule", "create", "--gateway-id", "gateway-1", "--actions", "[]"], + /--priority/, + ], + [ + "Rule actions", + ["gateway", "rule", "create", "--gateway-id", "gateway-1", "--priority", "10"], + /--actions/, + ], + ] as const)("rejects missing or inconsistent %s before Core", async (_name, args, error) => { + await expect(run([...args])).rejects.toThrow(error); + }); + + test("rejects conflicting Target inputs", async () => { + await expect( + run([ + "gateway", + "target", + "create", + "--gateway-id", + "gateway-1", + "--name", + "calendar", + "--endpoint", + "https://calendar.example.test/mcp", + "--target-configuration", + '{"mcp":{"mcpServer":{"endpoint":"https://other.example.test/mcp"}}}', + ]), + ).rejects.toThrow(/specify exactly one/); + }); + + test("rejects malformed Target JSON", async () => { + await expect( + run([ + "gateway", + "target", + "create", + "--gateway-id", + "gateway-1", + "--name", + "broken", + "--target-configuration", + "{not json", + ]), + ).rejects.toThrow(/Invalid JSON for option '--target-configuration'/); + }); + + test("rejects a non-Connector exact configuration", async () => { + await expect( + run([ + "gateway", + "connector", + "create", + "--gateway-id", + "gateway-1", + "--name", + "not-connector", + "--connector-configuration", + '{"mcp":{"mcpServer":{"endpoint":"https://example.test/mcp"}}}', + ]), + ).rejects.toThrow(/connector Target/); + }); +}); + +describe("Gateway fixture-backed creates", () => { + test( + "creates a Gateway, Target, Connector, and Rule through the real Core", + async () => { + const state: FixtureState = { targetIds: [] }; + const roleArn = await fixtureRoleArn(); + + try { + const gatewayStdout = await run([ + "gateway", + "create", + "--name", + GATEWAY_NAME, + "--role-arn", + roleArn, + "--authorizer-type", + "NONE", + "--description", + "Disposable Gateway Create fixture", + ]); + matchGolden(FIXTURES, "gateway-create.golden.json", gatewayStdout); + const gateway = JSON.parse(gatewayStdout); + expect(gateway.name).toBe(GATEWAY_NAME); + expect(gateway.gatewayId).toBeString(); + expect(gateway.gatewayArn).toBeString(); + state.gatewayId = gateway.gatewayId; + + await pollUntil( + ["gateway", "get", "--id", state.gatewayId!], + (response) => response.status === "READY", + ); + + const targetStdout = await run([ + "gateway", + "target", + "create", + "--gateway-id", + state.gatewayId!, + "--name", + HTTP_TARGET_NAME, + "--target-configuration", + JSON.stringify({ + http: { + passthrough: { + endpoint: "https://example.com", + protocolType: "CUSTOM", + }, + }, + }), + ]); + matchGolden(FIXTURES, "target-create.golden.json", targetStdout); + const target = JSON.parse(targetStdout); + expect(target.targetId).toBeString(); + state.targetIds.push(target.targetId); + + await pollUntil( + [ + "gateway", + "target", + "get", + "--gateway-id", + state.gatewayId!, + "--target-id", + target.targetId, + ], + (response) => response.status === "READY", + ); + + // TODO: Remove this fixture-only grant when managed Gateway roles reconcile Target permissions. + if (isRecording()) { + await createIamClient({ region: REGION }).send( + new PutRolePolicyCommand({ + RoleName: ROLE_NAME, + PolicyName: WEB_SEARCH_POLICY_NAME, + PolicyDocument: JSON.stringify(webSearchPolicy(gateway.gatewayArn)), + }), + ); + } + + const connectorStdout = await run([ + "gateway", + "connector", + "create", + "--gateway-id", + state.gatewayId!, + "--name", + CONNECTOR_TARGET_NAME, + "--connector", + "web-search", + "--credential-provider-configurations", + '[{"credentialProviderType":"GATEWAY_IAM_ROLE"}]', + "--description", + "Disposable Gateway Connector Create fixture", + ]); + matchGolden(FIXTURES, "connector-create.golden.json", connectorStdout); + const connector = JSON.parse(connectorStdout); + expect(connector.targetId).toBeString(); + expect(connector.targetConfiguration).toEqual({ + mcp: { + connector: { + source: { connectorId: "web-search" }, + configurations: [ + { + name: "WebSearch", + parameterValues: { maxResults: 10 }, + }, + ], + }, + }, + }); + state.targetIds.push(connector.targetId); + + await pollUntil( + [ + "gateway", + "connector", + "get", + "--gateway-id", + state.gatewayId!, + "--id", + connector.targetId, + ], + (response) => response.status === "READY", + ); + + const ruleStdout = await run([ + "gateway", + "rule", + "create", + "--gateway-id", + state.gatewayId!, + "--priority", + "10", + "--actions", + JSON.stringify([ + { + routeToTarget: { + staticRoute: { + targetName: HTTP_TARGET_NAME, + }, + }, + }, + ]), + "--description", + "Disposable Gateway Rule Create fixture", + ]); + matchGolden(FIXTURES, "rule-create.golden.json", ruleStdout); + const rule = JSON.parse(ruleStdout); + expect(rule.ruleId).toBeString(); + state.ruleId = rule.ruleId; + + await pollUntil( + ["gateway", "rule", "get", "--gateway-id", state.gatewayId!, "--rule-id", state.ruleId!], + (response) => response.status === "ACTIVE", + ); + } finally { + await cleanup(state); + } + }, + FLOW_TIMEOUT, + ); +}); + +function webSearchPolicy(gatewayArn: string): Record { + const [prefix, partition, service, region] = gatewayArn.split(":"); + if (prefix !== "arn" || service !== "bedrock-agentcore" || !region) { + throw new Error(`Unexpected Gateway ARN: ${gatewayArn}`); + } + return { + Version: "2012-10-17", + Statement: [ + { + Effect: "Allow", + Action: "bedrock-agentcore:InvokeGateway", + Resource: gatewayArn, + }, + { + Effect: "Allow", + Action: "bedrock-agentcore:InvokeWebSearch", + Resource: `arn:${partition}:${service}:${region}:aws:tool/web-search.v1`, + }, + ], + }; +} diff --git a/src/handlers/gateway/gateway.test.tsx b/src/handlers/gateway/gateway.test.tsx index 1dc8fe0ae..08845e29b 100644 --- a/src/handlers/gateway/gateway.test.tsx +++ b/src/handlers/gateway/gateway.test.tsx @@ -59,7 +59,7 @@ async function run( } describe("gateway command hierarchy", () => { - test("registers the Gateway read command hierarchy", () => { + test("registers the Gateway command hierarchy", () => { const root = createRootHandler(new TestCoreClient(), { io: testIO().io, logger: createSilentLogger(), @@ -72,15 +72,16 @@ describe("gateway command hierarchy", () => { expect(gateway?.flags().map((flag) => flag.name)).not.toContain("interactive"); expect(gateway?.children().map((child) => child.name())).toEqual([ + "create", "get", "list", "target", "connector", "rule", ]); - expect(target?.children().map((child) => child.name())).toEqual(["get", "list"]); - expect(connector?.children().map((child) => child.name())).toEqual(["get", "list"]); - expect(rule?.children().map((child) => child.name())).toEqual(["get", "list"]); + expect(target?.children().map((child) => child.name())).toEqual(["create", "get", "list"]); + expect(connector?.children().map((child) => child.name())).toEqual(["create", "get", "list"]); + expect(rule?.children().map((child) => child.name())).toEqual(["create", "get", "list"]); }); test.each(["gateway", "gateway target", "gateway connector", "gateway rule"])( diff --git a/src/handlers/gateway/index.tsx b/src/handlers/gateway/index.tsx index 698e3719e..e17669880 100644 --- a/src/handlers/gateway/index.tsx +++ b/src/handlers/gateway/index.tsx @@ -3,6 +3,7 @@ import { Router } from "../../router"; import { createHelpDefault } from "../help"; import type { Core } from "../types"; import { createGatewayConnectorHandler } from "./connector"; +import { createCreateGatewayHandler } from "./create"; import { createGetGatewayHandler } from "./get"; import { createListGatewaysHandler } from "./list"; import { createGatewayRuleHandler } from "./rule"; @@ -11,6 +12,7 @@ import { createGatewayTargetHandler } from "./target"; export function createGatewayHandler(core: Core, io: AppIO): Router { return new Router("gateway", "inspect AgentCore Gateways") .default(createHelpDefault(io)) + .handler(createCreateGatewayHandler(core, io)) .handler(createGetGatewayHandler(core)) .handler(createListGatewaysHandler(core)) .handler(createGatewayTargetHandler(core, io)) diff --git a/src/handlers/gateway/rule/create/index.tsx b/src/handlers/gateway/rule/create/index.tsx new file mode 100644 index 000000000..323cc3098 --- /dev/null +++ b/src/handlers/gateway/rule/create/index.tsx @@ -0,0 +1,57 @@ +import type { Action, Condition } from "@aws-sdk/client-bedrock-agentcore-control"; +import z from "zod"; +import { InputValidationError } from "../../../../errors"; +import { type AppIO, SourceResolver } from "../../../../io"; +import { createHandler, flag } from "../../../../router"; +import { JsonRendererKey } from "../../../../tui"; +import type { Core } from "../../../types"; +import { coreOptsFromCtx, parseJsonArrayFlag } from "../../../utils"; +import type { CreateGatewayRuleInput } from "../../types"; + +export const createCreateGatewayRuleHandler = (core: Core, io: AppIO) => + createHandler({ + name: "create", + description: "create a Gateway Rule", + flags: [ + flag("gateway-id", "the parent Gateway ID", z.string().optional()), + flag( + "priority", + "Rule priority from 1 to 1000000", + z.number().int().min(1).max(1_000_000).optional(), + ), + flag("conditions", "Rule conditions (JSON Condition[])", z.string().optional()), + flag("actions", "Rule actions (JSON Action[])", z.string().optional()), + flag("description", "Rule description", z.string().optional()), + ], + handle: async (ctx, flags) => { + if (!flags["gateway-id"]) { + throw new InputValidationError("required option '--gateway-id ' not specified"); + } + if (flags.priority === undefined) { + throw new InputValidationError("required option '--priority ' not specified"); + } + if (flags.actions === undefined) { + throw new InputValidationError("required option '--actions ' not specified"); + } + + const source = new SourceResolver({ stdin: io.stdin }); + const conditions = parseJsonArrayFlag( + "conditions", + await source.resolveText("conditions", flags.conditions), + ); + const actions = parseJsonArrayFlag( + "actions", + await source.resolveText("actions", flags.actions), + )!; + const input: CreateGatewayRuleInput = { + gatewayIdentifier: flags["gateway-id"], + priority: flags.priority, + actions, + ...(conditions !== undefined ? { conditions } : {}), + ...(flags.description ? { description: flags.description } : {}), + }; + ctx + .require(JsonRendererKey) + .renderJson(await core.gateway.createGatewayRule(input, coreOptsFromCtx(ctx))); + }, + }); diff --git a/src/handlers/gateway/rule/index.tsx b/src/handlers/gateway/rule/index.tsx index 40ed75f68..be6c1a494 100644 --- a/src/handlers/gateway/rule/index.tsx +++ b/src/handlers/gateway/rule/index.tsx @@ -2,12 +2,14 @@ import type { AppIO } from "../../../io"; import { Router } from "../../../router"; import { createHelpDefault } from "../../help"; import type { Core } from "../../types"; +import { createCreateGatewayRuleHandler } from "./create"; import { createGetGatewayRuleHandler } from "./get"; import { createListGatewayRulesHandler } from "./list"; export function createGatewayRuleHandler(core: Core, io: AppIO): Router { return new Router("rule", "inspect rules for an AgentCore Gateway") .default(createHelpDefault(io)) + .handler(createCreateGatewayRuleHandler(core, io)) .handler(createGetGatewayRuleHandler(core)) .handler(createListGatewayRulesHandler(core)); } diff --git a/src/handlers/gateway/target/create/index.tsx b/src/handlers/gateway/target/create/index.tsx new file mode 100644 index 000000000..cd399347d --- /dev/null +++ b/src/handlers/gateway/target/create/index.tsx @@ -0,0 +1,121 @@ +import type { + CredentialProviderConfiguration, + McpToolSchemaConfiguration, + MetadataConfiguration, + PrivateEndpoint, + TargetConfiguration, +} from "@aws-sdk/client-bedrock-agentcore-control"; +import z from "zod"; +import { InputValidationError } from "../../../../errors"; +import { type AppIO, SourceResolver } from "../../../../io"; +import { createHandler, flag } from "../../../../router"; +import { JsonRendererKey } from "../../../../tui"; +import type { Core } from "../../../types"; +import { coreOptsFromCtx, parseJsonArrayFlag, parseJsonObjectFlag } from "../../../utils"; +import type { CreateGatewayTargetInput } from "../../types"; + +export const createCreateGatewayTargetHandler = (core: Core, io: AppIO) => + createHandler({ + name: "create", + description: "create a Gateway Target", + flags: [ + flag("gateway-id", "the parent Gateway ID", z.string().optional()), + flag( + "name", + "Target name; optional only for AgentCore Runtime Targets", + z.string().optional(), + ), + flag("description", "Target description", z.string().optional()), + flag("endpoint", "MCP server HTTPS endpoint", z.string().optional()), + flag( + "target-configuration", + "complete Target configuration (JSON; inline, file://, or - for stdin)", + z.string().optional(), + ), + flag("tool-schema", "MCP tool schema source", z.string().optional()), + flag( + "credential-provider-configurations", + "outbound credentials (JSON array; inline, file://, or - for stdin)", + z.string().optional(), + ), + flag( + "metadata-configuration", + "metadata propagation (JSON; inline, file://, or - for stdin)", + z.string().optional(), + ), + flag( + "private-endpoint", + "private endpoint (JSON; inline, file://, or - for stdin)", + z.string().optional(), + ), + flag("client-token", "idempotency token", z.string().optional()), + ], + handle: async (ctx, flags) => { + if (!flags["gateway-id"]) { + throw new InputValidationError("required option '--gateway-id ' not specified"); + } + if ((flags.endpoint === undefined) === (flags["target-configuration"] === undefined)) { + throw new InputValidationError( + "specify exactly one of '--endpoint' or '--target-configuration'", + ); + } + if (flags["tool-schema"] !== undefined && flags.endpoint === undefined) { + throw new InputValidationError("--tool-schema requires --endpoint"); + } + + const source = new SourceResolver({ stdin: io.stdin }); + const exactConfiguration = parseJsonObjectFlag( + "target-configuration", + await source.resolveText("target-configuration", flags["target-configuration"]), + ); + const toolSchemaSource = flags["tool-schema"]; + let mcpToolSchema: McpToolSchemaConfiguration | undefined; + if (toolSchemaSource !== undefined) { + mcpToolSchema = toolSchemaSource.startsWith("s3://") + ? { s3: { uri: toolSchemaSource } } + : { + inlinePayload: (await source.resolveText("tool-schema", toolSchemaSource))!, + }; + } + const targetConfiguration: TargetConfiguration = exactConfiguration ?? { + mcp: { + mcpServer: { + endpoint: flags.endpoint!, + ...(mcpToolSchema ? { mcpToolSchema } : {}), + }, + }, + }; + if (!flags.name && targetConfiguration.http?.agentcoreRuntime === undefined) { + throw new InputValidationError("Target name is required for non-Runtime targets"); + } + const credentialProviderConfigurations = parseJsonArrayFlag( + "credential-provider-configurations", + await source.resolveText( + "credential-provider-configurations", + flags["credential-provider-configurations"], + ), + ); + const metadataConfiguration = parseJsonObjectFlag( + "metadata-configuration", + await source.resolveText("metadata-configuration", flags["metadata-configuration"]), + ); + const privateEndpoint = parseJsonObjectFlag( + "private-endpoint", + await source.resolveText("private-endpoint", flags["private-endpoint"]), + ); + const input: CreateGatewayTargetInput = { + gatewayIdentifier: flags["gateway-id"], + targetConfiguration, + ...(flags.name ? { name: flags.name } : {}), + ...(flags.description ? { description: flags.description } : {}), + ...(credentialProviderConfigurations ? { credentialProviderConfigurations } : {}), + ...(metadataConfiguration ? { metadataConfiguration } : {}), + ...(privateEndpoint ? { privateEndpoint } : {}), + ...(flags["client-token"] ? { clientToken: flags["client-token"] } : {}), + }; + + ctx + .require(JsonRendererKey) + .renderJson(await core.gateway.createGatewayTarget(input, coreOptsFromCtx(ctx))); + }, + }); diff --git a/src/handlers/gateway/target/index.tsx b/src/handlers/gateway/target/index.tsx index 96f05dee9..93cd6dac6 100644 --- a/src/handlers/gateway/target/index.tsx +++ b/src/handlers/gateway/target/index.tsx @@ -2,12 +2,14 @@ import type { AppIO } from "../../../io"; import { Router } from "../../../router"; import { createHelpDefault } from "../../help"; import type { Core } from "../../types"; +import { createCreateGatewayTargetHandler } from "./create"; import { createGetGatewayTargetHandler } from "./get"; import { createListGatewayTargetsHandler } from "./list"; export function createGatewayTargetHandler(core: Core, io: AppIO): Router { return new Router("target", "inspect targets for an AgentCore Gateway") .default(createHelpDefault(io)) + .handler(createCreateGatewayTargetHandler(core, io)) .handler(createGetGatewayTargetHandler(core)) .handler(createListGatewayTargetsHandler(core)); } diff --git a/src/handlers/gateway/types.tsx b/src/handlers/gateway/types.tsx index 0ed370130..f1c090847 100644 --- a/src/handlers/gateway/types.tsx +++ b/src/handlers/gateway/types.tsx @@ -1,4 +1,10 @@ import type { + CreateGatewayRequest, + CreateGatewayResponse, + CreateGatewayRuleRequest, + CreateGatewayRuleResponse, + CreateGatewayTargetRequest, + CreateGatewayTargetResponse, GetGatewayResponse, GetGatewayRuleResponse, GetGatewayTargetResponse, @@ -8,7 +14,18 @@ import type { } from "@aws-sdk/client-bedrock-agentcore-control"; import type { CoreOptions } from "../../core/types"; +export type GatewayProtocol = "mcp"; + +export type CreateGatewayInput = Omit & { + protocol?: GatewayProtocol; +}; + +export type CreateGatewayTargetInput = CreateGatewayTargetRequest; + +export type CreateGatewayRuleInput = CreateGatewayRuleRequest; + export interface CoreGatewayClient { + createGateway(input: CreateGatewayInput, options: CoreOptions): Promise; getGateway(id: string, options: CoreOptions): Promise; listGateways( nextToken: string | undefined, @@ -26,6 +43,10 @@ export interface CoreGatewayClient { maxResults: number | undefined, options: CoreOptions, ): Promise; + createGatewayTarget( + input: CreateGatewayTargetInput, + options: CoreOptions, + ): Promise; getGatewayRule( gatewayId: string, ruleId: string, @@ -37,4 +58,8 @@ export interface CoreGatewayClient { maxResults: number | undefined, options: CoreOptions, ): Promise; + createGatewayRule( + input: CreateGatewayRuleInput, + options: CoreOptions, + ): Promise; } diff --git a/src/handlers/utils.test.tsx b/src/handlers/utils.test.tsx index b9d5a384a..6b354f52b 100644 --- a/src/handlers/utils.test.tsx +++ b/src/handlers/utils.test.tsx @@ -1,5 +1,18 @@ import { describe, expect, test } from "bun:test"; -import { parseTags } from "./utils"; +import { parseJsonArrayFlag, parseJsonObjectFlag, parseTags } from "./utils"; + +describe("structured JSON flags", () => { + test("parses object and array values", () => { + expect(parseJsonObjectFlag("config", '{"enabled":true}')).toEqual({ enabled: true }); + expect(parseJsonArrayFlag("items", '[{"id":"a"}]')).toEqual([{ id: "a" }]); + }); + + test("rejects the wrong top-level shape", () => { + expect(() => parseJsonObjectFlag("config", "[]")).toThrow("must be a JSON object"); + expect(() => parseJsonObjectFlag("config", "null")).toThrow("must be a JSON object"); + expect(() => parseJsonArrayFlag("items", "{}")).toThrow("must be a JSON array"); + }); +}); describe("parseTags", () => { test("returns undefined for undefined input", () => { diff --git a/src/handlers/utils.tsx b/src/handlers/utils.tsx index 514f94e3c..737f02455 100644 --- a/src/handlers/utils.tsx +++ b/src/handlers/utils.tsx @@ -51,6 +51,27 @@ export function parseJsonFlagWithSchema( return result.data; } +export function parseJsonObjectFlag( + name: string, + raw: string | undefined, +): T | undefined { + const parsed = parseJsonFlag(name, raw); + if (parsed === undefined) return undefined; + if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) { + throw new InputValidationError(`Option '--${name}' must be a JSON object`); + } + return parsed as T; +} + +export function parseJsonArrayFlag(name: string, raw: string | undefined): T[] | undefined { + const parsed = parseJsonFlag(name, raw); + if (parsed === undefined) return undefined; + if (!Array.isArray(parsed)) { + throw new InputValidationError(`Option '--${name}' must be a JSON array`); + } + return parsed as T[]; +} + // parseTags parses a tags flag that accepts two mutually exclusive forms: // - Repeated key=value shorthand: ["env=prod", "team=foo"] // - A single JSON object: ['{"env":"prod","team":"foo"}'] diff --git a/src/testing/TestCoreClient.tsx b/src/testing/TestCoreClient.tsx index da72b69df..f845c3faa 100644 --- a/src/testing/TestCoreClient.tsx +++ b/src/testing/TestCoreClient.tsx @@ -1,6 +1,9 @@ import type { CreateApiKeyCredentialProviderResponse, CreateOauth2CredentialProviderResponse, + CreateGatewayResponse, + CreateGatewayRuleResponse, + CreateGatewayTargetResponse, CreateHarnessEndpointRequest, CreateHarnessEndpointResponse, CreateHarnessResponse, @@ -78,7 +81,12 @@ import type { } from "@aws-sdk/client-bedrock-agentcore"; import type { Core } from "../handlers/types"; import type { CoreHarnessClient, CreateHarnessInput } from "../handlers/harness/types"; -import type { CoreGatewayClient } from "../handlers/gateway/types"; +import type { + CoreGatewayClient, + CreateGatewayInput, + CreateGatewayRuleInput, + CreateGatewayTargetInput, +} from "../handlers/gateway/types"; import type { CoreIdentityClient, CreateApiKeyCredentialProviderInput, @@ -160,10 +168,13 @@ const DEFAULT_GET_MEMORY_RECORD_RESPONSE: GetMemoryRecordOutput = { memoryRecord const DEFAULT_LIST_MEMORY_RECORDS_RESPONSE: ListMemoryRecordsOutput = { memoryRecordSummaries: [], }; +const DEFAULT_CREATE_GATEWAY_RESPONSE = {} as CreateGatewayResponse; const DEFAULT_GET_GATEWAY_RESPONSE = {} as GetGatewayResponse; const DEFAULT_LIST_GATEWAYS_RESPONSE: ListGatewaysResponse = { items: [] }; +const DEFAULT_CREATE_GATEWAY_TARGET_RESPONSE = {} as CreateGatewayTargetResponse; const DEFAULT_GET_GATEWAY_TARGET_RESPONSE = {} as GetGatewayTargetResponse; const DEFAULT_LIST_GATEWAY_TARGETS_RESPONSE: ListGatewayTargetsResponse = { items: [] }; +const DEFAULT_CREATE_GATEWAY_RULE_RESPONSE = {} as CreateGatewayRuleResponse; const DEFAULT_GET_GATEWAY_RULE_RESPONSE = {} as GetGatewayRuleResponse; const DEFAULT_LIST_GATEWAY_RULES_RESPONSE: ListGatewayRulesResponse = { gatewayRules: [] }; const DEFAULT_CREATE_OAUTH2_RESPONSE = {} as CreateOauth2CredentialProviderResponse; @@ -850,6 +861,15 @@ export class TestGatewayClient implements CoreGatewayClient { return this; } + async createGateway( + input: CreateGatewayInput, + options: CoreOptions, + ): Promise { + this.calls.push({ method: "createGateway", args: [input, options] }); + if (this.error) throw this.error; + return DEFAULT_CREATE_GATEWAY_RESPONSE; + } + async getGateway(id: string, options: CoreOptions): Promise { this.calls.push({ method: "getGateway", args: [id, options] }); if (this.error) throw this.error; @@ -870,6 +890,15 @@ export class TestGatewayClient implements CoreGatewayClient { ); } + async createGatewayTarget( + input: CreateGatewayTargetInput, + options: CoreOptions, + ): Promise { + this.calls.push({ method: "createGatewayTarget", args: [input, options] }); + if (this.error) throw this.error; + return DEFAULT_CREATE_GATEWAY_TARGET_RESPONSE; + } + async getGatewayTarget( gatewayId: string, targetId: string, @@ -898,6 +927,15 @@ export class TestGatewayClient implements CoreGatewayClient { ); } + async createGatewayRule( + input: CreateGatewayRuleInput, + options: CoreOptions, + ): Promise { + this.calls.push({ method: "createGatewayRule", args: [input, options] }); + if (this.error) throw this.error; + return DEFAULT_CREATE_GATEWAY_RULE_RESPONSE; + } + async getGatewayRule( gatewayId: string, ruleId: string,