diff --git a/packages/cdk/consts.ts b/packages/cdk/consts.ts index 4559facf2..c5a476003 100644 --- a/packages/cdk/consts.ts +++ b/packages/cdk/consts.ts @@ -1,5 +1,6 @@ import * as lambda from 'aws-cdk-lib/aws-lambda'; export const LAMBDA_RUNTIME_NODEJS = lambda.Runtime.NODEJS_22_X; +export const LAMBDA_RUNTIME_PYTHON = lambda.Runtime.PYTHON_3_12; export const TAG_KEY = 'GenU'; 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 04f9d7551..cfd92636d 100644 --- a/packages/cdk/lambda-python/generic-agent-core-runtime/app.py +++ b/packages/cdk/lambda-python/generic-agent-core-runtime/app.py @@ -67,6 +67,7 @@ async def invocations(request: Request): model_info = request_data.get("model", {}) user_id = request_data.get("user_id") mcp_servers = request_data.get("mcp_servers") + sub_agents = request_data.get("sub_agents", []) 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) @@ -87,6 +88,7 @@ async def generate(): model_info=model_info, user_id=user_id, mcp_servers=mcp_servers, + sub_agents=sub_agents, session_id=agent_session_id or session_id, agent_id=agent_id, code_execution_enabled=code_execution_enabled, 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 b63fbae46..4450c8b8e 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 @@ -2,12 +2,15 @@ import json import logging +import uuid from collections.abc import AsyncGenerator from typing import Any import boto3 from strands import Agent as StrandsAgent from strands.models import BedrockModel +from strands.tools import tool +from strands_tools.browser import AgentCoreBrowser from .config import extract_model_info, get_max_iterations, get_system_prompt, supports_prompt_cache, supports_tools_cache from .tools import ToolManager @@ -20,6 +23,93 @@ logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) +# Initialize Bedrock AgentCore client (will be set when needed) +agent_core_client = None + +# Initialize the Browser tool +browser_tool = AgentCoreBrowser(region="us-east-1") + + +class SubAgent: + """Class to represent a sub-agent with its description and ARN""" + + def __init__(self, name: str, description: str, arn: str): + self.name = name + self.description = description + self.arn = arn + + def create_tool_function(self): + """Create a tool function for this specific sub-agent""" + + # Capture self in closure + sub_agent_instance = self + + def agent_tool(task: str, session_id: str | None = None) -> str: + """Dynamic docstring for sub-agent tool. + + Args: + task: The task or question to delegate to the sub-agent + session_id: Optional session ID for maintaining conversation context + + Returns: + The response from the sub-agent + """ + return sub_agent_instance._invoke_agent(task, session_id) + + # Set function metadata + agent_tool.__name__ = f"call_{self.name.lower().replace(' ', '_')}_agent" + agent_tool.__doc__ = f"{self.description}\nUse this when you need: {self.description.lower()}" + + return tool(agent_tool) + + def _invoke_agent(self, task: str, session_id: str | None = None) -> str: + """Internal method to invoke this specific agent""" + global agent_core_client + + # Initialize client if not already done + if agent_core_client is None: + agent_core_client = boto3.client("bedrock-agentcore", region_name="us-east-1") # fixed to us-east-1 for now + + sid = session_id or str(uuid.uuid4()) + + try: + # Format payload according to Bedrock AgentCore API requirements + formatted_payload = {"prompt": task} + + # Serialize payload to JSON bytes as required by AWS API + payload_bytes = json.dumps(formatted_payload).encode("utf-8") + + logger.info(f"Invoking sub-agent {self.name} with ARN: {self.arn}") + + response = agent_core_client.invoke_agent_runtime(agentRuntimeArn=self.arn, runtimeSessionId=sid, payload=payload_bytes) + + # Process and return the response + if "text/event-stream" in response.get("contentType", ""): + # Handle streaming response + content = [] + for line in response["response"].iter_lines(chunk_size=10): + if line: + line = line.decode("utf-8") + if line.startswith("data: "): + line = line[6:] + content.append(line) + return "\n".join(content) + + elif response.get("contentType") == "application/json": + # Handle standard JSON response + content = [] + for chunk in response.get("response", []): + content.append(chunk.decode("utf-8")) + return "".join(content) + + else: + # Handle other response types + return str(response) + + except Exception as e: + logger.error(f"Error calling {self.name} agent: {str(e)}") + return f"Error: Failed to call {self.name} agent - {str(e)}" + class IterationLimitExceededError(Exception): """Exception raised when iteration limit is exceeded""" @@ -55,6 +145,7 @@ async def process_request_streaming( model_info: ModelInfo, user_id: str | None = None, mcp_servers: list[str] | None = None, + sub_agents: list[dict[str, str]] | None = None, session_id: str | None = None, agent_id: str | None = None, code_execution_enabled: bool | None = False, @@ -73,7 +164,29 @@ async def process_request_streaming( # 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})") + logger.info(f"Loaded {len(tools)} base tools (code execution: {code_execution_enabled})") + + # Log sub-agents if provided and add them as tools + if sub_agents: + logger.info(f"Sub-agents configured: {len(sub_agents)}") + + # Create SubAgent instances from the provided configuration + SUB_AGENTS = [] + for sub_agent_config in sub_agents: + sub_agent = SubAgent(name=sub_agent_config.get("name", "Unknown"), description=sub_agent_config.get("description", "No description"), arn=sub_agent_config.get("arn", "")) + SUB_AGENTS.append(sub_agent) + logger.debug(f" - {sub_agent.name}: {sub_agent.arn}") + + # Create tool functions for each sub-agent and add to tools list + for sub_agent in SUB_AGENTS: + try: + sub_agent_tool = sub_agent.create_tool_function() + tools.append(sub_agent_tool) + logger.info(f"Added sub-agent tool: {sub_agent.name}") + except Exception as e: + logger.error(f"Failed to create tool for sub-agent {sub_agent.name}: {e}") + + logger.info(f"Total tools available: {len(tools)}") # Log agent info if agent_id: diff --git a/packages/cdk/lambda-python/list-agent-core-runtimes/list_agent_core_runtimes.py b/packages/cdk/lambda-python/list-agent-core-runtimes/list_agent_core_runtimes.py new file mode 100644 index 000000000..bf0525101 --- /dev/null +++ b/packages/cdk/lambda-python/list-agent-core-runtimes/list_agent_core_runtimes.py @@ -0,0 +1,103 @@ +"""Lambda function to list AgentCore Runtime agents.""" + +import json +import logging +import os +from typing import Any, Dict + +import boto3 +from botocore.exceptions import ClientError + +# Configure logging +logger = logging.getLogger() +logger.setLevel(logging.INFO) + +# Get region from environment +REGION = os.environ.get("REGION", "us-east-1") + + +def handler(event: Dict[str, Any], context: Any) -> Dict[str, Any]: + """ + List all available AgentCore Runtime agents. + + Args: + event: API Gateway event + context: Lambda context + + Returns: + API Gateway response with list of runtimes + """ + logger.info(f"Event: {json.dumps(event)}") + + try: + # Initialize Bedrock Agent Runtime client + client = boto3.client("bedrock-agentcore-control", region_name=REGION) + + # List all agent runtimes + response = client.list_agent_runtimes() + + # Format the response + runtimes = [] + for runtime in response.get("agentRuntimes", []): + runtime_name = runtime.get("agentRuntimeName", "Unnamed Runtime") + if runtime_name not in ("GenUGenericRuntime", "GenUAgentBuilderRuntime"): + runtimes.append({ + "name": runtime_name, + "description": runtime.get("description", "No description available"), + "arn": runtime.get("agentRuntimeArn", ""), + "status": runtime.get("status", "UNKNOWN"), + "createdAt": runtime.get("createdAt").isoformat() if runtime.get("createdAt") else None, + "updatedAt": runtime.get("updatedAt").isoformat() if runtime.get("updatedAt") else None, + }) + + logger.info(f"Found {len(runtimes)} AgentCore runtimes") + + return { + "statusCode": 200, + "headers": { + "Content-Type": "application/json", + "Access-Control-Allow-Origin": "*", + }, + "body": json.dumps({ + "runtimes": runtimes, + "count": len(runtimes), + }), + } + + except ClientError as e: + error_code = e.response.get("Error", {}).get("Code", "Unknown") + error_message = e.response.get("Error", {}).get("Message", str(e)) + + logger.error(f"AWS ClientError ({error_code}): {error_message}") + + return { + "statusCode": 500, + "headers": { + "Content-Type": "application/json", + "Access-Control-Allow-Origin": "*", + }, + "body": json.dumps({ + "error": "Failed to list AgentCore runtimes", + "message": error_message, + "code": error_code, + "runtimes": [], + "count": 0, + }), + } + + except Exception as e: + logger.error(f"Unexpected error: {str(e)}", exc_info=True) + + return { + "statusCode": 500, + "headers": { + "Content-Type": "application/json", + "Access-Control-Allow-Origin": "*", + }, + "body": json.dumps({ + "error": "Failed to list AgentCore runtimes", + "message": str(e), + "runtimes": [], + "count": 0, + }), + } diff --git a/packages/cdk/lambda/agentBuilder/repositories/agent-repository.ts b/packages/cdk/lambda/agentBuilder/repositories/agent-repository.ts index ac1be1e51..90d1aba9a 100644 --- a/packages/cdk/lambda/agentBuilder/repositories/agent-repository.ts +++ b/packages/cdk/lambda/agentBuilder/repositories/agent-repository.ts @@ -316,6 +316,7 @@ export const createAgent = async ( systemPrompt: content.systemPrompt, modelId: content.modelId, mcpServers: content.mcpServers, + subAgents: content.subAgents || [], codeExecutionEnabled: content.codeExecutionEnabled ?? false, tags: content.tags || [], isPublic: content.isPublic ?? false, @@ -391,7 +392,7 @@ export const updateAgent = async ( 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', + 'set #name = :name, description = :description, systemPrompt = :systemPrompt, modelId = :modelId, mcpServers = :mcpServers, subAgents = :subAgents, codeExecutionEnabled = :codeExecutionEnabled, tags = :tags, isPublic = :isPublic, updatedAt = :updatedAt, createdByEmail = :createdByEmail', ExpressionAttributeNames: { '#name': 'name' }, ExpressionAttributeValues: { ':name': content.name, @@ -399,6 +400,7 @@ export const updateAgent = async ( ':systemPrompt': content.systemPrompt, ':modelId': content.modelId, ':mcpServers': content.mcpServers, + ':subAgents': content.subAgents || [], ':codeExecutionEnabled': content.codeExecutionEnabled ?? false, ':tags': content.tags || [], ':isPublic': isNowPublic, @@ -413,6 +415,7 @@ export const updateAgent = async ( ...agent, ...content, description: content.description || '', + subAgents: content.subAgents || [], codeExecutionEnabled: content.codeExecutionEnabled ?? false, tags: content.tags || [], isPublic: isNowPublic, @@ -635,6 +638,7 @@ export const listFavoriteAgents = async ( systemPrompt: '', modelId: '', mcpServers: [], + subAgents: [], codeExecutionEnabled: false, tags: [], isPublic: false, diff --git a/packages/cdk/lambda/agentBuilder/services/agent-service.ts b/packages/cdk/lambda/agentBuilder/services/agent-service.ts index c427f6ee1..15bc23d5d 100644 --- a/packages/cdk/lambda/agentBuilder/services/agent-service.ts +++ b/packages/cdk/lambda/agentBuilder/services/agent-service.ts @@ -27,6 +27,7 @@ function convertToAgentConfiguration( description: agent.description, systemPrompt: agent.systemPrompt, mcpServers: agent.mcpServers || [], + subAgents: agent.subAgents || [], modelId: agent.modelId, codeExecutionEnabled: agent.codeExecutionEnabled || false, isPublic: agent.isPublic || false, @@ -52,6 +53,9 @@ export async function createAgent( // MCP server names (no sanitization needed for string array) const mcpServerNames = request.mcpServers || []; + // Sub-agents (no sanitization needed, validated by schema) + const subAgents = request.subAgents || []; + // Get user email from Cognito const userEmail = await getUserEmail(userId); @@ -60,6 +64,7 @@ export async function createAgent( description: (request.description || '').trim(), systemPrompt: request.systemPrompt.trim(), mcpServers: mcpServerNames, + subAgents: subAgents, modelId: request.modelId, codeExecutionEnabled: request.codeExecutionEnabled ?? false, isPublic: request.isPublic ?? false, @@ -108,6 +113,9 @@ export async function updateAgent( // MCP server names const mcpServerNames = request.mcpServers || existingAgent.mcpServers; + // Sub-agents + const subAgents = request.subAgents ?? existingAgent.subAgents ?? []; + // Get user email from Cognito if not provided in request const userEmail = request.createdByEmail || (await getUserEmail(userId)); @@ -117,6 +125,7 @@ export async function updateAgent( description: request.description?.trim() || existingAgent.description, systemPrompt: request.systemPrompt?.trim() || existingAgent.systemPrompt, mcpServers: mcpServerNames, + subAgents: subAgents, modelId: request.modelId || existingAgent.modelId, codeExecutionEnabled: request.codeExecutionEnabled ?? @@ -298,6 +307,7 @@ export async function cloneAgent( systemPrompt: sourceAgent.systemPrompt, modelId: sourceAgent.modelId, mcpServers: sourceAgent.mcpServers || [], + subAgents: sourceAgent.subAgents || [], codeExecutionEnabled: sourceAgent.codeExecutionEnabled || false, tags: sourceAgent.tags || [], isPublic: false, // Cloned agents are private by default diff --git a/packages/cdk/lambda/agentBuilder/validation/schemas.ts b/packages/cdk/lambda/agentBuilder/validation/schemas.ts index 7a7d0af37..c1a008975 100644 --- a/packages/cdk/lambda/agentBuilder/validation/schemas.ts +++ b/packages/cdk/lambda/agentBuilder/validation/schemas.ts @@ -7,6 +7,24 @@ export const MCPServerReferenceSchema = z .max(100, 'MCP server name must be 100 characters or less') .regex(/^[a-zA-Z0-9._-]+$/, 'MCP server name contains invalid characters'); +// Sub-Agent Schema (for multi-agent collaboration) +export const SubAgentSchema = z.object({ + name: z + .string() + .min(1, 'Sub-agent name is required') + .max(100, 'Sub-agent name must be 100 characters or less'), + description: z + .string() + .max(500, 'Sub-agent description must be 500 characters or less'), + arn: z + .string() + .min(1, 'Sub-agent ARN is required') + .regex( + /^arn:aws:bedrock-agentcore:[a-z0-9-]+:\d{12}:runtime\/[a-zA-Z0-9_-]+$/, + 'Invalid AgentCore Runtime ARN format' + ), +}); + // MCP Server Configuration Schema (for internal use) export const MCPServerConfigSchema = z .object({ @@ -103,6 +121,11 @@ export const CreateAgentRequestSchema = z.object({ .max(10, 'Maximum 10 MCP servers allowed') .optional() .default([]), + subAgents: z + .array(SubAgentSchema) + .max(5, 'Maximum 5 sub-agents allowed') + .optional() + .default([]), codeExecutionEnabled: z.boolean().optional().default(false), isPublic: z.boolean().optional().default(false), tags: z @@ -130,6 +153,11 @@ export const UpdateAgentRequestSchema = z.object({ mcpServers: z .array(MCPServerReferenceSchema) .max(10, 'Maximum 10 MCP servers allowed'), + subAgents: z + .array(SubAgentSchema) + .max(5, 'Maximum 5 sub-agents allowed') + .optional() + .default([]), codeExecutionEnabled: z.boolean().optional().default(false), isPublic: z.boolean().optional().default(false), tags: z diff --git a/packages/cdk/lib/construct/agent-builder.ts b/packages/cdk/lib/construct/agent-builder.ts index 10c303b35..048ec5fd3 100644 --- a/packages/cdk/lib/construct/agent-builder.ts +++ b/packages/cdk/lib/construct/agent-builder.ts @@ -12,9 +12,11 @@ import { import { Duration } from 'aws-cdk-lib'; import { UserPool } from 'aws-cdk-lib/aws-cognito'; import * as ddb from 'aws-cdk-lib/aws-dynamodb'; -import { LAMBDA_RUNTIME_NODEJS } from '../../consts'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import { LAMBDA_RUNTIME_NODEJS, LAMBDA_RUNTIME_PYTHON } from '../../consts'; import { ISecurityGroup, IVpc } from 'aws-cdk-lib/aws-ec2'; import { Effect, PolicyStatement } from 'aws-cdk-lib/aws-iam'; +import * as path from 'path'; export interface AgentBuilderProps { readonly userPool: UserPool; @@ -22,6 +24,11 @@ export interface AgentBuilderProps { readonly vpc?: IVpc; readonly securityGroups?: ISecurityGroup[]; readonly agentBuilderRuntimeArn: string; + readonly agentCoreExternalRuntimes: Array<{ + name: string; + description: string; + arn: string; + }>; readonly useCaseBuilderTable: ddb.Table; readonly useCaseIdIndexName: string; readonly cognitoUserPoolProxyEndpoint?: string; @@ -79,6 +86,39 @@ export class AgentBuilder extends Construct { }); agentBuilderFunction.role?.addToPrincipalPolicy(cognitoPolicyForAgent); + // Lambda function to list AgentCore Runtimes (Python) + const listAgentCoreRuntimesFunction = new lambda.Function( + this, + 'ListAgentCoreRuntimes', + { + runtime: LAMBDA_RUNTIME_PYTHON, + handler: 'list_agent_core_runtimes.handler', + code: lambda.Code.fromAsset( + path.join(__dirname, '../../lambda-python/list-agent-core-runtimes') + ), + memorySize: 512, + timeout: Duration.seconds(30), + environment: { + REGION: process.env.MODEL_REGION || 'us-east-1', + }, + vpc: props.vpc, + securityGroups: props.securityGroups, + } + ); + + // Grant permissions to list AgentCore runtimes + const agentCoreListPolicy = new PolicyStatement({ + effect: Effect.ALLOW, + resources: ['*'], + actions: [ + 'bedrock-agent-runtime:ListAgentRuntimes', + 'bedrock-agentcore:ListAgentRuntimes', + ], + }); + listAgentCoreRuntimesFunction.role?.addToPrincipalPolicy( + agentCoreListPolicy + ); + // API Gateway setup const authorizer = new CognitoUserPoolsAuthorizer(this, 'Authorizer', { cognitoUserPools: [userPool], @@ -89,6 +129,18 @@ export class AgentBuilder extends Construct { authorizer, }; + // AgentCore Runtimes API endpoint + const agentCoreRuntimesResource = api.root.addResource( + 'agent-core-runtimes' + ); + agentCoreRuntimesResource + .addResource('list') + .addMethod( + 'GET', + new LambdaIntegration(listAgentCoreRuntimesFunction), + commonAuthorizerProps + ); + // Agent Builder API endpoints - all routes handled by proxy+ integration const agentsResource = api.root.addResource('agents'); diff --git a/packages/cdk/lib/generative-ai-use-cases-stack.ts b/packages/cdk/lib/generative-ai-use-cases-stack.ts index 622f821e9..c29a4b27b 100644 --- a/packages/cdk/lib/generative-ai-use-cases-stack.ts +++ b/packages/cdk/lib/generative-ai-use-cases-stack.ts @@ -402,6 +402,7 @@ export class GenerativeAiUseCasesStack extends Stack { vpc: props.vpc, securityGroups, agentBuilderRuntimeArn, + agentCoreExternalRuntimes: params.agentCoreExternalRuntimes || [], useCaseBuilderTable: useCaseBuilder.useCaseBuilderTable, useCaseIdIndexName: useCaseBuilder.useCaseIdIndexName, cognitoUserPoolProxyEndpoint: props.cognitoUserPoolProxyEndpoint, 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 171eac461..22059de02 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 @@ -4505,7 +4505,7 @@ exports[`GenerativeAiUseCases matches the snapshot (closed network mode) 4`] = ` "AgentRuntimeArtifact": { "ContainerConfiguration": { "ContainerUri": { - "Fn::Sub": "123456890123.dkr.ecr.us-east-1.\${AWS::URLSuffix}/cdk-hnb659fds-container-assets-123456890123-us-east-1:269f4f4a951791e332d743bff1e6d6850b8c4702c14f0c17086d0fb9a616a782", + "Fn::Sub": "123456890123.dkr.ecr.us-east-1.\${AWS::URLSuffix}/cdk-hnb659fds-container-assets-123456890123-us-east-1:f42a396540b453877a563dbcb6b179df3ceeed214fc3260faddb4ea8356dbe7e", }, }, }, @@ -4894,7 +4894,7 @@ exports[`GenerativeAiUseCases matches the snapshot (closed network mode) 4`] = ` "AgentRuntimeArtifact": { "ContainerConfiguration": { "ContainerUri": { - "Fn::Sub": "123456890123.dkr.ecr.us-east-1.\${AWS::URLSuffix}/cdk-hnb659fds-container-assets-123456890123-us-east-1:269f4f4a951791e332d743bff1e6d6850b8c4702c14f0c17086d0fb9a616a782", + "Fn::Sub": "123456890123.dkr.ecr.us-east-1.\${AWS::URLSuffix}/cdk-hnb659fds-container-assets-123456890123-us-east-1:f42a396540b453877a563dbcb6b179df3ceeed214fc3260faddb4ea8356dbe7e", }, }, }, @@ -5419,11 +5419,16 @@ exports[`GenerativeAiUseCases matches the snapshot (closed network mode) 6`] = ` "Type": "AWS::IAM::Role", "UpdateReplacePolicy": "Delete", }, - "APIApiDeployment3A5021234533d7a90339971d4b652d34a0e5b458": { + "APIApiDeployment3A502123443164f28712b5d8580115e2cbfba744": { "DeletionPolicy": "Delete", "DependsOn": [ "APIApiApi4XXDCF913C8", "APIApiApi5XX11B67643", + "APIApiagentcoreruntimeslistGET7F4D6AF5", + "APIApiagentcoreruntimeslistOPTIONSC9350495", + "APIApiagentcoreruntimeslist9E8E595F", + "APIApiagentcoreruntimesOPTIONS4A704D2C", + "APIApiagentcoreruntimes82DA2CE9", "APIApiagentsproxyANY44A4A08E", "APIApiagentsproxyOPTIONS65C845F9", "APIApiagentsproxy440913A2", @@ -5581,7 +5586,7 @@ exports[`GenerativeAiUseCases matches the snapshot (closed network mode) 6`] = ` ], "Properties": { "DeploymentId": { - "Ref": "APIApiDeployment3A5021234533d7a90339971d4b652d34a0e5b458", + "Ref": "APIApiDeployment3A502123443164f28712b5d8580115e2cbfba744", }, "RestApiId": { "Ref": "APIApiFFA96F67", @@ -5674,6 +5679,228 @@ exports[`GenerativeAiUseCases matches the snapshot (closed network mode) 6`] = ` "Type": "AWS::ApiGateway::Method", "UpdateReplacePolicy": "Delete", }, + "APIApiagentcoreruntimes82DA2CE9": { + "DeletionPolicy": "Delete", + "Properties": { + "ParentId": { + "Fn::GetAtt": [ + "APIApiFFA96F67", + "RootResourceId", + ], + }, + "PathPart": "agent-core-runtimes", + "RestApiId": { + "Ref": "APIApiFFA96F67", + }, + }, + "Type": "AWS::ApiGateway::Resource", + "UpdateReplacePolicy": "Delete", + }, + "APIApiagentcoreruntimesOPTIONS4A704D2C": { + "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": "APIApiagentcoreruntimes82DA2CE9", + }, + "RestApiId": { + "Ref": "APIApiFFA96F67", + }, + }, + "Type": "AWS::ApiGateway::Method", + "UpdateReplacePolicy": "Delete", + }, + "APIApiagentcoreruntimeslist9E8E595F": { + "DeletionPolicy": "Delete", + "Properties": { + "ParentId": { + "Ref": "APIApiagentcoreruntimes82DA2CE9", + }, + "PathPart": "list", + "RestApiId": { + "Ref": "APIApiFFA96F67", + }, + }, + "Type": "AWS::ApiGateway::Resource", + "UpdateReplacePolicy": "Delete", + }, + "APIApiagentcoreruntimeslistGET7F4D6AF5": { + "DeletionPolicy": "Delete", + "Properties": { + "AuthorizationType": "COGNITO_USER_POOLS", + "AuthorizerId": { + "Ref": "AgentBuilderAuthorizerE8188502", + }, + "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": [ + "AgentBuilderListAgentCoreRuntimes12B7904A", + "Arn", + ], + }, + "/invocations", + ], + ], + }, + }, + "ResourceId": { + "Ref": "APIApiagentcoreruntimeslist9E8E595F", + }, + "RestApiId": { + "Ref": "APIApiFFA96F67", + }, + }, + "Type": "AWS::ApiGateway::Method", + "UpdateReplacePolicy": "Delete", + }, + "APIApiagentcoreruntimeslistGETApiPermissionGenerativeAiUseCasesStackAPIApi89219E17GETagentcoreruntimeslistDE21581D": { + "DeletionPolicy": "Delete", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "AgentBuilderListAgentCoreRuntimes12B7904A", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":execute-api:us-east-1:123456890123:", + { + "Ref": "APIApiFFA96F67", + }, + "/", + { + "Ref": "APIApiDeploymentStageapiCD55D117", + }, + "/GET/agent-core-runtimes/list", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + "UpdateReplacePolicy": "Delete", + }, + "APIApiagentcoreruntimeslistGETApiPermissionTestGenerativeAiUseCasesStackAPIApi89219E17GETagentcoreruntimeslist4A6163C3": { + "DeletionPolicy": "Delete", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "AgentBuilderListAgentCoreRuntimes12B7904A", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":execute-api:us-east-1:123456890123:", + { + "Ref": "APIApiFFA96F67", + }, + "/test-invoke-stage/GET/agent-core-runtimes/list", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + "UpdateReplacePolicy": "Delete", + }, + "APIApiagentcoreruntimeslistOPTIONSC9350495": { + "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": "APIApiagentcoreruntimeslist9E8E595F", + }, + "RestApiId": { + "Ref": "APIApiFFA96F67", + }, + }, + "Type": "AWS::ApiGateway::Method", + "UpdateReplacePolicy": "Delete", + }, "APIApiagents70FB378D": { "DeletionPolicy": "Delete", "Properties": { @@ -18277,6 +18504,125 @@ exports[`GenerativeAiUseCases matches the snapshot (closed network mode) 6`] = ` "Type": "AWS::ApiGateway::Authorizer", "UpdateReplacePolicy": "Delete", }, + "AgentBuilderListAgentCoreRuntimes12B7904A": { + "DeletionPolicy": "Delete", + "DependsOn": [ + "AgentBuilderListAgentCoreRuntimesServiceRoleDefaultPolicyC30F50BC", + "AgentBuilderListAgentCoreRuntimesServiceRoleA666E5A5", + ], + "Properties": { + "Code": { + "S3Bucket": "cdk-hnb659fds-assets-123456890123-us-east-1", + "S3Key": "HASH-REPLACED.zip", + }, + "Environment": { + "Variables": { + "REGION": "us-east-1", + }, + }, + "Handler": "list_agent_core_runtimes.handler", + "MemorySize": 512, + "Role": { + "Fn::GetAtt": [ + "AgentBuilderListAgentCoreRuntimesServiceRoleA666E5A5", + "Arn", + ], + }, + "Runtime": "python3.12", + "Timeout": 30, + "VpcConfig": { + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "LambdaSeurityGroup14B5161A", + "GroupId", + ], + }, + ], + "SubnetIds": [ + { + "Fn::ImportValue": "ClosedNetworkStack:ExportsOutputRefClosedVpcisolatedSubnet1Subnet2EF6D3F36370B312", + }, + { + "Fn::ImportValue": "ClosedNetworkStack:ExportsOutputRefClosedVpcisolatedSubnet2SubnetB169C8D3D828FC40", + }, + ], + }, + }, + "Type": "AWS::Lambda::Function", + "UpdateReplacePolicy": "Delete", + }, + "AgentBuilderListAgentCoreRuntimesServiceRoleA666E5A5": { + "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", + }, + "AgentBuilderListAgentCoreRuntimesServiceRoleDefaultPolicyC30F50BC": { + "DeletionPolicy": "Delete", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "bedrock-agent-runtime:ListAgentRuntimes", + "bedrock-agentcore:ListAgentRuntimes", + ], + "Effect": "Allow", + "Resource": "*", + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "AgentBuilderListAgentCoreRuntimesServiceRoleDefaultPolicyC30F50BC", + "Roles": [ + { + "Ref": "AgentBuilderListAgentCoreRuntimesServiceRoleA666E5A5", + }, + ], + }, + "Type": "AWS::IAM::Policy", + "UpdateReplacePolicy": "Delete", + }, "AgentBuilderServiceRole8C5962B5": { "DeletionPolicy": "Delete", "Properties": { @@ -26415,7 +26761,7 @@ exports[`GenerativeAiUseCases matches the snapshot 4`] = ` "AgentRuntimeArtifact": { "ContainerConfiguration": { "ContainerUri": { - "Fn::Sub": "123456890123.dkr.ecr.us-east-1.\${AWS::URLSuffix}/cdk-hnb659fds-container-assets-123456890123-us-east-1:269f4f4a951791e332d743bff1e6d6850b8c4702c14f0c17086d0fb9a616a782", + "Fn::Sub": "123456890123.dkr.ecr.us-east-1.\${AWS::URLSuffix}/cdk-hnb659fds-container-assets-123456890123-us-east-1:f42a396540b453877a563dbcb6b179df3ceeed214fc3260faddb4ea8356dbe7e", }, }, }, @@ -26804,7 +27150,7 @@ exports[`GenerativeAiUseCases matches the snapshot 4`] = ` "AgentRuntimeArtifact": { "ContainerConfiguration": { "ContainerUri": { - "Fn::Sub": "123456890123.dkr.ecr.us-east-1.\${AWS::URLSuffix}/cdk-hnb659fds-container-assets-123456890123-us-east-1:269f4f4a951791e332d743bff1e6d6850b8c4702c14f0c17086d0fb9a616a782", + "Fn::Sub": "123456890123.dkr.ecr.us-east-1.\${AWS::URLSuffix}/cdk-hnb659fds-container-assets-123456890123-us-east-1:f42a396540b453877a563dbcb6b179df3ceeed214fc3260faddb4ea8356dbe7e", }, }, }, @@ -27342,11 +27688,16 @@ exports[`GenerativeAiUseCases matches the snapshot 6`] = ` "Type": "AWS::IAM::Role", "UpdateReplacePolicy": "Delete", }, - "APIApiDeployment3A5021234db71f4ccf16e49a8440e8926bf1f516": { + "APIApiDeployment3A502123fe7f5652021120e8086a7357ce9a665b": { "DeletionPolicy": "Delete", "DependsOn": [ "APIApiApi4XXDCF913C8", "APIApiApi5XX11B67643", + "APIApiagentcoreruntimeslistGET7F4D6AF5", + "APIApiagentcoreruntimeslistOPTIONSC9350495", + "APIApiagentcoreruntimeslist9E8E595F", + "APIApiagentcoreruntimesOPTIONS4A704D2C", + "APIApiagentcoreruntimes82DA2CE9", "APIApiagentsproxyANY44A4A08E", "APIApiagentsproxyOPTIONS65C845F9", "APIApiagentsproxy440913A2", @@ -27504,7 +27855,7 @@ exports[`GenerativeAiUseCases matches the snapshot 6`] = ` ], "Properties": { "DeploymentId": { - "Ref": "APIApiDeployment3A5021234db71f4ccf16e49a8440e8926bf1f516", + "Ref": "APIApiDeployment3A502123fe7f5652021120e8086a7357ce9a665b", }, "RestApiId": { "Ref": "APIApiFFA96F67", @@ -27567,6 +27918,228 @@ exports[`GenerativeAiUseCases matches the snapshot 6`] = ` "Type": "AWS::ApiGateway::Method", "UpdateReplacePolicy": "Delete", }, + "APIApiagentcoreruntimes82DA2CE9": { + "DeletionPolicy": "Delete", + "Properties": { + "ParentId": { + "Fn::GetAtt": [ + "APIApiFFA96F67", + "RootResourceId", + ], + }, + "PathPart": "agent-core-runtimes", + "RestApiId": { + "Ref": "APIApiFFA96F67", + }, + }, + "Type": "AWS::ApiGateway::Resource", + "UpdateReplacePolicy": "Delete", + }, + "APIApiagentcoreruntimesOPTIONS4A704D2C": { + "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": "APIApiagentcoreruntimes82DA2CE9", + }, + "RestApiId": { + "Ref": "APIApiFFA96F67", + }, + }, + "Type": "AWS::ApiGateway::Method", + "UpdateReplacePolicy": "Delete", + }, + "APIApiagentcoreruntimeslist9E8E595F": { + "DeletionPolicy": "Delete", + "Properties": { + "ParentId": { + "Ref": "APIApiagentcoreruntimes82DA2CE9", + }, + "PathPart": "list", + "RestApiId": { + "Ref": "APIApiFFA96F67", + }, + }, + "Type": "AWS::ApiGateway::Resource", + "UpdateReplacePolicy": "Delete", + }, + "APIApiagentcoreruntimeslistGET7F4D6AF5": { + "DeletionPolicy": "Delete", + "Properties": { + "AuthorizationType": "COGNITO_USER_POOLS", + "AuthorizerId": { + "Ref": "AgentBuilderAuthorizerE8188502", + }, + "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": [ + "AgentBuilderListAgentCoreRuntimes12B7904A", + "Arn", + ], + }, + "/invocations", + ], + ], + }, + }, + "ResourceId": { + "Ref": "APIApiagentcoreruntimeslist9E8E595F", + }, + "RestApiId": { + "Ref": "APIApiFFA96F67", + }, + }, + "Type": "AWS::ApiGateway::Method", + "UpdateReplacePolicy": "Delete", + }, + "APIApiagentcoreruntimeslistGETApiPermissionGenerativeAiUseCasesStackAPIApi89219E17GETagentcoreruntimeslistDE21581D": { + "DeletionPolicy": "Delete", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "AgentBuilderListAgentCoreRuntimes12B7904A", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":execute-api:us-east-1:123456890123:", + { + "Ref": "APIApiFFA96F67", + }, + "/", + { + "Ref": "APIApiDeploymentStageapiCD55D117", + }, + "/GET/agent-core-runtimes/list", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + "UpdateReplacePolicy": "Delete", + }, + "APIApiagentcoreruntimeslistGETApiPermissionTestGenerativeAiUseCasesStackAPIApi89219E17GETagentcoreruntimeslist4A6163C3": { + "DeletionPolicy": "Delete", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "AgentBuilderListAgentCoreRuntimes12B7904A", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":execute-api:us-east-1:123456890123:", + { + "Ref": "APIApiFFA96F67", + }, + "/test-invoke-stage/GET/agent-core-runtimes/list", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + "UpdateReplacePolicy": "Delete", + }, + "APIApiagentcoreruntimeslistOPTIONSC9350495": { + "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": "APIApiagentcoreruntimeslist9E8E595F", + }, + "RestApiId": { + "Ref": "APIApiFFA96F67", + }, + }, + "Type": "AWS::ApiGateway::Method", + "UpdateReplacePolicy": "Delete", + }, "APIApiagents70FB378D": { "DeletionPolicy": "Delete", "Properties": { @@ -39183,6 +39756,95 @@ exports[`GenerativeAiUseCases matches the snapshot 6`] = ` "Type": "AWS::ApiGateway::Authorizer", "UpdateReplacePolicy": "Delete", }, + "AgentBuilderListAgentCoreRuntimes12B7904A": { + "DeletionPolicy": "Delete", + "DependsOn": [ + "AgentBuilderListAgentCoreRuntimesServiceRoleDefaultPolicyC30F50BC", + "AgentBuilderListAgentCoreRuntimesServiceRoleA666E5A5", + ], + "Properties": { + "Code": { + "S3Bucket": "cdk-hnb659fds-assets-123456890123-us-east-1", + "S3Key": "HASH-REPLACED.zip", + }, + "Environment": { + "Variables": { + "REGION": "us-east-1", + }, + }, + "Handler": "list_agent_core_runtimes.handler", + "MemorySize": 512, + "Role": { + "Fn::GetAtt": [ + "AgentBuilderListAgentCoreRuntimesServiceRoleA666E5A5", + "Arn", + ], + }, + "Runtime": "python3.12", + "Timeout": 30, + }, + "Type": "AWS::Lambda::Function", + "UpdateReplacePolicy": "Delete", + }, + "AgentBuilderListAgentCoreRuntimesServiceRoleA666E5A5": { + "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", + }, + "AgentBuilderListAgentCoreRuntimesServiceRoleDefaultPolicyC30F50BC": { + "DeletionPolicy": "Delete", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "bedrock-agent-runtime:ListAgentRuntimes", + "bedrock-agentcore:ListAgentRuntimes", + ], + "Effect": "Allow", + "Resource": "*", + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "AgentBuilderListAgentCoreRuntimesServiceRoleDefaultPolicyC30F50BC", + "Roles": [ + { + "Ref": "AgentBuilderListAgentCoreRuntimesServiceRoleA666E5A5", + }, + ], + }, + "Type": "AWS::IAM::Policy", + "UpdateReplacePolicy": "Delete", + }, "AgentBuilderServiceRole8C5962B5": { "DeletionPolicy": "Delete", "Properties": { diff --git a/packages/types/src/agent-builder.d.ts b/packages/types/src/agent-builder.d.ts index 5a36b8d79..5723ff050 100644 --- a/packages/types/src/agent-builder.d.ts +++ b/packages/types/src/agent-builder.d.ts @@ -13,6 +13,13 @@ export type MCPServerConfig = { // MCP Server Reference (what users specify) export type MCPServerReference = string; +// Sub-Agent Configuration (for multi-agent collaboration) +export type SubAgent = { + name: string; + description: string; + arn: string; +}; + // Common items for all agent data // Table: PartitionKey=id, SortKey=dataType export type AgentCommon = { @@ -27,6 +34,7 @@ export type AgentContent = { description?: string; systemPrompt: string; mcpServers: MCPServerReference[]; // Changed to string array + subAgents?: SubAgent[]; // Optional array of sub-agents modelId: string; codeExecutionEnabled?: boolean; tags?: string[]; @@ -41,6 +49,7 @@ export type AgentInTable = AgentCommon & codeExecutionEnabled: boolean; isPublic: boolean; tags: string[]; + subAgents: SubAgent[]; // Required in table (default to empty array) starCount: number; // Number of users who favorited this agent createdAt: string; updatedAt: string; @@ -184,6 +193,7 @@ export type AgentCoreRuntimeRequest = { files?: File[]; // Added support for file uploads userId?: string; // User ID for MCP server management mcpServers?: MCPServerReference[]; // Changed to string array + subAgents?: SubAgent[]; // Sub-agents for multi-agent collaboration agentId?: string; // Agent ID for logging/tracking codeExecutionEnabled?: boolean; // Code execution setting }; diff --git a/packages/web/public/locales/translation/en.yaml b/packages/web/public/locales/translation/en.yaml index 20d081f1d..4ef221aab 100644 --- a/packages/web/public/locales/translation/en.yaml +++ b/packages/web/public/locales/translation/en.yaml @@ -71,6 +71,7 @@ agent_builder: mcp_servers: MCP Servers mcp_servers_description: Add MCP servers to provide tools and capabilities to your agent model: Model + multi_agent_configuration: Multi Agent Configuration my_agents: My Agents name: Name name_too_long: Agent name must be 100 characters or less @@ -82,6 +83,8 @@ agent_builder: 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 + no_sub_agents_available: No AgentCore Runtime agents are currently available. + no_sub_agents_selected: No sub-agents selected. Your agent will operate independently without delegation capabilities. overwrite_confirm: Overwrite overwrite_prompt_message: A system prompt already exists. Do you want to overwrite it with AI-generated prompt? overwrite_prompt_title: Overwrite System Prompt Confirmation @@ -112,6 +115,7 @@ agent_builder: share_publicly: Share publicly showing_results: Showing {{start}}-{{end}} of {{total}} results start_chatting_with: Start chatting with {{name}} + sub_agent_description: Select AgentCore Runtime agents to enable multi-agent collaboration. Sub-agents can be delegated specialized tasks. system_prompt: System Prompt tags: Tags test: Test diff --git a/packages/web/src/components/agentBuilder/AgentChatUnified.tsx b/packages/web/src/components/agentBuilder/AgentChatUnified.tsx index 0691c1d6f..61c9988b2 100644 --- a/packages/web/src/components/agentBuilder/AgentChatUnified.tsx +++ b/packages/web/src/components/agentBuilder/AgentChatUnified.tsx @@ -204,7 +204,8 @@ Please respond as this agent with the specified behavior and personality.`; agent.mcpServers, agent.agentId, modelId, - agent.codeExecutionEnabled ?? false + agent.codeExecutionEnabled ?? false, + agent.subAgents ); setChatContent(''); diff --git a/packages/web/src/components/agentBuilder/AgentForm.tsx b/packages/web/src/components/agentBuilder/AgentForm.tsx index a7345d967..7519dc7bf 100644 --- a/packages/web/src/components/agentBuilder/AgentForm.tsx +++ b/packages/web/src/components/agentBuilder/AgentForm.tsx @@ -6,6 +6,7 @@ import InputText from '../InputText'; import Textarea from '../Textarea'; import Select from '../Select'; import MCPServerManager from './MCPServerManager'; +import SubAgentManager, { SubAgent } from './SubAgentManager'; import ModalDialog from '../ModalDialog'; import { MODELS } from '../../hooks/useModel'; import { AgentConfiguration } from 'generative-ai-use-cases'; @@ -19,6 +20,7 @@ export interface AgentFormData { systemPrompt: string; modelId: string; mcpServers: string[]; + subAgents: SubAgent[]; codeExecutionEnabled: boolean; isPublic: boolean; tags: string[]; @@ -54,6 +56,7 @@ const AgentForm: React.FC = ({ modelId: availableModels[0] || 'us.anthropic.claude-3-5-sonnet-20241022-v2:0', mcpServers: [], + subAgents: [], codeExecutionEnabled: false, isPublic: false, tags: [], @@ -112,6 +115,7 @@ const AgentForm: React.FC = ({ systemPrompt: initialData.systemPrompt || '', modelId: initialData.modelId || availableModels[0] || '', mcpServers: initialData.mcpServers || [], + subAgents: initialData.subAgents || [], codeExecutionEnabled: initialData.codeExecutionEnabled || false, isPublic: initialData.isPublic || false, tags: initialData.tags || [], @@ -334,6 +338,16 @@ const AgentForm: React.FC = ({ /> + {/* Multi Agent Configuration */} +
+ + setFormData({ ...formData, subAgents: subAgents }) + } + /> +
+ {/* Tool Settings */}

