From 5bcfb35c929a73119d36d15a1056944d30f705d3 Mon Sep 17 00:00:00 2001 From: Taiki Maekawa Date: Fri, 26 Sep 2025 00:05:39 +0900 Subject: [PATCH 01/16] feat: agent builder --- package-lock.json | 1116 ++++++++-- .../generic-agent-core-runtime/app.py | 49 +- .../generic-agent-core-runtime/mcp.json | 68 +- .../generic-agent-core-runtime/src/agent.py | 49 +- .../generic-agent-core-runtime/src/tools.py | 225 +- .../generic-agent-core-runtime/src/types.py | 5 + packages/cdk/lambda/agentBuilder.ts | 34 + .../agentBuilder/handlers/agent-handlers.ts | 276 +++ .../repositories/agent-repository.ts | 697 ++++++ packages/cdk/lambda/agentBuilder/router.ts | 157 ++ .../agentBuilder/services/agent-service.ts | 323 +++ .../lambda/agentBuilder/utils/auth-utils.ts | 44 + .../agentBuilder/utils/error-handling.ts | 89 + .../agentBuilder/utils/response-utils.ts | 62 + .../validation/agent-validation.ts | 50 + .../validation/request-validation.ts | 127 ++ .../lambda/agentBuilder/validation/schemas.ts | 178 ++ .../cdk/lib/construct/generic-agent-core.ts | 33 +- .../cdk/lib/construct/use-case-builder.ts | 63 +- packages/cdk/lib/create-stacks.ts | 1 + .../cdk/lib/generative-ai-use-cases-stack.ts | 10 + packages/cdk/lib/utils/mcp-config-loader.ts | 64 + packages/cdk/package.json | 2 + .../generative-ai-use-cases.test.ts.snap | 1864 +++++++++++++---- packages/types/src/agent-builder.d.ts | 188 ++ packages/types/src/agent-core.d.ts | 8 +- packages/types/src/index.d.ts | 2 + packages/types/src/mcp-servers.d.ts | 7 + .../web/public/locales/translation/en.yaml | 135 +- .../web/public/locales/translation/ja.yaml | 109 + packages/web/src/App.tsx | 12 + packages/web/src/components/ChatMessage.tsx | 8 +- .../web/src/components/InputChatContent.tsx | 25 +- packages/web/src/components/Select.tsx | 9 +- .../agentBuilder/AgentChatUnified.tsx | 463 ++++ .../src/components/agentBuilder/AgentForm.tsx | 282 +++ .../components/agentBuilder/AgentTester.tsx | 23 + .../agentBuilder/MCPServerManager.tsx | 260 +++ .../src/hooks/agentBuilder/useAgentBuilder.ts | 263 +++ .../hooks/agentBuilder/useAgentBuilderApi.ts | 209 ++ .../hooks/agentBuilder/useAgentBuilderList.ts | 385 ++++ packages/web/src/hooks/useAgentCore.ts | 30 +- packages/web/src/hooks/useAgentCoreApi.ts | 27 +- packages/web/src/main.tsx | 32 +- packages/web/src/pages/AgentCorePage.tsx | 1 + .../agentBuilder/AgentBuilderChatPage.tsx | 88 + .../agentBuilder/AgentBuilderEditPage.tsx | 159 ++ .../agentBuilder/AgentBuilderListPage.tsx | 449 ++++ packages/web/src/vite-env.d.ts | 1 + setup-env.sh | 1 + 50 files changed, 8023 insertions(+), 739 deletions(-) create mode 100644 packages/cdk/lambda/agentBuilder.ts create mode 100644 packages/cdk/lambda/agentBuilder/handlers/agent-handlers.ts create mode 100644 packages/cdk/lambda/agentBuilder/repositories/agent-repository.ts create mode 100644 packages/cdk/lambda/agentBuilder/router.ts create mode 100644 packages/cdk/lambda/agentBuilder/services/agent-service.ts create mode 100644 packages/cdk/lambda/agentBuilder/utils/auth-utils.ts create mode 100644 packages/cdk/lambda/agentBuilder/utils/error-handling.ts create mode 100644 packages/cdk/lambda/agentBuilder/utils/response-utils.ts create mode 100644 packages/cdk/lambda/agentBuilder/validation/agent-validation.ts create mode 100644 packages/cdk/lambda/agentBuilder/validation/request-validation.ts create mode 100644 packages/cdk/lambda/agentBuilder/validation/schemas.ts create mode 100644 packages/cdk/lib/utils/mcp-config-loader.ts create mode 100644 packages/types/src/agent-builder.d.ts create mode 100644 packages/types/src/mcp-servers.d.ts create mode 100644 packages/web/src/components/agentBuilder/AgentChatUnified.tsx create mode 100644 packages/web/src/components/agentBuilder/AgentForm.tsx create mode 100644 packages/web/src/components/agentBuilder/AgentTester.tsx create mode 100644 packages/web/src/components/agentBuilder/MCPServerManager.tsx create mode 100644 packages/web/src/hooks/agentBuilder/useAgentBuilder.ts create mode 100644 packages/web/src/hooks/agentBuilder/useAgentBuilderApi.ts create mode 100644 packages/web/src/hooks/agentBuilder/useAgentBuilderList.ts create mode 100644 packages/web/src/pages/agentBuilder/AgentBuilderChatPage.tsx create mode 100644 packages/web/src/pages/agentBuilder/AgentBuilderEditPage.tsx create mode 100644 packages/web/src/pages/agentBuilder/AgentBuilderListPage.tsx diff --git a/package-lock.json b/package-lock.json index cd6af6b63..a473727e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1145,6 +1145,506 @@ "node": ">=18.0.0" } }, + "node_modules/@aws-sdk/client-cognito-identity-provider": { + "version": "3.888.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity-provider/-/client-cognito-identity-provider-3.888.0.tgz", + "integrity": "sha512-ZRrWqS3avq6KTb2vpwAWeIYFsATEirTC/SGGtRb+LQdy5dLE9Le3nyI+MpNzLnIuBCkoAtdkr7ZVNn6vR/2ivg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.888.0", + "@aws-sdk/credential-provider-node": "3.888.0", + "@aws-sdk/middleware-host-header": "3.887.0", + "@aws-sdk/middleware-logger": "3.887.0", + "@aws-sdk/middleware-recursion-detection": "3.887.0", + "@aws-sdk/middleware-user-agent": "3.888.0", + "@aws-sdk/region-config-resolver": "3.887.0", + "@aws-sdk/types": "3.887.0", + "@aws-sdk/util-endpoints": "3.887.0", + "@aws-sdk/util-user-agent-browser": "3.887.0", + "@aws-sdk/util-user-agent-node": "3.888.0", + "@smithy/config-resolver": "^4.2.1", + "@smithy/core": "^3.11.0", + "@smithy/fetch-http-handler": "^5.2.1", + "@smithy/hash-node": "^4.1.1", + "@smithy/invalid-dependency": "^4.1.1", + "@smithy/middleware-content-length": "^4.1.1", + "@smithy/middleware-endpoint": "^4.2.1", + "@smithy/middleware-retry": "^4.2.1", + "@smithy/middleware-serde": "^4.1.1", + "@smithy/middleware-stack": "^4.1.1", + "@smithy/node-config-provider": "^4.2.1", + "@smithy/node-http-handler": "^4.2.1", + "@smithy/protocol-http": "^5.2.1", + "@smithy/smithy-client": "^4.6.1", + "@smithy/types": "^4.5.0", + "@smithy/url-parser": "^4.1.1", + "@smithy/util-base64": "^4.1.0", + "@smithy/util-body-length-browser": "^4.1.0", + "@smithy/util-body-length-node": "^4.1.0", + "@smithy/util-defaults-mode-browser": "^4.1.1", + "@smithy/util-defaults-mode-node": "^4.1.1", + "@smithy/util-endpoints": "^3.1.1", + "@smithy/util-middleware": "^4.1.1", + "@smithy/util-retry": "^4.1.1", + "@smithy/util-utf8": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@aws-sdk/client-sso": { + "version": "3.888.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.888.0.tgz", + "integrity": "sha512-8CLy/ehGKUmekjH+VtZJ4w40PqDg3u0K7uPziq/4P8Q7LLgsy8YQoHNbuY4am7JU3HWrqLXJI9aaz1+vPGPoWA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.888.0", + "@aws-sdk/middleware-host-header": "3.887.0", + "@aws-sdk/middleware-logger": "3.887.0", + "@aws-sdk/middleware-recursion-detection": "3.887.0", + "@aws-sdk/middleware-user-agent": "3.888.0", + "@aws-sdk/region-config-resolver": "3.887.0", + "@aws-sdk/types": "3.887.0", + "@aws-sdk/util-endpoints": "3.887.0", + "@aws-sdk/util-user-agent-browser": "3.887.0", + "@aws-sdk/util-user-agent-node": "3.888.0", + "@smithy/config-resolver": "^4.2.1", + "@smithy/core": "^3.11.0", + "@smithy/fetch-http-handler": "^5.2.1", + "@smithy/hash-node": "^4.1.1", + "@smithy/invalid-dependency": "^4.1.1", + "@smithy/middleware-content-length": "^4.1.1", + "@smithy/middleware-endpoint": "^4.2.1", + "@smithy/middleware-retry": "^4.2.1", + "@smithy/middleware-serde": "^4.1.1", + "@smithy/middleware-stack": "^4.1.1", + "@smithy/node-config-provider": "^4.2.1", + "@smithy/node-http-handler": "^4.2.1", + "@smithy/protocol-http": "^5.2.1", + "@smithy/smithy-client": "^4.6.1", + "@smithy/types": "^4.5.0", + "@smithy/url-parser": "^4.1.1", + "@smithy/util-base64": "^4.1.0", + "@smithy/util-body-length-browser": "^4.1.0", + "@smithy/util-body-length-node": "^4.1.0", + "@smithy/util-defaults-mode-browser": "^4.1.1", + "@smithy/util-defaults-mode-node": "^4.1.1", + "@smithy/util-endpoints": "^3.1.1", + "@smithy/util-middleware": "^4.1.1", + "@smithy/util-retry": "^4.1.1", + "@smithy/util-utf8": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@aws-sdk/core": { + "version": "3.888.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.888.0.tgz", + "integrity": "sha512-L3S2FZywACo4lmWv37Y4TbefuPJ1fXWyWwIJ3J4wkPYFJ47mmtUPqThlVrSbdTHkEjnZgJe5cRfxk0qCLsFh1w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.887.0", + "@aws-sdk/xml-builder": "3.887.0", + "@smithy/core": "^3.11.0", + "@smithy/node-config-provider": "^4.2.1", + "@smithy/property-provider": "^4.0.5", + "@smithy/protocol-http": "^5.2.1", + "@smithy/signature-v4": "^5.1.3", + "@smithy/smithy-client": "^4.6.1", + "@smithy/types": "^4.5.0", + "@smithy/util-base64": "^4.1.0", + "@smithy/util-body-length-browser": "^4.1.0", + "@smithy/util-middleware": "^4.1.1", + "@smithy/util-utf8": "^4.1.0", + "fast-xml-parser": "5.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.888.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.888.0.tgz", + "integrity": "sha512-shPi4AhUKbIk7LugJWvNpeZA8va7e5bOHAEKo89S0Ac8WDZt2OaNzbh/b9l0iSL2eEyte8UgIsYGcFxOwIF1VA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.888.0", + "@aws-sdk/types": "3.887.0", + "@smithy/property-provider": "^4.0.5", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.888.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.888.0.tgz", + "integrity": "sha512-Jvuk6nul0lE7o5qlQutcqlySBHLXOyoPtiwE6zyKbGc7RVl0//h39Lab7zMeY2drMn8xAnIopL4606Fd8JI/Hw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.888.0", + "@aws-sdk/types": "3.887.0", + "@smithy/fetch-http-handler": "^5.2.1", + "@smithy/node-http-handler": "^4.2.1", + "@smithy/property-provider": "^4.0.5", + "@smithy/protocol-http": "^5.2.1", + "@smithy/smithy-client": "^4.6.1", + "@smithy/types": "^4.5.0", + "@smithy/util-stream": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.888.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.888.0.tgz", + "integrity": "sha512-M82ItvS5yq+tO6ZOV1ruaVs2xOne+v8HW85GFCXnz8pecrzYdgxh6IsVqEbbWruryG/mUGkWMbkBZoEsy4MgyA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.888.0", + "@aws-sdk/credential-provider-env": "3.888.0", + "@aws-sdk/credential-provider-http": "3.888.0", + "@aws-sdk/credential-provider-process": "3.888.0", + "@aws-sdk/credential-provider-sso": "3.888.0", + "@aws-sdk/credential-provider-web-identity": "3.888.0", + "@aws-sdk/nested-clients": "3.888.0", + "@aws-sdk/types": "3.887.0", + "@smithy/credential-provider-imds": "^4.0.7", + "@smithy/property-provider": "^4.0.5", + "@smithy/shared-ini-file-loader": "^4.0.5", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.888.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.888.0.tgz", + "integrity": "sha512-KCrQh1dCDC8Y+Ap3SZa6S81kHk+p+yAaOQ5jC3dak4zhHW3RCrsGR/jYdemTOgbEGcA6ye51UbhWfrrlMmeJSA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.888.0", + "@aws-sdk/credential-provider-http": "3.888.0", + "@aws-sdk/credential-provider-ini": "3.888.0", + "@aws-sdk/credential-provider-process": "3.888.0", + "@aws-sdk/credential-provider-sso": "3.888.0", + "@aws-sdk/credential-provider-web-identity": "3.888.0", + "@aws-sdk/types": "3.887.0", + "@smithy/credential-provider-imds": "^4.0.7", + "@smithy/property-provider": "^4.0.5", + "@smithy/shared-ini-file-loader": "^4.0.5", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.888.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.888.0.tgz", + "integrity": "sha512-+aX6piSukPQ8DUS4JAH344GePg8/+Q1t0+kvSHAZHhYvtQ/1Zek3ySOJWH2TuzTPCafY4nmWLcQcqvU1w9+4Lw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.888.0", + "@aws-sdk/types": "3.887.0", + "@smithy/property-provider": "^4.0.5", + "@smithy/shared-ini-file-loader": "^4.0.5", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.888.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.888.0.tgz", + "integrity": "sha512-b1ZJji7LJ6E/j1PhFTyvp51in2iCOQ3VP6mj5H6f5OUnqn7efm41iNMoinKr87n0IKZw7qput5ggXVxEdPhouA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.888.0", + "@aws-sdk/core": "3.888.0", + "@aws-sdk/token-providers": "3.888.0", + "@aws-sdk/types": "3.887.0", + "@smithy/property-provider": "^4.0.5", + "@smithy/shared-ini-file-loader": "^4.0.5", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.888.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.888.0.tgz", + "integrity": "sha512-7P0QNtsDzMZdmBAaY/vY1BsZHwTGvEz3bsn2bm5VSKFAeMmZqsHK1QeYdNsFjLtegnVh+wodxMq50jqLv3LFlA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.888.0", + "@aws-sdk/nested-clients": "3.888.0", + "@aws-sdk/types": "3.887.0", + "@smithy/property-provider": "^4.0.5", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.887.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.887.0.tgz", + "integrity": "sha512-ulzqXv6NNqdu/kr0sgBYupWmahISHY+azpJidtK6ZwQIC+vBUk9NdZeqQpy7KVhIk2xd4+5Oq9rxapPwPI21CA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.887.0", + "@smithy/protocol-http": "^5.2.1", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@aws-sdk/middleware-logger": { + "version": "3.887.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.887.0.tgz", + "integrity": "sha512-YbbgLI6jKp2qSoAcHnXrQ5jcuc5EYAmGLVFgMVdk8dfCfJLfGGSaOLxF4CXC7QYhO50s+mPPkhBYejCik02Kug==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.887.0", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.887.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.887.0.tgz", + "integrity": "sha512-tjrUXFtQnFLo+qwMveq5faxP5MQakoLArXtqieHphSqZTXm21wDJM73hgT4/PQQGTwgYjDKqnqsE1hvk0hcfDw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.887.0", + "@aws/lambda-invoke-store": "^0.0.1", + "@smithy/protocol-http": "^5.2.1", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.888.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.888.0.tgz", + "integrity": "sha512-ZkcUkoys8AdrNNG7ATjqw2WiXqrhTvT+r4CIK3KhOqIGPHX0p0DQWzqjaIl7ZhSUToKoZ4Ud7MjF795yUr73oA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.888.0", + "@aws-sdk/types": "3.887.0", + "@aws-sdk/util-endpoints": "3.887.0", + "@smithy/core": "^3.11.0", + "@smithy/protocol-http": "^5.2.1", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@aws-sdk/nested-clients": { + "version": "3.888.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.888.0.tgz", + "integrity": "sha512-py4o4RPSGt+uwGvSBzR6S6cCBjS4oTX5F8hrHFHfPCdIOMVjyOBejn820jXkCrcdpSj3Qg1yUZXxsByvxc9Lyg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.888.0", + "@aws-sdk/middleware-host-header": "3.887.0", + "@aws-sdk/middleware-logger": "3.887.0", + "@aws-sdk/middleware-recursion-detection": "3.887.0", + "@aws-sdk/middleware-user-agent": "3.888.0", + "@aws-sdk/region-config-resolver": "3.887.0", + "@aws-sdk/types": "3.887.0", + "@aws-sdk/util-endpoints": "3.887.0", + "@aws-sdk/util-user-agent-browser": "3.887.0", + "@aws-sdk/util-user-agent-node": "3.888.0", + "@smithy/config-resolver": "^4.2.1", + "@smithy/core": "^3.11.0", + "@smithy/fetch-http-handler": "^5.2.1", + "@smithy/hash-node": "^4.1.1", + "@smithy/invalid-dependency": "^4.1.1", + "@smithy/middleware-content-length": "^4.1.1", + "@smithy/middleware-endpoint": "^4.2.1", + "@smithy/middleware-retry": "^4.2.1", + "@smithy/middleware-serde": "^4.1.1", + "@smithy/middleware-stack": "^4.1.1", + "@smithy/node-config-provider": "^4.2.1", + "@smithy/node-http-handler": "^4.2.1", + "@smithy/protocol-http": "^5.2.1", + "@smithy/smithy-client": "^4.6.1", + "@smithy/types": "^4.5.0", + "@smithy/url-parser": "^4.1.1", + "@smithy/util-base64": "^4.1.0", + "@smithy/util-body-length-browser": "^4.1.0", + "@smithy/util-body-length-node": "^4.1.0", + "@smithy/util-defaults-mode-browser": "^4.1.1", + "@smithy/util-defaults-mode-node": "^4.1.1", + "@smithy/util-endpoints": "^3.1.1", + "@smithy/util-middleware": "^4.1.1", + "@smithy/util-retry": "^4.1.1", + "@smithy/util-utf8": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.887.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.887.0.tgz", + "integrity": "sha512-VdSMrIqJ3yjJb/fY+YAxrH/lCVv0iL8uA+lbMNfQGtO5tB3Zx6SU9LEpUwBNX8fPK1tUpI65CNE4w42+MY/7Mg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.887.0", + "@smithy/node-config-provider": "^4.2.1", + "@smithy/types": "^4.5.0", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.1.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@aws-sdk/token-providers": { + "version": "3.888.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.888.0.tgz", + "integrity": "sha512-WA3NF+3W8GEuCMG1WvkDYbB4z10G3O8xuhT7QSjhvLYWQ9CPt3w4VpVIfdqmUn131TCIbhCzD0KN/1VJTjAjyw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.888.0", + "@aws-sdk/nested-clients": "3.888.0", + "@aws-sdk/types": "3.887.0", + "@smithy/property-provider": "^4.0.5", + "@smithy/shared-ini-file-loader": "^4.0.5", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@aws-sdk/types": { + "version": "3.887.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.887.0.tgz", + "integrity": "sha512-fmTEJpUhsPsovQ12vZSpVTEP/IaRoJAMBGQXlQNjtCpkBp6Iq3KQDa/HDaPINE+3xxo6XvTdtibsNOd5zJLV9A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@aws-sdk/util-endpoints": { + "version": "3.887.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.887.0.tgz", + "integrity": "sha512-kpegvT53KT33BMeIcGLPA65CQVxLUL/C3gTz9AzlU/SDmeusBHX4nRApAicNzI/ltQ5lxZXbQn18UczzBuwF1w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.887.0", + "@smithy/types": "^4.5.0", + "@smithy/url-parser": "^4.1.1", + "@smithy/util-endpoints": "^3.1.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.887.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.887.0.tgz", + "integrity": "sha512-X71UmVsYc6ZTH4KU6hA5urOzYowSXc3qvroagJNLJYU1ilgZ529lP4J9XOYfEvTXkLR1hPFSRxa43SrwgelMjA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.887.0", + "@smithy/types": "^4.5.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.888.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.888.0.tgz", + "integrity": "sha512-rSB3OHyuKXotIGfYEo//9sU0lXAUrTY28SUUnxzOGYuQsAt0XR5iYwBAp+RjV6x8f+Hmtbg0PdCsy1iNAXa0UQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.888.0", + "@aws-sdk/types": "3.887.0", + "@smithy/node-config-provider": "^4.2.1", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@aws-sdk/xml-builder": { + "version": "3.887.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.887.0.tgz", + "integrity": "sha512-lMwgWK1kNgUhHGfBvO/5uLe7TKhycwOn3eRCqsKPT9aPCx/HWuTlpcQp8oW2pCRGLS7qzcxqpQulcD+bbUL7XQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@smithy/util-utf8": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.1.0.tgz", + "integrity": "sha512-mEu1/UIXAdNYuBcyEPbjScKi/+MQVXNIuY/7Cm5XLIWe319kDrT5SizBE95jqtmEXoDbGoZxKLCMttdZdqTZKQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/types": { "version": "3.862.0", "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.862.0.tgz", @@ -8649,6 +9149,15 @@ "constructs": "^10.0.0" } }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.0.1.tgz", + "integrity": "sha512-ORHRQ2tmvnBXc8t/X9Z8IcSbBA4xTLKuN873FopzklHMeqBst7YG0d+AX97inkvDX+NChYtSr+qGfcqGFaI8Zw==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -13567,12 +14076,12 @@ } }, "node_modules/@smithy/abort-controller": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.5.tgz", - "integrity": "sha512-jcrqdTQurIrBbUm4W2YdLVMQDoL0sA9DTxYd2s+R/y+2U9NLOP7Xf/YqfSg1FZhlZIYEnvk2mwbyvIfdLEPo8g==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.1.1.tgz", + "integrity": "sha512-vkzula+IwRvPR6oKQhMYioM3A/oX/lFCZiwuxkQbRhqJS2S4YRY2k7k/SyR2jMf3607HLtbEwlRxi0ndXHMjRg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.2", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { @@ -13605,15 +14114,15 @@ } }, "node_modules/@smithy/config-resolver": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.1.5.tgz", - "integrity": "sha512-viuHMxBAqydkB0AfWwHIdwf/PRH2z5KHGUzqyRtS/Wv+n3IHI993Sk76VCA7dD/+GzgGOmlJDITfPcJC1nIVIw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.2.1.tgz", + "integrity": "sha512-FXil8q4QN7mgKwU2hCLm0ltab8NyY/1RiqEf25Jnf6WLS3wmb11zGAoLETqg1nur2Aoibun4w4MjeN9CMJ4G6A==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.1.4", - "@smithy/types": "^4.3.2", - "@smithy/util-config-provider": "^4.0.0", - "@smithy/util-middleware": "^4.0.5", + "@smithy/node-config-provider": "^4.2.1", + "@smithy/types": "^4.5.0", + "@smithy/util-config-provider": "^4.1.0", + "@smithy/util-middleware": "^4.1.1", "tslib": "^2.6.2" }, "engines": { @@ -13621,19 +14130,19 @@ } }, "node_modules/@smithy/core": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.8.0.tgz", - "integrity": "sha512-EYqsIYJmkR1VhVE9pccnk353xhs+lB6btdutJEtsp7R055haMJp2yE16eSxw8fv+G0WUY6vqxyYOP8kOqawxYQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/middleware-serde": "^4.0.9", - "@smithy/protocol-http": "^5.1.3", - "@smithy/types": "^4.3.2", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-middleware": "^4.0.5", - "@smithy/util-stream": "^4.2.4", - "@smithy/util-utf8": "^4.0.0", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.11.0.tgz", + "integrity": "sha512-Abs5rdP1o8/OINtE49wwNeWuynCu0kme1r4RI3VXVrHr4odVDG7h7mTnw1WXXfN5Il+c25QOnrdL2y56USfxkA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-serde": "^4.1.1", + "@smithy/protocol-http": "^5.2.1", + "@smithy/types": "^4.5.0", + "@smithy/util-base64": "^4.1.0", + "@smithy/util-body-length-browser": "^4.1.0", + "@smithy/util-middleware": "^4.1.1", + "@smithy/util-stream": "^4.3.1", + "@smithy/util-utf8": "^4.1.0", "@types/uuid": "^9.0.1", "tslib": "^2.6.2", "uuid": "^9.0.1" @@ -13643,12 +14152,12 @@ } }, "node_modules/@smithy/core/node_modules/@smithy/util-utf8": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", - "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.1.0.tgz", + "integrity": "sha512-mEu1/UIXAdNYuBcyEPbjScKi/+MQVXNIuY/7Cm5XLIWe319kDrT5SizBE95jqtmEXoDbGoZxKLCMttdZdqTZKQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-buffer-from": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -13656,15 +14165,15 @@ } }, "node_modules/@smithy/credential-provider-imds": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.7.tgz", - "integrity": "sha512-dDzrMXA8d8riFNiPvytxn0mNwR4B3h8lgrQ5UjAGu6T9z/kRg/Xncf4tEQHE/+t25sY8IH3CowcmWi+1U5B1Gw==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.1.1.tgz", + "integrity": "sha512-1WdBfM9DwA59pnpIizxnUvBf/de18p4GP+6zP2AqrlFzoW3ERpZaT4QueBR0nS9deDMaQRkBlngpVlnkuuTisQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.1.4", - "@smithy/property-provider": "^4.0.5", - "@smithy/types": "^4.3.2", - "@smithy/url-parser": "^4.0.5", + "@smithy/node-config-provider": "^4.2.1", + "@smithy/property-provider": "^4.1.1", + "@smithy/types": "^4.5.0", + "@smithy/url-parser": "^4.1.1", "tslib": "^2.6.2" }, "engines": { @@ -13754,15 +14263,15 @@ } }, "node_modules/@smithy/fetch-http-handler": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.1.1.tgz", - "integrity": "sha512-61WjM0PWmZJR+SnmzaKI7t7G0UkkNFboDpzIdzSoy7TByUzlxo18Qlh9s71qug4AY4hlH/CwXdubMtkcNEb/sQ==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.2.1.tgz", + "integrity": "sha512-5/3wxKNtV3wO/hk1is+CZUhL8a1yy/U+9u9LKQ9kZTkMsHaQjJhc3stFfiujtMnkITjzWfndGA2f7g9Uh9vKng==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.1.3", - "@smithy/querystring-builder": "^4.0.5", - "@smithy/types": "^4.3.2", - "@smithy/util-base64": "^4.0.0", + "@smithy/protocol-http": "^5.2.1", + "@smithy/querystring-builder": "^4.1.1", + "@smithy/types": "^4.5.0", + "@smithy/util-base64": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -13785,14 +14294,14 @@ } }, "node_modules/@smithy/hash-node": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.5.tgz", - "integrity": "sha512-cv1HHkKhpyRb6ahD8Vcfb2Hgz67vNIXEp2vnhzfxLFGRukLCNEA5QdsorbUEzXma1Rco0u3rx5VTqbM06GcZqQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.1.1.tgz", + "integrity": "sha512-H9DIU9WBLhYrvPs9v4sYvnZ1PiAI0oc8CgNQUJ1rpN3pP7QADbTOUjchI2FB764Ub0DstH5xbTqcMJu1pnVqxA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.2", - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", + "@smithy/types": "^4.5.0", + "@smithy/util-buffer-from": "^4.1.0", + "@smithy/util-utf8": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -13800,12 +14309,12 @@ } }, "node_modules/@smithy/hash-node/node_modules/@smithy/util-utf8": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", - "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.1.0.tgz", + "integrity": "sha512-mEu1/UIXAdNYuBcyEPbjScKi/+MQVXNIuY/7Cm5XLIWe319kDrT5SizBE95jqtmEXoDbGoZxKLCMttdZdqTZKQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-buffer-from": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -13840,12 +14349,12 @@ } }, "node_modules/@smithy/invalid-dependency": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.5.tgz", - "integrity": "sha512-IVnb78Qtf7EJpoEVo7qJ8BEXQwgC4n3igeJNNKEj/MLYtapnx8A67Zt/J3RXAj2xSO1910zk0LdFiygSemuLow==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.1.1.tgz", + "integrity": "sha512-1AqLyFlfrrDkyES8uhINRlJXmHA2FkG+3DY8X+rmLSqmFwk3DJnvhyGzyByPyewh2jbmV+TYQBEfngQax8IFGg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.2", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { @@ -13853,9 +14362,9 @@ } }, "node_modules/@smithy/is-array-buffer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", - "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.1.0.tgz", + "integrity": "sha512-ePTYUOV54wMogio+he4pBybe8fwg4sDvEVDBU8ZlHOZXbXK3/C0XfJgUCu6qAZcawv05ZhZzODGUerFBPsPUDQ==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -13892,13 +14401,13 @@ } }, "node_modules/@smithy/middleware-content-length": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.5.tgz", - "integrity": "sha512-l1jlNZoYzoCC7p0zCtBDE5OBXZ95yMKlRlftooE5jPWQn4YBPLgsp+oeHp7iMHaTGoUdFqmHOPa8c9G3gBsRpQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.1.1.tgz", + "integrity": "sha512-9wlfBBgTsRvC2JxLJxv4xDGNBrZuio3AgSl0lSFX7fneW2cGskXTYpFxCdRYD2+5yzmsiTuaAJD1Wp7gWt9y9w==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.1.3", - "@smithy/types": "^4.3.2", + "@smithy/protocol-http": "^5.2.1", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { @@ -13906,18 +14415,18 @@ } }, "node_modules/@smithy/middleware-endpoint": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.18.tgz", - "integrity": "sha512-ZhvqcVRPZxnZlokcPaTwb+r+h4yOIOCJmx0v2d1bpVlmP465g3qpVSf7wxcq5zZdu4jb0H4yIMxuPwDJSQc3MQ==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.2.1.tgz", + "integrity": "sha512-fUTMmQvQQZakXOuKizfu7fBLDpwvWZjfH6zUK2OLsoNZRZGbNUdNSdLJHpwk1vS208jtDjpUIskh+JoA8zMzZg==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.8.0", - "@smithy/middleware-serde": "^4.0.9", - "@smithy/node-config-provider": "^4.1.4", - "@smithy/shared-ini-file-loader": "^4.0.5", - "@smithy/types": "^4.3.2", - "@smithy/url-parser": "^4.0.5", - "@smithy/util-middleware": "^4.0.5", + "@smithy/core": "^3.11.0", + "@smithy/middleware-serde": "^4.1.1", + "@smithy/node-config-provider": "^4.2.1", + "@smithy/shared-ini-file-loader": "^4.1.1", + "@smithy/types": "^4.5.0", + "@smithy/url-parser": "^4.1.1", + "@smithy/util-middleware": "^4.1.1", "tslib": "^2.6.2" }, "engines": { @@ -13925,18 +14434,18 @@ } }, "node_modules/@smithy/middleware-retry": { - "version": "4.1.19", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.19.tgz", - "integrity": "sha512-X58zx/NVECjeuUB6A8HBu4bhx72EoUz+T5jTMIyeNKx2lf+Gs9TmWPNNkH+5QF0COjpInP/xSpJGJ7xEnAklQQ==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.2.1.tgz", + "integrity": "sha512-JzfvjwSJXWRl7LkLgIRTUTd2Wj639yr3sQGpViGNEOjtb0AkAuYqRAHs+jSOI/LPC0ZTjmFVVtfrCICMuebexw==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.1.4", - "@smithy/protocol-http": "^5.1.3", - "@smithy/service-error-classification": "^4.0.7", - "@smithy/smithy-client": "^4.4.10", - "@smithy/types": "^4.3.2", - "@smithy/util-middleware": "^4.0.5", - "@smithy/util-retry": "^4.0.7", + "@smithy/node-config-provider": "^4.2.1", + "@smithy/protocol-http": "^5.2.1", + "@smithy/service-error-classification": "^4.1.1", + "@smithy/smithy-client": "^4.6.1", + "@smithy/types": "^4.5.0", + "@smithy/util-middleware": "^4.1.1", + "@smithy/util-retry": "^4.1.1", "@types/uuid": "^9.0.1", "tslib": "^2.6.2", "uuid": "^9.0.1" @@ -13946,13 +14455,13 @@ } }, "node_modules/@smithy/middleware-serde": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.9.tgz", - "integrity": "sha512-uAFFR4dpeoJPGz8x9mhxp+RPjo5wW0QEEIPPPbLXiRRWeCATf/Km3gKIVR5vaP8bN1kgsPhcEeh+IZvUlBv6Xg==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.1.1.tgz", + "integrity": "sha512-lh48uQdbCoj619kRouev5XbWhCwRKLmphAif16c4J6JgJ4uXjub1PI6RL38d3BLliUvSso6klyB/LTNpWSNIyg==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.1.3", - "@smithy/types": "^4.3.2", + "@smithy/protocol-http": "^5.2.1", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { @@ -13960,12 +14469,12 @@ } }, "node_modules/@smithy/middleware-stack": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.5.tgz", - "integrity": "sha512-/yoHDXZPh3ocRVyeWQFvC44u8seu3eYzZRveCMfgMOBcNKnAmOvjbL9+Cp5XKSIi9iYA9PECUuW2teDAk8T+OQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.1.1.tgz", + "integrity": "sha512-ygRnniqNcDhHzs6QAPIdia26M7e7z9gpkIMUe/pK0RsrQ7i5MblwxY8078/QCnGq6AmlUUWgljK2HlelsKIb/A==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.2", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { @@ -13973,14 +14482,14 @@ } }, "node_modules/@smithy/node-config-provider": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.1.4.tgz", - "integrity": "sha512-+UDQV/k42jLEPPHSn39l0Bmc4sB1xtdI9Gd47fzo/0PbXzJ7ylgaOByVjF5EeQIumkepnrJyfx86dPa9p47Y+w==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.2.1.tgz", + "integrity": "sha512-AIA0BJZq2h295J5NeCTKhg1WwtdTA/GqBCaVjk30bDgMHwniUETyh5cP9IiE9VrId7Kt8hS7zvREVMTv1VfA6g==", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.0.5", - "@smithy/shared-ini-file-loader": "^4.0.5", - "@smithy/types": "^4.3.2", + "@smithy/property-provider": "^4.1.1", + "@smithy/shared-ini-file-loader": "^4.1.1", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { @@ -13988,15 +14497,15 @@ } }, "node_modules/@smithy/node-http-handler": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.1.1.tgz", - "integrity": "sha512-RHnlHqFpoVdjSPPiYy/t40Zovf3BBHc2oemgD7VsVTFFZrU5erFFe0n52OANZZ/5sbshgD93sOh5r6I35Xmpaw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.2.1.tgz", + "integrity": "sha512-REyybygHlxo3TJICPF89N2pMQSf+p+tBJqpVe1+77Cfi9HBPReNjTgtZ1Vg73exq24vkqJskKDpfF74reXjxfw==", "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^4.0.5", - "@smithy/protocol-http": "^5.1.3", - "@smithy/querystring-builder": "^4.0.5", - "@smithy/types": "^4.3.2", + "@smithy/abort-controller": "^4.1.1", + "@smithy/protocol-http": "^5.2.1", + "@smithy/querystring-builder": "^4.1.1", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { @@ -14004,12 +14513,12 @@ } }, "node_modules/@smithy/property-provider": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.5.tgz", - "integrity": "sha512-R/bswf59T/n9ZgfgUICAZoWYKBHcsVDurAGX88zsiUtOTA/xUAPyiT+qkNCPwFn43pZqN84M4MiUsbSGQmgFIQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.1.1.tgz", + "integrity": "sha512-gm3ZS7DHxUbzC2wr8MUCsAabyiXY0gaj3ROWnhSx/9sPMc6eYLMM4rX81w1zsMaObj2Lq3PZtNCC1J6lpEY7zg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.2", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { @@ -14017,12 +14526,12 @@ } }, "node_modules/@smithy/protocol-http": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.3.tgz", - "integrity": "sha512-fCJd2ZR7D22XhDY0l+92pUag/7je2BztPRQ01gU5bMChcyI0rlly7QFibnYHzcxDvccMjlpM/Q1ev8ceRIb48w==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.2.1.tgz", + "integrity": "sha512-T8SlkLYCwfT/6m33SIU/JOVGNwoelkrvGjFKDSDtVvAXj/9gOT78JVJEas5a+ETjOu4SVvpCstKgd0PxSu/aHw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.2", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { @@ -14030,13 +14539,13 @@ } }, "node_modules/@smithy/querystring-builder": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.5.tgz", - "integrity": "sha512-NJeSCU57piZ56c+/wY+AbAw6rxCCAOZLCIniRE7wqvndqxcKKDOXzwWjrY7wGKEISfhL9gBbAaWWgHsUGedk+A==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.1.1.tgz", + "integrity": "sha512-J9b55bfimP4z/Jg1gNo+AT84hr90p716/nvxDkPGCD4W70MPms0h8KF50RDRgBGZeL83/u59DWNqJv6tEP/DHA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.2", - "@smithy/util-uri-escape": "^4.0.0", + "@smithy/types": "^4.5.0", + "@smithy/util-uri-escape": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -14044,12 +14553,12 @@ } }, "node_modules/@smithy/querystring-parser": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.5.tgz", - "integrity": "sha512-6SV7md2CzNG/WUeTjVe6Dj8noH32r4MnUeFKZrnVYsQxpGSIcphAanQMayi8jJLZAWm6pdM9ZXvKCpWOsIGg0w==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.1.1.tgz", + "integrity": "sha512-63TEp92YFz0oQ7Pj9IuI3IgnprP92LrZtRAkE3c6wLWJxfy/yOPRt39IOKerVr0JS770olzl0kGafXlAXZ1vng==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.2", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { @@ -14057,24 +14566,24 @@ } }, "node_modules/@smithy/service-error-classification": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.7.tgz", - "integrity": "sha512-XvRHOipqpwNhEjDf2L5gJowZEm5nsxC16pAZOeEcsygdjv9A2jdOh3YoDQvOXBGTsaJk6mNWtzWalOB9976Wlg==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.1.1.tgz", + "integrity": "sha512-Iam75b/JNXyDE41UvrlM6n8DNOa/r1ylFyvgruTUx7h2Uk7vDNV9AAwP1vfL1fOL8ls0xArwEGVcGZVd7IO/Cw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.2" + "@smithy/types": "^4.5.0" }, "engines": { "node": ">=18.0.0" } }, "node_modules/@smithy/shared-ini-file-loader": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.5.tgz", - "integrity": "sha512-YVVwehRDuehgoXdEL4r1tAAzdaDgaC9EQvhK0lEbfnbrd0bd5+CTQumbdPryX3J2shT7ZqQE+jPW4lmNBAB8JQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.1.1.tgz", + "integrity": "sha512-YkpikhIqGc4sfXeIbzSj10t2bJI/sSoP5qxLue6zG+tEE3ngOBSm8sO3+djacYvS/R5DfpxN/L9CyZsvwjWOAQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.2", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { @@ -14126,17 +14635,17 @@ } }, "node_modules/@smithy/smithy-client": { - "version": "4.4.10", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.4.10.tgz", - "integrity": "sha512-iW6HjXqN0oPtRS0NK/zzZ4zZeGESIFcxj2FkWed3mcK8jdSdHzvnCKXSjvewESKAgGKAbJRA+OsaqKhkdYRbQQ==", + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.6.1.tgz", + "integrity": "sha512-WolVLDb9UTPMEPPOncrCt6JmAMCSC/V2y5gst2STWJ5r7+8iNac+EFYQnmvDCYMfOLcilOSEpm5yXZXwbLak1Q==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.8.0", - "@smithy/middleware-endpoint": "^4.1.18", - "@smithy/middleware-stack": "^4.0.5", - "@smithy/protocol-http": "^5.1.3", - "@smithy/types": "^4.3.2", - "@smithy/util-stream": "^4.2.4", + "@smithy/core": "^3.11.0", + "@smithy/middleware-endpoint": "^4.2.1", + "@smithy/middleware-stack": "^4.1.1", + "@smithy/protocol-http": "^5.2.1", + "@smithy/types": "^4.5.0", + "@smithy/util-stream": "^4.3.1", "tslib": "^2.6.2" }, "engines": { @@ -14144,9 +14653,9 @@ } }, "node_modules/@smithy/types": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.2.tgz", - "integrity": "sha512-QO4zghLxiQ5W9UZmX2Lo0nta2PuE1sSrXUYDoaB6HMR762C0P7v/HEPHf6ZdglTVssJG1bsrSBxdc3quvDSihw==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.5.0.tgz", + "integrity": "sha512-RkUpIOsVlAwUIZXO1dsz8Zm+N72LClFfsNqf173catVlvRZiwPy0x2u0JLEA4byreOPKDZPGjmPDylMoP8ZJRg==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -14156,13 +14665,13 @@ } }, "node_modules/@smithy/url-parser": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.5.tgz", - "integrity": "sha512-j+733Um7f1/DXjYhCbvNXABV53NyCRRA54C7bNEIxNPs0YjfRxeMKjjgm2jvTYrciZyCjsicHwQ6Q0ylo+NAUw==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.1.1.tgz", + "integrity": "sha512-bx32FUpkhcaKlEoOMbScvc93isaSiRM75pQ5IgIBaMkT7qMlIibpPRONyx/0CvrXHzJLpOn/u6YiDX2hcvs7Dg==", "license": "Apache-2.0", "dependencies": { - "@smithy/querystring-parser": "^4.0.5", - "@smithy/types": "^4.3.2", + "@smithy/querystring-parser": "^4.1.1", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { @@ -14170,13 +14679,13 @@ } }, "node_modules/@smithy/util-base64": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", - "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.1.0.tgz", + "integrity": "sha512-RUGd4wNb8GeW7xk+AY5ghGnIwM96V0l2uzvs/uVHf+tIuVX2WSvynk5CxNoBCsM2rQRSZElAo9rt3G5mJ/gktQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", + "@smithy/util-buffer-from": "^4.1.0", + "@smithy/util-utf8": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -14184,12 +14693,12 @@ } }, "node_modules/@smithy/util-base64/node_modules/@smithy/util-utf8": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", - "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.1.0.tgz", + "integrity": "sha512-mEu1/UIXAdNYuBcyEPbjScKi/+MQVXNIuY/7Cm5XLIWe319kDrT5SizBE95jqtmEXoDbGoZxKLCMttdZdqTZKQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-buffer-from": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -14197,9 +14706,9 @@ } }, "node_modules/@smithy/util-body-length-browser": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz", - "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.1.0.tgz", + "integrity": "sha512-V2E2Iez+bo6bUMOTENPr6eEmepdY8Hbs+Uc1vkDKgKNA/brTJqOW/ai3JO1BGj9GbCeLqw90pbbH7HFQyFotGQ==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -14209,9 +14718,9 @@ } }, "node_modules/@smithy/util-body-length-node": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.0.0.tgz", - "integrity": "sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.1.0.tgz", + "integrity": "sha512-BOI5dYjheZdgR9XiEM3HJcEMCXSoqbzu7CzIgYrx0UtmvtC3tC2iDGpJLsSRFffUpy8ymsg2ARMP5fR8mtuUQQ==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -14221,12 +14730,12 @@ } }, "node_modules/@smithy/util-buffer-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", - "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.1.0.tgz", + "integrity": "sha512-N6yXcjfe/E+xKEccWEKzK6M+crMrlwaCepKja0pNnlSkm6SjAeLKKA++er5Ba0I17gvKfN/ThV+ZOx/CntKTVw==", "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^4.0.0", + "@smithy/is-array-buffer": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -14234,9 +14743,9 @@ } }, "node_modules/@smithy/util-config-provider": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz", - "integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.1.0.tgz", + "integrity": "sha512-swXz2vMjrP1ZusZWVTB/ai5gK+J8U0BWvP10v9fpcFvg+Xi/87LHvHfst2IgCs1i0v4qFZfGwCmeD/KNCdJZbQ==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -14246,14 +14755,14 @@ } }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.0.26", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.26.tgz", - "integrity": "sha512-xgl75aHIS/3rrGp7iTxQAOELYeyiwBu+eEgAk4xfKwJJ0L8VUjhO2shsDpeil54BOFsqmk5xfdesiewbUY5tKQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.1.1.tgz", + "integrity": "sha512-hA1AKIHFUMa9Tl6q6y8p0pJ9aWHCCG8s57flmIyLE0W7HcJeYrYtnqXDcGnftvXEhdQnSexyegXnzzTGk8bKLA==", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.0.5", - "@smithy/smithy-client": "^4.4.10", - "@smithy/types": "^4.3.2", + "@smithy/property-provider": "^4.1.1", + "@smithy/smithy-client": "^4.6.1", + "@smithy/types": "^4.5.0", "bowser": "^2.11.0", "tslib": "^2.6.2" }, @@ -14262,17 +14771,17 @@ } }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "4.0.26", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.26.tgz", - "integrity": "sha512-z81yyIkGiLLYVDetKTUeCZQ8x20EEzvQjrqJtb/mXnevLq2+w3XCEWTJ2pMp401b6BkEkHVfXb/cROBpVauLMQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.1.1.tgz", + "integrity": "sha512-RGSpmoBrA+5D2WjwtK7tto6Pc2wO9KSXKLpLONhFZ8VyuCbqlLdiDAfuDTNY9AJe4JoE+Cx806cpTQQoQ71zPQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/config-resolver": "^4.1.5", - "@smithy/credential-provider-imds": "^4.0.7", - "@smithy/node-config-provider": "^4.1.4", - "@smithy/property-provider": "^4.0.5", - "@smithy/smithy-client": "^4.4.10", - "@smithy/types": "^4.3.2", + "@smithy/config-resolver": "^4.2.1", + "@smithy/credential-provider-imds": "^4.1.1", + "@smithy/node-config-provider": "^4.2.1", + "@smithy/property-provider": "^4.1.1", + "@smithy/smithy-client": "^4.6.1", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { @@ -14280,13 +14789,13 @@ } }, "node_modules/@smithy/util-endpoints": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.7.tgz", - "integrity": "sha512-klGBP+RpBp6V5JbrY2C/VKnHXn3d5V2YrifZbmMY8os7M6m8wdYFoO6w/fe5VkP+YVwrEktW3IWYaSQVNZJ8oQ==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.1.1.tgz", + "integrity": "sha512-qB4R9kO0SetA11Rzu6MVGFIaGYX3p6SGGGfWwsKnC6nXIf0n/0AKVwRTsYsz9ToN8CeNNtNgQRwKFBndGJZdyw==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.1.4", - "@smithy/types": "^4.3.2", + "@smithy/node-config-provider": "^4.2.1", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { @@ -14306,12 +14815,12 @@ } }, "node_modules/@smithy/util-middleware": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.5.tgz", - "integrity": "sha512-N40PfqsZHRSsByGB81HhSo+uvMxEHT+9e255S53pfBw/wI6WKDI7Jw9oyu5tJTLwZzV5DsMha3ji8jk9dsHmQQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.1.1.tgz", + "integrity": "sha512-CGmZ72mL29VMfESz7S6dekqzCh8ZISj3B+w0g1hZFXaOjGTVaSqfAEFAq8EGp8fUL+Q2l8aqNmt8U1tglTikeg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.2", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { @@ -14319,13 +14828,13 @@ } }, "node_modules/@smithy/util-retry": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.7.tgz", - "integrity": "sha512-TTO6rt0ppK70alZpkjwy+3nQlTiqNfoXja+qwuAchIEAIoSZW8Qyd76dvBv3I5bCpE38APafG23Y/u270NspiQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.1.1.tgz", + "integrity": "sha512-jGeybqEZ/LIordPLMh5bnmnoIgsqnp4IEimmUp5c5voZ8yx+5kAlN5+juyr7p+f7AtZTgvhmInQk4Q0UVbrZ0Q==", "license": "Apache-2.0", "dependencies": { - "@smithy/service-error-classification": "^4.0.7", - "@smithy/types": "^4.3.2", + "@smithy/service-error-classification": "^4.1.1", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { @@ -14333,18 +14842,18 @@ } }, "node_modules/@smithy/util-stream": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.2.4.tgz", - "integrity": "sha512-vSKnvNZX2BXzl0U2RgCLOwWaAP9x/ddd/XobPK02pCbzRm5s55M53uwb1rl/Ts7RXZvdJZerPkA+en2FDghLuQ==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.3.1.tgz", + "integrity": "sha512-khKkW/Jqkgh6caxMWbMuox9+YfGlsk9OnHOYCGVEdYQb/XVzcORXHLYUubHmmda0pubEDncofUrPNniS9d+uAA==", "license": "Apache-2.0", "dependencies": { - "@smithy/fetch-http-handler": "^5.1.1", - "@smithy/node-http-handler": "^4.1.1", - "@smithy/types": "^4.3.2", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-hex-encoding": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", + "@smithy/fetch-http-handler": "^5.2.1", + "@smithy/node-http-handler": "^4.2.1", + "@smithy/types": "^4.5.0", + "@smithy/util-base64": "^4.1.0", + "@smithy/util-buffer-from": "^4.1.0", + "@smithy/util-hex-encoding": "^4.1.0", + "@smithy/util-utf8": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -14352,9 +14861,9 @@ } }, "node_modules/@smithy/util-stream/node_modules/@smithy/util-hex-encoding": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz", - "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.1.0.tgz", + "integrity": "sha512-1LcueNN5GYC4tr8mo14yVYbh/Ur8jHhWOxniZXii+1+ePiIbsLZ5fEI0QQGtbRRP5mOhmooos+rLmVASGGoq5w==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -14364,12 +14873,12 @@ } }, "node_modules/@smithy/util-stream/node_modules/@smithy/util-utf8": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", - "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.1.0.tgz", + "integrity": "sha512-mEu1/UIXAdNYuBcyEPbjScKi/+MQVXNIuY/7Cm5XLIWe319kDrT5SizBE95jqtmEXoDbGoZxKLCMttdZdqTZKQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-buffer-from": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -14377,9 +14886,9 @@ } }, "node_modules/@smithy/util-uri-escape": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", - "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.1.0.tgz", + "integrity": "sha512-b0EFQkq35K5NHUYxU72JuoheM6+pytEVUGlTwiFxWFpmddA+Bpz3LgsPRIpBk8lnPE47yT7AF2Egc3jVnKLuPg==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -18788,6 +19297,143 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "license": "MIT" }, + "node_modules/concurrently": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz", + "integrity": "sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "date-fns": "^2.30.0", + "lodash": "^4.17.21", + "rxjs": "^7.8.1", + "shell-quote": "^1.8.1", + "spawn-command": "0.0.2", + "supports-color": "^8.1.1", + "tree-kill": "^1.2.2", + "yargs": "^17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": "^14.13.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/concurrently/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/concurrently/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concurrently/node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, + "node_modules/concurrently/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/concurrently/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/confbox": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", @@ -31681,6 +32327,12 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/spawn-command": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", + "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==", + "dev": true + }, "node_modules/spdx-correct": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", @@ -32837,6 +33489,16 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "license": "MIT" }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, "node_modules/trim-canvas": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/trim-canvas/-/trim-canvas-0.1.2.tgz", @@ -35433,6 +36095,7 @@ "@aws-sdk/client-bedrock-agentcore": "^3.755.0", "@aws-sdk/client-bedrock-agentcore-control": "^3.755.0", "@aws-sdk/client-bedrock-runtime": "^3.755.0", + "@aws-sdk/client-cognito-identity-provider": "^3.755.0", "@aws-sdk/client-dynamodb": "^3.755.0", "@aws-sdk/client-kendra": "^3.755.0", "@aws-sdk/client-lambda": "^3.755.0", @@ -35454,6 +36117,7 @@ "sanitize-html": "^2.13.0", "source-map-support": "^0.5.21", "upsert-slr": "^1.0.4", + "uuid": "13.0.0", "ws": "^8.18.0", "zod": "^3.24.1" }, @@ -35472,6 +36136,19 @@ "typescript": "~5.4.5" } }, + "packages/cdk/node_modules/uuid": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz", + "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, "packages/common": { "name": "@generative-ai-use-cases/common", "dependencies": { @@ -35587,6 +36264,7 @@ "@typescript-eslint/parser": "^7.6.0", "@vitejs/plugin-react": "^1.3.2", "autoprefixer": "^10.4.19", + "concurrently": "^8.2.2", "eslint": "^8.57.0", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.6", diff --git a/packages/cdk/lambda-python/generic-agent-core-runtime/app.py b/packages/cdk/lambda-python/generic-agent-core-runtime/app.py index bf62811ed..98c8efcad 100644 --- a/packages/cdk/lambda-python/generic-agent-core-runtime/app.py +++ b/packages/cdk/lambda-python/generic-agent-core-runtime/app.py @@ -40,42 +40,55 @@ async def invocations(request: Request): Expects request with messages, system_prompt, prompt, and model """ - # Get session info from headers + # Setup session and workspace headers = dict(request.headers) session_id = headers.get("x-amzn-bedrock-agentcore-runtime-session-id") trace_id = headers.get("x-amzn-trace-id") - logger.info(f"New invocation: {session_id} {trace_id}") - - # Set session info in agent manager agent_manager.set_session_info(session_id, trace_id) - - # Ensure workspace directory exists create_ws_directory() try: - # Read and parse request body + # Parse request body body = await request.body() - body_str = body.decode() - request_data = json.loads(body_str) - - # Handle input field if present (AWS Lambda integration format) - if "input" in request_data and isinstance(request_data["input"], dict): - request_data = request_data["input"] - - # Extract required fields + try: + request_data = json.loads(body.decode()) + # Handle AWS Lambda integration format + if "input" in request_data and isinstance(request_data["input"], dict): + request_data = request_data["input"] + except json.JSONDecodeError as e: + logger.error(f"Invalid JSON: {e}") + return create_error_response("Invalid JSON in request body") + + # Extract fields messages = request_data.get("messages", []) system_prompt = request_data.get("system_prompt") prompt = request_data.get("prompt", []) model_info = request_data.get("model", {}) - - # Return streaming response + user_id = request_data.get("user_id") + mcp_servers = request_data.get("mcp_servers") + agent_session_id = request_data.get("session_id") + agent_id = request_data.get("agent_id") + code_execution_enabled = request_data.get("code_execution_enabled", False) + + # Validate required fields + if not model_info: + return create_error_response("Model information is required") + if not prompt and not messages: + return create_error_response("Either prompt or messages is required") + + # Stream response async def generate(): try: async for chunk in agent_manager.process_request_streaming( messages=messages, system_prompt=system_prompt, prompt=prompt, - model_info=model_info + model_info=model_info, + user_id=user_id, + mcp_servers=mcp_servers, + session_id=agent_session_id or session_id, + agent_id=agent_id, + code_execution_enabled=code_execution_enabled, ): yield chunk finally: diff --git a/packages/cdk/lambda-python/generic-agent-core-runtime/mcp.json b/packages/cdk/lambda-python/generic-agent-core-runtime/mcp.json index 6eadc70af..9dee76653 100644 --- a/packages/cdk/lambda-python/generic-agent-core-runtime/mcp.json +++ b/packages/cdk/lambda-python/generic-agent-core-runtime/mcp.json @@ -1,36 +1,78 @@ { - "_comment": "Generic AgentCore Runtime Configuration", - "_agentcore_requirements": { - "platform": "linux/arm64", - "port": 8080, - "endpoints": { - "/ping": "GET - Health check endpoint", - "/invocations": "POST - Main inference endpoint" - }, - "aws_credentials": "Required for Bedrock model access and S3 operations" + "_comment": "MCP Server Configuration", + "_metadata_info": { + "description": "Each server can include a 'metadata' object with 'category' and 'description' fields", + "category_examples": [ + "AWS", + "AI/ML", + "Utility", + "Search", + "Development", + "Other" + ] }, "mcpServers": { "time": { "command": "uvx", - "args": ["mcp-server-time"] + "args": ["mcp-server-time"], + "metadata": { + "category": "Utility", + "description": "Provides current time and date functionality" + } + }, + "aws-knowledge-mcp-server": { + "command": "npx", + "args": ["mcp-remote", "https://knowledge-mcp.global.api.aws"], + "metadata": { + "category": "AWS", + "description": "AWS Knowledge Base MCP server for enterprise knowledge access" + } }, "awslabs.aws-documentation-mcp-server": { "command": "uvx", - "args": ["awslabs.aws-documentation-mcp-server@latest"] + "args": ["awslabs.aws-documentation-mcp-server@latest"], + "metadata": { + "category": "AWS", + "description": "Access AWS documentation and guides" + } }, "awslabs.cdk-mcp-server": { "command": "uvx", - "args": ["awslabs.cdk-mcp-server@latest"] + "args": ["awslabs.cdk-mcp-server@latest"], + "metadata": { + "category": "AWS", + "description": "AWS CDK code generation and assistance" + } }, "awslabs.aws-diagram-mcp-server": { "command": "uvx", - "args": ["awslabs.aws-diagram-mcp-server@latest"] + "args": ["awslabs.aws-diagram-mcp-server@latest"], + "metadata": { + "category": "AWS", + "description": "Generate AWS architecture diagrams" + } }, "awslabs.nova-canvas-mcp-server": { "command": "uvx", "args": ["awslabs.nova-canvas-mcp-server@latest"], "env": { "AWS_REGION": "us-east-1" + }, + "metadata": { + "category": "AI/ML", + "description": "Amazon Nova Canvas image generation" + } + }, + "tavily-search": { + "command": "npx", + "args": [ + "-y", + "mcp-remote", + "https://mcp.tavily.com/mcp/?tavilyApiKey=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" + ], + "metadata": { + "category": "Search", + "description": "Web search and research capabilities powered by Tavily" } } } diff --git a/packages/cdk/lambda-python/generic-agent-core-runtime/src/agent.py b/packages/cdk/lambda-python/generic-agent-core-runtime/src/agent.py index d335b1c8d..2ed672fe2 100644 --- a/packages/cdk/lambda-python/generic-agent-core-runtime/src/agent.py +++ b/packages/cdk/lambda-python/generic-agent-core-runtime/src/agent.py @@ -8,15 +8,18 @@ from typing import List, Dict, Union, Any, Optional, AsyncGenerator from .config import get_system_prompt, extract_model_info from .tools import ToolManager + +# Removed dynamic_mcp_manager - using simplified approach in tools.py from .utils import ( - create_empty_response, + create_empty_response, create_error_response, process_messages, - process_prompt + process_prompt, ) from .types import ModelInfo, Message logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) class AgentManager: @@ -35,18 +38,36 @@ async def process_request_streaming( system_prompt: Optional[str], prompt: Union[str, List[Dict[str, Any]]], model_info: ModelInfo, + user_id: Optional[str] = None, + mcp_servers: Optional[List[str]] = None, + session_id: Optional[str] = None, + agent_id: Optional[str] = None, + code_execution_enabled: Optional[bool] = False, ) -> AsyncGenerator[str, None]: """Process a request and yield streaming responses as raw events""" try: - # Get model info + # Set session info if provided + if session_id: + self.set_session_info(session_id, session_id) + + # Extract model info model_id, region = extract_model_info(model_info) - + # Combine system prompts combined_system_prompt = get_system_prompt(system_prompt) - - # Get all tools - tools = self.tool_manager.get_all_tools() - + + # Get tools (MCP handling is done in ToolManager) + tools = self.tool_manager.get_tools_with_options( + code_execution_enabled=code_execution_enabled, mcp_servers=mcp_servers + ) + logger.info( + f"Loaded {len(tools)} tools (code execution: {code_execution_enabled})" + ) + + # Log agent info + if agent_id: + logger.debug(f"Processing agent: {agent_id}") + # Create boto3 session and Bedrock model session = boto3.Session(region_name=region) bedrock_model = BedrockModel( @@ -55,11 +76,11 @@ async def process_request_streaming( cache_prompt="default", cache_tools="default", ) - + # Process messages and prompt using utility functions processed_messages = process_messages(messages) processed_prompt = process_prompt(prompt) - + # Create Strands agent and stream response agent = StrandsAgent( system_prompt=combined_system_prompt, @@ -73,7 +94,7 @@ async def process_request_streaming( yield json.dumps(event, ensure_ascii=False) + "\n" except Exception as e: - logger.error(f"Error processing agent request: {e}") + logger.error(f"Error processing agent request: {e}", exc_info=True) error_event = { "event": { "internalServerException": { @@ -82,3 +103,9 @@ async def process_request_streaming( } } yield json.dumps(error_event, ensure_ascii=False) + "\n" + finally: + # Cleanup is handled automatically by the dynamic MCP client + if user_id: + logger.debug( + f"Session cleanup for user {user_id} handled automatically" + ) diff --git a/packages/cdk/lambda-python/generic-agent-core-runtime/src/tools.py b/packages/cdk/lambda-python/generic-agent-core-runtime/src/tools.py index b0101f6bf..75b2cb583 100644 --- a/packages/cdk/lambda-python/generic-agent-core-runtime/src/tools.py +++ b/packages/cdk/lambda-python/generic-agent-core-runtime/src/tools.py @@ -1,18 +1,21 @@ """Tool management for the agent core runtime.""" -import os -import boto3 import json import logging +import os +from typing import Any + +import boto3 +from mcp import StdioServerParameters, stdio_client from strands import tool from strands.tools.mcp import MCPClient -from mcp import stdio_client, StdioServerParameters -from typing import List, Any -from .config import get_uv_environment, get_aws_credentials, WORKSPACE_DIR + +from .config import WORKSPACE_DIR, get_aws_credentials, get_uv_environment # Import strands-agents code interpreter tool try: from strands_tools.code_interpreter import AgentCoreCodeInterpreter + CODE_INTERPRETER_AVAILABLE = True except ImportError as e: CODE_INTERPRETER_AVAILABLE = False @@ -25,65 +28,113 @@ class ToolManager: """Manages tools including MCP tools and built-in tools.""" - + def __init__(self): self.mcp_tools = None self.session_id = None self.trace_id = None - + def set_session_info(self, session_id: str, trace_id: str): """Set session and trace IDs for tool operations""" self.session_id = session_id self.trace_id = trace_id - - def load_mcp_tools(self) -> List[Any]: - """Load MCP tools from mcp.json""" + + def load_mcp_tools(self) -> list[Any]: + """Load MCP tools from environment variable""" if self.mcp_tools is not None: return self.mcp_tools - + try: - with open("mcp.json", "r") as f: - mcp_json = json.loads(f.read()) - - if "mcpServers" not in mcp_json: - logger.warning("mcpServers not defined in mcp.json") - self.mcp_tools = [] - return self.mcp_tools - - mcp_servers = mcp_json["mcpServers"] - mcp_clients = [] - uv_env = get_uv_environment() - - for server_name, server in mcp_servers.items(): - try: - client = MCPClient( - lambda: stdio_client( - StdioServerParameters( - command=server["command"], - args=server.get("args", []), - env={**uv_env, **server.get("env", {})}, - ) + # Load MCP configuration from environment variable + mcp_config_str = os.environ.get("MCP_SERVERS_CONFIG") + if not mcp_config_str: + logger.warning("MCP_SERVERS_CONFIG environment variable not set") + self.mcp_tools = [] + return self.mcp_tools + + mcp_servers = json.loads(mcp_config_str) + mcp_clients = [] + uv_env = get_uv_environment() + + for server_name, server in mcp_servers.items(): + try: + client = MCPClient( + lambda server=server: stdio_client( + StdioServerParameters( + command=server["command"], + args=server.get("args", []), + env={**uv_env, **server.get("env", {})}, ) ) - client.start() - mcp_clients.append(client) - except Exception as e: - logger.error(f"Error creating MCP client for {server_name}: {e}") - - # Flatten the tools - self.mcp_tools = sum([c.list_tools_sync() for c in mcp_clients], []) - logger.info(f"Loaded {len(self.mcp_tools)} MCP tools") - return self.mcp_tools + ) + client.start() + mcp_clients.append(client) + except Exception as e: + logger.error(f"Error creating MCP client for {server_name}: {e}") + + # Flatten the tools + self.mcp_tools = sum([c.list_tools_sync() for c in mcp_clients], []) + logger.info(f"Loaded {len(self.mcp_tools)} MCP tools") + return self.mcp_tools + except Exception as e: logger.error(f"Error loading MCP tools: {e}") self.mcp_tools = [] return self.mcp_tools - + + def load_mcp_tools_by_names(self, server_names: list[str]) -> list[Any]: + """Load MCP tools from environment variable by server names""" + if not server_names: + return [] + + try: + # Load MCP configuration from environment variable + mcp_config_str = os.environ.get("MCP_SERVERS_CONFIG") + if not mcp_config_str: + logger.warning("MCP_SERVERS_CONFIG environment variable not set") + return [] + + available_servers = json.loads(mcp_config_str) + mcp_clients = [] + uv_env = get_uv_environment() + + for server_name in server_names: + if server_name not in available_servers: + logger.warning(f"MCP server '{server_name}' not found in mcp.json") + continue + + server_config = available_servers[server_name] + try: + client = MCPClient( + lambda server=server_config: stdio_client( + StdioServerParameters( + command=server["command"], + args=server.get("args", []), + env={**uv_env, **server.get("env", {})}, + ) + ) + ) + client.start() + mcp_clients.append(client) + logger.info(f"Successfully loaded MCP server: {server_name}") + except Exception as e: + logger.error(f"Error creating MCP client for {server_name}: {e}") + + # Flatten the tools + dynamic_tools = sum([c.list_tools_sync() for c in mcp_clients], []) + logger.info( + f"Loaded {len(dynamic_tools)} MCP tools from {len(mcp_clients)} servers" + ) + return dynamic_tools + + except Exception as e: + logger.error(f"Error loading MCP tools by names: {e}") + return [] + def get_upload_tool(self): """Get the S3 upload tool with session context""" - session_id = self.session_id trace_id = self.trace_id - + @tool def upload_file_to_s3_and_retrieve_s3_url(filepath: str) -> str: """Upload the file at /tmp/ws/* and retrieve the s3 path @@ -119,13 +170,13 @@ def upload_file_to_s3_and_retrieve_s3_url(filepath: str) -> str: logger.error(f"Error uploading file to S3: {e}") # For local testing, provide a fallback return f"Error uploading to S3: {str(e)}. Local file path: {filepath}" - + return upload_file_to_s3_and_retrieve_s3_url - - def get_code_interpreter_tool(self) -> List[Any]: + + def get_code_interpreter_tool(self) -> list[Any]: """Get code interpreter tool if available""" code_interpreter_tools = [] - + if CODE_INTERPRETER_AVAILABLE and AgentCoreCodeInterpreter: try: aws_creds = get_aws_credentials() @@ -135,16 +186,72 @@ def get_code_interpreter_tool(self) -> List[Any]: logger.info("Added code_interpreter tool (AgentCoreCodeInterpreter)") except Exception as e: logger.warning(f"Failed to initialize AgentCoreCodeInterpreter: {e}") - + return code_interpreter_tools - - def get_all_tools(self) -> List[Any]: - """Get all available tools (MCP + built-in + code interpreter)""" - mcp_tools = self.load_mcp_tools() + + def get_tools_with_options( + self, code_execution_enabled: bool = False, mcp_servers=None + ) -> list[Any]: + """ + Get tools with optional code execution and MCP servers. + + Args: + code_execution_enabled: Whether to include code interpreter tools + mcp_servers: MCP server configurations + - None: Load default MCP servers from mcp.json + - []: Empty list, no MCP servers (File Upload only) + - [...]: Load specified MCP servers + + Returns: + List of all available tools + """ + logger.info( + f"get_tools_with_options called with code_execution_enabled={code_execution_enabled}" + ) + logger.info(f"mcp_servers parameter: {mcp_servers} (type: {type(mcp_servers)})") + + all_tools = [] + + # Handle MCP servers based on parameter + if mcp_servers is None: + # Load default MCP servers from mcp.json + logger.info("Loading default MCP servers from mcp.json") + mcp_tools = self.load_mcp_tools() + elif isinstance(mcp_servers, list) and len(mcp_servers) == 0: + # Empty list: no MCP servers + logger.info("Empty MCP servers list provided, skipping MCP tools") + mcp_tools = [] + elif isinstance(mcp_servers, list): + # Load specified MCP servers by name + logger.info( + f"Loading {len(mcp_servers)} user-specified MCP servers by name" + ) + mcp_tools = self.load_mcp_tools_by_names(mcp_servers) + else: + # Fallback to default + logger.warning( + f"Unexpected mcp_servers type: {type(mcp_servers)}, using default" + ) + mcp_tools = self.load_mcp_tools() + + all_tools.extend(mcp_tools) + + # Add built-in tools (always included) upload_tool = self.get_upload_tool() - code_interpreter_tools = self.get_code_interpreter_tool() - - all_tools = mcp_tools + [upload_tool] + code_interpreter_tools - logger.info(f"Total tools loaded: {len(all_tools)} (MCP: {len(mcp_tools)}, Built-in: 1, Code Interpreter: {len(code_interpreter_tools)})") - - return all_tools \ No newline at end of file + all_tools.append(upload_tool) + + # Add code interpreter tools if enabled + code_interpreter_tools = [] + if code_execution_enabled: + code_interpreter_tools = self.get_code_interpreter_tool() + all_tools.extend(code_interpreter_tools) + + # Log final tool count + logger.info( + f"Total tools loaded: {len(all_tools)} " + f"(MCP: {len(mcp_tools)}, Built-in: 1, " + f"Code Interpreter: {len(code_interpreter_tools)} - " + f"{'enabled' if code_execution_enabled else 'disabled'})" + ) + + return all_tools diff --git a/packages/cdk/lambda-python/generic-agent-core-runtime/src/types.py b/packages/cdk/lambda-python/generic-agent-core-runtime/src/types.py index 53446c00a..91204a0c2 100644 --- a/packages/cdk/lambda-python/generic-agent-core-runtime/src/types.py +++ b/packages/cdk/lambda-python/generic-agent-core-runtime/src/types.py @@ -11,7 +11,12 @@ class ModelInfo(BaseModel): class AgentCoreRequest(BaseModel): + """Request model for AgentCore Runtime.""" messages: Union[List[Message], List[Dict[str, Any]]] = [] system_prompt: Optional[str] = None prompt: Union[str, List[Dict[str, Any]]] = "" model: ModelInfo = {} + user_id: Optional[str] = None # User identification for MCP isolation + mcp_servers: Optional[List[str]] = None # MCP server names from mcp.json + session_id: Optional[str] = None # Session identifier + agent_id: Optional[str] = None # Agent identifier for logging and tracking diff --git a/packages/cdk/lambda/agentBuilder.ts b/packages/cdk/lambda/agentBuilder.ts new file mode 100644 index 000000000..7ecb244f2 --- /dev/null +++ b/packages/cdk/lambda/agentBuilder.ts @@ -0,0 +1,34 @@ +import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; +import { getUserIdFromEvent } from './agentBuilder/utils/auth-utils'; +import { createSuccessResponse } from './agentBuilder/utils/response-utils'; +import { handleError } from './agentBuilder/utils/error-handling'; +import { routeRequest } from './agentBuilder/router'; + +/** + * Main Lambda handler for Agent Builder API + * + * This handler has been refactored to use a modular architecture: + * - Utilities: Common functions for auth, responses, and security + * - Validation: Request and data validation logic + * - Services: Business logic layer + * - Handlers: Individual endpoint handlers + * - Router: Centralized routing logic + */ +export const handler = async ( + event: APIGatewayProxyEvent +): Promise => { + // Handle CORS preflight + if (event.httpMethod === 'OPTIONS') { + return createSuccessResponse({}); + } + + try { + const userId = getUserIdFromEvent(event); + + // Route to appropriate handler based on resource and method + return await routeRequest(event, userId); + } catch (error) { + console.error('Handler error:', error); + return handleError(error as Error); + } +}; diff --git a/packages/cdk/lambda/agentBuilder/handlers/agent-handlers.ts b/packages/cdk/lambda/agentBuilder/handlers/agent-handlers.ts new file mode 100644 index 000000000..d00b10691 --- /dev/null +++ b/packages/cdk/lambda/agentBuilder/handlers/agent-handlers.ts @@ -0,0 +1,276 @@ +import { APIGatewayProxyResult } from 'aws-lambda'; +import { + CreateAgentRequest, + UpdateAgentRequest, + CloneAgentRequest, +} from 'generative-ai-use-cases'; +import * as agentService from '../services/agent-service'; +import { + validateUserId, + validateAgentId, + validateCloneAgentRequest, +} from '../validation/request-validation'; +import { + validateCreateAgentRequest, + validateUpdateAgentRequest, +} from '../validation/agent-validation'; +import { createSuccessResponse } from '../utils/response-utils'; +import { handleError, ValidationError } from '../utils/error-handling'; + +/** + * Handle create agent request + */ +export async function handleCreateAgent( + userId: string, + request: CreateAgentRequest +): Promise { + try { + // Input validation (data format, types, required fields) + const userValidation = validateUserId(userId); + if (!userValidation.isValid) { + throw new ValidationError(userValidation.error!); + } + + const validation = validateCreateAgentRequest(request); + if (!validation.isValid) { + throw new ValidationError(validation.error!); + } + + // Service layer handles business logic with validated data + const agent = await agentService.createAgent(userId, validation.data!); + return createSuccessResponse(agent, 201); + } catch (error) { + return handleError(error as Error); + } +} + +/** + * Handle get agent request + */ +export async function handleGetAgent( + userId: string, + agentId: string +): Promise { + try { + // Validate user ID + const userValidation = validateUserId(userId); + if (!userValidation.isValid) { + throw new ValidationError(userValidation.error!); + } + + // Validate agent ID + const agentValidation = validateAgentId(agentId); + if (!agentValidation.isValid) { + throw new ValidationError(agentValidation.error!); + } + + // Get agent + const agent = await agentService.getAgent(userId, agentId); + + return createSuccessResponse(agent); + } catch (error) { + return handleError(error as Error); + } +} + +/** + * Handle update agent request + */ +export async function handleUpdateAgent( + userId: string, + agentId: string, + request: UpdateAgentRequest +): Promise { + try { + // Input validation (data format, types, required fields) + const userValidation = validateUserId(userId); + if (!userValidation.isValid) { + throw new ValidationError(userValidation.error!); + } + + const agentValidation = validateAgentId(agentId); + if (!agentValidation.isValid) { + throw new ValidationError(agentValidation.error!); + } + + const validation = validateUpdateAgentRequest(request); + if (!validation.isValid) { + throw new ValidationError(validation.error!); + } + + // Service layer handles business logic with validated data + const agent = await agentService.updateAgent( + userId, + agentId, + validation.data! + ); + return createSuccessResponse(agent); + } catch (error) { + return handleError(error as Error); + } +} + +/** + * Handle delete agent request + */ +export async function handleDeleteAgent( + userId: string, + agentId: string +): Promise { + try { + // Validate user ID + const userValidation = validateUserId(userId); + if (!userValidation.isValid) { + throw new ValidationError(userValidation.error!); + } + + // Validate agent ID + const agentValidation = validateAgentId(agentId); + if (!agentValidation.isValid) { + throw new ValidationError(agentValidation.error!); + } + + // Delete agent + await agentService.deleteAgent(userId, agentId); + + return createSuccessResponse({ message: 'Agent deleted successfully' }); + } catch (error) { + return handleError(error as Error); + } +} + +/** + * Handle list user agents request + */ +export async function handleListUserAgents( + userId: string, + exclusiveStartKey?: string, + limit?: number +): Promise { + try { + // Validate user ID + const userValidation = validateUserId(userId); + if (!userValidation.isValid) { + throw new ValidationError(userValidation.error!); + } + + // List agents + const result = await agentService.listUserAgents( + userId, + limit, + exclusiveStartKey + ); + + return createSuccessResponse(result); + } catch (error) { + return handleError(error as Error); + } +} + +/** + * Handle list public agents request + */ +export async function handleListPublicAgents( + userId: string, + exclusiveStartKey?: string, + limit?: number +): Promise { + try { + // Input validation + const userValidation = validateUserId(userId); + if (!userValidation.isValid) { + throw new ValidationError(userValidation.error!); + } + + // Service layer handles business logic + const result = await agentService.listPublicAgents( + userId, + limit, + exclusiveStartKey + ); + return createSuccessResponse(result); + } catch (error) { + return handleError(error as Error); + } +} + +/** + * Handle clone agent request + */ +export async function handleCloneAgent( + userId: string, + request: CloneAgentRequest +): Promise { + try { + // Input validation + const userValidation = validateUserId(userId); + if (!userValidation.isValid) { + throw new ValidationError(userValidation.error!); + } + + const validation = validateCloneAgentRequest(request); + if (!validation.isValid) { + throw new ValidationError(validation.error!); + } + + // Service layer handles business logic with validated data + const agent = await agentService.cloneAgent(userId, validation.data!); + return createSuccessResponse(agent); + } catch (error) { + return handleError(error as Error); + } +} + +/** + * Handle list favorite agents request + */ +export async function handleListFavoriteAgents( + userId: string, + exclusiveStartKey?: string, + limit?: number +): Promise { + try { + // Input validation + const userValidation = validateUserId(userId); + if (!userValidation.isValid) { + throw new ValidationError(userValidation.error!); + } + + // Service layer handles business logic + const result = await agentService.listFavoriteAgents( + userId, + limit, + exclusiveStartKey + ); + return createSuccessResponse(result); + } catch (error) { + return handleError(error as Error); + } +} + +/** + * Handle toggle agent favorite request + */ +export async function handleToggleAgentFavorite( + userId: string, + agentId: string +): Promise { + try { + // Input validation + const userValidation = validateUserId(userId); + if (!userValidation.isValid) { + throw new ValidationError(userValidation.error!); + } + + const agentValidation = validateAgentId(agentId); + if (!agentValidation.isValid) { + throw new ValidationError(agentValidation.error!); + } + + // Service layer handles business logic + const result = await agentService.toggleAgentFavorite(userId, agentId); + return createSuccessResponse(result); + } catch (error) { + return handleError(error as Error); + } +} diff --git a/packages/cdk/lambda/agentBuilder/repositories/agent-repository.ts b/packages/cdk/lambda/agentBuilder/repositories/agent-repository.ts new file mode 100644 index 000000000..aafa22799 --- /dev/null +++ b/packages/cdk/lambda/agentBuilder/repositories/agent-repository.ts @@ -0,0 +1,697 @@ +/** + * Agent Repository - Simplified version + */ + +import { + DeleteCommand, + DynamoDBDocumentClient, + GetCommand, + PutCommand, + QueryCommand, + UpdateCommand, +} from '@aws-sdk/lib-dynamodb'; +import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; +import { v7 as uuidv7 } from 'uuid'; +import { + AgentInTable, + AgentAsOutput, + AgentContent, + RepositoryListAgentsResponse, +} from 'generative-ai-use-cases'; + +const TABLE_NAME: string = process.env.USECASE_TABLE_NAME!; +const dynamoDb = new DynamoDBClient({}); +const dynamoDbDocument = DynamoDBDocumentClient.from(dynamoDb); + +// Get agent by agentId with userId context +const findAgentByAgentId = async ( + agentId: string, + userId: string +): Promise => { + // First, try user's own agent + const userAgentResult = await dynamoDbDocument.send( + new GetCommand({ + TableName: TABLE_NAME, + Key: { id: `agent#${userId}`, dataType: `agent#${agentId}` }, + }) + ); + if (userAgentResult.Item) { + const item = userAgentResult.Item; + return { + ...item, + starCount: item.starCount || 0, // Default for backward compatibility + } as AgentInTable; + } + + // If not found, try public agent (with full data) + const publicResult = await dynamoDbDocument.send( + new GetCommand({ + TableName: TABLE_NAME, + Key: { id: 'public-agents', dataType: `public#${agentId}` }, + }) + ); + if (publicResult.Item) { + const record = publicResult.Item; + return { + id: `agent#${record.createdBy}`, + dataType: `agent#${record.agentId}`, + agentId: record.agentId, + name: record.name, + description: record.description, + systemPrompt: record.systemPrompt, + modelId: record.modelId, + mcpServers: record.mcpServers, + codeExecutionEnabled: record.codeExecutionEnabled, + tags: record.tags, + isPublic: record.isPublic, + starCount: record.starCount || 0, + createdAt: record.createdAt, + updatedAt: record.updatedAt, + createdByEmail: record.createdByEmail, + createdBy: record.createdBy, + } as AgentInTable; + } + + return null; +}; + +// Get agent list by userId +const findAgentsByUserId = async ( + userId: string, + exclusiveStartKey?: string, + limit?: number +): Promise<{ agents: AgentInTable[]; lastEvaluatedKey?: string }> => { + const startKey = exclusiveStartKey + ? JSON.parse(Buffer.from(exclusiveStartKey, 'base64').toString()) + : undefined; + + const result = await dynamoDbDocument.send( + new QueryCommand({ + TableName: TABLE_NAME, + KeyConditionExpression: + '#id = :id and begins_with(#dataType, :dataTypePrefix)', + ExpressionAttributeNames: { + '#id': 'id', + '#dataType': 'dataType', + }, + ExpressionAttributeValues: { + ':id': `agent#${userId}`, + ':dataTypePrefix': 'agent#', + }, + ScanIndexForward: true, + Limit: limit || 30, + ExclusiveStartKey: startKey, + }) + ); + + const agents = (result.Items || []).map((item) => ({ + ...item, + starCount: item.starCount || 0, // Default for backward compatibility + })) as AgentInTable[]; + + return { + agents, + lastEvaluatedKey: result.LastEvaluatedKey + ? Buffer.from(JSON.stringify(result.LastEvaluatedKey)).toString('base64') + : undefined, + }; +}; + +// Get favorites by userId +const findFavoritesByUserId = async ( + userId: string, + exclusiveStartKey?: string, + limit?: number +): Promise<{ + favorites: Array<{ dataType: string; agentId: string }>; + lastEvaluatedKey?: string; +}> => { + const startKey = exclusiveStartKey + ? JSON.parse(Buffer.from(exclusiveStartKey, 'base64').toString()) + : undefined; + + const result = await dynamoDbDocument.send( + new QueryCommand({ + TableName: TABLE_NAME, + KeyConditionExpression: + '#id = :id and begins_with(#dataType, :dataTypePrefix)', + ExpressionAttributeNames: { + '#id': 'id', + '#dataType': 'dataType', + }, + ExpressionAttributeValues: { + ':id': `agent#${userId}`, + ':dataTypePrefix': 'favorite#', + }, + ScanIndexForward: true, + Limit: limit || 20, + ExclusiveStartKey: startKey, + }) + ); + + const favorites = (result.Items || []).map((item) => ({ + dataType: item.dataType, + agentId: item.dataType.replace('favorite#', ''), + })); + + return { + favorites, + lastEvaluatedKey: result.LastEvaluatedKey + ? Buffer.from(JSON.stringify(result.LastEvaluatedKey)).toString('base64') + : undefined, + }; +}; + +// Get agents by agentIds (optimized for favorites) +const findAgentsByAgentIds = async ( + agentIds: string[], + userId: string +): Promise => { + if (agentIds.length === 0) return []; + + // Get user's own agents first + const { agents: userAgents } = await findAgentsByUserId(userId); + const userAgentMap = new Map( + userAgents.map((agent) => [agent.agentId, agent]) + ); + + const foundAgents: AgentInTable[] = []; + const publicAgentIds: string[] = []; + + // Separate user's own agents from public agents + for (const agentId of agentIds) { + const userAgent = userAgentMap.get(agentId); + if (userAgent) { + foundAgents.push(userAgent); + } else { + publicAgentIds.push(agentId); + } + } + + // Get public agents + if (publicAgentIds.length > 0) { + const publicAgentPromises = publicAgentIds.map(async (agentId) => { + const result = await dynamoDbDocument.send( + new GetCommand({ + TableName: TABLE_NAME, + Key: { id: 'public-agents', dataType: `public#${agentId}` }, + }) + ); + if (result.Item) { + const record = result.Item; + return { + id: `agent#${record.createdBy}`, + dataType: `agent#${record.agentId}`, + agentId: record.agentId, + name: record.name, + description: record.description, + systemPrompt: record.systemPrompt, + modelId: record.modelId, + mcpServers: record.mcpServers, + codeExecutionEnabled: record.codeExecutionEnabled, + tags: record.tags, + isPublic: record.isPublic, + starCount: record.starCount || 0, + createdAt: record.createdAt, + updatedAt: record.updatedAt, + createdByEmail: record.createdByEmail, + createdBy: record.createdBy, + } as AgentInTable; + } + return null; + }); + + const publicAgents = (await Promise.all(publicAgentPromises)).filter( + (agent): agent is AgentInTable => agent !== null + ); + + foundAgents.push(...publicAgents); + } + + return foundAgents; +}; + +// Manage public agent record +const managePublicAgentRecord = async ( + agentId: string, + isPublic: boolean, + agentData?: AgentInTable +): Promise => { + const key = { id: 'public-agents', dataType: `public#${agentId}` }; + + console.log( + `managePublicAgentRecord: agentId=${agentId}, isPublic=${isPublic}, hasAgentData=${!!agentData}` + ); + + if (isPublic && agentData) { + // Add/update public record with full data, but preserve the public record keys + console.log(`Creating/updating public record for agent ${agentId}`); + await dynamoDbDocument.send( + new PutCommand({ + TableName: TABLE_NAME, + Item: { + ...agentData, // All agent data except id and dataType + ...key, // Public record keys: id: 'public-agents', dataType: 'public#agentId' + }, + }) + ); + console.log(`Public record created/updated for agent ${agentId}`); + } else { + // Remove public record + console.log(`Removing public record for agent ${agentId}`); + await dynamoDbDocument.send( + new DeleteCommand({ TableName: TABLE_NAME, Key: key }) + ); + console.log(`Public record removed for agent ${agentId}`); + } +}; + +// Get all public agents +export const listPublicAgents = async (): Promise => { + const result = await dynamoDbDocument.send( + new QueryCommand({ + TableName: TABLE_NAME, + KeyConditionExpression: + '#id = :id AND begins_with(#dataType, :dataTypePrefix)', + ExpressionAttributeNames: { + '#id': 'id', + '#dataType': 'dataType', + }, + ExpressionAttributeValues: { + ':id': 'public-agents', + ':dataTypePrefix': 'public#', + }, + }) + ); + + return (result.Items || []).map( + (item) => + ({ + id: `agent#${item.createdBy}`, + dataType: `agent#${item.agentId}`, + agentId: item.agentId, + name: item.name, + description: item.description, + systemPrompt: item.systemPrompt, + modelId: item.modelId, + mcpServers: item.mcpServers, + codeExecutionEnabled: item.codeExecutionEnabled, + tags: item.tags, + isPublic: item.isPublic, + starCount: item.starCount || 0, + createdAt: item.createdAt, + updatedAt: item.updatedAt, + createdByEmail: item.createdByEmail, + createdBy: item.createdBy, + }) as AgentInTable + ); +}; + +export const createAgent = async ( + userId: string, + content: AgentContent +): Promise => { + const agentId = uuidv7(); + const now = new Date().toISOString(); + + const item: AgentInTable = { + id: `agent#${userId}`, + dataType: `agent#${agentId}`, + agentId, + name: content.name, + description: content.description || '', + systemPrompt: content.systemPrompt, + modelId: content.modelId, + mcpServers: content.mcpServers, + codeExecutionEnabled: content.codeExecutionEnabled ?? false, + tags: content.tags || [], + isPublic: content.isPublic ?? false, + starCount: 0, + createdAt: now, + updatedAt: now, + createdByEmail: content.createdByEmail, + createdBy: userId, + }; + + await dynamoDbDocument.send( + new PutCommand({ TableName: TABLE_NAME, Item: item }) + ); + + if (item.isPublic) { + await managePublicAgentRecord(agentId, true, item); + } + + return { ...item, isMyAgent: true }; +}; + +export const getAgent = async ( + userId: string, + agentId: string +): Promise => { + const agent = await findAgentByAgentId(agentId, userId); + if (!agent) return null; + + const isMyAgent = agent.createdBy === userId; + + // Access control: must be my agent or public + if (!isMyAgent && !agent.isPublic) { + return null; + } + + return { ...agent, isMyAgent }; +}; + +export const listAgents = async ( + userId: string, + exclusiveStartKey?: string, + limit?: number +): Promise => { + const { agents, lastEvaluatedKey } = await findAgentsByUserId( + userId, + exclusiveStartKey, + limit + ); + + return { + data: agents.map((agent) => ({ ...agent, isMyAgent: true })), + lastEvaluatedKey, + }; +}; + +export const updateAgent = async ( + userId: string, + agentId: string, + content: AgentContent +): Promise => { + const agent = await findAgentByAgentId(agentId, userId); + if (!agent || agent.createdBy !== userId) { + throw new Error(`Agent not found or access denied: ${agentId}`); + } + + const now = new Date().toISOString(); + const wasPublic = agent.isPublic; + const isNowPublic = content.isPublic ?? false; + + // Update main agent record + await dynamoDbDocument.send( + new UpdateCommand({ + TableName: TABLE_NAME, + Key: { id: agent.id, dataType: agent.dataType }, + UpdateExpression: + 'set #name = :name, description = :description, systemPrompt = :systemPrompt, modelId = :modelId, mcpServers = :mcpServers, codeExecutionEnabled = :codeExecutionEnabled, tags = :tags, isPublic = :isPublic, updatedAt = :updatedAt, createdByEmail = :createdByEmail', + ExpressionAttributeNames: { '#name': 'name' }, + ExpressionAttributeValues: { + ':name': content.name, + ':description': content.description || '', + ':systemPrompt': content.systemPrompt, + ':modelId': content.modelId, + ':mcpServers': content.mcpServers, + ':codeExecutionEnabled': content.codeExecutionEnabled ?? false, + ':tags': content.tags || [], + ':isPublic': isNowPublic, + ':updatedAt': now, + ':createdByEmail': content.createdByEmail, + }, + }) + ); + + // Update public record if needed + const updatedAgent: AgentInTable = { + ...agent, + ...content, + description: content.description || '', + codeExecutionEnabled: content.codeExecutionEnabled ?? false, + tags: content.tags || [], + isPublic: isNowPublic, + updatedAt: now, + }; + + if (wasPublic !== isNowPublic || isNowPublic) { + await managePublicAgentRecord(agentId, isNowPublic, updatedAgent); + } +}; + +export const deleteAgent = async ( + userId: string, + agentId: string +): Promise => { + const agent = await findAgentByAgentId(agentId, userId); + if (!agent || agent.createdBy !== userId) { + throw new Error(`Agent not found or access denied: ${agentId}`); + } + + await dynamoDbDocument.send( + new DeleteCommand({ + TableName: TABLE_NAME, + Key: { id: agent.id, dataType: agent.dataType }, + }) + ); + + if (agent.isPublic) { + await managePublicAgentRecord(agentId, false); + } +}; + +export const listAgentsWithFavorites = async ( + userId: string, + exclusiveStartKey?: string, + limit?: number +): Promise<{ + data: (AgentAsOutput & { isFavorite: boolean })[]; + lastEvaluatedKey?: string; +}> => { + const [agentsResult, favoritesResult] = await Promise.all([ + findAgentsByUserId(userId, exclusiveStartKey, limit), + findFavoritesByUserId(userId), + ]); + + const favoriteIds = new Set(favoritesResult.favorites.map((f) => f.agentId)); + + return { + data: agentsResult.agents.map((agent) => ({ + ...agent, + isMyAgent: true, + isFavorite: favoriteIds.has(agent.agentId), + })), + lastEvaluatedKey: agentsResult.lastEvaluatedKey, + }; +}; + +export const listPublicAgentsWithFavorites = async ( + userId: string +): Promise<(AgentInTable & { isFavorite: boolean; isMyAgent: boolean })[]> => { + const [publicAgents, favoritesResult] = await Promise.all([ + listPublicAgents(), + findFavoritesByUserId(userId), + ]); + + const favoriteIds = new Set(favoritesResult.favorites.map((f) => f.agentId)); + + return publicAgents.map((agent) => ({ + ...agent, + isFavorite: favoriteIds.has(agent.agentId), + isMyAgent: agent.createdBy === userId, + })); +}; + +export const listPublicAgentsWithFavoritesPaginated = async ( + userId: string, + exclusiveStartKey?: string, + limit?: number +): Promise<{ + data: (AgentAsOutput & { isFavorite: boolean; isMyAgent: boolean })[]; + lastEvaluatedKey?: string; +}> => { + const startKey = exclusiveStartKey + ? JSON.parse(Buffer.from(exclusiveStartKey, 'base64').toString()) + : undefined; + + const [publicResult, favoritesResult] = await Promise.all([ + dynamoDbDocument.send( + new QueryCommand({ + TableName: TABLE_NAME, + KeyConditionExpression: + '#id = :id AND begins_with(#dataType, :dataTypePrefix)', + ExpressionAttributeNames: { + '#id': 'id', + '#dataType': 'dataType', + }, + ExpressionAttributeValues: { + ':id': 'public-agents', + ':dataTypePrefix': 'public#', + }, + ScanIndexForward: true, + Limit: limit || 12, + ExclusiveStartKey: startKey, + }) + ), + findFavoritesByUserId(userId), + ]); + + const favoriteIds = new Set(favoritesResult.favorites.map((f) => f.agentId)); + const agents = (publicResult.Items || []).map( + (item) => + ({ + id: `agent#${item.createdBy}`, + dataType: `agent#${item.agentId}`, + agentId: item.agentId, + name: item.name, + description: item.description, + systemPrompt: item.systemPrompt, + modelId: item.modelId, + mcpServers: item.mcpServers, + codeExecutionEnabled: item.codeExecutionEnabled, + tags: item.tags, + isPublic: item.isPublic, + starCount: item.starCount || 0, + createdAt: item.createdAt, + updatedAt: item.updatedAt, + createdByEmail: item.createdByEmail, + createdBy: item.createdBy, + }) as AgentInTable + ); + + return { + data: agents.map((agent) => ({ + ...agent, + isFavorite: favoriteIds.has(agent.agentId), + isMyAgent: agent.createdBy === userId, + })), + lastEvaluatedKey: publicResult.LastEvaluatedKey + ? Buffer.from(JSON.stringify(publicResult.LastEvaluatedKey)).toString( + 'base64' + ) + : undefined, + }; +}; + +export const listFavoriteAgents = async ( + userId: string, + exclusiveStartKey?: string, + limit?: number +): Promise<{ data: AgentAsOutput[]; lastEvaluatedKey?: string }> => { + const favoritesResult = await findFavoritesByUserId( + userId, + exclusiveStartKey, + limit + ); + + const agentIds = favoritesResult.favorites.map((f) => f.agentId); + const agents = await findAgentsByAgentIds(agentIds, userId); + + return { + data: agents + .filter((agent) => agent.createdBy === userId || agent.isPublic) + .map((agent) => ({ + ...agent, + isMyAgent: agent.createdBy === userId, + })), + lastEvaluatedKey: favoritesResult.lastEvaluatedKey, + }; +}; + +export const isFavoriteAgent = async ( + userId: string, + agentId: string +): Promise => { + const result = await dynamoDbDocument.send( + new GetCommand({ + TableName: TABLE_NAME, + Key: { id: `agent#${userId}`, dataType: `favorite#${agentId}` }, + }) + ); + return !!result.Item; +}; + +export const toggleFavorite = async ( + userId: string, + agentId: string +): Promise<{ isFavorite: boolean }> => { + const key = { id: `agent#${userId}`, dataType: `favorite#${agentId}` }; + const existing = await dynamoDbDocument.send( + new GetCommand({ TableName: TABLE_NAME, Key: key }) + ); + + // Get the agent to update its star count + const agent = await findAgentByAgentId(agentId, userId); + if (!agent) { + throw new Error(`Agent not found: ${agentId}`); + } + + if (existing.Item) { + // Remove from favorites and decrement star count + await Promise.all([ + dynamoDbDocument.send( + new DeleteCommand({ TableName: TABLE_NAME, Key: key }) + ), + // Update star count in main agent record + dynamoDbDocument.send( + new UpdateCommand({ + TableName: TABLE_NAME, + Key: { id: agent.id, dataType: agent.dataType }, + UpdateExpression: 'ADD starCount :dec', + ExpressionAttributeValues: { + ':dec': -1, + }, + }) + ), + ]); + + // Update public record if agent is public + if (agent.isPublic) { + await dynamoDbDocument.send( + new UpdateCommand({ + TableName: TABLE_NAME, + Key: { id: 'public-agents', dataType: `public#${agentId}` }, + UpdateExpression: 'ADD starCount :dec', + ExpressionAttributeValues: { + ':dec': -1, + }, + }) + ); + } + + return { isFavorite: false }; + } else { + // Add to favorites and increment star count + await Promise.all([ + dynamoDbDocument.send( + new PutCommand({ + TableName: TABLE_NAME, + Item: { + ...key, + agentId, + createdAt: new Date().toISOString(), + }, + }) + ), + // Update star count in main agent record + dynamoDbDocument.send( + new UpdateCommand({ + TableName: TABLE_NAME, + Key: { id: agent.id, dataType: agent.dataType }, + UpdateExpression: 'ADD starCount :inc', + ExpressionAttributeValues: { + ':inc': 1, + }, + }) + ), + ]); + + // Update public record if agent is public + if (agent.isPublic) { + await dynamoDbDocument.send( + new UpdateCommand({ + TableName: TABLE_NAME, + Key: { id: 'public-agents', dataType: `public#${agentId}` }, + UpdateExpression: 'ADD starCount :inc', + ExpressionAttributeValues: { + ':inc': 1, + }, + }) + ); + } + + return { isFavorite: true }; + } +}; diff --git a/packages/cdk/lambda/agentBuilder/router.ts b/packages/cdk/lambda/agentBuilder/router.ts new file mode 100644 index 000000000..8218c706d --- /dev/null +++ b/packages/cdk/lambda/agentBuilder/router.ts @@ -0,0 +1,157 @@ +import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; + +// Import handlers +import { + handleCreateAgent, + handleGetAgent, + handleUpdateAgent, + handleDeleteAgent, + handleListUserAgents, + handleListPublicAgents, + handleCloneAgent, + handleListFavoriteAgents, + handleToggleAgentFavorite, +} from './handlers/agent-handlers'; + +import { createNotFoundResponse } from './utils/response-utils'; +import { validateAndParseRequestBody } from './validation/request-validation'; +import { handleError } from './utils/error-handling'; +import { + CreateAgentRequest, + CloneAgentRequest, + UpdateAgentRequest, +} from 'generative-ai-use-cases'; + +/** + * Route requests to appropriate handlers with error handling + */ +export async function routeRequest( + event: APIGatewayProxyEvent, + userId: string +): Promise { + try { + const { + resource, + httpMethod, + pathParameters, + body, + queryStringParameters, + } = event; + const exclusiveStartKey = + queryStringParameters?.exclusiveStartKey || + queryStringParameters?.nextToken; + const limit = queryStringParameters?.limit + ? parseInt(queryStringParameters.limit, 10) + : undefined; + + // Agent routes + if (resource === '/agents') { + if (httpMethod === 'POST') { + const parseResult = validateAndParseRequestBody(body); + if (!parseResult.isValid) { + return handleError(new Error(parseResult.error!)); + } + return await handleCreateAgent( + userId, + parseResult.data as CreateAgentRequest + ); + } + if (httpMethod === 'GET') { + return await handleListUserAgents(userId, exclusiveStartKey, limit); + } + } + + // Public agents route + if (resource === '/agents/public' && httpMethod === 'GET') { + return await handleListPublicAgents(userId, exclusiveStartKey, limit); + } + + // Import agent route + if (resource === '/agents/clone' && httpMethod === 'POST') { + const parseResult = validateAndParseRequestBody(body); + if (!parseResult.isValid) { + return handleError(new Error(parseResult.error!)); + } + return await handleCloneAgent( + userId, + parseResult.data as CloneAgentRequest + ); + } + + // Favorite agents route + if (resource === '/agents/favorites' && httpMethod === 'GET') { + return await handleListFavoriteAgents(userId, exclusiveStartKey, limit); + } + + // Individual agent routes and sub-routes + if (resource === '/agents/{proxy+}') { + const pathParts = (pathParameters?.proxy || '').split('/'); + const agentId = pathParts[0]; + + if (!agentId) { + return createNotFoundResponse('Agent ID is required'); + } + + // Handle special routes with clear semantics + if (agentId === 'my' && httpMethod === 'GET') { + return await handleListUserAgents(userId, exclusiveStartKey, limit); + } + + if (agentId === 'public' && httpMethod === 'GET') { + return await handleListPublicAgents(userId, exclusiveStartKey, limit); + } + + if (agentId === 'favorites' && httpMethod === 'GET') { + return await handleListFavoriteAgents(userId, exclusiveStartKey, limit); + } + + if (agentId === 'import' && httpMethod === 'POST') { + const parseResult = validateAndParseRequestBody(body); + if (!parseResult.isValid) { + return handleError(new Error(parseResult.error!)); + } + return await handleCloneAgent( + userId, + parseResult.data as CloneAgentRequest + ); + } + + // Individual agent operations + if (pathParts.length === 1) { + if (httpMethod === 'GET') { + return await handleGetAgent(userId, agentId); + } + if (httpMethod === 'PUT') { + const parseResult = validateAndParseRequestBody(body); + if (!parseResult.isValid) { + return handleError(new Error(parseResult.error!)); + } + return await handleUpdateAgent( + userId, + agentId, + parseResult.data as UpdateAgentRequest + ); + } + if (httpMethod === 'DELETE') { + return await handleDeleteAgent(userId, agentId); + } + } + + // Sub-routes + if (pathParts.length > 1) { + const subRoute = pathParts[1]; + + // share endpoint removed - use PUT /agents/{agentId} with isPublic flag instead + + if (subRoute === 'favorite' && httpMethod === 'POST') { + return await handleToggleAgentFavorite(userId, agentId); + } + } + } + + // Default: not found + return createNotFoundResponse(); + } catch (error) { + return handleError(error as Error); + } +} diff --git a/packages/cdk/lambda/agentBuilder/services/agent-service.ts b/packages/cdk/lambda/agentBuilder/services/agent-service.ts new file mode 100644 index 000000000..c427f6ee1 --- /dev/null +++ b/packages/cdk/lambda/agentBuilder/services/agent-service.ts @@ -0,0 +1,323 @@ +import { + AgentConfiguration, + ListAgentsResponse, + CreateAgentRequest, + UpdateAgentRequest, + AgentAsOutput, + CloneAgentRequest, + ClonedAgentResponse, +} from 'generative-ai-use-cases'; +import * as agentRepository from '../repositories/agent-repository'; +import { getUserEmail } from '../utils/auth-utils'; +import { + NotFoundError, + ForbiddenError, + UnauthorizedError, +} from '../utils/error-handling'; + +/** + * Convert AgentAsOutput to AgentConfiguration + */ +function convertToAgentConfiguration( + agent: AgentAsOutput & { isFavorite?: boolean } +): AgentConfiguration { + return { + agentId: agent.agentId, + name: agent.name, + description: agent.description, + systemPrompt: agent.systemPrompt, + mcpServers: agent.mcpServers || [], + modelId: agent.modelId, + codeExecutionEnabled: agent.codeExecutionEnabled || false, + isPublic: agent.isPublic || false, + shareId: undefined, // Not available in AgentAsOutput + createdAt: agent.createdAt, + updatedAt: agent.updatedAt, + tags: agent.tags || [], + starCount: agent.starCount || 0, + createdBy: agent.id ? agent.id.split('#')[1] : 'Unknown', + createdByEmail: agent.createdByEmail, + isFavorite: agent.isFavorite ?? false, // Use the actual favorite status + isMyAgent: agent.isMyAgent, + }; +} + +/** + * Create a new agent + */ +export async function createAgent( + userId: string, + request: CreateAgentRequest +): Promise { + // MCP server names (no sanitization needed for string array) + const mcpServerNames = request.mcpServers || []; + + // Get user email from Cognito + const userEmail = await getUserEmail(userId); + + const agent = await agentRepository.createAgent(userId, { + name: request.name.trim(), + description: (request.description || '').trim(), + systemPrompt: request.systemPrompt.trim(), + mcpServers: mcpServerNames, + modelId: request.modelId, + codeExecutionEnabled: request.codeExecutionEnabled ?? false, + isPublic: request.isPublic ?? false, + tags: (request.tags || []) + .map((tag) => tag.trim()) + .filter((tag) => tag.length > 0), + createdByEmail: userEmail, + }); + + console.log(`Agent created: ${agent.agentId} by user: ${userId}`); + return convertToAgentConfiguration(agent); +} + +/** + * Get an agent by ID + */ +export async function getAgent( + userId: string, + agentId: string +): Promise { + const agent = await agentRepository.getAgent(userId, agentId); + + if (!agent) { + throw new NotFoundError('Agent not found'); + } + + return convertToAgentConfiguration(agent); +} + +/** + * Update an existing agent + */ +export async function updateAgent( + userId: string, + agentId: string, + request: UpdateAgentRequest +): Promise { + // Business validation: Check if agent exists and user owns it + const existingAgent = await agentRepository.getAgent(userId, agentId); + if (!existingAgent) { + throw new NotFoundError('Agent not found'); + } + + // Version check removed - using starCount instead + + // MCP server names + const mcpServerNames = request.mcpServers || existingAgent.mcpServers; + + // Get user email from Cognito if not provided in request + const userEmail = request.createdByEmail || (await getUserEmail(userId)); + + try { + await agentRepository.updateAgent(userId, agentId, { + name: request.name?.trim() || existingAgent.name, + description: request.description?.trim() || existingAgent.description, + systemPrompt: request.systemPrompt?.trim() || existingAgent.systemPrompt, + mcpServers: mcpServerNames, + modelId: request.modelId || existingAgent.modelId, + codeExecutionEnabled: + request.codeExecutionEnabled ?? + existingAgent.codeExecutionEnabled ?? + false, + tags: + request.tags + ?.map((tag) => tag.trim()) + .filter((tag) => tag.length > 0) || existingAgent.tags, + isPublic: request.isPublic ?? existingAgent.isPublic ?? false, + createdByEmail: userEmail, + }); + } catch (error) { + if (error instanceof Error && error.message.includes('Access denied')) { + throw new ForbiddenError( + 'You do not have permission to update this agent' + ); + } + if (error instanceof Error && error.message.includes('Agent not found')) { + throw new NotFoundError('Agent not found'); + } + throw error; + } + + // Get updated agent + const agent = await agentRepository.getAgent(userId, agentId); + if (!agent) { + throw new NotFoundError('Agent not found after update'); + } + + console.log(`Agent updated: ${agentId} by user: ${userId}`); + return convertToAgentConfiguration(agent); +} + +/** + * Delete an agent + */ +export async function deleteAgent( + userId: string, + agentId: string +): Promise { + // Check if agent exists first + const existingAgent = await agentRepository.getAgent(userId, agentId); + if (!existingAgent) { + throw new NotFoundError('Agent not found'); + } + + try { + await agentRepository.deleteAgent(userId, agentId); + console.log(`Agent deleted: ${agentId} by user: ${userId}`); + } catch (error) { + if (error instanceof Error && error.message.includes('Access denied')) { + throw new ForbiddenError( + 'You do not have permission to delete this agent' + ); + } + if (error instanceof Error && error.message.includes('Agent not found')) { + throw new NotFoundError('Agent not found'); + } + throw error; + } +} + +/** + * List user's agents + */ +export async function listUserAgents( + userId: string, + limit?: number, + nextToken?: string +): Promise { + console.log( + `listUserAgents service: userId=${userId}, limit=${limit}, nextToken=${nextToken}` + ); + + const result = await agentRepository.listAgentsWithFavorites( + userId, + nextToken, + limit + ); + + console.log( + `listUserAgents service result: agents=${result.data.length}, nextToken=${result.lastEvaluatedKey}` + ); + + return { + agents: result.data.map((agent) => convertToAgentConfiguration(agent)), + nextToken: result.lastEvaluatedKey, + totalCount: undefined, // Remove totalCount to avoid full table scan + type: 'my', + }; +} + +/** + * List public agents (always returns latest data) + */ +export async function listPublicAgents( + userId: string, + limit?: number, + nextToken?: string +): Promise { + const result = await agentRepository.listPublicAgentsWithFavoritesPaginated( + userId, + nextToken, + limit + ); + + return { + agents: result.data.map((agent) => convertToAgentConfiguration(agent)), + nextToken: result.lastEvaluatedKey, + totalCount: undefined, // Remove totalCount to avoid full table scan + type: 'public', + }; +} + +/** + * List favorite agents (always returns latest data) + */ +export async function listFavoriteAgents( + userId: string, + limit?: number, + nextToken?: string +): Promise { + const result = await agentRepository.listFavoriteAgents( + userId, + nextToken, + limit + ); + + return { + agents: result.data.map((agent) => ({ + ...convertToAgentConfiguration(agent), + isFavorite: true, // All agents in this list are favorites + isMyAgent: agent.isMyAgent || false, // Ensure boolean type + })), + nextToken: result.lastEvaluatedKey, + totalCount: undefined, // Remove totalCount to avoid full table scan + type: 'favorites', + }; +} + +/** + * Toggle agent favorite status + */ +export async function toggleAgentFavorite( + userId: string, + agentId: string +): Promise<{ isFavorite: boolean }> { + return await agentRepository.toggleFavorite(userId, agentId); +} + +/** + * Clone an agent + */ +export async function cloneAgent( + userId: string, + request: CloneAgentRequest +): Promise { + // Use agent repository for consistent access pattern + const sourceAgent = await agentRepository.getAgent( + userId, + request.sourceAgentId + ); + + if (!sourceAgent) { + throw new NotFoundError('Source agent not found'); + } + + // Check if the source agent is public or owned by the user + const isPublic = sourceAgent.isPublic ?? false; + if (!isPublic && !sourceAgent.isMyAgent) { + throw new UnauthorizedError('Agent is not public and you do not own it'); + } + + // Create a new agent using the repository + const newAgent = await agentRepository.createAgent(userId, { + name: request.name || `${sourceAgent.name} (Cloned)`, + description: sourceAgent.description, + systemPrompt: sourceAgent.systemPrompt, + modelId: sourceAgent.modelId, + mcpServers: sourceAgent.mcpServers || [], + codeExecutionEnabled: sourceAgent.codeExecutionEnabled || false, + tags: sourceAgent.tags || [], + isPublic: false, // Cloned agents are private by default + }); + + console.log( + `Agent cloned: ${newAgent.agentId} from ${request.sourceAgentId} by user: ${userId}` + ); + + return { + agentId: newAgent.agentId, + name: newAgent.name, + description: newAgent.description, + systemPrompt: newAgent.systemPrompt, + modelId: newAgent.modelId, + mcpServers: newAgent.mcpServers, + codeExecutionEnabled: newAgent.codeExecutionEnabled, + tags: newAgent.tags, + isPublic: newAgent.isPublic, + createdAt: newAgent.createdAt, + updatedAt: newAgent.updatedAt, + }; +} diff --git a/packages/cdk/lambda/agentBuilder/utils/auth-utils.ts b/packages/cdk/lambda/agentBuilder/utils/auth-utils.ts new file mode 100644 index 000000000..403d4293b --- /dev/null +++ b/packages/cdk/lambda/agentBuilder/utils/auth-utils.ts @@ -0,0 +1,44 @@ +import { APIGatewayProxyEvent } from 'aws-lambda'; +import { + CognitoIdentityProviderClient, + AdminGetUserCommand, + AttributeType, +} from '@aws-sdk/client-cognito-identity-provider'; + +// Initialize Cognito client +const cognitoClient = new CognitoIdentityProviderClient({}); + +/** + * Extract user ID from API Gateway event + */ +export function getUserIdFromEvent(event: APIGatewayProxyEvent): string { + const userId = event.requestContext.authorizer!.claims['cognito:username']; + if (!userId) { + throw new Error('User not authenticated'); + } + return userId; +} + +/** + * Get user email from Cognito + */ +export async function getUserEmail( + userId: string +): Promise { + try { + const command = new AdminGetUserCommand({ + UserPoolId: process.env.USER_POOL_ID!, + Username: userId, + }); + + const response = await cognitoClient.send(command); + const emailAttribute = response.UserAttributes?.find( + (attr: AttributeType) => attr.Name === 'email' + ); + + return emailAttribute?.Value; + } catch (error) { + console.error('Error getting user email:', error); + return undefined; + } +} diff --git a/packages/cdk/lambda/agentBuilder/utils/error-handling.ts b/packages/cdk/lambda/agentBuilder/utils/error-handling.ts new file mode 100644 index 000000000..03bb9c0d6 --- /dev/null +++ b/packages/cdk/lambda/agentBuilder/utils/error-handling.ts @@ -0,0 +1,89 @@ +import { APIGatewayProxyResult } from 'aws-lambda'; +import { + createErrorResponse, + createNotFoundResponse, + createUnauthorizedResponse, + createInternalServerErrorResponse, +} from './response-utils'; + +/** + * Custom error classes for better error handling + */ +export class ValidationError extends Error { + constructor(message: string) { + super(message); + this.name = 'ValidationError'; + } +} + +export class NotFoundError extends Error { + constructor(message: string) { + super(message); + this.name = 'NotFoundError'; + } +} + +export class UnauthorizedError extends Error { + constructor(message: string) { + super(message); + this.name = 'UnauthorizedError'; + } +} + +export class ConflictError extends Error { + public currentVersion?: number; + + constructor(message: string, currentVersion?: number) { + super(message); + this.name = 'ConflictError'; + this.currentVersion = currentVersion; + } +} + +export class ForbiddenError extends Error { + constructor(message: string) { + super(message); + this.name = 'ForbiddenError'; + } +} + +/** + * Handle errors and return appropriate API Gateway response + */ +export function handleError(error: Error): APIGatewayProxyResult { + console.error('Error occurred:', error); + + if (error instanceof ValidationError) { + return createErrorResponse(error.message, 400); + } + + if (error instanceof NotFoundError) { + return createNotFoundResponse(error.message); + } + + if (error instanceof UnauthorizedError) { + return createUnauthorizedResponse(error.message); + } + + if (error instanceof ForbiddenError) { + return createErrorResponse(error.message, 403); + } + + if (error instanceof ConflictError) { + const body = error.currentVersion + ? { error: error.message, currentVersion: error.currentVersion } + : { error: error.message }; + + return { + statusCode: 409, + headers: { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*', + }, + body: JSON.stringify(body), + }; + } + + // Default to internal server error for unexpected errors + return createInternalServerErrorResponse('Internal server error'); +} diff --git a/packages/cdk/lambda/agentBuilder/utils/response-utils.ts b/packages/cdk/lambda/agentBuilder/utils/response-utils.ts new file mode 100644 index 000000000..998022849 --- /dev/null +++ b/packages/cdk/lambda/agentBuilder/utils/response-utils.ts @@ -0,0 +1,62 @@ +import { APIGatewayProxyResult } from 'aws-lambda'; + +/** + * Create a standardized success response + */ +export function createSuccessResponse( + data: unknown, + statusCode: number = 200 +): APIGatewayProxyResult { + return { + statusCode, + headers: { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*', + }, + body: JSON.stringify(data), + }; +} + +/** + * Create a standardized error response + */ +export function createErrorResponse( + error: string, + statusCode: number = 400 +): APIGatewayProxyResult { + return { + statusCode, + headers: { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*', + }, + body: JSON.stringify({ error }), + }; +} + +/** + * Create a not found response + */ +export function createNotFoundResponse( + message: string = 'Not found' +): APIGatewayProxyResult { + return createErrorResponse(message, 404); +} + +/** + * Create an unauthorized response + */ +export function createUnauthorizedResponse( + message: string = 'Unauthorized' +): APIGatewayProxyResult { + return createErrorResponse(message, 403); +} + +/** + * Create an internal server error response + */ +export function createInternalServerErrorResponse( + message: string = 'Internal server error' +): APIGatewayProxyResult { + return createErrorResponse(message, 500); +} diff --git a/packages/cdk/lambda/agentBuilder/validation/agent-validation.ts b/packages/cdk/lambda/agentBuilder/validation/agent-validation.ts new file mode 100644 index 000000000..527c241e0 --- /dev/null +++ b/packages/cdk/lambda/agentBuilder/validation/agent-validation.ts @@ -0,0 +1,50 @@ +import { z } from 'zod'; +import { + CreateAgentRequestSchema, + UpdateAgentRequestSchema, + CreateAgentRequestValidation, + UpdateAgentRequestValidation, +} from './schemas'; +import { ValidationResult } from 'generative-ai-use-cases'; + +// ValidationResult interface is now imported from generative-ai-use-cases types + +/** + * Validate create agent request using Zod + * @param request Create agent request to validate + * @returns Validation result with validated data + */ +export function validateCreateAgentRequest( + request: unknown +): ValidationResult { + try { + const validatedData = CreateAgentRequestSchema.parse(request); + return { isValid: true, data: validatedData }; + } catch (error) { + if (error instanceof z.ZodError) { + const errorMessage = error.errors.map((err) => err.message).join(', '); + return { isValid: false, error: errorMessage }; + } + return { isValid: false, error: 'Invalid request format' }; + } +} + +/** + * Validate update agent request using Zod + * @param request Update agent request to validate + * @returns Validation result with validated data + */ +export function validateUpdateAgentRequest( + request: unknown +): ValidationResult { + try { + const validatedData = UpdateAgentRequestSchema.parse(request); + return { isValid: true, data: validatedData }; + } catch (error) { + if (error instanceof z.ZodError) { + const errorMessage = error.errors.map((err) => err.message).join(', '); + return { isValid: false, error: errorMessage }; + } + return { isValid: false, error: 'Invalid request format' }; + } +} diff --git a/packages/cdk/lambda/agentBuilder/validation/request-validation.ts b/packages/cdk/lambda/agentBuilder/validation/request-validation.ts new file mode 100644 index 000000000..a1486b7bb --- /dev/null +++ b/packages/cdk/lambda/agentBuilder/validation/request-validation.ts @@ -0,0 +1,127 @@ +import { z } from 'zod'; +import { + ShareAgentRequestSchema, + CloneAgentRequestSchema, + AgentIdSchema, + UserIdSchema, + PaginationKeySchema, + RequestBodySchema, + ShareAgentRequestValidation, + CloneAgentRequestValidation, +} from './schemas'; +import { ValidationResult } from 'generative-ai-use-cases'; + +/** + * Validate share agent request using Zod + * @param request Share agent request to validate + * @returns Validation result with validated data + */ +export function validateShareAgentRequest( + request: unknown +): ValidationResult { + try { + const validatedData = ShareAgentRequestSchema.parse(request); + return { isValid: true, data: validatedData }; + } catch (error) { + if (error instanceof z.ZodError) { + const errorMessage = error.errors.map((err) => err.message).join(', '); + return { isValid: false, error: errorMessage }; + } + return { isValid: false, error: 'Invalid request format' }; + } +} + +/** + * Validate import agent request using Zod + * @param request Import agent request to validate + * @returns Validation result with validated data + */ +export function validateCloneAgentRequest( + request: unknown +): ValidationResult { + try { + const validatedData = CloneAgentRequestSchema.parse(request); + return { isValid: true, data: validatedData }; + } catch (error) { + if (error instanceof z.ZodError) { + const errorMessage = error.errors.map((err) => err.message).join(', '); + return { isValid: false, error: errorMessage }; + } + return { isValid: false, error: 'Invalid request format' }; + } +} + +/** + * Validate agent ID parameter using Zod + * @param agentId Agent ID to validate + * @returns Validation result + */ +export function validateAgentId(agentId: unknown): ValidationResult { + try { + AgentIdSchema.parse(agentId); + return { isValid: true }; + } catch (error) { + if (error instanceof z.ZodError) { + const errorMessage = error.errors.map((err) => err.message).join(', '); + return { isValid: false, error: errorMessage }; + } + return { isValid: false, error: 'Invalid agent ID format' }; + } +} + +/** + * Validate user ID parameter using Zod + * @param userId User ID to validate + * @returns Validation result + */ +export function validateUserId(userId: unknown): ValidationResult { + try { + UserIdSchema.parse(userId); + return { isValid: true }; + } catch (error) { + if (error instanceof z.ZodError) { + const errorMessage = error.errors.map((err) => err.message).join(', '); + return { isValid: false, error: errorMessage }; + } + return { isValid: false, error: 'Invalid user ID format' }; + } +} + +/** + * Validate pagination parameters using Zod + * @param exclusiveStartKey Exclusive start key for pagination + * @returns Validation result + */ +export function validatePaginationParams( + exclusiveStartKey: unknown +): ValidationResult { + try { + PaginationKeySchema.parse(exclusiveStartKey); + return { isValid: true }; + } catch (error) { + return { isValid: false, error: 'Invalid pagination key format' }; + } +} + +/** + * Validate and parse request body using Zod + * @param body Request body string + * @returns Parsed object or validation error + */ +export function validateAndParseRequestBody( + body: string | null +): { isValid: true; data: T } | ValidationResult { + try { + // First validate that we have a body + const validBody = RequestBodySchema.parse(body); + + // Then parse JSON + const data = JSON.parse(validBody) as T; + return { isValid: true, data }; + } catch (error) { + if (error instanceof z.ZodError) { + return { isValid: false, error: 'Request body is required' }; + } + return { isValid: false, error: 'Invalid JSON in request body' }; + } +} diff --git a/packages/cdk/lambda/agentBuilder/validation/schemas.ts b/packages/cdk/lambda/agentBuilder/validation/schemas.ts new file mode 100644 index 000000000..7a7d0af37 --- /dev/null +++ b/packages/cdk/lambda/agentBuilder/validation/schemas.ts @@ -0,0 +1,178 @@ +import { z } from 'zod'; + +// MCP Server Reference Schema (what users specify) +export const MCPServerReferenceSchema = z + .string() + .min(1, 'MCP server name is required') + .max(100, 'MCP server name must be 100 characters or less') + .regex(/^[a-zA-Z0-9._-]+$/, 'MCP server name contains invalid characters'); + +// MCP Server Configuration Schema (for internal use) +export const MCPServerConfigSchema = z + .object({ + name: z + .string() + .min(1, 'MCP server name is required') + .max(100, 'MCP server name must be 100 characters or less'), + command: z.enum(['uvx', 'npx', 'node', 'python', 'python3'], { + errorMap: () => ({ + message: + 'Only uvx, npx, node, python, and python3 commands are allowed for MCP servers', + }), + }), + args: z.array(z.string()).optional().default([]), + env: z.record(z.string()).optional().default({}), + enabled: z.boolean().optional().default(true), + description: z + .string() + .max(500, 'MCP server description must be 500 characters or less') + .optional(), + version: z.string().optional(), + }) + .refine( + (data) => { + // Validate args don't contain dangerous patterns + const dangerousPatterns = [ + '--', + '&&', + '||', + ';', + '|', + '>', + '<', + '`', + '$', + 'rm', + 'sudo', + 'chmod', + ]; + for (const arg of data.args || []) { + if ( + dangerousPatterns.some((pattern) => String(arg).includes(pattern)) + ) { + return false; + } + } + return true; + }, + { + message: 'Dangerous pattern detected in MCP server arguments', + } + ) + .refine( + (data) => { + // Validate environment variables + const allowedEnvVars = [ + 'AWS_REGION', + 'FASTMCP_LOG_LEVEL', + 'MCP_SERVER_NAME', + 'LOG_LEVEL', + 'DEBUG', + 'VERBOSE', + ]; + + for (const key of Object.keys(data.env || {})) { + if (!allowedEnvVars.includes(key) && !key.startsWith('MCP_')) { + return false; + } + } + return true; + }, + { + message: 'Environment variable not allowed', + } + ); + +// Agent Schemas +export const CreateAgentRequestSchema = z.object({ + name: z + .string() + .min(1, 'Agent name is required') + .max(100, 'Agent name must be 100 characters or less'), + description: z + .string() + .max(500, 'Description must be 500 characters or less') + .optional(), + systemPrompt: z + .string() + .min(1, 'System prompt is required') + .max(10000, 'System prompt must be 10,000 characters or less'), + modelId: z.string().min(1, 'Model ID is required'), + mcpServers: z + .array(MCPServerReferenceSchema) + .max(10, 'Maximum 10 MCP servers allowed') + .optional() + .default([]), + codeExecutionEnabled: z.boolean().optional().default(false), + isPublic: z.boolean().optional().default(false), + tags: z + .array(z.string().max(50, 'Each tag must be 50 characters or less')) + .max(10, 'Maximum 10 tags allowed') + .optional() + .default([]), + createdByEmail: z.string().email().optional(), +}); + +export const UpdateAgentRequestSchema = z.object({ + agentId: z.string().uuid('Invalid agent ID format'), + name: z + .string() + .min(1, 'Agent name is required') + .max(100, 'Agent name must be 100 characters or less'), + description: z + .string() + .max(500, 'Description must be 500 characters or less'), + systemPrompt: z + .string() + .min(1, 'System prompt is required') + .max(10000, 'System prompt must be 10,000 characters or less'), + modelId: z.string().min(1, 'Model ID is required'), + mcpServers: z + .array(MCPServerReferenceSchema) + .max(10, 'Maximum 10 MCP servers allowed'), + codeExecutionEnabled: z.boolean().optional().default(false), + isPublic: z.boolean().optional().default(false), + tags: z + .array(z.string().max(50, 'Each tag must be 50 characters or less')) + .max(10, 'Maximum 10 tags allowed') + .optional() + .default([]), + // version removed - using starCount instead + createdByEmail: z.string().email().optional(), +}); + +export const ShareAgentRequestSchema = z.object({ + isPublic: z.boolean({ required_error: 'isPublic must be a boolean value' }), +}); + +export const CloneAgentRequestSchema = z.object({ + sourceAgentId: z.string().uuid('Invalid source agent ID format'), + name: z + .string() + .max(100, 'Agent name must be 100 characters or less') + .optional(), +}); + +// Common validation schemas +export const AgentIdSchema = z.string().uuid('Invalid agent ID format'); +export const UserIdSchema = z.string().min(1, 'User ID is required'); +export const PaginationKeySchema = z.string().optional(); + +// Request body validation helper +export const RequestBodySchema = z.string().min(1, 'Request body is required'); + +// Export types for TypeScript - use common types from generative-ai-use-cases +// These are kept for validation purposes only +export type CreateAgentRequestValidation = z.infer< + typeof CreateAgentRequestSchema +>; +export type UpdateAgentRequestValidation = z.infer< + typeof UpdateAgentRequestSchema +>; +export type ShareAgentRequestValidation = z.infer< + typeof ShareAgentRequestSchema +>; +export type CloneAgentRequestValidation = z.infer< + typeof CloneAgentRequestSchema +>; +export type MCPServerConfigValidation = z.infer; diff --git a/packages/cdk/lib/construct/generic-agent-core.ts b/packages/cdk/lib/construct/generic-agent-core.ts index c32f798ee..09952ed62 100644 --- a/packages/cdk/lib/construct/generic-agent-core.ts +++ b/packages/cdk/lib/construct/generic-agent-core.ts @@ -19,6 +19,10 @@ import { import { BucketInfo } from 'generative-ai-use-cases'; import * as path from 'path'; import { LAMBDA_RUNTIME_NODEJS } from '../../consts'; +import { + loadMCPConfig, + extractSafeMCPConfig, +} from '../utils/mcp-config-loader'; export interface AgentCoreRuntimeConfig { name: string; @@ -59,6 +63,10 @@ export class GenericAgentCore extends Construct { autoDeleteObjects: true, }); + // Load MCP configuration and prepare environment variables + const mcpServers = loadMCPConfig(); + const safeMCPConfig = extractSafeMCPConfig(mcpServers); + // Default configuration for Generic AgentCore Runtime this.genericRuntimeConfig = { name: `GenericAgentCoreRuntime${env}`, @@ -69,6 +77,8 @@ export class GenericAgentCore extends Construct { serverProtocol: 'HTTP', environmentVariables: { FILE_BUCKET: this._fileBucket.bucketName, + MCP_SERVERS_CONFIG: JSON.stringify(mcpServers), + MCP_SERVERS_SAFE_CONFIG: safeMCPConfig, }, }; @@ -321,27 +331,12 @@ export class GenericAgentCore extends Construct { }) ); - // S3 File Bucket Access - this._fileBucket.grantReadWrite(role); + // Tools - role.addToPolicy( - new PolicyStatement({ - sid: 'S3BucketAccess', - effect: Effect.ALLOW, - actions: [ - 's3:GetObject', - 's3:PutObject', - 's3:ListBucket', - 's3:DeleteObject', - ], - resources: [ - this._fileBucket.bucketArn, - `${this._fileBucket.bucketArn}/*`, - ], - }) - ); + // S3 File Bucket Access (Write Only) + this._fileBucket.grantWrite(role); - // Tools + // CodeInterpreter role.addToPolicy( new PolicyStatement({ sid: 'Tools', diff --git a/packages/cdk/lib/construct/use-case-builder.ts b/packages/cdk/lib/construct/use-case-builder.ts index 3011d1a95..cd767082f 100644 --- a/packages/cdk/lib/construct/use-case-builder.ts +++ b/packages/cdk/lib/construct/use-case-builder.ts @@ -14,18 +14,20 @@ import { UserPool } from 'aws-cdk-lib/aws-cognito'; import * as ddb from 'aws-cdk-lib/aws-dynamodb'; import { LAMBDA_RUNTIME_NODEJS } from '../../consts'; import { ISecurityGroup, IVpc } from 'aws-cdk-lib/aws-ec2'; +import { Effect, PolicyStatement } from 'aws-cdk-lib/aws-iam'; export interface UseCaseBuilderProps { readonly userPool: UserPool; readonly api: RestApi; readonly vpc?: IVpc; readonly securityGroups?: ISecurityGroup[]; + readonly createGenericAgentCoreRuntime?: boolean; } export class UseCaseBuilder extends Construct { constructor(scope: Construct, id: string, props: UseCaseBuilderProps) { super(scope, id); - const { userPool, api } = props; + const { userPool, api, createGenericAgentCoreRuntime } = props; const useCaseIdIndexName = 'UseCaseIdIndexName'; const useCaseBuilderTable = new ddb.Table(this, 'UseCaseBuilderTable', { @@ -240,5 +242,64 @@ export class UseCaseBuilder extends Construct { new LambdaIntegration(updateRecentlyUsedUseCaseFunction), commonAuthorizerProps ); + + if (createGenericAgentCoreRuntime) { + // Add Agent Builder related APIs + const agentBuilderFunction = new NodejsFunction(this, 'AgentBuilder', { + ...commonProperty, + memorySize: 1024, + entry: './lambda/agentBuilder.ts', + environment: { + ...commonProperty.environment, + MODEL_REGION: process.env.MODEL_REGION || 'us-east-1', + USER_POOL_ID: userPool.userPoolId, + }, + bundling: { + nodeModules: ['@aws-sdk/client-bedrock-runtime'], + }, + }); + useCaseBuilderTable.grantReadWriteData(agentBuilderFunction); + + // Grant Bedrock permissions for agent testing + const bedrockPolicyForAgent = new PolicyStatement({ + effect: Effect.ALLOW, + resources: ['*'], + actions: ['bedrock:*', 'logs:*'], + }); + agentBuilderFunction.role?.addToPrincipalPolicy(bedrockPolicyForAgent); + + // Grant Cognito permissions for getting user information + const cognitoPolicyForAgent = new PolicyStatement({ + effect: Effect.ALLOW, + resources: [userPool.userPoolArn], + actions: ['cognito-idp:AdminGetUser'], + }); + agentBuilderFunction.role?.addToPrincipalPolicy(cognitoPolicyForAgent); + + // Agent Builder API endpoints + const agentsResource = api.root.addResource('agents'); + + // GET: /agents + agentsResource.addMethod( + 'GET', + new LambdaIntegration(agentBuilderFunction), + commonAuthorizerProps + ); + + // POST: /agents + agentsResource.addMethod( + 'POST', + new LambdaIntegration(agentBuilderFunction), + commonAuthorizerProps + ); + + // All agent sub-routes handled by proxy+ integration + const agentResource = agentsResource.addResource('{proxy+}'); + agentResource.addMethod( + 'ANY', + new LambdaIntegration(agentBuilderFunction), + commonAuthorizerProps + ); + } } } diff --git a/packages/cdk/lib/create-stacks.ts b/packages/cdk/lib/create-stacks.ts index 76fa94693..f92af0ec0 100644 --- a/packages/cdk/lib/create-stacks.ts +++ b/packages/cdk/lib/create-stacks.ts @@ -229,6 +229,7 @@ export const createStacks = (app: cdk.App, params: ProcessedStackInput) => { agents: agentStack?.agents, // Agent Core agentCoreStack: agentCoreStack || undefined, + createGenericAgentCoreRuntime: params.createGenericAgentCoreRuntime, // Video Generation videoBucketRegionMap, // Guardrail diff --git a/packages/cdk/lib/generative-ai-use-cases-stack.ts b/packages/cdk/lib/generative-ai-use-cases-stack.ts index 09110b298..5bbebb145 100644 --- a/packages/cdk/lib/generative-ai-use-cases-stack.ts +++ b/packages/cdk/lib/generative-ai-use-cases-stack.ts @@ -13,6 +13,7 @@ import { McpApi, AgentCore, } from './construct'; +import { loadMCPConfig, extractSafeMCPConfig } from './utils/mcp-config-loader'; import { CfnWebACLAssociation } from 'aws-cdk-lib/aws-wafv2'; import * as cognito from 'aws-cdk-lib/aws-cognito'; import { ICertificate } from 'aws-cdk-lib/aws-certificatemanager'; @@ -37,6 +38,7 @@ export interface GenerativeAiUseCasesStackProps extends StackProps { // Agent readonly agents?: Agent[]; // Agent Core + readonly createGenericAgentCoreRuntime?: boolean; readonly agentCoreStack?: AgentCoreStack; // Video Generation readonly videoBucketRegionMap: Record; @@ -324,6 +326,7 @@ export class GenerativeAiUseCasesStack extends Stack { api: api.api, vpc: props.vpc, securityGroups, + createGenericAgentCoreRuntime: props.createGenericAgentCoreRuntime, }); } @@ -479,6 +482,13 @@ export class GenerativeAiUseCasesStack extends Stack { value: JSON.stringify(params.agentCoreExternalRuntimes), }); + // Load MCP configuration and add as output + const mcpServers = loadMCPConfig(); + const safeMCPConfig = extractSafeMCPConfig(mcpServers); + new CfnOutput(this, 'McpServersConfig', { + value: safeMCPConfig, + }); + this.userPool = auth.userPool; this.userPoolClient = auth.client; diff --git a/packages/cdk/lib/utils/mcp-config-loader.ts b/packages/cdk/lib/utils/mcp-config-loader.ts new file mode 100644 index 000000000..9ed0b614c --- /dev/null +++ b/packages/cdk/lib/utils/mcp-config-loader.ts @@ -0,0 +1,64 @@ +import * as fs from 'fs'; +import * as path from 'path'; + +export interface MCPServerMetadata { + category?: string; + description?: string; +} + +export interface MCPServerConfig { + command?: string; + args?: string[]; + env?: Record; + url?: string; + metadata?: MCPServerMetadata; +} + +export interface MCPConfig { + mcpServers: Record; +} + +/** + * Load MCP configuration from mcp.json file + * Returns only the server names and metadata for frontend use + */ +export function loadMCPConfig(): Record { + try { + const mcpJsonPath = path.join( + __dirname, + '../../lambda-python/generic-agent-core-runtime/mcp.json' + ); + + if (!fs.existsSync(mcpJsonPath)) { + console.warn('mcp.json not found, using empty configuration'); + return {}; + } + + const mcpConfig: MCPConfig = JSON.parse( + fs.readFileSync(mcpJsonPath, 'utf8') + ); + return mcpConfig.mcpServers || {}; + } catch (error) { + console.error('Error loading MCP configuration:', error); + return {}; + } +} + +/** + * Extract safe MCP server information for frontend + * Excludes sensitive information like commands, args, and env variables + */ +export function extractSafeMCPConfig( + mcpServers: Record +): string { + const safeConfig: Record = {}; + + Object.keys(mcpServers).forEach((serverName) => { + const serverConfig = mcpServers[serverName]; + safeConfig[serverName] = { + metadata: serverConfig.metadata, + }; + }); + + return JSON.stringify(safeConfig); +} diff --git a/packages/cdk/package.json b/packages/cdk/package.json index 5dee1fb18..e4a3f333c 100644 --- a/packages/cdk/package.json +++ b/packages/cdk/package.json @@ -32,6 +32,7 @@ "@aws-sdk/client-bedrock-agentcore": "^3.755.0", "@aws-sdk/client-bedrock-agentcore-control": "^3.755.0", "@aws-sdk/client-bedrock-runtime": "^3.755.0", + "@aws-sdk/client-cognito-identity-provider": "^3.755.0", "@aws-sdk/client-dynamodb": "^3.755.0", "@aws-sdk/client-kendra": "^3.755.0", "@aws-sdk/client-lambda": "^3.755.0", @@ -53,6 +54,7 @@ "sanitize-html": "^2.13.0", "source-map-support": "^0.5.21", "upsert-slr": "^1.0.4", + "uuid": "13.0.0", "ws": "^8.18.0", "zod": "^3.24.1" } diff --git a/packages/cdk/test/__snapshots__/generative-ai-use-cases.test.ts.snap b/packages/cdk/test/__snapshots__/generative-ai-use-cases.test.ts.snap index a171c267a..8f100daec 100644 --- a/packages/cdk/test/__snapshots__/generative-ai-use-cases.test.ts.snap +++ b/packages/cdk/test/__snapshots__/generative-ai-use-cases.test.ts.snap @@ -4652,9 +4652,6 @@ exports[`GenerativeAiUseCases matches the snapshot (closed network mode) 4`] = ` }, { "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", "s3:DeleteObject*", "s3:PutObject", "s3:PutObjectLegalHold", @@ -4687,38 +4684,6 @@ exports[`GenerativeAiUseCases matches the snapshot (closed network mode) 4`] = ` }, ], }, - { - "Action": [ - "s3:GetObject", - "s3:PutObject", - "s3:ListBucket", - "s3:DeleteObject", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "GenericAgentCoreAgentCoreFileBucket0430DA42", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "GenericAgentCoreAgentCoreFileBucket0430DA42", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - "Sid": "S3BucketAccess", - }, { "Action": [ "bedrock-agentcore:CreateCodeInterpreter", @@ -4792,12 +4757,14 @@ exports[`GenerativeAiUseCases matches the snapshot (closed network mode) 4`] = ` "AgentCoreRuntimeName": "GenericAgentCoreRuntime", "CustomConfig": { "containerImageUri": { - "Fn::Sub": "123456890123.dkr.ecr.us-east-1.\${AWS::URLSuffix}/cdk-hnb659fds-container-assets-123456890123-us-east-1:64ba68f71e3d29f5b84d8e8d062e841cb600c436bb68a540d6fce32fded36c08", + "Fn::Sub": "123456890123.dkr.ecr.us-east-1.\${AWS::URLSuffix}/cdk-hnb659fds-container-assets-123456890123-us-east-1:fb512f99465e5b1c77271c13b48bd585132ba261918026874eeb804a8c651e95", }, "environmentVariables": { "FILE_BUCKET": { "Ref": "GenericAgentCoreAgentCoreFileBucket0430DA42", }, + "MCP_SERVERS_CONFIG": "{"time":{"command":"uvx","args":["mcp-server-time"],"metadata":{"category":"Utility","description":"Provides current time and date functionality"}},"aws-knowledge-mcp-server":{"command":"npx","args":["mcp-remote","https://knowledge-mcp.global.api.aws"],"metadata":{"category":"AWS","description":"AWS Knowledge Base MCP server for enterprise knowledge access"}},"awslabs.aws-documentation-mcp-server":{"command":"uvx","args":["awslabs.aws-documentation-mcp-server@latest"],"metadata":{"category":"AWS","description":"Access AWS documentation and guides"}},"awslabs.cdk-mcp-server":{"command":"uvx","args":["awslabs.cdk-mcp-server@latest"],"metadata":{"category":"AWS","description":"AWS CDK code generation and assistance"}},"awslabs.aws-diagram-mcp-server":{"command":"uvx","args":["awslabs.aws-diagram-mcp-server@latest"],"metadata":{"category":"AWS","description":"Generate AWS architecture diagrams"}},"awslabs.nova-canvas-mcp-server":{"command":"uvx","args":["awslabs.nova-canvas-mcp-server@latest"],"env":{"AWS_REGION":"us-east-1"},"metadata":{"category":"AI/ML","description":"Amazon Nova Canvas image generation"}},"tavily-search":{"command":"npx","args":["-y","mcp-remote","https://mcp.tavily.com/mcp/?tavilyApiKey="],"metadata":{"category":"Search","description":"Web search and research capabilities powered by Tavily"}}}", + "MCP_SERVERS_SAFE_CONFIG": "{"time":{"metadata":{"category":"Utility","description":"Provides current time and date functionality"}},"aws-knowledge-mcp-server":{"metadata":{"category":"AWS","description":"AWS Knowledge Base MCP server for enterprise knowledge access"}},"awslabs.aws-documentation-mcp-server":{"metadata":{"category":"AWS","description":"Access AWS documentation and guides"}},"awslabs.cdk-mcp-server":{"metadata":{"category":"AWS","description":"AWS CDK code generation and assistance"}},"awslabs.aws-diagram-mcp-server":{"metadata":{"category":"AWS","description":"Generate AWS architecture diagrams"}},"awslabs.nova-canvas-mcp-server":{"metadata":{"category":"AI/ML","description":"Amazon Nova Canvas image generation"}},"tavily-search":{"metadata":{"category":"Search","description":"Web search and research capabilities powered by Tavily"}}}", }, }, "NetworkMode": "PUBLIC", @@ -5147,6 +5114,9 @@ exports[`GenerativeAiUseCases matches the snapshot (closed network mode) 6`] = ` "McpEndpoint": { "Value": "", }, + "McpServersConfig": { + "Value": "{"time":{"metadata":{"category":"Utility","description":"Provides current time and date functionality"}},"aws-knowledge-mcp-server":{"metadata":{"category":"AWS","description":"AWS Knowledge Base MCP server for enterprise knowledge access"}},"awslabs.aws-documentation-mcp-server":{"metadata":{"category":"AWS","description":"Access AWS documentation and guides"}},"awslabs.cdk-mcp-server":{"metadata":{"category":"AWS","description":"AWS CDK code generation and assistance"}},"awslabs.aws-diagram-mcp-server":{"metadata":{"category":"AWS","description":"Generate AWS architecture diagrams"}},"awslabs.nova-canvas-mcp-server":{"metadata":{"category":"AI/ML","description":"Amazon Nova Canvas image generation"}},"tavily-search":{"metadata":{"category":"Search","description":"Web search and research capabilities powered by Tavily"}}}", + }, "ModelIds": { "Value": { "Fn::Join": [ @@ -5351,11 +5321,18 @@ exports[`GenerativeAiUseCases matches the snapshot (closed network mode) 6`] = ` "Type": "AWS::IAM::Role", "UpdateReplacePolicy": "Delete", }, - "APIApiDeployment3A5021231e35051e75cb37230e3365d2f55dbe60": { + "APIApiDeployment3A502123b4c182070f01d378b2e5ccab86f6b1c4": { "DeletionPolicy": "Delete", "DependsOn": [ "APIApiApi4XXDCF913C8", "APIApiApi5XX11B67643", + "APIApiagentsproxyANY44A4A08E", + "APIApiagentsproxyOPTIONS65C845F9", + "APIApiagentsproxy440913A2", + "APIApiagentsGETFE9F47E4", + "APIApiagentsOPTIONS064D2F41", + "APIApiagentsPOST28B48D5F", + "APIApiagents70FB378D", "APIApichatschatIdDELETE4578D41B", "APIApichatschatIdfeedbacksOPTIONS7AB34AA5", "APIApichatschatIdfeedbacksPOST3E9ACBEC", @@ -5507,7 +5484,7 @@ exports[`GenerativeAiUseCases matches the snapshot (closed network mode) 6`] = ` ], "Properties": { "DeploymentId": { - "Ref": "APIApiDeployment3A5021231e35051e75cb37230e3365d2f55dbe60", + "Ref": "APIApiDeployment3A502123b4c182070f01d378b2e5ccab86f6b1c4", }, "RestApiId": { "Ref": "APIApiFFA96F67", @@ -5600,7 +5577,7 @@ exports[`GenerativeAiUseCases matches the snapshot (closed network mode) 6`] = ` "Type": "AWS::ApiGateway::Method", "UpdateReplacePolicy": "Delete", }, - "APIApichatsE061702A": { + "APIApiagents70FB378D": { "DeletionPolicy": "Delete", "Properties": { "ParentId": { @@ -5609,7 +5586,7 @@ exports[`GenerativeAiUseCases matches the snapshot (closed network mode) 6`] = ` "RootResourceId", ], }, - "PathPart": "chats", + "PathPart": "agents", "RestApiId": { "Ref": "APIApiFFA96F67", }, @@ -5617,54 +5594,13 @@ exports[`GenerativeAiUseCases matches the snapshot (closed network mode) 6`] = ` "Type": "AWS::ApiGateway::Resource", "UpdateReplacePolicy": "Delete", }, - "APIApichatsGET40767623": { - "DeletionPolicy": "Delete", - "Properties": { - "AuthorizationType": "COGNITO_USER_POOLS", - "AuthorizerId": { - "Ref": "APIAuthorizer9DCC037B", - }, - "HttpMethod": "GET", - "Integration": { - "IntegrationHttpMethod": "POST", - "Type": "AWS_PROXY", - "Uri": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":apigateway:us-east-1:lambda:path/2015-03-31/functions/", - { - "Fn::GetAtt": [ - "APIListChats12807275", - "Arn", - ], - }, - "/invocations", - ], - ], - }, - }, - "ResourceId": { - "Ref": "APIApichatsE061702A", - }, - "RestApiId": { - "Ref": "APIApiFFA96F67", - }, - }, - "Type": "AWS::ApiGateway::Method", - "UpdateReplacePolicy": "Delete", - }, - "APIApichatsGETApiPermissionGenerativeAiUseCasesStackAPIApi89219E17GETchats3464EF3A": { + "APIApiagentsGETApiPermissionGenerativeAiUseCasesStackAPIApi89219E17GETagents5FFFAEBF": { "DeletionPolicy": "Delete", "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { "Fn::GetAtt": [ - "APIListChats12807275", + "UseCaseBuilderAgentBuilderDAB96216", "Arn", ], }, @@ -5685,7 +5621,7 @@ exports[`GenerativeAiUseCases matches the snapshot (closed network mode) 6`] = ` { "Ref": "APIApiDeploymentStageapiCD55D117", }, - "/GET/chats", + "/GET/agents", ], ], }, @@ -5693,13 +5629,13 @@ exports[`GenerativeAiUseCases matches the snapshot (closed network mode) 6`] = ` "Type": "AWS::Lambda::Permission", "UpdateReplacePolicy": "Delete", }, - "APIApichatsGETApiPermissionTestGenerativeAiUseCasesStackAPIApi89219E17GETchatsE1DE3C5A": { + "APIApiagentsGETApiPermissionTestGenerativeAiUseCasesStackAPIApi89219E17GETagents2809AEEE": { "DeletionPolicy": "Delete", "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { "Fn::GetAtt": [ - "APIListChats12807275", + "UseCaseBuilderAgentBuilderDAB96216", "Arn", ], }, @@ -5716,7 +5652,7 @@ exports[`GenerativeAiUseCases matches the snapshot (closed network mode) 6`] = ` { "Ref": "APIApiFFA96F67", }, - "/test-invoke-stage/GET/chats", + "/test-invoke-stage/GET/agents", ], ], }, @@ -5724,7 +5660,48 @@ exports[`GenerativeAiUseCases matches the snapshot (closed network mode) 6`] = ` "Type": "AWS::Lambda::Permission", "UpdateReplacePolicy": "Delete", }, - "APIApichatsOPTIONSDFF708CB": { + "APIApiagentsGETFE9F47E4": { + "DeletionPolicy": "Delete", + "Properties": { + "AuthorizationType": "COGNITO_USER_POOLS", + "AuthorizerId": { + "Ref": "UseCaseBuilderAuthorizer8C07733E", + }, + "HttpMethod": "GET", + "Integration": { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":apigateway:us-east-1:lambda:path/2015-03-31/functions/", + { + "Fn::GetAtt": [ + "UseCaseBuilderAgentBuilderDAB96216", + "Arn", + ], + }, + "/invocations", + ], + ], + }, + }, + "ResourceId": { + "Ref": "APIApiagents70FB378D", + }, + "RestApiId": { + "Ref": "APIApiFFA96F67", + }, + }, + "Type": "AWS::ApiGateway::Method", + "UpdateReplacePolicy": "Delete", + }, + "APIApiagentsOPTIONS064D2F41": { "DeletionPolicy": "Delete", "Properties": { "ApiKeyRequired": false, @@ -5757,7 +5734,7 @@ exports[`GenerativeAiUseCases matches the snapshot (closed network mode) 6`] = ` }, ], "ResourceId": { - "Ref": "APIApichatsE061702A", + "Ref": "APIApiagents70FB378D", }, "RestApiId": { "Ref": "APIApiFFA96F67", @@ -5766,78 +5743,12 @@ exports[`GenerativeAiUseCases matches the snapshot (closed network mode) 6`] = ` "Type": "AWS::ApiGateway::Method", "UpdateReplacePolicy": "Delete", }, - "APIApichatsPOSTApiPermissionGenerativeAiUseCasesStackAPIApi89219E17POSTchats6FB5CEC9": { - "DeletionPolicy": "Delete", - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "APICreateChatE07AFAF4", - "Arn", - ], - }, - "Principal": "apigateway.amazonaws.com", - "SourceArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":execute-api:us-east-1:123456890123:", - { - "Ref": "APIApiFFA96F67", - }, - "/", - { - "Ref": "APIApiDeploymentStageapiCD55D117", - }, - "/POST/chats", - ], - ], - }, - }, - "Type": "AWS::Lambda::Permission", - "UpdateReplacePolicy": "Delete", - }, - "APIApichatsPOSTApiPermissionTestGenerativeAiUseCasesStackAPIApi89219E17POSTchatsE0E202B2": { - "DeletionPolicy": "Delete", - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "APICreateChatE07AFAF4", - "Arn", - ], - }, - "Principal": "apigateway.amazonaws.com", - "SourceArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":execute-api:us-east-1:123456890123:", - { - "Ref": "APIApiFFA96F67", - }, - "/test-invoke-stage/POST/chats", - ], - ], - }, - }, - "Type": "AWS::Lambda::Permission", - "UpdateReplacePolicy": "Delete", - }, - "APIApichatsPOSTF32299AA": { + "APIApiagentsPOST28B48D5F": { "DeletionPolicy": "Delete", "Properties": { "AuthorizationType": "COGNITO_USER_POOLS", "AuthorizerId": { - "Ref": "APIAuthorizer9DCC037B", + "Ref": "UseCaseBuilderAuthorizer8C07733E", }, "HttpMethod": "POST", "Integration": { @@ -5854,7 +5765,7 @@ exports[`GenerativeAiUseCases matches the snapshot (closed network mode) 6`] = ` ":apigateway:us-east-1:lambda:path/2015-03-31/functions/", { "Fn::GetAtt": [ - "APICreateChatE07AFAF4", + "UseCaseBuilderAgentBuilderDAB96216", "Arn", ], }, @@ -5864,48 +5775,7 @@ exports[`GenerativeAiUseCases matches the snapshot (closed network mode) 6`] = ` }, }, "ResourceId": { - "Ref": "APIApichatsE061702A", - }, - "RestApiId": { - "Ref": "APIApiFFA96F67", - }, - }, - "Type": "AWS::ApiGateway::Method", - "UpdateReplacePolicy": "Delete", - }, - "APIApichatschatIdDELETE4578D41B": { - "DeletionPolicy": "Delete", - "Properties": { - "AuthorizationType": "COGNITO_USER_POOLS", - "AuthorizerId": { - "Ref": "APIAuthorizer9DCC037B", - }, - "HttpMethod": "DELETE", - "Integration": { - "IntegrationHttpMethod": "POST", - "Type": "AWS_PROXY", - "Uri": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":apigateway:us-east-1:lambda:path/2015-03-31/functions/", - { - "Fn::GetAtt": [ - "APIDeleteChat1517278C", - "Arn", - ], - }, - "/invocations", - ], - ], - }, - }, - "ResourceId": { - "Ref": "APIApichatschatIdE67019A6", + "Ref": "APIApiagents70FB378D", }, "RestApiId": { "Ref": "APIApiFFA96F67", @@ -5914,13 +5784,13 @@ exports[`GenerativeAiUseCases matches the snapshot (closed network mode) 6`] = ` "Type": "AWS::ApiGateway::Method", "UpdateReplacePolicy": "Delete", }, - "APIApichatschatIdDELETEApiPermissionGenerativeAiUseCasesStackAPIApi89219E17DELETEchatschatIdCBAD7732": { + "APIApiagentsPOSTApiPermissionGenerativeAiUseCasesStackAPIApi89219E17POSTagentsBB862BB1": { "DeletionPolicy": "Delete", "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { "Fn::GetAtt": [ - "APIDeleteChat1517278C", + "UseCaseBuilderAgentBuilderDAB96216", "Arn", ], }, @@ -5941,7 +5811,7 @@ exports[`GenerativeAiUseCases matches the snapshot (closed network mode) 6`] = ` { "Ref": "APIApiDeploymentStageapiCD55D117", }, - "/DELETE/chats/*", + "/POST/agents", ], ], }, @@ -5949,13 +5819,13 @@ exports[`GenerativeAiUseCases matches the snapshot (closed network mode) 6`] = ` "Type": "AWS::Lambda::Permission", "UpdateReplacePolicy": "Delete", }, - "APIApichatschatIdDELETEApiPermissionTestGenerativeAiUseCasesStackAPIApi89219E17DELETEchatschatId6DBAD3C3": { + "APIApiagentsPOSTApiPermissionTestGenerativeAiUseCasesStackAPIApi89219E17POSTagents9F230B2C": { "DeletionPolicy": "Delete", "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { "Fn::GetAtt": [ - "APIDeleteChat1517278C", + "UseCaseBuilderAgentBuilderDAB96216", "Arn", ], }, @@ -5972,7 +5842,7 @@ exports[`GenerativeAiUseCases matches the snapshot (closed network mode) 6`] = ` { "Ref": "APIApiFFA96F67", }, - "/test-invoke-stage/DELETE/chats/*", + "/test-invoke-stage/POST/agents", ], ], }, @@ -5980,13 +5850,13 @@ exports[`GenerativeAiUseCases matches the snapshot (closed network mode) 6`] = ` "Type": "AWS::Lambda::Permission", "UpdateReplacePolicy": "Delete", }, - "APIApichatschatIdE67019A6": { + "APIApiagentsproxy440913A2": { "DeletionPolicy": "Delete", "Properties": { "ParentId": { - "Ref": "APIApichatsE061702A", + "Ref": "APIApiagents70FB378D", }, - "PathPart": "{chatId}", + "PathPart": "{proxy+}", "RestApiId": { "Ref": "APIApiFFA96F67", }, @@ -5994,14 +5864,14 @@ exports[`GenerativeAiUseCases matches the snapshot (closed network mode) 6`] = ` "Type": "AWS::ApiGateway::Resource", "UpdateReplacePolicy": "Delete", }, - "APIApichatschatIdGET152C9123": { + "APIApiagentsproxyANY44A4A08E": { "DeletionPolicy": "Delete", "Properties": { "AuthorizationType": "COGNITO_USER_POOLS", "AuthorizerId": { - "Ref": "APIAuthorizer9DCC037B", + "Ref": "UseCaseBuilderAuthorizer8C07733E", }, - "HttpMethod": "GET", + "HttpMethod": "ANY", "Integration": { "IntegrationHttpMethod": "POST", "Type": "AWS_PROXY", @@ -6016,7 +5886,7 @@ exports[`GenerativeAiUseCases matches the snapshot (closed network mode) 6`] = ` ":apigateway:us-east-1:lambda:path/2015-03-31/functions/", { "Fn::GetAtt": [ - "APIFindChatbyId74476825", + "UseCaseBuilderAgentBuilderDAB96216", "Arn", ], }, @@ -6026,7 +5896,7 @@ exports[`GenerativeAiUseCases matches the snapshot (closed network mode) 6`] = ` }, }, "ResourceId": { - "Ref": "APIApichatschatIdE67019A6", + "Ref": "APIApiagentsproxy440913A2", }, "RestApiId": { "Ref": "APIApiFFA96F67", @@ -6035,13 +5905,13 @@ exports[`GenerativeAiUseCases matches the snapshot (closed network mode) 6`] = ` "Type": "AWS::ApiGateway::Method", "UpdateReplacePolicy": "Delete", }, - "APIApichatschatIdGETApiPermissionGenerativeAiUseCasesStackAPIApi89219E17GETchatschatIdC82A138E": { + "APIApiagentsproxyANYApiPermissionGenerativeAiUseCasesStackAPIApi89219E17ANYagentsproxyB1492E94": { "DeletionPolicy": "Delete", "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { "Fn::GetAtt": [ - "APIFindChatbyId74476825", + "UseCaseBuilderAgentBuilderDAB96216", "Arn", ], }, @@ -6062,7 +5932,7 @@ exports[`GenerativeAiUseCases matches the snapshot (closed network mode) 6`] = ` { "Ref": "APIApiDeploymentStageapiCD55D117", }, - "/GET/chats/*", + "/*/agents/*", ], ], }, @@ -6070,13 +5940,13 @@ exports[`GenerativeAiUseCases matches the snapshot (closed network mode) 6`] = ` "Type": "AWS::Lambda::Permission", "UpdateReplacePolicy": "Delete", }, - "APIApichatschatIdGETApiPermissionTestGenerativeAiUseCasesStackAPIApi89219E17GETchatschatIdCA130D83": { + "APIApiagentsproxyANYApiPermissionTestGenerativeAiUseCasesStackAPIApi89219E17ANYagentsproxy619296F3": { "DeletionPolicy": "Delete", "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { "Fn::GetAtt": [ - "APIFindChatbyId74476825", + "UseCaseBuilderAgentBuilderDAB96216", "Arn", ], }, @@ -6093,7 +5963,7 @@ exports[`GenerativeAiUseCases matches the snapshot (closed network mode) 6`] = ` { "Ref": "APIApiFFA96F67", }, - "/test-invoke-stage/GET/chats/*", + "/test-invoke-stage/*/agents/*", ], ], }, @@ -6101,7 +5971,550 @@ exports[`GenerativeAiUseCases matches the snapshot (closed network mode) 6`] = ` "Type": "AWS::Lambda::Permission", "UpdateReplacePolicy": "Delete", }, - "APIApichatschatIdOPTIONS4498230E": { + "APIApiagentsproxyOPTIONS65C845F9": { + "DeletionPolicy": "Delete", + "Properties": { + "ApiKeyRequired": false, + "AuthorizationType": "NONE", + "HttpMethod": "OPTIONS", + "Integration": { + "IntegrationResponses": [ + { + "ResponseParameters": { + "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent'", + "method.response.header.Access-Control-Allow-Methods": "'OPTIONS,GET,PUT,POST,DELETE,PATCH,HEAD'", + "method.response.header.Access-Control-Allow-Origin": "'*'", + }, + "StatusCode": "204", + }, + ], + "RequestTemplates": { + "application/json": "{ statusCode: 200 }", + }, + "Type": "MOCK", + }, + "MethodResponses": [ + { + "ResponseParameters": { + "method.response.header.Access-Control-Allow-Headers": true, + "method.response.header.Access-Control-Allow-Methods": true, + "method.response.header.Access-Control-Allow-Origin": true, + }, + "StatusCode": "204", + }, + ], + "ResourceId": { + "Ref": "APIApiagentsproxy440913A2", + }, + "RestApiId": { + "Ref": "APIApiFFA96F67", + }, + }, + "Type": "AWS::ApiGateway::Method", + "UpdateReplacePolicy": "Delete", + }, + "APIApichatsE061702A": { + "DeletionPolicy": "Delete", + "Properties": { + "ParentId": { + "Fn::GetAtt": [ + "APIApiFFA96F67", + "RootResourceId", + ], + }, + "PathPart": "chats", + "RestApiId": { + "Ref": "APIApiFFA96F67", + }, + }, + "Type": "AWS::ApiGateway::Resource", + "UpdateReplacePolicy": "Delete", + }, + "APIApichatsGET40767623": { + "DeletionPolicy": "Delete", + "Properties": { + "AuthorizationType": "COGNITO_USER_POOLS", + "AuthorizerId": { + "Ref": "APIAuthorizer9DCC037B", + }, + "HttpMethod": "GET", + "Integration": { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":apigateway:us-east-1:lambda:path/2015-03-31/functions/", + { + "Fn::GetAtt": [ + "APIListChats12807275", + "Arn", + ], + }, + "/invocations", + ], + ], + }, + }, + "ResourceId": { + "Ref": "APIApichatsE061702A", + }, + "RestApiId": { + "Ref": "APIApiFFA96F67", + }, + }, + "Type": "AWS::ApiGateway::Method", + "UpdateReplacePolicy": "Delete", + }, + "APIApichatsGETApiPermissionGenerativeAiUseCasesStackAPIApi89219E17GETchats3464EF3A": { + "DeletionPolicy": "Delete", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "APIListChats12807275", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":execute-api:us-east-1:123456890123:", + { + "Ref": "APIApiFFA96F67", + }, + "/", + { + "Ref": "APIApiDeploymentStageapiCD55D117", + }, + "/GET/chats", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + "UpdateReplacePolicy": "Delete", + }, + "APIApichatsGETApiPermissionTestGenerativeAiUseCasesStackAPIApi89219E17GETchatsE1DE3C5A": { + "DeletionPolicy": "Delete", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "APIListChats12807275", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":execute-api:us-east-1:123456890123:", + { + "Ref": "APIApiFFA96F67", + }, + "/test-invoke-stage/GET/chats", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + "UpdateReplacePolicy": "Delete", + }, + "APIApichatsOPTIONSDFF708CB": { + "DeletionPolicy": "Delete", + "Properties": { + "ApiKeyRequired": false, + "AuthorizationType": "NONE", + "HttpMethod": "OPTIONS", + "Integration": { + "IntegrationResponses": [ + { + "ResponseParameters": { + "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent'", + "method.response.header.Access-Control-Allow-Methods": "'OPTIONS,GET,PUT,POST,DELETE,PATCH,HEAD'", + "method.response.header.Access-Control-Allow-Origin": "'*'", + }, + "StatusCode": "204", + }, + ], + "RequestTemplates": { + "application/json": "{ statusCode: 200 }", + }, + "Type": "MOCK", + }, + "MethodResponses": [ + { + "ResponseParameters": { + "method.response.header.Access-Control-Allow-Headers": true, + "method.response.header.Access-Control-Allow-Methods": true, + "method.response.header.Access-Control-Allow-Origin": true, + }, + "StatusCode": "204", + }, + ], + "ResourceId": { + "Ref": "APIApichatsE061702A", + }, + "RestApiId": { + "Ref": "APIApiFFA96F67", + }, + }, + "Type": "AWS::ApiGateway::Method", + "UpdateReplacePolicy": "Delete", + }, + "APIApichatsPOSTApiPermissionGenerativeAiUseCasesStackAPIApi89219E17POSTchats6FB5CEC9": { + "DeletionPolicy": "Delete", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "APICreateChatE07AFAF4", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":execute-api:us-east-1:123456890123:", + { + "Ref": "APIApiFFA96F67", + }, + "/", + { + "Ref": "APIApiDeploymentStageapiCD55D117", + }, + "/POST/chats", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + "UpdateReplacePolicy": "Delete", + }, + "APIApichatsPOSTApiPermissionTestGenerativeAiUseCasesStackAPIApi89219E17POSTchatsE0E202B2": { + "DeletionPolicy": "Delete", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "APICreateChatE07AFAF4", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":execute-api:us-east-1:123456890123:", + { + "Ref": "APIApiFFA96F67", + }, + "/test-invoke-stage/POST/chats", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + "UpdateReplacePolicy": "Delete", + }, + "APIApichatsPOSTF32299AA": { + "DeletionPolicy": "Delete", + "Properties": { + "AuthorizationType": "COGNITO_USER_POOLS", + "AuthorizerId": { + "Ref": "APIAuthorizer9DCC037B", + }, + "HttpMethod": "POST", + "Integration": { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":apigateway:us-east-1:lambda:path/2015-03-31/functions/", + { + "Fn::GetAtt": [ + "APICreateChatE07AFAF4", + "Arn", + ], + }, + "/invocations", + ], + ], + }, + }, + "ResourceId": { + "Ref": "APIApichatsE061702A", + }, + "RestApiId": { + "Ref": "APIApiFFA96F67", + }, + }, + "Type": "AWS::ApiGateway::Method", + "UpdateReplacePolicy": "Delete", + }, + "APIApichatschatIdDELETE4578D41B": { + "DeletionPolicy": "Delete", + "Properties": { + "AuthorizationType": "COGNITO_USER_POOLS", + "AuthorizerId": { + "Ref": "APIAuthorizer9DCC037B", + }, + "HttpMethod": "DELETE", + "Integration": { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":apigateway:us-east-1:lambda:path/2015-03-31/functions/", + { + "Fn::GetAtt": [ + "APIDeleteChat1517278C", + "Arn", + ], + }, + "/invocations", + ], + ], + }, + }, + "ResourceId": { + "Ref": "APIApichatschatIdE67019A6", + }, + "RestApiId": { + "Ref": "APIApiFFA96F67", + }, + }, + "Type": "AWS::ApiGateway::Method", + "UpdateReplacePolicy": "Delete", + }, + "APIApichatschatIdDELETEApiPermissionGenerativeAiUseCasesStackAPIApi89219E17DELETEchatschatIdCBAD7732": { + "DeletionPolicy": "Delete", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "APIDeleteChat1517278C", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":execute-api:us-east-1:123456890123:", + { + "Ref": "APIApiFFA96F67", + }, + "/", + { + "Ref": "APIApiDeploymentStageapiCD55D117", + }, + "/DELETE/chats/*", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + "UpdateReplacePolicy": "Delete", + }, + "APIApichatschatIdDELETEApiPermissionTestGenerativeAiUseCasesStackAPIApi89219E17DELETEchatschatId6DBAD3C3": { + "DeletionPolicy": "Delete", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "APIDeleteChat1517278C", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":execute-api:us-east-1:123456890123:", + { + "Ref": "APIApiFFA96F67", + }, + "/test-invoke-stage/DELETE/chats/*", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + "UpdateReplacePolicy": "Delete", + }, + "APIApichatschatIdE67019A6": { + "DeletionPolicy": "Delete", + "Properties": { + "ParentId": { + "Ref": "APIApichatsE061702A", + }, + "PathPart": "{chatId}", + "RestApiId": { + "Ref": "APIApiFFA96F67", + }, + }, + "Type": "AWS::ApiGateway::Resource", + "UpdateReplacePolicy": "Delete", + }, + "APIApichatschatIdGET152C9123": { + "DeletionPolicy": "Delete", + "Properties": { + "AuthorizationType": "COGNITO_USER_POOLS", + "AuthorizerId": { + "Ref": "APIAuthorizer9DCC037B", + }, + "HttpMethod": "GET", + "Integration": { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":apigateway:us-east-1:lambda:path/2015-03-31/functions/", + { + "Fn::GetAtt": [ + "APIFindChatbyId74476825", + "Arn", + ], + }, + "/invocations", + ], + ], + }, + }, + "ResourceId": { + "Ref": "APIApichatschatIdE67019A6", + }, + "RestApiId": { + "Ref": "APIApiFFA96F67", + }, + }, + "Type": "AWS::ApiGateway::Method", + "UpdateReplacePolicy": "Delete", + }, + "APIApichatschatIdGETApiPermissionGenerativeAiUseCasesStackAPIApi89219E17GETchatschatIdC82A138E": { + "DeletionPolicy": "Delete", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "APIFindChatbyId74476825", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":execute-api:us-east-1:123456890123:", + { + "Ref": "APIApiFFA96F67", + }, + "/", + { + "Ref": "APIApiDeploymentStageapiCD55D117", + }, + "/GET/chats/*", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + "UpdateReplacePolicy": "Delete", + }, + "APIApichatschatIdGETApiPermissionTestGenerativeAiUseCasesStackAPIApi89219E17GETchatschatIdCA130D83": { + "DeletionPolicy": "Delete", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "APIFindChatbyId74476825", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":execute-api:us-east-1:123456890123:", + { + "Ref": "APIApiFFA96F67", + }, + "/test-invoke-stage/GET/chats/*", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + "UpdateReplacePolicy": "Delete", + }, + "APIApichatschatIdOPTIONS4498230E": { "DeletionPolicy": "Delete", "Properties": { "ApiKeyRequired": false, @@ -21590,76 +22003,266 @@ exports[`GenerativeAiUseCases matches the snapshot (closed network mode) 6`] = ` "Type": "AWS::IAM::Role", "UpdateReplacePolicy": "Delete", }, - "TranscribeTranscriptBucketAutoDeleteObjectsCustomResourceE45B855F": { + "TranscribeTranscriptBucketAutoDeleteObjectsCustomResourceE45B855F": { + "DeletionPolicy": "Delete", + "DependsOn": [ + "TranscribeTranscriptBucketPolicy80F67AB4", + ], + "Properties": { + "BucketName": { + "Ref": "TranscribeTranscriptBucketE67CAF92", + }, + "ServiceToken": { + "Fn::GetAtt": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderHandler9D90184F", + "Arn", + ], + }, + }, + "Type": "Custom::S3AutoDeleteObjects", + "UpdateReplacePolicy": "Delete", + }, + "TranscribeTranscriptBucketE67CAF92": { + "DeletionPolicy": "Delete", + "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "SSEAlgorithm": "AES256", + }, + }, + ], + }, + "PublicAccessBlockConfiguration": { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true, + }, + "Tags": [ + { + "Key": "aws-cdk:auto-delete-objects", + "Value": "true", + }, + ], + }, + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Delete", + }, + "TranscribeTranscriptBucketPolicy80F67AB4": { + "DeletionPolicy": "Delete", + "Properties": { + "Bucket": { + "Ref": "TranscribeTranscriptBucketE67CAF92", + }, + "PolicyDocument": { + "Statement": [ + { + "Action": "s3:*", + "Condition": { + "Bool": { + "aws:SecureTransport": "false", + }, + }, + "Effect": "Deny", + "Principal": { + "AWS": "*", + }, + "Resource": [ + { + "Fn::GetAtt": [ + "TranscribeTranscriptBucketE67CAF92", + "Arn", + ], + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "TranscribeTranscriptBucketE67CAF92", + "Arn", + ], + }, + "/*", + ], + ], + }, + ], + }, + { + "Action": [ + "s3:PutBucketPolicy", + "s3:GetBucket*", + "s3:List*", + "s3:DeleteObject*", + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092", + "Arn", + ], + }, + }, + "Resource": [ + { + "Fn::GetAtt": [ + "TranscribeTranscriptBucketE67CAF92", + "Arn", + ], + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "TranscribeTranscriptBucketE67CAF92", + "Arn", + ], + }, + "/*", + ], + ], + }, + ], + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::S3::BucketPolicy", + "UpdateReplacePolicy": "Delete", + }, + "UseCaseBuilderAgentBuilderDAB96216": { + "DeletionPolicy": "Delete", + "DependsOn": [ + "UseCaseBuilderAgentBuilderServiceRoleDefaultPolicy55D3FBBC", + "UseCaseBuilderAgentBuilderServiceRole9C6C40B9", + ], + "Properties": { + "Code": { + "S3Bucket": "cdk-hnb659fds-assets-123456890123-us-east-1", + "S3Key": "HASH-REPLACED.zip", + }, + "Environment": { + "Variables": { + "MODEL_REGION": "us-east-1", + "USECASE_ID_INDEX_NAME": "UseCaseIdIndexName", + "USECASE_TABLE_NAME": { + "Ref": "UseCaseBuilderUseCaseBuilderTable449740F3", + }, + "USER_POOL_ID": { + "Ref": "AuthUserPool8115E87F", + }, + }, + }, + "Handler": "index.handler", + "MemorySize": 1024, + "Role": { + "Fn::GetAtt": [ + "UseCaseBuilderAgentBuilderServiceRole9C6C40B9", + "Arn", + ], + }, + "Runtime": "nodejs22.x", + "Timeout": 900, + "VpcConfig": { + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "LambdaSeurityGroup14B5161A", + "GroupId", + ], + }, + ], + "SubnetIds": [ + { + "Fn::ImportValue": "ClosedNetworkStack:ExportsOutputRefClosedVpcisolatedSubnet1Subnet2EF6D3F36370B312", + }, + { + "Fn::ImportValue": "ClosedNetworkStack:ExportsOutputRefClosedVpcisolatedSubnet2SubnetB169C8D3D828FC40", + }, + ], + }, + }, + "Type": "AWS::Lambda::Function", + "UpdateReplacePolicy": "Delete", + }, + "UseCaseBuilderAgentBuilderServiceRole9C6C40B9": { + "DeletionPolicy": "Delete", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + ], + ], + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole", + ], + ], + }, + ], + }, + "Type": "AWS::IAM::Role", + "UpdateReplacePolicy": "Delete", + }, + "UseCaseBuilderAgentBuilderServiceRoleDefaultPolicy55D3FBBC": { "DeletionPolicy": "Delete", - "DependsOn": [ - "TranscribeTranscriptBucketPolicy80F67AB4", - ], "Properties": { - "BucketName": { - "Ref": "TranscribeTranscriptBucketE67CAF92", - }, - "ServiceToken": { - "Fn::GetAtt": [ - "CustomS3AutoDeleteObjectsCustomResourceProviderHandler9D90184F", - "Arn", - ], - }, - }, - "Type": "Custom::S3AutoDeleteObjects", - "UpdateReplacePolicy": "Delete", - }, - "TranscribeTranscriptBucketE67CAF92": { - "DeletionPolicy": "Delete", - "Properties": { - "BucketEncryption": { - "ServerSideEncryptionConfiguration": [ - { - "ServerSideEncryptionByDefault": { - "SSEAlgorithm": "AES256", - }, - }, - ], - }, - "PublicAccessBlockConfiguration": { - "BlockPublicAcls": true, - "BlockPublicPolicy": true, - "IgnorePublicAcls": true, - "RestrictPublicBuckets": true, - }, - "Tags": [ - { - "Key": "aws-cdk:auto-delete-objects", - "Value": "true", - }, - ], - }, - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Delete", - }, - "TranscribeTranscriptBucketPolicy80F67AB4": { - "DeletionPolicy": "Delete", - "Properties": { - "Bucket": { - "Ref": "TranscribeTranscriptBucketE67CAF92", - }, "PolicyDocument": { "Statement": [ { - "Action": "s3:*", - "Condition": { - "Bool": { - "aws:SecureTransport": "false", - }, - }, - "Effect": "Deny", - "Principal": { - "AWS": "*", - }, + "Action": [ + "dynamodb:BatchGetItem", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator", + "dynamodb:Query", + "dynamodb:GetItem", + "dynamodb:Scan", + "dynamodb:ConditionCheckItem", + "dynamodb:BatchWriteItem", + "dynamodb:PutItem", + "dynamodb:UpdateItem", + "dynamodb:DeleteItem", + "dynamodb:DescribeTable", + ], + "Effect": "Allow", "Resource": [ { "Fn::GetAtt": [ - "TranscribeTranscriptBucketE67CAF92", + "UseCaseBuilderUseCaseBuilderTable449740F3", "Arn", ], }, @@ -21669,11 +22272,11 @@ exports[`GenerativeAiUseCases matches the snapshot (closed network mode) 6`] = ` [ { "Fn::GetAtt": [ - "TranscribeTranscriptBucketE67CAF92", + "UseCaseBuilderUseCaseBuilderTable449740F3", "Arn", ], }, - "/*", + "/index/*", ], ], }, @@ -21681,48 +22284,33 @@ exports[`GenerativeAiUseCases matches the snapshot (closed network mode) 6`] = ` }, { "Action": [ - "s3:PutBucketPolicy", - "s3:GetBucket*", - "s3:List*", - "s3:DeleteObject*", + "bedrock:*", + "logs:*", ], "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::GetAtt": [ - "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092", - "Arn", - ], - }, + "Resource": "*", + }, + { + "Action": "cognito-idp:AdminGetUser", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "AuthUserPool8115E87F", + "Arn", + ], }, - "Resource": [ - { - "Fn::GetAtt": [ - "TranscribeTranscriptBucketE67CAF92", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "TranscribeTranscriptBucketE67CAF92", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], }, ], "Version": "2012-10-17", }, + "PolicyName": "UseCaseBuilderAgentBuilderServiceRoleDefaultPolicy55D3FBBC", + "Roles": [ + { + "Ref": "UseCaseBuilderAgentBuilderServiceRole9C6C40B9", + }, + ], }, - "Type": "AWS::S3::BucketPolicy", + "Type": "AWS::IAM::Policy", "UpdateReplacePolicy": "Delete", }, "UseCaseBuilderAuthorizer8C07733E": { @@ -25845,9 +26433,6 @@ exports[`GenerativeAiUseCases matches the snapshot 4`] = ` }, { "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", "s3:DeleteObject*", "s3:PutObject", "s3:PutObjectLegalHold", @@ -25880,38 +26465,6 @@ exports[`GenerativeAiUseCases matches the snapshot 4`] = ` }, ], }, - { - "Action": [ - "s3:GetObject", - "s3:PutObject", - "s3:ListBucket", - "s3:DeleteObject", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "GenericAgentCoreAgentCoreFileBucket0430DA42", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "GenericAgentCoreAgentCoreFileBucket0430DA42", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - "Sid": "S3BucketAccess", - }, { "Action": [ "bedrock-agentcore:CreateCodeInterpreter", @@ -25985,12 +26538,14 @@ exports[`GenerativeAiUseCases matches the snapshot 4`] = ` "AgentCoreRuntimeName": "GenericAgentCoreRuntime", "CustomConfig": { "containerImageUri": { - "Fn::Sub": "123456890123.dkr.ecr.us-east-1.\${AWS::URLSuffix}/cdk-hnb659fds-container-assets-123456890123-us-east-1:64ba68f71e3d29f5b84d8e8d062e841cb600c436bb68a540d6fce32fded36c08", + "Fn::Sub": "123456890123.dkr.ecr.us-east-1.\${AWS::URLSuffix}/cdk-hnb659fds-container-assets-123456890123-us-east-1:fb512f99465e5b1c77271c13b48bd585132ba261918026874eeb804a8c651e95", }, "environmentVariables": { "FILE_BUCKET": { "Ref": "GenericAgentCoreAgentCoreFileBucket0430DA42", }, + "MCP_SERVERS_CONFIG": "{"time":{"command":"uvx","args":["mcp-server-time"],"metadata":{"category":"Utility","description":"Provides current time and date functionality"}},"aws-knowledge-mcp-server":{"command":"npx","args":["mcp-remote","https://knowledge-mcp.global.api.aws"],"metadata":{"category":"AWS","description":"AWS Knowledge Base MCP server for enterprise knowledge access"}},"awslabs.aws-documentation-mcp-server":{"command":"uvx","args":["awslabs.aws-documentation-mcp-server@latest"],"metadata":{"category":"AWS","description":"Access AWS documentation and guides"}},"awslabs.cdk-mcp-server":{"command":"uvx","args":["awslabs.cdk-mcp-server@latest"],"metadata":{"category":"AWS","description":"AWS CDK code generation and assistance"}},"awslabs.aws-diagram-mcp-server":{"command":"uvx","args":["awslabs.aws-diagram-mcp-server@latest"],"metadata":{"category":"AWS","description":"Generate AWS architecture diagrams"}},"awslabs.nova-canvas-mcp-server":{"command":"uvx","args":["awslabs.nova-canvas-mcp-server@latest"],"env":{"AWS_REGION":"us-east-1"},"metadata":{"category":"AI/ML","description":"Amazon Nova Canvas image generation"}},"tavily-search":{"command":"npx","args":["-y","mcp-remote","https://mcp.tavily.com/mcp/?tavilyApiKey="],"metadata":{"category":"Search","description":"Web search and research capabilities powered by Tavily"}}}", + "MCP_SERVERS_SAFE_CONFIG": "{"time":{"metadata":{"category":"Utility","description":"Provides current time and date functionality"}},"aws-knowledge-mcp-server":{"metadata":{"category":"AWS","description":"AWS Knowledge Base MCP server for enterprise knowledge access"}},"awslabs.aws-documentation-mcp-server":{"metadata":{"category":"AWS","description":"Access AWS documentation and guides"}},"awslabs.cdk-mcp-server":{"metadata":{"category":"AWS","description":"AWS CDK code generation and assistance"}},"awslabs.aws-diagram-mcp-server":{"metadata":{"category":"AWS","description":"Generate AWS architecture diagrams"}},"awslabs.nova-canvas-mcp-server":{"metadata":{"category":"AI/ML","description":"Amazon Nova Canvas image generation"}},"tavily-search":{"metadata":{"category":"Search","description":"Web search and research capabilities powered by Tavily"}}}", }, }, "NetworkMode": "PUBLIC", @@ -26340,6 +26895,9 @@ exports[`GenerativeAiUseCases matches the snapshot 6`] = ` "McpEndpoint": { "Value": "", }, + "McpServersConfig": { + "Value": "{"time":{"metadata":{"category":"Utility","description":"Provides current time and date functionality"}},"aws-knowledge-mcp-server":{"metadata":{"category":"AWS","description":"AWS Knowledge Base MCP server for enterprise knowledge access"}},"awslabs.aws-documentation-mcp-server":{"metadata":{"category":"AWS","description":"Access AWS documentation and guides"}},"awslabs.cdk-mcp-server":{"metadata":{"category":"AWS","description":"AWS CDK code generation and assistance"}},"awslabs.aws-diagram-mcp-server":{"metadata":{"category":"AWS","description":"Generate AWS architecture diagrams"}},"awslabs.nova-canvas-mcp-server":{"metadata":{"category":"AI/ML","description":"Amazon Nova Canvas image generation"}},"tavily-search":{"metadata":{"category":"Search","description":"Web search and research capabilities powered by Tavily"}}}", + }, "ModelIds": { "Value": { "Fn::Join": [ @@ -26557,11 +27115,18 @@ exports[`GenerativeAiUseCases matches the snapshot 6`] = ` "Type": "AWS::IAM::Role", "UpdateReplacePolicy": "Delete", }, - "APIApiDeployment3A502123f0975eea7d864770e54fb622c5a1b52e": { + "APIApiDeployment3A502123c1c381eebb266bc52e27b7e87659ddd8": { "DeletionPolicy": "Delete", "DependsOn": [ "APIApiApi4XXDCF913C8", "APIApiApi5XX11B67643", + "APIApiagentsproxyANY44A4A08E", + "APIApiagentsproxyOPTIONS65C845F9", + "APIApiagentsproxy440913A2", + "APIApiagentsGETFE9F47E4", + "APIApiagentsOPTIONS064D2F41", + "APIApiagentsPOST28B48D5F", + "APIApiagents70FB378D", "APIApichatschatIdDELETE4578D41B", "APIApichatschatIdfeedbacksOPTIONS7AB34AA5", "APIApichatschatIdfeedbacksPOST3E9ACBEC", @@ -26713,7 +27278,7 @@ exports[`GenerativeAiUseCases matches the snapshot 6`] = ` ], "Properties": { "DeploymentId": { - "Ref": "APIApiDeployment3A502123f0975eea7d864770e54fb622c5a1b52e", + "Ref": "APIApiDeployment3A502123c1c381eebb266bc52e27b7e87659ddd8", }, "RestApiId": { "Ref": "APIApiFFA96F67", @@ -26765,10 +27330,446 @@ exports[`GenerativeAiUseCases matches the snapshot 6`] = ` ], "ResourceId": { "Fn::GetAtt": [ - "APIApiFFA96F67", - "RootResourceId", + "APIApiFFA96F67", + "RootResourceId", + ], + }, + "RestApiId": { + "Ref": "APIApiFFA96F67", + }, + }, + "Type": "AWS::ApiGateway::Method", + "UpdateReplacePolicy": "Delete", + }, + "APIApiagents70FB378D": { + "DeletionPolicy": "Delete", + "Properties": { + "ParentId": { + "Fn::GetAtt": [ + "APIApiFFA96F67", + "RootResourceId", + ], + }, + "PathPart": "agents", + "RestApiId": { + "Ref": "APIApiFFA96F67", + }, + }, + "Type": "AWS::ApiGateway::Resource", + "UpdateReplacePolicy": "Delete", + }, + "APIApiagentsGETApiPermissionGenerativeAiUseCasesStackAPIApi89219E17GETagents5FFFAEBF": { + "DeletionPolicy": "Delete", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "UseCaseBuilderAgentBuilderDAB96216", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":execute-api:us-east-1:123456890123:", + { + "Ref": "APIApiFFA96F67", + }, + "/", + { + "Ref": "APIApiDeploymentStageapiCD55D117", + }, + "/GET/agents", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + "UpdateReplacePolicy": "Delete", + }, + "APIApiagentsGETApiPermissionTestGenerativeAiUseCasesStackAPIApi89219E17GETagents2809AEEE": { + "DeletionPolicy": "Delete", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "UseCaseBuilderAgentBuilderDAB96216", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":execute-api:us-east-1:123456890123:", + { + "Ref": "APIApiFFA96F67", + }, + "/test-invoke-stage/GET/agents", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + "UpdateReplacePolicy": "Delete", + }, + "APIApiagentsGETFE9F47E4": { + "DeletionPolicy": "Delete", + "Properties": { + "AuthorizationType": "COGNITO_USER_POOLS", + "AuthorizerId": { + "Ref": "UseCaseBuilderAuthorizer8C07733E", + }, + "HttpMethod": "GET", + "Integration": { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":apigateway:us-east-1:lambda:path/2015-03-31/functions/", + { + "Fn::GetAtt": [ + "UseCaseBuilderAgentBuilderDAB96216", + "Arn", + ], + }, + "/invocations", + ], + ], + }, + }, + "ResourceId": { + "Ref": "APIApiagents70FB378D", + }, + "RestApiId": { + "Ref": "APIApiFFA96F67", + }, + }, + "Type": "AWS::ApiGateway::Method", + "UpdateReplacePolicy": "Delete", + }, + "APIApiagentsOPTIONS064D2F41": { + "DeletionPolicy": "Delete", + "Properties": { + "ApiKeyRequired": false, + "AuthorizationType": "NONE", + "HttpMethod": "OPTIONS", + "Integration": { + "IntegrationResponses": [ + { + "ResponseParameters": { + "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent'", + "method.response.header.Access-Control-Allow-Methods": "'OPTIONS,GET,PUT,POST,DELETE,PATCH,HEAD'", + "method.response.header.Access-Control-Allow-Origin": "'*'", + }, + "StatusCode": "204", + }, + ], + "RequestTemplates": { + "application/json": "{ statusCode: 200 }", + }, + "Type": "MOCK", + }, + "MethodResponses": [ + { + "ResponseParameters": { + "method.response.header.Access-Control-Allow-Headers": true, + "method.response.header.Access-Control-Allow-Methods": true, + "method.response.header.Access-Control-Allow-Origin": true, + }, + "StatusCode": "204", + }, + ], + "ResourceId": { + "Ref": "APIApiagents70FB378D", + }, + "RestApiId": { + "Ref": "APIApiFFA96F67", + }, + }, + "Type": "AWS::ApiGateway::Method", + "UpdateReplacePolicy": "Delete", + }, + "APIApiagentsPOST28B48D5F": { + "DeletionPolicy": "Delete", + "Properties": { + "AuthorizationType": "COGNITO_USER_POOLS", + "AuthorizerId": { + "Ref": "UseCaseBuilderAuthorizer8C07733E", + }, + "HttpMethod": "POST", + "Integration": { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":apigateway:us-east-1:lambda:path/2015-03-31/functions/", + { + "Fn::GetAtt": [ + "UseCaseBuilderAgentBuilderDAB96216", + "Arn", + ], + }, + "/invocations", + ], + ], + }, + }, + "ResourceId": { + "Ref": "APIApiagents70FB378D", + }, + "RestApiId": { + "Ref": "APIApiFFA96F67", + }, + }, + "Type": "AWS::ApiGateway::Method", + "UpdateReplacePolicy": "Delete", + }, + "APIApiagentsPOSTApiPermissionGenerativeAiUseCasesStackAPIApi89219E17POSTagentsBB862BB1": { + "DeletionPolicy": "Delete", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "UseCaseBuilderAgentBuilderDAB96216", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":execute-api:us-east-1:123456890123:", + { + "Ref": "APIApiFFA96F67", + }, + "/", + { + "Ref": "APIApiDeploymentStageapiCD55D117", + }, + "/POST/agents", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + "UpdateReplacePolicy": "Delete", + }, + "APIApiagentsPOSTApiPermissionTestGenerativeAiUseCasesStackAPIApi89219E17POSTagents9F230B2C": { + "DeletionPolicy": "Delete", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "UseCaseBuilderAgentBuilderDAB96216", + "Arn", ], }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":execute-api:us-east-1:123456890123:", + { + "Ref": "APIApiFFA96F67", + }, + "/test-invoke-stage/POST/agents", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + "UpdateReplacePolicy": "Delete", + }, + "APIApiagentsproxy440913A2": { + "DeletionPolicy": "Delete", + "Properties": { + "ParentId": { + "Ref": "APIApiagents70FB378D", + }, + "PathPart": "{proxy+}", + "RestApiId": { + "Ref": "APIApiFFA96F67", + }, + }, + "Type": "AWS::ApiGateway::Resource", + "UpdateReplacePolicy": "Delete", + }, + "APIApiagentsproxyANY44A4A08E": { + "DeletionPolicy": "Delete", + "Properties": { + "AuthorizationType": "COGNITO_USER_POOLS", + "AuthorizerId": { + "Ref": "UseCaseBuilderAuthorizer8C07733E", + }, + "HttpMethod": "ANY", + "Integration": { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":apigateway:us-east-1:lambda:path/2015-03-31/functions/", + { + "Fn::GetAtt": [ + "UseCaseBuilderAgentBuilderDAB96216", + "Arn", + ], + }, + "/invocations", + ], + ], + }, + }, + "ResourceId": { + "Ref": "APIApiagentsproxy440913A2", + }, + "RestApiId": { + "Ref": "APIApiFFA96F67", + }, + }, + "Type": "AWS::ApiGateway::Method", + "UpdateReplacePolicy": "Delete", + }, + "APIApiagentsproxyANYApiPermissionGenerativeAiUseCasesStackAPIApi89219E17ANYagentsproxyB1492E94": { + "DeletionPolicy": "Delete", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "UseCaseBuilderAgentBuilderDAB96216", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":execute-api:us-east-1:123456890123:", + { + "Ref": "APIApiFFA96F67", + }, + "/", + { + "Ref": "APIApiDeploymentStageapiCD55D117", + }, + "/*/agents/*", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + "UpdateReplacePolicy": "Delete", + }, + "APIApiagentsproxyANYApiPermissionTestGenerativeAiUseCasesStackAPIApi89219E17ANYagentsproxy619296F3": { + "DeletionPolicy": "Delete", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "UseCaseBuilderAgentBuilderDAB96216", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":execute-api:us-east-1:123456890123:", + { + "Ref": "APIApiFFA96F67", + }, + "/test-invoke-stage/*/agents/*", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + "UpdateReplacePolicy": "Delete", + }, + "APIApiagentsproxyOPTIONS65C845F9": { + "DeletionPolicy": "Delete", + "Properties": { + "ApiKeyRequired": false, + "AuthorizationType": "NONE", + "HttpMethod": "OPTIONS", + "Integration": { + "IntegrationResponses": [ + { + "ResponseParameters": { + "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent'", + "method.response.header.Access-Control-Allow-Methods": "'OPTIONS,GET,PUT,POST,DELETE,PATCH,HEAD'", + "method.response.header.Access-Control-Allow-Origin": "'*'", + }, + "StatusCode": "204", + }, + ], + "RequestTemplates": { + "application/json": "{ statusCode: 200 }", + }, + "Type": "MOCK", + }, + "MethodResponses": [ + { + "ResponseParameters": { + "method.response.header.Access-Control-Allow-Headers": true, + "method.response.header.Access-Control-Allow-Methods": true, + "method.response.header.Access-Control-Allow-Origin": true, + }, + "StatusCode": "204", + }, + ], + "ResourceId": { + "Ref": "APIApiagentsproxy440913A2", + }, "RestApiId": { "Ref": "APIApiFFA96F67", }, @@ -42625,6 +43626,151 @@ exports[`GenerativeAiUseCases matches the snapshot 6`] = ` "Type": "AWS::S3::BucketPolicy", "UpdateReplacePolicy": "Delete", }, + "UseCaseBuilderAgentBuilderDAB96216": { + "DeletionPolicy": "Delete", + "DependsOn": [ + "UseCaseBuilderAgentBuilderServiceRoleDefaultPolicy55D3FBBC", + "UseCaseBuilderAgentBuilderServiceRole9C6C40B9", + ], + "Properties": { + "Code": { + "S3Bucket": "cdk-hnb659fds-assets-123456890123-us-east-1", + "S3Key": "HASH-REPLACED.zip", + }, + "Environment": { + "Variables": { + "MODEL_REGION": "us-east-1", + "USECASE_ID_INDEX_NAME": "UseCaseIdIndexName", + "USECASE_TABLE_NAME": { + "Ref": "UseCaseBuilderUseCaseBuilderTable449740F3", + }, + "USER_POOL_ID": { + "Ref": "AuthUserPool8115E87F", + }, + }, + }, + "Handler": "index.handler", + "MemorySize": 1024, + "Role": { + "Fn::GetAtt": [ + "UseCaseBuilderAgentBuilderServiceRole9C6C40B9", + "Arn", + ], + }, + "Runtime": "nodejs22.x", + "Timeout": 900, + }, + "Type": "AWS::Lambda::Function", + "UpdateReplacePolicy": "Delete", + }, + "UseCaseBuilderAgentBuilderServiceRole9C6C40B9": { + "DeletionPolicy": "Delete", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + ], + ], + }, + ], + }, + "Type": "AWS::IAM::Role", + "UpdateReplacePolicy": "Delete", + }, + "UseCaseBuilderAgentBuilderServiceRoleDefaultPolicy55D3FBBC": { + "DeletionPolicy": "Delete", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "dynamodb:BatchGetItem", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator", + "dynamodb:Query", + "dynamodb:GetItem", + "dynamodb:Scan", + "dynamodb:ConditionCheckItem", + "dynamodb:BatchWriteItem", + "dynamodb:PutItem", + "dynamodb:UpdateItem", + "dynamodb:DeleteItem", + "dynamodb:DescribeTable", + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "UseCaseBuilderUseCaseBuilderTable449740F3", + "Arn", + ], + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "UseCaseBuilderUseCaseBuilderTable449740F3", + "Arn", + ], + }, + "/index/*", + ], + ], + }, + ], + }, + { + "Action": [ + "bedrock:*", + "logs:*", + ], + "Effect": "Allow", + "Resource": "*", + }, + { + "Action": "cognito-idp:AdminGetUser", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "AuthUserPool8115E87F", + "Arn", + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "UseCaseBuilderAgentBuilderServiceRoleDefaultPolicy55D3FBBC", + "Roles": [ + { + "Ref": "UseCaseBuilderAgentBuilderServiceRole9C6C40B9", + }, + ], + }, + "Type": "AWS::IAM::Policy", + "UpdateReplacePolicy": "Delete", + }, "UseCaseBuilderAuthorizer8C07733E": { "DeletionPolicy": "Delete", "Properties": { diff --git a/packages/types/src/agent-builder.d.ts b/packages/types/src/agent-builder.d.ts new file mode 100644 index 000000000..cb8e0d3a9 --- /dev/null +++ b/packages/types/src/agent-builder.d.ts @@ -0,0 +1,188 @@ +import { UnrecordedMessage, Model } from './message'; + +// MCP Server Configuration (for internal use only) +export type MCPServerConfig = { + name: string; + command: string; + args: string[]; + env: Record; + enabled: boolean; + description?: string; +}; + +// MCP Server Reference (what users specify) +export type MCPServerReference = string; + +// Common items for all agent data +// Table: PartitionKey=id, SortKey=dataType +export type AgentCommon = { + id: string; // agent#{userId} + dataType: string; // agent#{agentId} + agentId: string; +}; + +// Agent content (request type for creating or updating Agent) +export type AgentContent = { + name: string; + description?: string; + systemPrompt: string; + mcpServers: MCPServerReference[]; // Changed to string array + modelId: string; + codeExecutionEnabled?: boolean; + tags?: string[]; + isPublic?: boolean; + createdByEmail?: string; +}; + +// Content recorded in Table +export type AgentInTable = AgentCommon & + AgentContent & { + description: string; // Required in table + codeExecutionEnabled: boolean; + isPublic: boolean; + tags: string[]; + starCount: number; // Number of users who favorited this agent + createdAt: string; + updatedAt: string; + createdBy: string; // User ID of the agent creator + }; + +// Content returned to Frontend +// isFavorite, isMyAgent are dynamically added +export type AgentAsOutput = AgentInTable & { + isFavorite?: boolean; + isMyAgent: boolean; +}; + +// Agent Configuration for API responses +export type AgentConfiguration = Omit & { + shareId?: string; + starCount: number; + createdBy: string; +}; + +// API Request Types +export type CreateAgentRequest = AgentContent; + +export type UpdateAgentRequest = CreateAgentRequest & { + agentId: string; +}; + +export type CloneAgentRequest = { + sourceAgentId: string; + name?: string; +}; + +// API Response Types +export type CreateAgentResponse = { + agent: AgentConfiguration; +}; + +export type UpdateAgentResponse = { + agent: AgentConfiguration; +}; + +export type DeleteAgentResponse = { + success: boolean; +}; + +export type CloneAgentResponse = { + success: boolean; + agent: AgentConfiguration; + missingDependencies?: string[]; + error?: string; +}; + +export type ListAgentsRequest = { + limit?: number; + nextToken?: string; + type?: 'my' | 'public' | 'favorites'; +}; + +export type ListAgentsResponse = { + agents: AgentConfiguration[]; + nextToken?: string; + totalCount?: number; + type: 'my' | 'public' | 'favorites'; + // For SWR Infinite compatibility + data?: AgentConfiguration[]; + lastEvaluatedKey?: string; +}; + +// Base agent response (common fields for sharing/importing) +export type BaseAgentResponse = { + agentId: string; + name: string; + description: string; + systemPrompt: string; + modelId: string; + mcpServers: MCPServerReference[]; // Changed to string array + codeExecutionEnabled: boolean; + tags: string[]; + isPublic: boolean; + createdAt: string; + updatedAt: string; +}; + +// Shared Agent Response (for getSharedAgent) +export type SharedAgentResponse = BaseAgentResponse & { + createdBy: string; + createdByEmail?: string; + isMyAgent: boolean; +}; + +// Clone Agent Response (for cloneAgent) +export type ClonedAgentResponse = BaseAgentResponse; + +// Public Agent Item (for listPublicAgents) +export type PublicAgentItem = SharedAgentResponse & { + isFavorite: boolean; +}; + +// Repository-specific response types +export type RepositoryListAgentsResponse = { + data: AgentAsOutput[]; + lastEvaluatedKey?: string; +}; + +export type ListFavoriteAgentsResponse = { + data: AgentAsOutput[]; + lastEvaluatedKey?: string; +}; + +// Validation types +export type ValidationResult = { + data?: T; + isValid: boolean; + error?: string; +}; + +// Extended types with additional fields +export type AgentWithFavorite = AgentConfiguration & { + isFavorite: boolean; + isMyAgent: boolean; +}; + +// Agent Builder State Types +export type AgentBuilderState = { + agents: AgentConfiguration[]; + currentAgent?: AgentConfiguration; + loading: boolean; + error?: string; +}; + +// Agent Core Runtime Request Types +export type AgentCoreRuntimeRequest = { + agentRuntimeArn: string; + sessionId?: string; + qualifier?: string; + system_prompt?: string; // Keep this name for backward compatibility with useAgentCore + prompt: string; // User prompt as string + previousMessages?: UnrecordedMessage[]; // Raw messages that will be converted to Strands format + model: Model; + files?: File[]; // Added support for file uploads + userId?: string; // User ID for MCP server management + mcpServers?: MCPServerReference[]; // Changed to string array + agentId?: string; // Agent ID for logging/tracking + codeExecutionEnabled?: boolean; // Code execution setting +}; diff --git a/packages/types/src/agent-core.d.ts b/packages/types/src/agent-core.d.ts index 93126d0c1..21cedc0b7 100644 --- a/packages/types/src/agent-core.d.ts +++ b/packages/types/src/agent-core.d.ts @@ -5,8 +5,12 @@ export type AgentCoreConfiguration = { arn: string; }; -// AgentCore Runtime Request (compatible with Strands) -export type AgentCoreRequest = StrandsRequest; +// AgentCore Runtime Request (extended from Strands with additional fields) +export type AgentCoreRequest = StrandsRequest & { + mcp_servers?: string[]; // Changed to string array + session_id?: string; + code_execution_enabled?: boolean; +}; export type AgentCoreStreamResponse = StrandsStreamEvent; diff --git a/packages/types/src/index.d.ts b/packages/types/src/index.d.ts index 2279f8258..2798d6ae5 100644 --- a/packages/types/src/index.d.ts +++ b/packages/types/src/index.d.ts @@ -18,3 +18,5 @@ export * from './speech-to-speech'; export * from './stat'; export * from './mcp'; export * from './agent-core'; +export * from './agent-builder'; +export * from './mcp-servers'; diff --git a/packages/types/src/mcp-servers.d.ts b/packages/types/src/mcp-servers.d.ts new file mode 100644 index 000000000..b9ea43f17 --- /dev/null +++ b/packages/types/src/mcp-servers.d.ts @@ -0,0 +1,7 @@ +export interface AvailableMCPServer { + name: string; + description: string; + category: string; +} + +export type AvailableMCPServers = AvailableMCPServer[]; diff --git a/packages/web/public/locales/translation/en.yaml b/packages/web/public/locales/translation/en.yaml index b046f3d3d..2d22f5bf9 100644 --- a/packages/web/public/locales/translation/en.yaml +++ b/packages/web/public/locales/translation/en.yaml @@ -1,11 +1,114 @@ agent: drop_files: Drop files to upload title: Agent Chat +agent_builder: + add_agents_to_favorites_description: Add agents to your favorites to see them here + add_mcp_server: Add MCP Server + add_mcp_server_button: Add MCP Server + add_mcp_servers_help: Click "Add MCP Server" to add tools and capabilities to your agent + add_new_mcp_server: Add New MCP Server + agent_chat: Agent Chat + agent_cloned_successfully: Agent "{{name}}" cloned successfully! + agent_created_successfully: Agent created successfully + agent_deleted_successfully: Agent deleted successfully + agent_not_found: Agent Not Found + agent_updated_successfully: Agent updated successfully + all_categories: All + arguments: Arguments + arguments_help: Package name or command arguments (space-separated) + arguments_label: Arguments + arguments_placeholder: package-name arg1 arg2 + basic_information: Basic Information + chat: chat + check_back_later_description: Check back later for new public agents + clone: Clone + code_execution: Code execution + code_execution_description: Allows this agent to run the code and run the scripts. + command: Command + command_help: Only 'uvx' command is allowed for security reasons + command_label: Command + command_placeholder: uvx + confirm_delete: Are you sure you want to delete this agent? + create_agent: Create Agent + create_first_agent_description: Create your first agent to get started + create_your_first_agent: Create Your First Agent + created_by: Created by + describe_agent: Please explain the functions of the agent + description: Description + description_label: Description + description_placeholder: Describe what your agent does... + edit_agent: Edit Agent + enable_code_execution: Enabling code execution + enter_agent_name: Enter the agent name + enter_system_prompt: Enter a system prompt that defines the agent behavior + enter_tags_comma_separated: Enter tags separated by commas + environment_help: JSON format environment variables (optional) + environment_variables: Environment Variables + environment_variables_label: Environment Variables + example_github: GitHub + failed_to_clone_agent: Failed to clone agent + failed_to_create_agent: Failed to create agent + failed_to_delete_agent: Failed to delete agent + failed_to_load_agents: Failed to load agents + failed_to_toggle_favorite: Failed to toggle favorite + failed_to_update_agent: Failed to update agent + favorites: Favorites + filter_by_tag: Filter by tag + loading_agent: Loading agent... + marketplace: Marketplace + mcp_server_configuration: MCP Server Configuration + mcp_server_description: >- + Select MCP servers to enable additional capabilities for your agent. These + servers are pre-configured by administrators for security. + mcp_servers: MCP Servers + mcp_servers_description: Add MCP servers to provide tools and capabilities to your agent + model: Model + my_agents: My Agents + name: Name + no_agents_yet: No agents yet + no_favorite_agents_yet: No favorite agents yet + no_mcp_servers: No MCP servers configured + no_mcp_servers_available: No MCP servers are currently available. + no_mcp_servers_match_filter: No MCP servers match your current filter. + no_mcp_servers_selected: No MCP servers selected. Your agent will have basic functionality only. + no_public_agents_available: No public agents available + public_sharing_description: >- + Make this agent available on public marketplaces and be discovered and + available to others. + + The agent is visible to all users, but you cannot modify the original. + quick_examples: Quick Examples + quick_examples_title: Quick Examples + search_agents: Search agents... + search_mcp_servers: Search MCP servers... + security_notice: Security Notice + security_notice_description: >- + Only uvx command is allowed. External network access is restricted for + security. + security_restrictions: >- + Only uvx command is allowed. External network access is restricted for + security. + separate_tags_with_commas: Separate multiple tags with commas + server_configuration: Server Configuration + server_description_placeholder: Brief description of what this server provides... + server_name: Server Name + server_name_label: Server Name + server_name_placeholder: e.g., github-mcp-server + share_publicly: Share publicly + showing_results: Showing {{start}}-{{end}} of {{total}} results + start_chatting_with: Start chatting with {{name}} + system_prompt: System Prompt + tags: Tags + test: Test + title: Agent Builder + tool_settings: Tool Settings + updated: Updated date agent_core: model: Model runtime: Runtime start_conversation: Start a conversation with AgentCore title: AgentCore +agent_dashboard: {} auth: loading: Loading... login: Login @@ -40,6 +143,8 @@ chat: title: Chat view_prompt_examples: View Prompt Examples common: + add: Add + back: Back cancel: Cancel clear: Clear close: Close @@ -47,7 +152,13 @@ common: colon: ':' complete: Complete create: Create + default: Default delete: Delete + disable: Disable + disabled: Disabled + edit: Edit + enable: Enable + enabled: Enabled enter_text: Enter text error: An error occurred errorNoSuggestions: No suggestions were found. @@ -55,10 +166,14 @@ common: expand: Expand export: Export feedback_received: Feedback received. Thank you. + first_page: First page + form: Form here: here + last_page: Last page load_more: Load more loading: Loading... next: Next + next_page: Next page no_options_found: No options found none: None notAuthenticated: You are not authenticated. @@ -67,6 +182,8 @@ common: optional: Optional other: Other previous: Previous + previous_page: Previous page + remove: Remove required: Required reviewComplete: Review completed save: Save @@ -76,6 +193,7 @@ common: title: Title trace: Trace try: Try + update: Update demo: inter_use_cases: Use Case Integration diagram: @@ -545,7 +663,9 @@ landing: Amazon Bedrock. title: Agent Chat agent_core: - description: AgentCore Chat is a feature that utilizes various Agents created with Bedrock AgentCore. + description: >- + AgentCore Chat is a feature that utilizes various Agents created with + Bedrock AgentCore. title: AgentCore chat: description: >- @@ -878,7 +998,10 @@ transcribe: result_placeholder: Speech recognition results will be displayed here screen_audio: Screen Audio screen_audio_error: Screen Audio Error - screen_audio_notice: 'For Windows: Select "Entire Screen" tab and turn ON "Share system audio".
For Mac: Select "Chrome Tab" and turn ON "Also share tab audio".
Chrome and Edge work. Firefox does not work.' + screen_audio_notice: >- + For Windows: Select "Entire Screen" tab and turn ON "Share system + audio".
For Mac: Select "Chrome Tab" and turn ON "Also share tab + audio".
Chrome and Edge work. Firefox does not work. select_input_method: Please select from microphone input or file upload speaker_names: Speaker names (comma separated) speaker_recognition: Speaker recognition @@ -891,7 +1014,9 @@ translate: additional_context_placeholder: You can enter additional points to consider (e.g., casualness, etc.) auto_detect_language: Auto detect language auto_translate: Auto translate - contextHelp: Context information helps improve translation accuracy by providing background information about the meeting. + contextHelp: >- + Context information helps improve translation accuracy by providing + background information about the meeting. continue_output: Continue output enter_text: Enter text model: Real-time Translation Model @@ -905,7 +1030,9 @@ translate: translating: Translating... translation: Translation userDefinedContext: User-defined Context - userDefinedContextPlaceholder: 'Enter context to improve translation accuracy (e.g., meeting topic, technical terms, etc.)' + userDefinedContextPlaceholder: >- + Enter context to improve translation accuracy (e.g., meeting topic, + technical terms, etc.) useCaseBuilder: accessError: Access Error addInputExample: Add Input Example diff --git a/packages/web/public/locales/translation/ja.yaml b/packages/web/public/locales/translation/ja.yaml index 3b765d1c3..fb40877d7 100644 --- a/packages/web/public/locales/translation/ja.yaml +++ b/packages/web/public/locales/translation/ja.yaml @@ -1,11 +1,105 @@ agent: drop_files: ファイルをドロップしてアップロード title: Agent チャット +agent_builder: + add_agents_to_favorites_description: マーケットプレイスからエージェントをお気に入りに追加 + add_mcp_server: MCPサーバーを追加 + add_mcp_server_button: MCPサーバーを追加 + add_mcp_servers_help: 「MCPサーバーを追加」をクリックして、エージェントにツールと機能を追加します + add_new_mcp_server: 新しいMCPサーバーを追加 + agent_chat: エージェントチャット + agent_cloned_successfully: エージェント「{{name}}」が正常にクローンされました! + agent_created_successfully: エージェントが正常に作成されました + agent_deleted_successfully: エージェントが正常に削除されました + agent_not_found: エージェントが見つかりません + agent_updated_successfully: エージェントが正常に更新されました + all_categories: すべて + arguments: 引数 + arguments_help: パッケージ名またはコマンド引数(スペース区切り) + arguments_label: 引数 + arguments_placeholder: パッケージ名 引数1 引数2 + basic_information: 基本情報 + chat: チャット + check_back_later_description: 公開エージェントについては後でもう一度確認してください + clone: クローン + code_execution: コード実行 + code_execution_description: >- + このエージェントがコードを実行し、スクリプトを実行できるようにします。 + command: コマンド + command_help: セキュリティ上の理由により、'uvx'コマンドのみ許可されています + command_label: コマンド + command_placeholder: uvx + confirm_delete: このエージェントを削除してもよろしいですか? + create_agent: エージェントを作成 + create_first_agent_description: 最初のエージェントを作成して始めましょう + create_your_first_agent: 最初のエージェントを作成 + created_by: 作成者 + describe_agent: エージェントの機能を説明してください + description: 説明 + description_label: 説明 + description_placeholder: エージェントの機能を説明してください... + edit_agent: エージェントを編集 + enable_code_execution: コード実行を有効にする + enter_agent_name: エージェント名を入力 + enter_system_prompt: エージェントの動作を定義するシステムプロンプトを入力 + enter_tags_comma_separated: タグをカンマ区切りで入力 + environment_help: JSON形式の環境変数(オプション) + environment_variables: 環境変数 + environment_variables_label: 環境変数 + example_github: GitHub + failed_to_clone_agent: エージェントのクローンに失敗しました + failed_to_create_agent: エージェントの作成に失敗しました + failed_to_delete_agent: エージェントの削除に失敗しました + failed_to_toggle_favorite: お気に入りの切り替えに失敗しました + failed_to_update_agent: エージェントの更新に失敗しました + favorites: お気に入り + filter_by_tag: タグでフィルター + loading_agent: エージェントを読み込み中... + marketplace: マーケットプレイス + mcp_server_configuration: MCPサーバー設定 + mcp_server_description: エージェントに追加機能を提供するMCPサーバーを選択してください。これらのサーバーはセキュリティのため管理者によって事前設定されています。 + mcp_servers: MCPサーバー + mcp_servers_description: MCPサーバーを追加して、エージェントにツールと機能を提供します + model: モデル + my_agents: マイエージェント + name: 名前 + no_agents_yet: まだエージェントがありません + no_favorite_agents_yet: まだお気に入りのエージェントがありません + no_mcp_servers: MCPサーバーが設定されていません + no_mcp_servers_available: 現在利用可能なMCPサーバーがありません。 + no_mcp_servers_match_filter: 現在のフィルターに一致するMCPサーバーがありません。 + no_mcp_servers_selected: MCPサーバーが選択されていません。エージェントは基本機能のみ利用できます。 + no_public_agents_available: 利用可能な公開エージェントがありません + public_sharing_description: >- + このエージェントを公開マーケットプレイスで利用可能にし、他のユーザーが発見して利用できるようにします。エージェントはすべてのユーザーに表示されますが、オリジナルを変更することはできません。 + quick_examples: クイック例 + quick_examples_title: クイック例 + search_agents: エージェントを検索... + search_mcp_servers: MCPサーバーを検索... + security_notice: セキュリティ通知 + security_notice_description: uvxコマンドのみ許可されています。セキュリティのため外部ネットワークアクセスは制限されています。 + security_restrictions: uvxコマンドのみ許可されています。セキュリティのため外部ネットワークアクセスは制限されています。 + separate_tags_with_commas: 複数のタグはカンマで区切ってください + server_configuration: サーバー設定 + server_description_placeholder: このサーバーが提供する機能の簡単な説明... + server_name: サーバー名 + server_name_label: サーバー名 + server_name_placeholder: 例:github-mcp-server + share_publicly: 公開で共有 + showing_results: '{{total}}件中 {{start}}-{{end}}件を表示' + start_chatting_with: '{{name}}とのチャットを開始' + system_prompt: システムプロンプト + tags: タグ + test: テスト + title: エージェントビルダー + tool_settings: ツール設定 + updated: 更新日 agent_core: model: モデル runtime: ランタイム start_conversation: AgentCore と会話を始める title: AgentCore +agent_dashboard: {} auth: loading: 読み込み中... login: ログイン @@ -38,6 +132,8 @@ chat: title: チャット view_prompt_examples: プロンプト例を見る common: + add: 追加 + back: 戻る cancel: キャンセル clear: クリア close: 閉じる @@ -45,7 +141,13 @@ common: colon: ':' complete: 完了 create: 作成 + default: デフォルト delete: 削除 + disable: 無効化 + disabled: 無効 + edit: 編集 + enable: 有効化 + enabled: 有効 enter_text: 入力してください error: エラー errorNoSuggestions: 候補が見つかりませんでした。 @@ -53,10 +155,14 @@ common: expand: 展開 export: エクスポート feedback_received: フィードバックを受け付けました。ありがとうございます。 + first_page: 最初のページ + form: フォーム here: こちら + last_page: 最後のページ load_more: さらに読み込む loading: 読み込み中... next: 次 + next_page: 次のページ no_options_found: オプションが見つかりません none: ありません notAuthenticated: 認証されていません。 @@ -65,6 +171,8 @@ common: optional: 任意 other: その他 previous: 前 + previous_page: 前のページ + remove: 削除 required: 必須 reviewComplete: レビュー完了 save: 保存 @@ -74,6 +182,7 @@ common: title: タイトル trace: トレース try: 試す + update: 更新 demo: inter_use_cases: ユースケース連携 diagram: diff --git a/packages/web/src/App.tsx b/packages/web/src/App.tsx index 3967c1515..12d81e1c9 100644 --- a/packages/web/src/App.tsx +++ b/packages/web/src/App.tsx @@ -46,6 +46,9 @@ const inlineAgents: boolean = import.meta.env.VITE_APP_INLINE_AGENTS === 'true'; const mcpEnabled: boolean = import.meta.env.VITE_APP_MCP_ENABLED === 'true'; const agentCoreEnabled: boolean = import.meta.env.VITE_APP_AGENT_CORE_ENABLED === 'true'; +const agentCoreGenericRuntimeEnabled: boolean = + import.meta.env.VITE_APP_AGENT_CORE_GENERIC_RUNTIME !== 'null'; + const { visionEnabled, imageGenModelIds, @@ -139,6 +142,15 @@ const App: React.FC = () => { sub: 'Deprecated', } : null, + agentCoreGenericRuntimeEnabled + ? { + label: 'Agent Builder', + to: '/agent-builder', + icon: , + display: 'usecase' as const, + sub: 'Experimental', + } + : null, agentCoreEnabled ? { label: t('agent_core.title'), diff --git a/packages/web/src/components/ChatMessage.tsx b/packages/web/src/components/ChatMessage.tsx index 5e902af91..dc0f59fe8 100644 --- a/packages/web/src/components/ChatMessage.tsx +++ b/packages/web/src/components/ChatMessage.tsx @@ -277,18 +277,18 @@ const ChatMessage: React.FC = (props) => { {chatContent.metadata.usage.inputTokens} {chatContent.metadata.usage.outputTokens} - {chatContent.metadata.usage.cacheWriteInputTokens && ( + {chatContent.metadata.usage.cacheWriteInputTokens ? ( <> {chatContent.metadata.usage.cacheWriteInputTokens} - )} - {chatContent.metadata.usage.cacheReadInputTokens && ( + ) : null} + {chatContent.metadata.usage.cacheReadInputTokens ? ( <> {chatContent.metadata.usage.cacheReadInputTokens} - )} + ) : null} )} diff --git a/packages/web/src/components/InputChatContent.tsx b/packages/web/src/components/InputChatContent.tsx index 1a7249c9f..a5b7060ae 100644 --- a/packages/web/src/components/InputChatContent.tsx +++ b/packages/web/src/components/InputChatContent.tsx @@ -30,6 +30,7 @@ type Props = { fullWidth?: boolean; resetDisabled?: boolean; loading?: boolean; + isEmpty?: boolean; onChangeContent: (content: string) => void; onSend: () => void; sendIcon?: React.ReactNode; @@ -59,7 +60,7 @@ const InputChatContent: React.FC = (props) => { const { t } = useTranslation(); const { settingSubmitCmdOrCtrlEnter } = useUserSetting(); const { pathname } = useLocation(); - const { loading: chatLoading, isEmpty } = useChat(pathname); + const { loading: chatLoading, isEmpty: chatIsEmpty } = useChat(pathname); const { uploadedFiles, uploadFiles, @@ -276,16 +277,18 @@ const InputChatContent: React.FC = (props) => { - {!isEmpty && !props.resetDisabled && !props.hideReset && ( - - )} + {!(props.isEmpty ?? chatIsEmpty) && + !props.resetDisabled && + !props.hideReset && ( + + )} {/* Show keyboard shortcut hint when cmd/ctrl+enter setting is enabled */} diff --git a/packages/web/src/components/Select.tsx b/packages/web/src/components/Select.tsx index cd7ce5763..8a35e830b 100644 --- a/packages/web/src/components/Select.tsx +++ b/packages/web/src/components/Select.tsx @@ -18,6 +18,7 @@ type Props = RowItemProps & { fullWidth?: boolean; showColorChips?: boolean; showTags?: boolean; + placeholder?: string; onChange: (value: string) => void; }; @@ -103,14 +104,18 @@ const Select: React.FC = (props) => {
+ className={`relative cursor-pointer rounded border border-black/30 bg-white py-1.5 pl-3 pr-10 text-left focus:outline-none ${props.fullWidth ? 'w-full' : 'w-fit'}`}> - {props.value && ( + {props.value ? ( + ) : ( + + {props.placeholder || 'Select an option'} + )} diff --git a/packages/web/src/components/agentBuilder/AgentChatUnified.tsx b/packages/web/src/components/agentBuilder/AgentChatUnified.tsx new file mode 100644 index 000000000..151968053 --- /dev/null +++ b/packages/web/src/components/agentBuilder/AgentChatUnified.tsx @@ -0,0 +1,463 @@ +import React, { useState, useCallback, useEffect, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { v4 as uuidv4 } from 'uuid'; +import InputChatContent from '../InputChatContent'; +import ChatMessage from '../ChatMessage'; +import ScrollTopBottom from '../ScrollTopBottom'; +import useFollow from '../../hooks/useFollow'; +import { useAgentCore } from '../../hooks/useAgentCore'; +import { MODELS } from '../../hooks/useModel'; +import useFiles from '../../hooks/useFiles'; +import { FileLimit, AgentConfiguration } from 'generative-ai-use-cases'; +import BedrockIcon from '../../assets/bedrock.svg?react'; +import { PiRobot as RobotIcon, PiPencil as EditIcon } from 'react-icons/pi'; +import ButtonIcon from '../ButtonIcon'; +import Select from '../Select'; + +// Define file limits for the chat interface +const fileLimit: FileLimit = { + accept: { + doc: [ + '.csv', + '.doc', + '.docx', + '.html', + '.md', + '.pdf', + '.txt', + '.xls', + '.xlsx', + '.yaml', + '.json', + ], + image: ['.jpg', '.jpeg', '.png', '.gif', '.webp'], + video: [], + }, + maxFileCount: 5, + maxFileSizeMB: 10, + maxImageFileCount: 5, + maxImageFileSizeMB: 5, + maxVideoFileCount: 0, + maxVideoFileSizeMB: 0, +}; + +interface AgentChatProps { + agent: AgentConfiguration; + sessionId?: string; + className?: string; + showHeader?: boolean; + layout?: 'fullscreen' | 'card'; + showAgentInfo?: boolean; + showEditButton?: boolean; + onEdit?: () => void; + onBack?: () => void; + actions?: React.ReactNode; +} + +const AgentChatUnified: React.FC = ({ + agent, + sessionId: providedSessionId, + className = '', + showHeader = true, + layout = 'fullscreen', + showAgentInfo = true, + showEditButton = false, + onEdit, + actions, +}) => { + const { t } = useTranslation(); + + // Generate session ID if not provided + const [sessionId] = useState(() => providedSessionId || uuidv4()); + const chatId = `agent-chat-${agent.agentId}-${sessionId}`; + const { scrollableContainer, setFollowing } = useFollow(); + + // AgentCore for chat functionality + const { + messages: chatMessages, + isEmpty: chatIsEmpty, + clear: clearChat, + loading: chatLoading, + invokeAgentRuntime, + getGenericRuntime, + updateSystemContext, + getModelId, + setModelId, + } = useAgentCore(chatId); + + const [chatContent, setChatContent] = useState(''); + const [initialized, setInitialized] = useState(false); + const [isOver, setIsOver] = useState(false); + + // Get models from MODELS + const { modelIds: availableModels, modelDisplayName } = MODELS; + const modelId = getModelId(); + + // File handling + const { + clear: clearFiles, + uploadFiles, + uploadedFiles, + } = useFiles(`/agent-chat-${agent.agentId}`); + + // Initialize model ID when agent is loaded + useEffect(() => { + if (agent && availableModels.length > 0) { + const agentModelId = agent.modelId || availableModels[0]; + setModelId(agentModelId); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [agent, availableModels]); + + // Initialize chat when component mounts + useEffect(() => { + if (!initialized && agent) { + console.log( + 'Starting chat with agent:', + agent.name, + 'Model ID:', + agent.modelId + ); + console.log( + 'Code execution enabled in agent:', + agent.codeExecutionEnabled + ); + + // Set the system context to the agent's system prompt + const systemPrompt = `${agent.systemPrompt || 'You are a helpful assistant.'} + +Agent Name: ${agent.name} +Agent Description: ${agent.description || 'No description provided'} + +Please respond as this agent with the specified behavior and personality.`; + + updateSystemContext(systemPrompt); + setInitialized(true); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [agent, initialized]); + + // Accept file types based on model + const accept = useMemo(() => { + if (!modelId) return []; + const feature = MODELS.getModelMetadata(modelId); + return [ + ...(feature.flags.doc ? fileLimit.accept.doc : []), + ...(feature.flags.image ? fileLimit.accept.image : []), + ...(feature.flags.video ? fileLimit.accept.video : []), + ]; + }, [modelId]); + + // File upload enabled + const fileUpload = useMemo(() => { + return accept.length > 0; + }, [accept]); + + const handleSendMessage = useCallback(async () => { + if (!chatContent.trim() || chatLoading) return; + + setFollowing(true); + + try { + const genericRuntime = getGenericRuntime(); + if (!genericRuntime) { + throw new Error('No AgentCore runtime available'); + } + + console.log('Sending message with agent:', agent.name); + console.log('Using sessionId:', sessionId); + console.log('MCP servers count:', agent.mcpServers?.length || 0); + console.log('Code execution enabled:', agent.codeExecutionEnabled); + + // Get uploaded files from the useFiles hook + const uploadedFileObjects = uploadedFiles.filter( + (file) => !file.errorMessages.length && !file.uploading + ); + const filesToSend = + uploadedFileObjects.length > 0 + ? uploadedFileObjects.map((uploadedFile) => uploadedFile.file) + : undefined; + + // Use AgentCore's invokeAgentRuntime with MCP servers + invokeAgentRuntime( + genericRuntime.arn, + sessionId, + chatContent, + 'DEFAULT', + filesToSend, + 'current-user', + agent.mcpServers, + agent.agentId, + agent.modelId, + agent.codeExecutionEnabled ?? false + ); + + setChatContent(''); + clearFiles(); + } catch (error) { + console.error('Error sending message:', error); + alert( + `Error: ${error instanceof Error ? error.message : 'Unknown error occurred'}` + ); + } + }, [ + chatContent, + agent, + chatLoading, + setFollowing, + invokeAgentRuntime, + getGenericRuntime, + sessionId, + uploadedFiles, + clearFiles, + ]); + + const handleResetChat = useCallback(() => { + clearChat(); + setChatContent(''); + clearFiles(); + }, [clearChat, clearFiles]); + + // Handle drag and drop for files + const handleDragOver = (event: React.DragEvent) => { + event.preventDefault(); + setIsOver(true); + }; + + const handleDragLeave = (event: React.DragEvent) => { + event.preventDefault(); + setIsOver(false); + }; + + const handleDrop = (event: React.DragEvent) => { + event.preventDefault(); + setIsOver(false); + if (event.dataTransfer.files) { + uploadFiles(Array.from(event.dataTransfer.files), fileLimit, accept); + } + }; + + // Render unified header with all controls + const renderUnifiedHeader = () => ( +
+ {/* Main header with navigation, agent info, and actions */} +
+
+ +
+
+ {agent?.name || t('agent_builder.agent_chat')} +
+ {agent?.description && ( +
{agent.description}
+ )} +
+
+
+ {showEditButton && onEdit && ( + + + + )} + {actions} +
+
+ + {/* Controls row */} + {showAgentInfo && ( +
+ {/* Model Selection */} +
+