diff --git a/packages/web/src/components/agentBuilder/SubAgentManager.tsx b/packages/web/src/components/agentBuilder/SubAgentManager.tsx new file mode 100644 index 000000000..003ad0185 --- /dev/null +++ b/packages/web/src/components/agentBuilder/SubAgentManager.tsx @@ -0,0 +1,143 @@ +import React, { useState, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; +import { PiInfo, PiSpinner } from 'react-icons/pi'; +import { + AgentCoreRuntime, + useAgentCoreRuntimes, +} from '../../hooks/useAgentCoreRuntimes'; + +export interface SubAgent { + name: string; + description: string; + arn: string; +} + +interface SubAgentManagerProps { + subAgents: SubAgent[]; + onChange: (subAgents: SubAgent[]) => void; +} + +const SubAgentManager: React.FC = ({ + subAgents, + onChange, +}) => { + const { t } = useTranslation(); + const { runtimes, loading, error } = useAgentCoreRuntimes(); + const [selectedArns, setSelectedArns] = useState>(new Set()); + + // Initialize selected ARNs from props + useEffect(() => { + setSelectedArns(new Set(subAgents.map((agent) => agent.arn))); + }, [subAgents]); + + // Handle sub-agent selection toggle + const handleSubAgentToggle = (runtime: AgentCoreRuntime) => { + const newSelected = new Set(selectedArns); + const newSubAgents = [...subAgents]; + + if (newSelected.has(runtime.arn)) { + // Remove from selection + newSelected.delete(runtime.arn); + const filteredSubAgents = newSubAgents.filter( + (agent) => agent.arn !== runtime.arn + ); + setSelectedArns(newSelected); + onChange(filteredSubAgents); + } else { + // Add to selection + newSelected.add(runtime.arn); + newSubAgents.push({ + name: runtime.name, + description: runtime.description, + arn: runtime.arn, + }); + setSelectedArns(newSelected); + onChange(newSubAgents); + } + }; + + return ( +
+
+

+ {t('agent_builder.multi_agent_configuration')} +

+
+ + {/* eslint-disable-next-line @shopify/jsx-no-hardcoded-content */} + {selectedArns.size} selected +
+
+ +
+

+ {t( + 'agent_builder.sub_agent_description', + 'Select AgentCore Runtime agents to enable multi-agent collaboration. Sub-agents can be delegated specialized tasks.' + )} +

+ + {loading ? ( +
+ + + {t('common.loading', 'Loading...')} + +
+ ) : error ? ( +
+

{error}

+
+ ) : runtimes.length === 0 ? ( +
+

+ {t( + 'agent_builder.no_sub_agents_available', + 'No AgentCore Runtime agents are currently available.' + )} +

+
+ ) : ( +
+ {runtimes.map((runtime) => ( + + ))} + + {selectedArns.size === 0 && ( +
+

+ {t( + 'agent_builder.no_sub_agents_selected', + 'No sub-agents selected. Your agent will operate independently without delegation capabilities.' + )} +

+
+ )} +
+ )} +
+
+ ); +}; + +export default SubAgentManager; diff --git a/packages/web/src/hooks/agentBuilder/useAgentBuilderList.ts b/packages/web/src/hooks/agentBuilder/useAgentBuilderList.ts index 944eeca67..b7304dbb0 100644 --- a/packages/web/src/hooks/agentBuilder/useAgentBuilderList.ts +++ b/packages/web/src/hooks/agentBuilder/useAgentBuilderList.ts @@ -93,6 +93,7 @@ const useAgentBuilderList = () => { description: t('agent_builder.external_agent_description'), systemPrompt: '', // External agents don't have editable system prompts mcpServers: [], + subAgents: [], modelId: '', codeExecutionEnabled: false, isPublic: false, @@ -116,6 +117,7 @@ const useAgentBuilderList = () => { agent.description || t('agent_builder.bedrock_agent_description'), systemPrompt: '', // Bedrock agents don't have editable system prompts mcpServers: [], + subAgents: [], modelId: agent.displayName, codeExecutionEnabled: false, isPublic: false, diff --git a/packages/web/src/hooks/useAgentCore.ts b/packages/web/src/hooks/useAgentCore.ts index 640dabbdb..c78f95306 100644 --- a/packages/web/src/hooks/useAgentCore.ts +++ b/packages/web/src/hooks/useAgentCore.ts @@ -56,7 +56,8 @@ const useAgentCore = (id: string) => { mcpServers?: string[], agentId?: string, modelId?: string, // Add modelId parameter - codeExecutionEnabled?: boolean // Add codeExecutionEnabled parameter + codeExecutionEnabled?: boolean, // Add codeExecutionEnabled parameter + subAgents?: Array<{ name: string; description: string; arn: string }> // Add subAgents parameter ) => { // Use provided modelId or fall back to current model ID const targetModelId = modelId || getModelId(); @@ -109,6 +110,7 @@ const useAgentCore = (id: string) => { files, // Pass the uploaded files - they will be converted to Strands format in useAgentCoreApi userId, mcpServers, + subAgents, agentId, codeExecutionEnabled, }; diff --git a/packages/web/src/hooks/useAgentCoreApi.ts b/packages/web/src/hooks/useAgentCoreApi.ts index 63291780e..4e62e1240 100644 --- a/packages/web/src/hooks/useAgentCoreApi.ts +++ b/packages/web/src/hooks/useAgentCoreApi.ts @@ -152,6 +152,7 @@ const useAgentCoreApi = (id: string) => { }, ...(req.userId && { user_id: req.userId }), ...(req.mcpServers && { mcp_servers: req.mcpServers }), + ...(req.subAgents && { sub_agents: req.subAgents }), ...(req.agentId && { agent_id: req.agentId }), ...(req.sessionId && { session_id: req.sessionId }), ...(req.codeExecutionEnabled !== undefined && { diff --git a/packages/web/src/hooks/useAgentCoreRuntimes.ts b/packages/web/src/hooks/useAgentCoreRuntimes.ts new file mode 100644 index 000000000..2ca6ccc52 --- /dev/null +++ b/packages/web/src/hooks/useAgentCoreRuntimes.ts @@ -0,0 +1,87 @@ +/** + * useAgentCoreRuntimes Hook + * + * Provides available AgentCore Runtime agents for multi-agent configuration. + */ + +import { useState, useEffect } from 'react'; +import { fetchAuthSession } from 'aws-amplify/auth'; + +export interface AgentCoreRuntime { + name: string; + description: string; + arn: string; + status?: string; + createdAt?: string; + updatedAt?: string; +} + +/** + * Custom hook that fetches available AgentCore Runtime agents from AWS. + * + * @returns Object containing runtimes array, loading state, and error + */ +export const useAgentCoreRuntimes = () => { + const [runtimes, setRuntimes] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchRuntimes = async () => { + try { + setLoading(true); + + // Get the API endpoint from environment + const apiEndpoint = import.meta.env.VITE_APP_API_ENDPOINT; + if (!apiEndpoint) { + throw new Error('API endpoint not configured'); + } + + // Get authentication token + const session = await fetchAuthSession(); + const token = session.tokens?.idToken?.toString(); + if (!token) { + throw new Error('User is not authenticated'); + } + + // Call the API to list AgentCore runtimes + const response = await fetch( + `${apiEndpoint}/agent-core-runtimes/list`, + { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + Authorization: token, + }, + } + ); + + if (!response.ok) { + throw new Error( + `Failed to fetch runtimes: ${response.status} ${response.statusText}` + ); + } + + const data = await response.json(); + setRuntimes(data.runtimes || []); + setError(null); + } catch (err) { + console.error('Error fetching AgentCore Runtimes:', err); + setError( + err instanceof Error + ? err.message + : 'Failed to load AgentCore Runtime agents' + ); + setRuntimes([]); + } finally { + setLoading(false); + } + }; + + fetchRuntimes(); + }, []); + + return { runtimes, loading, error }; +}; + +export default useAgentCoreRuntimes; diff --git a/packages/web/src/pages/agentBuilder/AgentBuilderEditPage.tsx b/packages/web/src/pages/agentBuilder/AgentBuilderEditPage.tsx index 7a2df3718..ead2aa953 100644 --- a/packages/web/src/pages/agentBuilder/AgentBuilderEditPage.tsx +++ b/packages/web/src/pages/agentBuilder/AgentBuilderEditPage.tsx @@ -43,6 +43,7 @@ const AgentBuilderEditPage: React.FC = () => { systemPrompt: formData.systemPrompt, modelId: formData.modelId, mcpServers: formData.mcpServers as string[], // Explicit type assertion + subAgents: formData.subAgents, codeExecutionEnabled: formData.codeExecutionEnabled, isPublic: formData.isPublic, tags: formData.tags, @@ -61,6 +62,7 @@ const AgentBuilderEditPage: React.FC = () => { systemPrompt: formData.systemPrompt, modelId: formData.modelId, mcpServers: formData.mcpServers as string[], // Explicit type assertion + subAgents: formData.subAgents, codeExecutionEnabled: formData.codeExecutionEnabled, isPublic: formData.isPublic, tags: formData.tags, @@ -201,6 +203,7 @@ const AgentBuilderEditPage: React.FC = () => { systemPrompt: currentFormData.systemPrompt, modelId: currentFormData.modelId, mcpServers: currentFormData.mcpServers as string[], + subAgents: currentFormData.subAgents || [], codeExecutionEnabled: currentFormData.codeExecutionEnabled, isPublic: currentFormData.isPublic, @@ -214,6 +217,7 @@ const AgentBuilderEditPage: React.FC = () => { : { ...agent!, agentId: agentId || 'temp-id', + subAgents: agent?.subAgents || [], isPublic: false, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), diff --git a/packages/web/tailwind.config.ts b/packages/web/tailwind.config.ts index c4343939f..c11dd56ae 100644 --- a/packages/web/tailwind.config.ts +++ b/packages/web/tailwind.config.ts @@ -12,6 +12,16 @@ export default { }, fontFamily: { body: ['"Noto Sans JP"', 'Sarabun'], + mono: [ + 'ui-monospace', + 'SFMono-Regular', + 'Menlo', + 'Monaco', + 'Consolas', + '"Liberation Mono"', + '"Courier New"', + 'monospace', + ], }, extend: { transitionProperty: {