diff --git a/integ-tests/create-protocols.test.ts b/integ-tests/create-protocols.test.ts new file mode 100644 index 00000000..b752ab27 --- /dev/null +++ b/integ-tests/create-protocols.test.ts @@ -0,0 +1,274 @@ +/* eslint-disable security/detect-non-literal-fs-filename */ +import { exists, prereqs, readProjectConfig, runCLI } from '../src/test-utils/index.js'; +import { randomUUID } from 'node:crypto'; +import { mkdir, readFile, rm } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; + +describe.skipIf(!prereqs.npm || !prereqs.git)('integration: create with protocol modes', () => { + let testDir: string; + + beforeAll(async () => { + testDir = join(tmpdir(), `agentcore-integ-protocols-${randomUUID()}`); + await mkdir(testDir, { recursive: true }); + }); + + afterAll(async () => { + await rm(testDir, { recursive: true, force: true }); + }); + + it('creates MCP standalone project (no framework, no model provider)', async () => { + const name = `Mcp${Date.now().toString().slice(-6)}`; + const result = await runCLI( + ['create', '--name', name, '--language', 'Python', '--protocol', 'MCP', '--json'], + testDir + ); + + expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0); + const json = JSON.parse(result.stdout); + expect(json.success).toBe(true); + + const agentName = json.agentName || name; + const agentDir = join(json.projectPath, 'app', agentName); + + expect(await exists(agentDir), 'Agent directory should exist').toBe(true); + expect(await exists(join(agentDir, 'main.py')), 'main.py should exist').toBe(true); + expect(await exists(join(agentDir, 'pyproject.toml')), 'pyproject.toml should exist').toBe(true); + + // Verify pyproject.toml references mcp (FastMCP) + const pyproject = await readFile(join(agentDir, 'pyproject.toml'), 'utf-8'); + expect(pyproject.toLowerCase().includes('mcp'), 'pyproject.toml should reference mcp').toBe(true); + + // Verify config has protocol set to MCP + const config = await readProjectConfig(json.projectPath); + const agents = config.agents as Record[]; + expect(agents).toBeDefined(); + expect(agents.length).toBe(1); + expect(agents[0]!.name).toBe(agentName); + expect(agents[0]!.protocol).toBe('MCP'); + + // MCP agents should have no credentials + const credentials = (config.credentials as Record[]) ?? []; + expect(credentials.length).toBe(0); + }); + + it('creates A2A project with Strands framework', async () => { + const name = `A2a${Date.now().toString().slice(-6)}`; + const result = await runCLI( + [ + 'create', + '--name', + name, + '--language', + 'Python', + '--protocol', + 'A2A', + '--framework', + 'Strands', + '--model-provider', + 'Bedrock', + '--memory', + 'none', + '--json', + ], + testDir + ); + + expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0); + const json = JSON.parse(result.stdout); + expect(json.success).toBe(true); + + const agentName = json.agentName || name; + const agentDir = join(json.projectPath, 'app', agentName); + + expect(await exists(agentDir), 'Agent directory should exist').toBe(true); + expect(await exists(join(agentDir, 'main.py')), 'main.py should exist').toBe(true); + + // Verify config has protocol set to A2A + const config = await readProjectConfig(json.projectPath); + const agents = config.agents as Record[]; + expect(agents.length).toBe(1); + expect(agents[0]!.protocol).toBe('A2A'); + }); + + it('creates HTTP project with explicit protocol HTTP', async () => { + const name = `Http${Date.now().toString().slice(-6)}`; + const result = await runCLI( + [ + 'create', + '--name', + name, + '--language', + 'Python', + '--framework', + 'Strands', + '--model-provider', + 'Bedrock', + '--memory', + 'none', + '--json', + ], + testDir + ); + + expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0); + const json = JSON.parse(result.stdout); + expect(json.success).toBe(true); + + // Verify config has explicit protocol: HTTP + const config = await readProjectConfig(json.projectPath); + const agents = config.agents as Record[]; + expect(agents.length).toBe(1); + expect(agents[0]!.protocol).toBe('HTTP'); + }); + + it('rejects invalid protocol', async () => { + const name = `Bad${Date.now().toString().slice(-6)}`; + const result = await runCLI( + ['create', '--name', name, '--language', 'Python', '--protocol', 'INVALID', '--json'], + testDir + ); + + expect(result.exitCode).not.toBe(0); + }); + + it('rejects MCP with --framework flag', async () => { + const name = `McpFw${Date.now().toString().slice(-6)}`; + const result = await runCLI( + ['create', '--name', name, '--language', 'Python', '--protocol', 'MCP', '--framework', 'Strands', '--json'], + testDir + ); + + expect(result.exitCode).not.toBe(0); + }); + + it('rejects A2A with unsupported framework (CrewAI)', async () => { + const name = `A2aCrew${Date.now().toString().slice(-6)}`; + const result = await runCLI( + [ + 'create', + '--name', + name, + '--language', + 'Python', + '--protocol', + 'A2A', + '--framework', + 'CrewAI', + '--model-provider', + 'Bedrock', + '--memory', + 'none', + '--json', + ], + testDir + ); + + expect(result.exitCode).not.toBe(0); + }); +}); + +describe.skipIf(!prereqs.npm || !prereqs.git)('integration: add agent with protocol modes', () => { + let testDir: string; + let projectPath: string; + + beforeAll(async () => { + testDir = join(tmpdir(), `agentcore-integ-add-protocol-${randomUUID()}`); + await mkdir(testDir, { recursive: true }); + + // Create a base project first (HTTP, no agent) + const result = await runCLI(['create', '--name', 'ProtoTest', '--no-agent', '--json'], testDir); + expect(result.exitCode, `setup stderr: ${result.stderr}`).toBe(0); + const json = JSON.parse(result.stdout); + projectPath = json.projectPath; + }); + + afterAll(async () => { + await rm(testDir, { recursive: true, force: true }); + }); + + it('adds MCP agent to existing project', async () => { + const name = `McpAgent${Date.now().toString().slice(-6)}`; + const result = await runCLI( + ['add', 'agent', '--name', name, '--protocol', 'MCP', '--language', 'Python', '--json'], + projectPath + ); + + expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0); + const json = JSON.parse(result.stdout); + expect(json.success).toBe(true); + + const config = await readProjectConfig(projectPath); + const agents = config.agents as Record[]; + const mcpAgent = agents.find(a => a.name === name); + expect(mcpAgent).toBeDefined(); + expect(mcpAgent!.protocol).toBe('MCP'); + }); + + it('adds A2A agent to existing project', async () => { + const name = `A2aAgent${Date.now().toString().slice(-6)}`; + const result = await runCLI( + [ + 'add', + 'agent', + '--name', + name, + '--protocol', + 'A2A', + '--framework', + 'Strands', + '--model-provider', + 'Bedrock', + '--memory', + 'none', + '--language', + 'Python', + '--json', + ], + projectPath + ); + + expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0); + const json = JSON.parse(result.stdout); + expect(json.success).toBe(true); + + const config = await readProjectConfig(projectPath); + const agents = config.agents as Record[]; + const a2aAgent = agents.find(a => a.name === name); + expect(a2aAgent).toBeDefined(); + expect(a2aAgent!.protocol).toBe('A2A'); + }); + + it('adds BYO agent with MCP protocol', async () => { + const name = `ByoMcp${Date.now().toString().slice(-6)}`; + const result = await runCLI( + [ + 'add', + 'agent', + '--name', + name, + '--type', + 'byo', + '--protocol', + 'MCP', + '--language', + 'Python', + '--code-location', + `app/${name}/`, + '--json', + ], + projectPath + ); + + expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0); + const json = JSON.parse(result.stdout); + expect(json.success).toBe(true); + + const config = await readProjectConfig(projectPath); + const agents = config.agents as Record[]; + const byoAgent = agents.find(a => a.name === name); + expect(byoAgent).toBeDefined(); + expect(byoAgent!.protocol).toBe('MCP'); + }); +}); diff --git a/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap b/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap index 0e2f5950..f40291ea 100644 --- a/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap +++ b/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap @@ -438,54 +438,78 @@ exports[`Assets Directory Snapshots > File listing > should match the expected f "mcp/python/README.md", "mcp/python/pyproject.toml", "mcp/python/server.py", - "python/autogen/base/README.md", - "python/autogen/base/gitignore.template", - "python/autogen/base/main.py", - "python/autogen/base/mcp_client/__init__.py", - "python/autogen/base/mcp_client/client.py", - "python/autogen/base/model/__init__.py", - "python/autogen/base/model/load.py", - "python/autogen/base/pyproject.toml", - "python/crewai/base/README.md", - "python/crewai/base/gitignore.template", - "python/crewai/base/main.py", - "python/crewai/base/model/__init__.py", - "python/crewai/base/model/load.py", - "python/crewai/base/pyproject.toml", - "python/googleadk/base/README.md", - "python/googleadk/base/gitignore.template", - "python/googleadk/base/main.py", - "python/googleadk/base/mcp_client/__init__.py", - "python/googleadk/base/mcp_client/client.py", - "python/googleadk/base/model/__init__.py", - "python/googleadk/base/model/load.py", - "python/googleadk/base/pyproject.toml", - "python/langchain_langgraph/base/README.md", - "python/langchain_langgraph/base/gitignore.template", - "python/langchain_langgraph/base/main.py", - "python/langchain_langgraph/base/mcp_client/__init__.py", - "python/langchain_langgraph/base/mcp_client/client.py", - "python/langchain_langgraph/base/model/__init__.py", - "python/langchain_langgraph/base/model/load.py", - "python/langchain_langgraph/base/pyproject.toml", - "python/openaiagents/base/README.md", - "python/openaiagents/base/gitignore.template", - "python/openaiagents/base/main.py", - "python/openaiagents/base/mcp_client/__init__.py", - "python/openaiagents/base/mcp_client/client.py", - "python/openaiagents/base/model/__init__.py", - "python/openaiagents/base/model/load.py", - "python/openaiagents/base/pyproject.toml", - "python/strands/base/README.md", - "python/strands/base/gitignore.template", - "python/strands/base/main.py", - "python/strands/base/mcp_client/__init__.py", - "python/strands/base/mcp_client/client.py", - "python/strands/base/model/__init__.py", - "python/strands/base/model/load.py", - "python/strands/base/pyproject.toml", - "python/strands/capabilities/memory/__init__.py", - "python/strands/capabilities/memory/session.py", + "python/a2a/googleadk/base/README.md", + "python/a2a/googleadk/base/gitignore.template", + "python/a2a/googleadk/base/main.py", + "python/a2a/googleadk/base/model/__init__.py", + "python/a2a/googleadk/base/model/load.py", + "python/a2a/googleadk/base/pyproject.toml", + "python/a2a/langchain_langgraph/base/README.md", + "python/a2a/langchain_langgraph/base/gitignore.template", + "python/a2a/langchain_langgraph/base/main.py", + "python/a2a/langchain_langgraph/base/model/__init__.py", + "python/a2a/langchain_langgraph/base/model/load.py", + "python/a2a/langchain_langgraph/base/pyproject.toml", + "python/a2a/strands/base/README.md", + "python/a2a/strands/base/gitignore.template", + "python/a2a/strands/base/main.py", + "python/a2a/strands/base/model/__init__.py", + "python/a2a/strands/base/model/load.py", + "python/a2a/strands/base/pyproject.toml", + "python/a2a/strands/capabilities/memory/__init__.py", + "python/a2a/strands/capabilities/memory/session.py", + "python/http/autogen/base/README.md", + "python/http/autogen/base/gitignore.template", + "python/http/autogen/base/main.py", + "python/http/autogen/base/mcp_client/__init__.py", + "python/http/autogen/base/mcp_client/client.py", + "python/http/autogen/base/model/__init__.py", + "python/http/autogen/base/model/load.py", + "python/http/autogen/base/pyproject.toml", + "python/http/crewai/base/README.md", + "python/http/crewai/base/gitignore.template", + "python/http/crewai/base/main.py", + "python/http/crewai/base/model/__init__.py", + "python/http/crewai/base/model/load.py", + "python/http/crewai/base/pyproject.toml", + "python/http/googleadk/base/README.md", + "python/http/googleadk/base/gitignore.template", + "python/http/googleadk/base/main.py", + "python/http/googleadk/base/mcp_client/__init__.py", + "python/http/googleadk/base/mcp_client/client.py", + "python/http/googleadk/base/model/__init__.py", + "python/http/googleadk/base/model/load.py", + "python/http/googleadk/base/pyproject.toml", + "python/http/langchain_langgraph/base/README.md", + "python/http/langchain_langgraph/base/gitignore.template", + "python/http/langchain_langgraph/base/main.py", + "python/http/langchain_langgraph/base/mcp_client/__init__.py", + "python/http/langchain_langgraph/base/mcp_client/client.py", + "python/http/langchain_langgraph/base/model/__init__.py", + "python/http/langchain_langgraph/base/model/load.py", + "python/http/langchain_langgraph/base/pyproject.toml", + "python/http/openaiagents/base/README.md", + "python/http/openaiagents/base/gitignore.template", + "python/http/openaiagents/base/main.py", + "python/http/openaiagents/base/mcp_client/__init__.py", + "python/http/openaiagents/base/mcp_client/client.py", + "python/http/openaiagents/base/model/__init__.py", + "python/http/openaiagents/base/model/load.py", + "python/http/openaiagents/base/pyproject.toml", + "python/http/strands/base/README.md", + "python/http/strands/base/gitignore.template", + "python/http/strands/base/main.py", + "python/http/strands/base/mcp_client/__init__.py", + "python/http/strands/base/mcp_client/client.py", + "python/http/strands/base/model/__init__.py", + "python/http/strands/base/model/load.py", + "python/http/strands/base/pyproject.toml", + "python/http/strands/capabilities/memory/__init__.py", + "python/http/strands/capabilities/memory/session.py", + "python/mcp/standalone/base/README.md", + "python/mcp/standalone/base/gitignore.template", + "python/mcp/standalone/base/main.py", + "python/mcp/standalone/base/pyproject.toml", "typescript/.gitkeep", ] `; @@ -861,7 +885,827 @@ packages = ["."] " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/autogen/base/README.md should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/a2a/googleadk/base/README.md should match snapshot 1`] = ` +"# {{ name }} + +An A2A (Agent-to-Agent) agent deployed on Amazon Bedrock AgentCore using Google ADK. + +## Overview + +This agent implements the A2A protocol using Google's Agent Development Kit, enabling agent-to-agent communication. + +## Local Development + +\`\`\`bash +uv sync +uv run python main.py +\`\`\` + +The agent starts on port 9000. + +## Deploy + +\`\`\`bash +agentcore deploy +\`\`\` +" +`; + +exports[`Assets Directory Snapshots > Python framework assets > python/python/a2a/googleadk/base/gitignore.template should match snapshot 1`] = ` +"# Environment variables +.env + +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# Virtual environments +.venv/ +venv/ +ENV/ +env/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db +" +`; + +exports[`Assets Directory Snapshots > Python framework assets > python/python/a2a/googleadk/base/main.py should match snapshot 1`] = ` +"from google.adk.agents import Agent +from google.adk.a2a.executor.a2a_agent_executor import A2aAgentExecutor +from google.adk.runners import Runner +from google.adk.sessions import InMemorySessionService +from a2a.types import AgentCapabilities, AgentCard, AgentSkill +from bedrock_agentcore.runtime import serve_a2a +from model.load import load_model + + +def add_numbers(a: int, b: int) -> int: + """Return the sum of two numbers.""" + return a + b + + +agent = Agent( + model=load_model(), + name="{{ name }}", + description="A helpful assistant that can use tools.", + instruction="You are a helpful assistant. Use tools when appropriate.", + tools=[add_numbers], +) + +runner = Runner( + app_name=agent.name, + agent=agent, + session_service=InMemorySessionService(), +) + +card = AgentCard( + name=agent.name, + description=agent.description, + url="http://localhost:9000/", + version="0.1.0", + capabilities=AgentCapabilities(streaming=True), + skills=[ + AgentSkill( + id="tools", + name="tools", + description="Use tools to help answer questions", + tags=["tools"], + ) + ], + default_input_modes=["text"], + default_output_modes=["text"], +) + +if __name__ == "__main__": + serve_a2a(A2aAgentExecutor(runner=runner), card) +" +`; + +exports[`Assets Directory Snapshots > Python framework assets > python/python/a2a/googleadk/base/model/__init__.py should match snapshot 1`] = ` +"# Package marker +" +`; + +exports[`Assets Directory Snapshots > Python framework assets > python/python/a2a/googleadk/base/model/load.py should match snapshot 1`] = ` +"import os +from bedrock_agentcore.identity.auth import requires_api_key + +IDENTITY_PROVIDER_NAME = "{{identityProviders.[0].name}}" +IDENTITY_ENV_VAR = "{{identityProviders.[0].envVarName}}" + + +@requires_api_key(provider_name=IDENTITY_PROVIDER_NAME) +def _agentcore_identity_api_key_provider(api_key: str) -> str: + """Fetch API key from AgentCore Identity.""" + return api_key + + +def _get_api_key() -> str: + """ + Uses AgentCore Identity for API key management in deployed environments. + For local development, run via 'agentcore dev' which loads agentcore/.env. + """ + if os.getenv("LOCAL_DEV") == "1": + api_key = os.getenv(IDENTITY_ENV_VAR) + if not api_key: + raise RuntimeError( + f"{IDENTITY_ENV_VAR} not found. Add {IDENTITY_ENV_VAR}=your-key to .env.local" + ) + return api_key + return _agentcore_identity_api_key_provider() + + +def load_model() -> None: + """ + Set up Gemini API key authentication. + Uses AgentCore Identity for API key management in deployed environments, + and falls back to .env file for local development. + Sets the GOOGLE_API_KEY environment variable for the Google ADK. + """ + api_key = _get_api_key() + # Use Google AI Studios API Key Authentication. + # https://google.github.io/adk-docs/agents/models/#google-ai-studio + os.environ["GOOGLE_API_KEY"] = api_key + # Set to TRUE is using Google Vertex AI, Set to FALSE for Google AI Studio + os.environ["GOOGLE_GENAI_USE_VERTEXAI"] = "FALSE" +" +`; + +exports[`Assets Directory Snapshots > Python framework assets > python/python/a2a/googleadk/base/pyproject.toml should match snapshot 1`] = ` +"[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "{{ name }}" +version = "0.1.0" +description = "AgentCore A2A Agent using Google ADK" +readme = "README.md" +requires-python = ">=3.10" +dependencies = [ + "a2a-sdk >= 0.2.0", + "aws-opentelemetry-distro", + "bedrock-agentcore[a2a] >= 1.0.3", + "google-adk >= 1.0.0", + "google-genai >= 1.0.0", +] + +[tool.hatch.build.targets.wheel] +packages = ["."] +" +`; + +exports[`Assets Directory Snapshots > Python framework assets > python/python/a2a/langchain_langgraph/base/README.md should match snapshot 1`] = ` +"# {{ name }} + +An A2A (Agent-to-Agent) agent deployed on Amazon Bedrock AgentCore using LangChain + LangGraph. + +## Overview + +This agent implements the A2A protocol using LangGraph, enabling agent-to-agent communication. + +## Local Development + +\`\`\`bash +uv sync +uv run python main.py +\`\`\` + +The agent starts on port 9000. + +## Deploy + +\`\`\`bash +agentcore deploy +\`\`\` +" +`; + +exports[`Assets Directory Snapshots > Python framework assets > python/python/a2a/langchain_langgraph/base/gitignore.template should match snapshot 1`] = ` +"# Environment variables +.env + +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# Virtual environments +.venv/ +venv/ +ENV/ +env/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db +" +`; + +exports[`Assets Directory Snapshots > Python framework assets > python/python/a2a/langchain_langgraph/base/main.py should match snapshot 1`] = ` +"from langchain_core.tools import tool +from langgraph.prebuilt import create_react_agent +from a2a.server.agent_execution import AgentExecutor, RequestContext +from a2a.server.events import EventQueue +from a2a.server.tasks import TaskUpdater +from a2a.types import AgentCapabilities, AgentCard, AgentSkill, Part, TextPart +from a2a.utils import new_task +from bedrock_agentcore.runtime import serve_a2a +from model.load import load_model + + +@tool +def add_numbers(a: int, b: int) -> int: + """Return the sum of two numbers.""" + return a + b + + +model = load_model() +graph = create_react_agent(model, tools=[add_numbers]) + + +class LangGraphA2AExecutor(AgentExecutor): + """Wraps a LangGraph CompiledGraph as an a2a-sdk AgentExecutor.""" + + def __init__(self, graph): + self.graph = graph + + async def execute(self, context: RequestContext, event_queue: EventQueue) -> None: + task = context.current_task or new_task(context.message) + if not context.current_task: + await event_queue.enqueue_event(task) + updater = TaskUpdater(event_queue, task.id, task.context_id) + + user_text = context.get_user_input() + result = await self.graph.ainvoke({"messages": [("user", user_text)]}) + response = result["messages"][-1].content + + await updater.add_artifact([Part(root=TextPart(text=response))]) + await updater.complete() + + async def cancel(self, context: RequestContext, event_queue: EventQueue) -> None: + pass + + +card = AgentCard( + name="{{ name }}", + description="A LangGraph agent on Bedrock AgentCore", + url="http://localhost:9000/", + version="0.1.0", + capabilities=AgentCapabilities(streaming=True), + skills=[ + AgentSkill( + id="tools", + name="tools", + description="Use tools to help answer questions", + tags=["tools"], + ) + ], + default_input_modes=["text"], + default_output_modes=["text"], +) + +if __name__ == "__main__": + serve_a2a(LangGraphA2AExecutor(graph), card) +" +`; + +exports[`Assets Directory Snapshots > Python framework assets > python/python/a2a/langchain_langgraph/base/model/__init__.py should match snapshot 1`] = ` +"# Package marker +" +`; + +exports[`Assets Directory Snapshots > Python framework assets > python/python/a2a/langchain_langgraph/base/model/load.py should match snapshot 1`] = ` +"{{#if (eq modelProvider "Bedrock")}} +from langchain_aws import ChatBedrock + +# Uses global inference profile for Claude Sonnet 4.5 +# https://docs.aws.amazon.com/bedrock/latest/userguide/inference-profiles-support.html +MODEL_ID = "global.anthropic.claude-sonnet-4-5-20250929-v1:0" + + +def load_model() -> ChatBedrock: + """Get Bedrock model client using IAM credentials.""" + return ChatBedrock(model_id=MODEL_ID) +{{/if}} +{{#if (eq modelProvider "Anthropic")}} +import os +from langchain_anthropic import ChatAnthropic +from bedrock_agentcore.identity.auth import requires_api_key + +IDENTITY_PROVIDER_NAME = "{{identityProviders.[0].name}}" +IDENTITY_ENV_VAR = "{{identityProviders.[0].envVarName}}" + + +@requires_api_key(provider_name=IDENTITY_PROVIDER_NAME) +def _agentcore_identity_api_key_provider(api_key: str) -> str: + """Fetch API key from AgentCore Identity.""" + return api_key + + +def _get_api_key() -> str: + """ + Uses AgentCore Identity for API key management in deployed environments. + For local development, run via 'agentcore dev' which loads agentcore/.env. + """ + if os.getenv("LOCAL_DEV") == "1": + api_key = os.getenv(IDENTITY_ENV_VAR) + if not api_key: + raise RuntimeError( + f"{IDENTITY_ENV_VAR} not found. Add {IDENTITY_ENV_VAR}=your-key to .env.local" + ) + return api_key + return _agentcore_identity_api_key_provider() + + +def load_model() -> ChatAnthropic: + """Get authenticated Anthropic model client.""" + return ChatAnthropic( + model="claude-sonnet-4-5-20250929", + api_key=_get_api_key() + ) +{{/if}} +{{#if (eq modelProvider "OpenAI")}} +import os +from langchain_openai import ChatOpenAI +from bedrock_agentcore.identity.auth import requires_api_key + +IDENTITY_PROVIDER_NAME = "{{identityProviders.[0].name}}" +IDENTITY_ENV_VAR = "{{identityProviders.[0].envVarName}}" + + +@requires_api_key(provider_name=IDENTITY_PROVIDER_NAME) +def _agentcore_identity_api_key_provider(api_key: str) -> str: + """Fetch API key from AgentCore Identity.""" + return api_key + + +def _get_api_key() -> str: + """ + Uses AgentCore Identity for API key management in deployed environments. + For local development, run via 'agentcore dev' which loads agentcore/.env. + """ + if os.getenv("LOCAL_DEV") == "1": + api_key = os.getenv(IDENTITY_ENV_VAR) + if not api_key: + raise RuntimeError( + f"{IDENTITY_ENV_VAR} not found. Add {IDENTITY_ENV_VAR}=your-key to .env.local" + ) + return api_key + return _agentcore_identity_api_key_provider() + + +def load_model() -> ChatOpenAI: + """Get authenticated OpenAI model client.""" + return ChatOpenAI( + model="gpt-4.1", + api_key=_get_api_key() + ) +{{/if}} +{{#if (eq modelProvider "Gemini")}} +import os +from langchain_google_genai import ChatGoogleGenerativeAI +from bedrock_agentcore.identity.auth import requires_api_key + +IDENTITY_PROVIDER_NAME = "{{identityProviders.[0].name}}" +IDENTITY_ENV_VAR = "{{identityProviders.[0].envVarName}}" + + +@requires_api_key(provider_name=IDENTITY_PROVIDER_NAME) +def _agentcore_identity_api_key_provider(api_key: str) -> str: + """Fetch API key from AgentCore Identity.""" + return api_key + + +def _get_api_key() -> str: + """ + Uses AgentCore Identity for API key management in deployed environments. + For local development, run via 'agentcore dev' which loads agentcore/.env. + """ + if os.getenv("LOCAL_DEV") == "1": + api_key = os.getenv(IDENTITY_ENV_VAR) + if not api_key: + raise RuntimeError( + f"{IDENTITY_ENV_VAR} not found. Add {IDENTITY_ENV_VAR}=your-key to .env.local" + ) + return api_key + return _agentcore_identity_api_key_provider() + + +def load_model() -> ChatGoogleGenerativeAI: + """Get authenticated Gemini model client.""" + return ChatGoogleGenerativeAI( + model="gemini-2.5-flash", + api_key=_get_api_key() + ) +{{/if}} +" +`; + +exports[`Assets Directory Snapshots > Python framework assets > python/python/a2a/langchain_langgraph/base/pyproject.toml should match snapshot 1`] = ` +"[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "{{ name }}" +version = "0.1.0" +description = "AgentCore A2A Agent using LangChain + LangGraph" +readme = "README.md" +requires-python = ">=3.10" +dependencies = [ + "a2a-sdk >= 0.2.0", + {{#if (eq modelProvider "Anthropic")}}"langchain-anthropic >= 0.3.0", + {{/if}}{{#if (eq modelProvider "Bedrock")}}"langchain-aws >= 0.2.0", + {{/if}}{{#if (eq modelProvider "Gemini")}}"langchain-google-genai >= 2.0.0", + {{/if}}{{#if (eq modelProvider "OpenAI")}}"langchain-openai >= 0.2.0", + {{/if}}"aws-opentelemetry-distro", + "bedrock-agentcore[a2a] >= 1.0.3", + "botocore[crt] >= 1.35.0", + "langgraph >= 0.2.0", +] + +[tool.hatch.build.targets.wheel] +packages = ["."] +" +`; + +exports[`Assets Directory Snapshots > Python framework assets > python/python/a2a/strands/base/README.md should match snapshot 1`] = ` +"# {{ name }} + +An A2A (Agent-to-Agent) agent deployed on Amazon Bedrock AgentCore using Strands SDK. + +## Overview + +This agent implements the A2A protocol, enabling agent-to-agent communication. Other agents can discover and interact with this agent via the \`/.well-known/agent-card.json\` endpoint. + +## Local Development + +\`\`\`bash +uv sync +uv run python main.py +\`\`\` + +The agent starts on port 9000. + +## Deploy + +\`\`\`bash +agentcore deploy +\`\`\` +" +`; + +exports[`Assets Directory Snapshots > Python framework assets > python/python/a2a/strands/base/gitignore.template should match snapshot 1`] = ` +"# Environment variables +.env + +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# Virtual environments +.venv/ +venv/ +ENV/ +env/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db +" +`; + +exports[`Assets Directory Snapshots > Python framework assets > python/python/a2a/strands/base/main.py should match snapshot 1`] = ` +"from strands import Agent, tool +from strands.multiagent.a2a.executor import StrandsA2AExecutor +from bedrock_agentcore.runtime import serve_a2a +from model.load import load_model +{{#if hasMemory}} +from memory.session import get_memory_session_manager +{{/if}} + + +@tool +def add_numbers(a: int, b: int) -> int: + """Return the sum of two numbers.""" + return a + b + + +tools = [add_numbers] + +{{#if hasMemory}} +def agent_factory(): + cache = {} + def get_or_create_agent(session_id, user_id): + key = f"{session_id}/{user_id}" + if key not in cache: + cache[key] = Agent( + model=load_model(), + session_manager=get_memory_session_manager(session_id, user_id), + system_prompt="You are a helpful assistant. Use tools when appropriate.", + tools=tools, + ) + return cache[key] + return get_or_create_agent + +get_or_create_agent = agent_factory() +agent = get_or_create_agent("default-session", "default-user") +{{else}} +agent = Agent( + model=load_model(), + system_prompt="You are a helpful assistant. Use tools when appropriate.", + tools=tools, +) +{{/if}} + +if __name__ == "__main__": + serve_a2a(StrandsA2AExecutor(agent)) +" +`; + +exports[`Assets Directory Snapshots > Python framework assets > python/python/a2a/strands/base/model/__init__.py should match snapshot 1`] = ` +"# Package marker +" +`; + +exports[`Assets Directory Snapshots > Python framework assets > python/python/a2a/strands/base/model/load.py should match snapshot 1`] = ` +"{{#if (eq modelProvider "Bedrock")}} +from strands.models.bedrock import BedrockModel + + +def load_model() -> BedrockModel: + """Get Bedrock model client using IAM credentials.""" + return BedrockModel(model_id="global.anthropic.claude-sonnet-4-5-20250929-v1:0") +{{/if}} +{{#if (eq modelProvider "Anthropic")}} +import os + +from strands.models.anthropic import AnthropicModel +from bedrock_agentcore.identity.auth import requires_api_key + +IDENTITY_PROVIDER_NAME = "{{identityProviders.[0].name}}" +IDENTITY_ENV_VAR = "{{identityProviders.[0].envVarName}}" + + +@requires_api_key(provider_name=IDENTITY_PROVIDER_NAME) +def _agentcore_identity_api_key_provider(api_key: str) -> str: + """Fetch API key from AgentCore Identity.""" + return api_key + + +def _get_api_key() -> str: + """ + Uses AgentCore Identity for API key management in deployed environments. + For local development, run via 'agentcore dev' which loads agentcore/.env. + """ + if os.getenv("LOCAL_DEV") == "1": + api_key = os.getenv(IDENTITY_ENV_VAR) + if not api_key: + raise RuntimeError( + f"{IDENTITY_ENV_VAR} not found. Add {IDENTITY_ENV_VAR}=your-key to .env.local" + ) + return api_key + return _agentcore_identity_api_key_provider() + + +def load_model() -> AnthropicModel: + """Get authenticated Anthropic model client.""" + return AnthropicModel( + client_args={"api_key": _get_api_key()}, + model_id="claude-sonnet-4-5-20250929", + max_tokens=5000, + ) +{{/if}} +{{#if (eq modelProvider "OpenAI")}} +import os + +from strands.models.openai import OpenAIModel +from bedrock_agentcore.identity.auth import requires_api_key + +IDENTITY_PROVIDER_NAME = "{{identityProviders.[0].name}}" +IDENTITY_ENV_VAR = "{{identityProviders.[0].envVarName}}" + + +@requires_api_key(provider_name=IDENTITY_PROVIDER_NAME) +def _agentcore_identity_api_key_provider(api_key: str) -> str: + """Fetch API key from AgentCore Identity.""" + return api_key + + +def _get_api_key() -> str: + """ + Uses AgentCore Identity for API key management in deployed environments. + For local development, run via 'agentcore dev' which loads agentcore/.env. + """ + if os.getenv("LOCAL_DEV") == "1": + api_key = os.getenv(IDENTITY_ENV_VAR) + if not api_key: + raise RuntimeError( + f"{IDENTITY_ENV_VAR} not found. Add {IDENTITY_ENV_VAR}=your-key to .env.local" + ) + return api_key + return _agentcore_identity_api_key_provider() + + +def load_model() -> OpenAIModel: + """Get authenticated OpenAI model client.""" + return OpenAIModel( + client_args={"api_key": _get_api_key()}, + model_id="gpt-4.1", + ) +{{/if}} +{{#if (eq modelProvider "Gemini")}} +import os + +from strands.models.gemini import GeminiModel +from bedrock_agentcore.identity.auth import requires_api_key + +IDENTITY_PROVIDER_NAME = "{{identityProviders.[0].name}}" +IDENTITY_ENV_VAR = "{{identityProviders.[0].envVarName}}" + + +@requires_api_key(provider_name=IDENTITY_PROVIDER_NAME) +def _agentcore_identity_api_key_provider(api_key: str) -> str: + """Fetch API key from AgentCore Identity.""" + return api_key + + +def _get_api_key() -> str: + """ + Uses AgentCore Identity for API key management in deployed environments. + For local development, run via 'agentcore dev' which loads agentcore/.env. + """ + if os.getenv("LOCAL_DEV") == "1": + api_key = os.getenv(IDENTITY_ENV_VAR) + if not api_key: + raise RuntimeError( + f"{IDENTITY_ENV_VAR} not found. Add {IDENTITY_ENV_VAR}=your-key to .env.local" + ) + return api_key + return _agentcore_identity_api_key_provider() + + +def load_model() -> GeminiModel: + """Get authenticated Gemini model client.""" + return GeminiModel( + client_args={"api_key": _get_api_key()}, + model_id="gemini-2.5-flash", + ) +{{/if}} +" +`; + +exports[`Assets Directory Snapshots > Python framework assets > python/python/a2a/strands/base/pyproject.toml should match snapshot 1`] = ` +"[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "{{ name }}" +version = "0.1.0" +description = "AgentCore A2A Agent using Strands SDK" +readme = "README.md" +requires-python = ">=3.10" +dependencies = [ + {{#if (eq modelProvider "Anthropic")}}"anthropic >= 0.30.0", + {{/if}}"aws-opentelemetry-distro", + "bedrock-agentcore[a2a] >= 1.0.3", + "botocore[crt] >= 1.35.0", + {{#if (eq modelProvider "Gemini")}}"google-genai >= 1.0.0", + {{/if}}{{#if (eq modelProvider "OpenAI")}}"openai >= 1.0.0", + {{/if}}"strands-agents >= 1.13.0", +] + +[tool.hatch.build.targets.wheel] +packages = ["."] +" +`; + +exports[`Assets Directory Snapshots > Python framework assets > python/python/a2a/strands/capabilities/memory/__init__.py should match snapshot 1`] = ` +"# Package marker +" +`; + +exports[`Assets Directory Snapshots > Python framework assets > python/python/a2a/strands/capabilities/memory/session.py should match snapshot 1`] = ` +"import os +from typing import Optional + +from bedrock_agentcore.memory.integrations.strands.config import AgentCoreMemoryConfig{{#if memoryProviders.[0].strategies.length}}, RetrievalConfig{{/if}} +from bedrock_agentcore.memory.integrations.strands.session_manager import AgentCoreMemorySessionManager + +MEMORY_ID = os.getenv("{{memoryProviders.[0].envVarName}}") +REGION = os.getenv("AWS_REGION") + +def get_memory_session_manager(session_id: str, actor_id: str) -> Optional[AgentCoreMemorySessionManager]: + if not MEMORY_ID: + return None + +{{#if memoryProviders.[0].strategies.length}} + retrieval_config = { +{{#if (includes memoryProviders.[0].strategies "SEMANTIC")}} + f"/users/{actor_id}/facts": RetrievalConfig(top_k=3, relevance_score=0.5), +{{/if}} +{{#if (includes memoryProviders.[0].strategies "USER_PREFERENCE")}} + f"/users/{actor_id}/preferences": RetrievalConfig(top_k=3, relevance_score=0.5), +{{/if}} +{{#if (includes memoryProviders.[0].strategies "SUMMARIZATION")}} + f"/summaries/{actor_id}/{session_id}": RetrievalConfig(top_k=3, relevance_score=0.5), +{{/if}} + } +{{/if}} + + return AgentCoreMemorySessionManager( + AgentCoreMemoryConfig( + memory_id=MEMORY_ID, + session_id=session_id, + actor_id=actor_id, +{{#if memoryProviders.[0].strategies.length}} + retrieval_config=retrieval_config, +{{/if}} + ), + REGION + ) +" +`; + +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/autogen/base/README.md should match snapshot 1`] = ` "This is a project generated by the agentcore create CLI tool! # Layout @@ -904,7 +1748,7 @@ Use \`agentcore invoke\` to invoke your deployed agent. " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/autogen/base/gitignore.template should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/autogen/base/gitignore.template should match snapshot 1`] = ` "# Environment variables .env @@ -949,7 +1793,7 @@ Thumbs.db " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/autogen/base/main.py should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/autogen/base/main.py should match snapshot 1`] = ` "import os from autogen_agentchat.agents import AssistantAgent from autogen_core.tools import FunctionTool @@ -1005,12 +1849,12 @@ if __name__ == "__main__": " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/autogen/base/mcp_client/__init__.py should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/autogen/base/mcp_client/__init__.py should match snapshot 1`] = ` "# Package marker " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/autogen/base/mcp_client/client.py should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/autogen/base/mcp_client/client.py should match snapshot 1`] = ` "from typing import List from autogen_ext.tools.mcp import ( StreamableHttpMcpToolAdapter, @@ -1032,12 +1876,12 @@ async def get_streamable_http_mcp_tools() -> List[StreamableHttpMcpToolAdapter]: " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/autogen/base/model/__init__.py should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/autogen/base/model/__init__.py should match snapshot 1`] = ` "# Package marker " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/autogen/base/model/load.py should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/autogen/base/model/load.py should match snapshot 1`] = ` "{{#if (eq modelProvider "Bedrock")}} import os from autogen_ext.models.anthropic import AnthropicBedrockChatCompletionClient @@ -1177,7 +2021,7 @@ def load_model() -> OpenAIChatCompletionClient: " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/autogen/base/pyproject.toml should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/autogen/base/pyproject.toml should match snapshot 1`] = ` "[build-system] requires = ["hatchling"] build-backend = "hatchling.build" @@ -1215,7 +2059,7 @@ packages = ["."] " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/crewai/base/README.md should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/crewai/base/README.md should match snapshot 1`] = ` "This is a project generated by the agentcore create CLI tool! # Layout @@ -1258,7 +2102,7 @@ Use \`agentcore invoke\` to invoke your deployed agent. " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/crewai/base/gitignore.template should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/crewai/base/gitignore.template should match snapshot 1`] = ` "# Environment variables .env @@ -1303,7 +2147,7 @@ Thumbs.db " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/crewai/base/main.py should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/crewai/base/main.py should match snapshot 1`] = ` "from crewai import Agent, Crew, Task, Process from crewai.tools import tool from bedrock_agentcore.runtime import BedrockAgentCoreApp @@ -1362,12 +2206,12 @@ if __name__ == "__main__": " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/crewai/base/model/__init__.py should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/crewai/base/model/__init__.py should match snapshot 1`] = ` "# Package marker " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/crewai/base/model/load.py should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/crewai/base/model/load.py should match snapshot 1`] = ` "{{#if (eq modelProvider "Bedrock")}} from crewai import LLM @@ -1504,7 +2348,7 @@ def load_model() -> LLM: " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/crewai/base/pyproject.toml should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/crewai/base/pyproject.toml should match snapshot 1`] = ` "[build-system] requires = ["hatchling"] build-backend = "hatchling.build" @@ -1539,7 +2383,7 @@ packages = ["."] " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/googleadk/base/README.md should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/googleadk/base/README.md should match snapshot 1`] = ` "This is a project generated by the agentcore create CLI tool! # Layout @@ -1582,7 +2426,7 @@ Use \`agentcore invoke\` to invoke your deployed agent. " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/googleadk/base/gitignore.template should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/googleadk/base/gitignore.template should match snapshot 1`] = ` "# Environment variables .env @@ -1627,7 +2471,7 @@ Thumbs.db " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/googleadk/base/main.py should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/googleadk/base/main.py should match snapshot 1`] = ` "import os from google.adk.agents import Agent from google.adk.runners import Runner @@ -1731,12 +2575,12 @@ if __name__ == "__main__": " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/googleadk/base/mcp_client/__init__.py should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/googleadk/base/mcp_client/__init__.py should match snapshot 1`] = ` "# Package marker " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/googleadk/base/mcp_client/client.py should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/googleadk/base/mcp_client/client.py should match snapshot 1`] = ` "import os import logging from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset @@ -1806,12 +2650,12 @@ def get_streamable_http_mcp_client() -> MCPToolset: " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/googleadk/base/model/__init__.py should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/googleadk/base/model/__init__.py should match snapshot 1`] = ` "# Package marker " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/googleadk/base/model/load.py should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/googleadk/base/model/load.py should match snapshot 1`] = ` "import os from bedrock_agentcore.identity.auth import requires_api_key @@ -1856,7 +2700,7 @@ def load_model() -> None: " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/googleadk/base/pyproject.toml should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/googleadk/base/pyproject.toml should match snapshot 1`] = ` "[build-system] requires = ["hatchling"] build-backend = "hatchling.build" @@ -1882,7 +2726,7 @@ packages = ["."] " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/langchain_langgraph/base/README.md should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/langchain_langgraph/base/README.md should match snapshot 1`] = ` "This is a project generated by the agentcore create CLI tool! # Layout @@ -1925,7 +2769,7 @@ Use \`agentcore invoke\` to invoke your deployed agent. " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/langchain_langgraph/base/gitignore.template should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/langchain_langgraph/base/gitignore.template should match snapshot 1`] = ` "# Environment variables .env @@ -1970,7 +2814,7 @@ Thumbs.db " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/langchain_langgraph/base/main.py should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/langchain_langgraph/base/main.py should match snapshot 1`] = ` "import os from langchain_core.messages import HumanMessage from langgraph.prebuilt import create_react_agent @@ -2040,12 +2884,12 @@ if __name__ == "__main__": " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/langchain_langgraph/base/mcp_client/__init__.py should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/langchain_langgraph/base/mcp_client/__init__.py should match snapshot 1`] = ` "# Package marker " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/langchain_langgraph/base/mcp_client/client.py should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/langchain_langgraph/base/mcp_client/client.py should match snapshot 1`] = ` "import os import logging from langchain_mcp_adapters.client import MultiServerMCPClient @@ -2117,12 +2961,12 @@ def get_streamable_http_mcp_client() -> MultiServerMCPClient: " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/langchain_langgraph/base/model/__init__.py should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/langchain_langgraph/base/model/__init__.py should match snapshot 1`] = ` "# Package marker " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/langchain_langgraph/base/model/load.py should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/langchain_langgraph/base/model/load.py should match snapshot 1`] = ` "{{#if (eq modelProvider "Bedrock")}} from langchain_aws import ChatBedrock @@ -2249,7 +3093,7 @@ def load_model() -> ChatGoogleGenerativeAI: " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/langchain_langgraph/base/pyproject.toml should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/langchain_langgraph/base/pyproject.toml should match snapshot 1`] = ` "[build-system] requires = ["hatchling"] build-backend = "hatchling.build" @@ -2290,7 +3134,7 @@ packages = ["."] " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/openaiagents/base/README.md should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/openaiagents/base/README.md should match snapshot 1`] = ` "This is a project generated by the agentcore create CLI tool! # Layout @@ -2333,7 +3177,7 @@ Use \`agentcore invoke\` to invoke your deployed agent. " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/openaiagents/base/gitignore.template should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/openaiagents/base/gitignore.template should match snapshot 1`] = ` "# Environment variables .env @@ -2378,7 +3222,7 @@ Thumbs.db " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/openaiagents/base/main.py should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/openaiagents/base/main.py should match snapshot 1`] = ` "import os from agents import Agent, Runner, function_tool from bedrock_agentcore.runtime import BedrockAgentCoreApp @@ -2485,12 +3329,12 @@ if __name__ == "__main__": " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/openaiagents/base/mcp_client/__init__.py should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/openaiagents/base/mcp_client/__init__.py should match snapshot 1`] = ` "# Package marker " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/openaiagents/base/mcp_client/client.py should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/openaiagents/base/mcp_client/client.py should match snapshot 1`] = ` "import os import logging from agents.mcp import MCPServerStreamableHttp @@ -2559,12 +3403,12 @@ def get_streamable_http_mcp_client() -> MCPServerStreamableHttp: " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/openaiagents/base/model/__init__.py should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/openaiagents/base/model/__init__.py should match snapshot 1`] = ` "# Package marker " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/openaiagents/base/model/load.py should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/openaiagents/base/model/load.py should match snapshot 1`] = ` "import os from bedrock_agentcore.identity.auth import requires_api_key @@ -2605,7 +3449,7 @@ def load_model() -> None: " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/openaiagents/base/pyproject.toml should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/openaiagents/base/pyproject.toml should match snapshot 1`] = ` "[build-system] requires = ["hatchling"] build-backend = "hatchling.build" @@ -2630,7 +3474,7 @@ packages = ["."] " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/strands/base/README.md should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/strands/base/README.md should match snapshot 1`] = ` "This is a project generated by the AgentCore CLI! # Layout @@ -2673,7 +3517,7 @@ Use \`agentcore invoke\` to invoke your deployed agent. " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/strands/base/gitignore.template should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/strands/base/gitignore.template should match snapshot 1`] = ` "# Environment variables .env @@ -2717,7 +3561,7 @@ env/ Thumbs.db" `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/strands/base/main.py should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/strands/base/main.py should match snapshot 1`] = ` "from strands import Agent, tool from bedrock_agentcore.runtime import BedrockAgentCoreApp from model.load import load_model @@ -2817,12 +3661,12 @@ if __name__ == "__main__": " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/strands/base/mcp_client/__init__.py should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/strands/base/mcp_client/__init__.py should match snapshot 1`] = ` "# Package marker " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/strands/base/mcp_client/client.py should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/strands/base/mcp_client/client.py should match snapshot 1`] = ` "import os import logging from mcp.client.streamable_http import streamablehttp_client @@ -2890,12 +3734,12 @@ def get_streamable_http_mcp_client() -> MCPClient: " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/strands/base/model/__init__.py should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/strands/base/model/__init__.py should match snapshot 1`] = ` "# Package marker " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/strands/base/model/load.py should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/strands/base/model/load.py should match snapshot 1`] = ` "{{#if (eq modelProvider "Bedrock")}} from strands.models.bedrock import BedrockModel @@ -3022,7 +3866,7 @@ def load_model() -> GeminiModel: " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/strands/base/pyproject.toml should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/strands/base/pyproject.toml should match snapshot 1`] = ` "[build-system] requires = ["hatchling"] build-backend = "hatchling.build" @@ -3051,12 +3895,12 @@ packages = ["."] " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/strands/capabilities/memory/__init__.py should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/strands/capabilities/memory/__init__.py should match snapshot 1`] = ` "# Package marker " `; -exports[`Assets Directory Snapshots > Python framework assets > python/python/strands/capabilities/memory/session.py should match snapshot 1`] = ` +exports[`Assets Directory Snapshots > Python framework assets > python/python/http/strands/capabilities/memory/session.py should match snapshot 1`] = ` "import os from typing import Optional @@ -3099,6 +3943,142 @@ def get_memory_session_manager(session_id: str, actor_id: str) -> Optional[Agent " `; +exports[`Assets Directory Snapshots > Python framework assets > python/python/mcp/standalone/base/README.md should match snapshot 1`] = ` +"# {{ name }} + +An MCP (Model Context Protocol) server deployed on Amazon Bedrock AgentCore. + +## Overview + +This project implements an MCP server using FastMCP. MCP servers expose tools that can be consumed by MCP clients (other agents or applications). + +## Local Development + +\`\`\`bash +# Install dependencies +uv sync + +# Run the MCP server locally +uv run python main.py +\`\`\` + +The server starts on port 8000 with Streamable HTTP transport. + +## Adding Tools + +Define tools using the \`@mcp.tool()\` decorator in \`main.py\`: + +\`\`\`python +@mcp.tool() +def my_tool(param: str) -> str: + """Description of what the tool does.""" + return f"Result: {param}" +\`\`\` + +## Deploy + +\`\`\`bash +agentcore deploy +\`\`\` +" +`; + +exports[`Assets Directory Snapshots > Python framework assets > python/python/mcp/standalone/base/gitignore.template should match snapshot 1`] = ` +"# Environment variables +.env + +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# Virtual environments +.venv/ +venv/ +ENV/ +env/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db +" +`; + +exports[`Assets Directory Snapshots > Python framework assets > python/python/mcp/standalone/base/main.py should match snapshot 1`] = ` +"from mcp.server.fastmcp import FastMCP +import uvicorn + +mcp = FastMCP("{{ name }}") + + +@mcp.tool() +def add_numbers(a: int, b: int) -> int: + """Return the sum of two numbers.""" + return a + b + + +@mcp.tool() +def greet(name: str) -> str: + """Return a greeting for the given name.""" + return f"Hello, {name}!" + + +if __name__ == "__main__": + uvicorn.run( + mcp.streamable_http_app(), + host="0.0.0.0", + port=8000, + ) +" +`; + +exports[`Assets Directory Snapshots > Python framework assets > python/python/mcp/standalone/base/pyproject.toml should match snapshot 1`] = ` +"[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "{{ name }}" +version = "0.1.0" +description = "AgentCore MCP Server" +readme = "README.md" +requires-python = ">=3.10" +dependencies = [ + "aws-opentelemetry-distro", + "bedrock-agentcore >= 1.0.3", + "mcp >= 1.19.0", + "uvicorn >= 0.30.0", +] + +[tool.hatch.build.targets.wheel] +packages = ["."] +" +`; + exports[`Assets Directory Snapshots > Root-level assets > AGENTS.md should match snapshot 1`] = ` "## AgentCore Templates diff --git a/src/assets/python/a2a/googleadk/base/README.md b/src/assets/python/a2a/googleadk/base/README.md new file mode 100644 index 00000000..03da54b6 --- /dev/null +++ b/src/assets/python/a2a/googleadk/base/README.md @@ -0,0 +1,22 @@ +# {{ name }} + +An A2A (Agent-to-Agent) agent deployed on Amazon Bedrock AgentCore using Google ADK. + +## Overview + +This agent implements the A2A protocol using Google's Agent Development Kit, enabling agent-to-agent communication. + +## Local Development + +```bash +uv sync +uv run python main.py +``` + +The agent starts on port 9000. + +## Deploy + +```bash +agentcore deploy +``` diff --git a/src/assets/python/autogen/base/gitignore.template b/src/assets/python/a2a/googleadk/base/gitignore.template similarity index 100% rename from src/assets/python/autogen/base/gitignore.template rename to src/assets/python/a2a/googleadk/base/gitignore.template diff --git a/src/assets/python/a2a/googleadk/base/main.py b/src/assets/python/a2a/googleadk/base/main.py new file mode 100644 index 00000000..f53f5704 --- /dev/null +++ b/src/assets/python/a2a/googleadk/base/main.py @@ -0,0 +1,48 @@ +from google.adk.agents import Agent +from google.adk.a2a.executor.a2a_agent_executor import A2aAgentExecutor +from google.adk.runners import Runner +from google.adk.sessions import InMemorySessionService +from a2a.types import AgentCapabilities, AgentCard, AgentSkill +from bedrock_agentcore.runtime import serve_a2a +from model.load import load_model + + +def add_numbers(a: int, b: int) -> int: + """Return the sum of two numbers.""" + return a + b + + +agent = Agent( + model=load_model(), + name="{{ name }}", + description="A helpful assistant that can use tools.", + instruction="You are a helpful assistant. Use tools when appropriate.", + tools=[add_numbers], +) + +runner = Runner( + app_name=agent.name, + agent=agent, + session_service=InMemorySessionService(), +) + +card = AgentCard( + name=agent.name, + description=agent.description, + url="http://localhost:9000/", + version="0.1.0", + capabilities=AgentCapabilities(streaming=True), + skills=[ + AgentSkill( + id="tools", + name="tools", + description="Use tools to help answer questions", + tags=["tools"], + ) + ], + default_input_modes=["text"], + default_output_modes=["text"], +) + +if __name__ == "__main__": + serve_a2a(A2aAgentExecutor(runner=runner), card) diff --git a/src/assets/python/autogen/base/mcp_client/__init__.py b/src/assets/python/a2a/googleadk/base/model/__init__.py similarity index 100% rename from src/assets/python/autogen/base/mcp_client/__init__.py rename to src/assets/python/a2a/googleadk/base/model/__init__.py diff --git a/src/assets/python/googleadk/base/model/load.py b/src/assets/python/a2a/googleadk/base/model/load.py similarity index 100% rename from src/assets/python/googleadk/base/model/load.py rename to src/assets/python/a2a/googleadk/base/model/load.py diff --git a/src/assets/python/a2a/googleadk/base/pyproject.toml b/src/assets/python/a2a/googleadk/base/pyproject.toml new file mode 100644 index 00000000..7fcb7516 --- /dev/null +++ b/src/assets/python/a2a/googleadk/base/pyproject.toml @@ -0,0 +1,20 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "{{ name }}" +version = "0.1.0" +description = "AgentCore A2A Agent using Google ADK" +readme = "README.md" +requires-python = ">=3.10" +dependencies = [ + "a2a-sdk >= 0.2.0", + "aws-opentelemetry-distro", + "bedrock-agentcore[a2a] >= 1.0.3", + "google-adk >= 1.0.0", + "google-genai >= 1.0.0", +] + +[tool.hatch.build.targets.wheel] +packages = ["."] diff --git a/src/assets/python/a2a/langchain_langgraph/base/README.md b/src/assets/python/a2a/langchain_langgraph/base/README.md new file mode 100644 index 00000000..16476262 --- /dev/null +++ b/src/assets/python/a2a/langchain_langgraph/base/README.md @@ -0,0 +1,22 @@ +# {{ name }} + +An A2A (Agent-to-Agent) agent deployed on Amazon Bedrock AgentCore using LangChain + LangGraph. + +## Overview + +This agent implements the A2A protocol using LangGraph, enabling agent-to-agent communication. + +## Local Development + +```bash +uv sync +uv run python main.py +``` + +The agent starts on port 9000. + +## Deploy + +```bash +agentcore deploy +``` diff --git a/src/assets/python/crewai/base/gitignore.template b/src/assets/python/a2a/langchain_langgraph/base/gitignore.template similarity index 100% rename from src/assets/python/crewai/base/gitignore.template rename to src/assets/python/a2a/langchain_langgraph/base/gitignore.template diff --git a/src/assets/python/a2a/langchain_langgraph/base/main.py b/src/assets/python/a2a/langchain_langgraph/base/main.py new file mode 100644 index 00000000..0412d50c --- /dev/null +++ b/src/assets/python/a2a/langchain_langgraph/base/main.py @@ -0,0 +1,64 @@ +from langchain_core.tools import tool +from langgraph.prebuilt import create_react_agent +from a2a.server.agent_execution import AgentExecutor, RequestContext +from a2a.server.events import EventQueue +from a2a.server.tasks import TaskUpdater +from a2a.types import AgentCapabilities, AgentCard, AgentSkill, Part, TextPart +from a2a.utils import new_task +from bedrock_agentcore.runtime import serve_a2a +from model.load import load_model + + +@tool +def add_numbers(a: int, b: int) -> int: + """Return the sum of two numbers.""" + return a + b + + +model = load_model() +graph = create_react_agent(model, tools=[add_numbers]) + + +class LangGraphA2AExecutor(AgentExecutor): + """Wraps a LangGraph CompiledGraph as an a2a-sdk AgentExecutor.""" + + def __init__(self, graph): + self.graph = graph + + async def execute(self, context: RequestContext, event_queue: EventQueue) -> None: + task = context.current_task or new_task(context.message) + if not context.current_task: + await event_queue.enqueue_event(task) + updater = TaskUpdater(event_queue, task.id, task.context_id) + + user_text = context.get_user_input() + result = await self.graph.ainvoke({"messages": [("user", user_text)]}) + response = result["messages"][-1].content + + await updater.add_artifact([Part(root=TextPart(text=response))]) + await updater.complete() + + async def cancel(self, context: RequestContext, event_queue: EventQueue) -> None: + pass + + +card = AgentCard( + name="{{ name }}", + description="A LangGraph agent on Bedrock AgentCore", + url="http://localhost:9000/", + version="0.1.0", + capabilities=AgentCapabilities(streaming=True), + skills=[ + AgentSkill( + id="tools", + name="tools", + description="Use tools to help answer questions", + tags=["tools"], + ) + ], + default_input_modes=["text"], + default_output_modes=["text"], +) + +if __name__ == "__main__": + serve_a2a(LangGraphA2AExecutor(graph), card) diff --git a/src/assets/python/autogen/base/model/__init__.py b/src/assets/python/a2a/langchain_langgraph/base/model/__init__.py similarity index 100% rename from src/assets/python/autogen/base/model/__init__.py rename to src/assets/python/a2a/langchain_langgraph/base/model/__init__.py diff --git a/src/assets/python/langchain_langgraph/base/model/load.py b/src/assets/python/a2a/langchain_langgraph/base/model/load.py similarity index 100% rename from src/assets/python/langchain_langgraph/base/model/load.py rename to src/assets/python/a2a/langchain_langgraph/base/model/load.py diff --git a/src/assets/python/a2a/langchain_langgraph/base/pyproject.toml b/src/assets/python/a2a/langchain_langgraph/base/pyproject.toml new file mode 100644 index 00000000..ab9b3cbe --- /dev/null +++ b/src/assets/python/a2a/langchain_langgraph/base/pyproject.toml @@ -0,0 +1,24 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "{{ name }}" +version = "0.1.0" +description = "AgentCore A2A Agent using LangChain + LangGraph" +readme = "README.md" +requires-python = ">=3.10" +dependencies = [ + "a2a-sdk >= 0.2.0", + {{#if (eq modelProvider "Anthropic")}}"langchain-anthropic >= 0.3.0", + {{/if}}{{#if (eq modelProvider "Bedrock")}}"langchain-aws >= 0.2.0", + {{/if}}{{#if (eq modelProvider "Gemini")}}"langchain-google-genai >= 2.0.0", + {{/if}}{{#if (eq modelProvider "OpenAI")}}"langchain-openai >= 0.2.0", + {{/if}}"aws-opentelemetry-distro", + "bedrock-agentcore[a2a] >= 1.0.3", + "botocore[crt] >= 1.35.0", + "langgraph >= 0.2.0", +] + +[tool.hatch.build.targets.wheel] +packages = ["."] diff --git a/src/assets/python/a2a/strands/base/README.md b/src/assets/python/a2a/strands/base/README.md new file mode 100644 index 00000000..a9d30cbb --- /dev/null +++ b/src/assets/python/a2a/strands/base/README.md @@ -0,0 +1,22 @@ +# {{ name }} + +An A2A (Agent-to-Agent) agent deployed on Amazon Bedrock AgentCore using Strands SDK. + +## Overview + +This agent implements the A2A protocol, enabling agent-to-agent communication. Other agents can discover and interact with this agent via the `/.well-known/agent-card.json` endpoint. + +## Local Development + +```bash +uv sync +uv run python main.py +``` + +The agent starts on port 9000. + +## Deploy + +```bash +agentcore deploy +``` diff --git a/src/assets/python/googleadk/base/gitignore.template b/src/assets/python/a2a/strands/base/gitignore.template similarity index 100% rename from src/assets/python/googleadk/base/gitignore.template rename to src/assets/python/a2a/strands/base/gitignore.template diff --git a/src/assets/python/a2a/strands/base/main.py b/src/assets/python/a2a/strands/base/main.py new file mode 100644 index 00000000..0e7a9f92 --- /dev/null +++ b/src/assets/python/a2a/strands/base/main.py @@ -0,0 +1,44 @@ +from strands import Agent, tool +from strands.multiagent.a2a.executor import StrandsA2AExecutor +from bedrock_agentcore.runtime import serve_a2a +from model.load import load_model +{{#if hasMemory}} +from memory.session import get_memory_session_manager +{{/if}} + + +@tool +def add_numbers(a: int, b: int) -> int: + """Return the sum of two numbers.""" + return a + b + + +tools = [add_numbers] + +{{#if hasMemory}} +def agent_factory(): + cache = {} + def get_or_create_agent(session_id, user_id): + key = f"{session_id}/{user_id}" + if key not in cache: + cache[key] = Agent( + model=load_model(), + session_manager=get_memory_session_manager(session_id, user_id), + system_prompt="You are a helpful assistant. Use tools when appropriate.", + tools=tools, + ) + return cache[key] + return get_or_create_agent + +get_or_create_agent = agent_factory() +agent = get_or_create_agent("default-session", "default-user") +{{else}} +agent = Agent( + model=load_model(), + system_prompt="You are a helpful assistant. Use tools when appropriate.", + tools=tools, +) +{{/if}} + +if __name__ == "__main__": + serve_a2a(StrandsA2AExecutor(agent)) diff --git a/src/assets/python/crewai/base/model/__init__.py b/src/assets/python/a2a/strands/base/model/__init__.py similarity index 100% rename from src/assets/python/crewai/base/model/__init__.py rename to src/assets/python/a2a/strands/base/model/__init__.py diff --git a/src/assets/python/strands/base/model/load.py b/src/assets/python/a2a/strands/base/model/load.py similarity index 100% rename from src/assets/python/strands/base/model/load.py rename to src/assets/python/a2a/strands/base/model/load.py diff --git a/src/assets/python/a2a/strands/base/pyproject.toml b/src/assets/python/a2a/strands/base/pyproject.toml new file mode 100644 index 00000000..b01475be --- /dev/null +++ b/src/assets/python/a2a/strands/base/pyproject.toml @@ -0,0 +1,22 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "{{ name }}" +version = "0.1.0" +description = "AgentCore A2A Agent using Strands SDK" +readme = "README.md" +requires-python = ">=3.10" +dependencies = [ + {{#if (eq modelProvider "Anthropic")}}"anthropic >= 0.30.0", + {{/if}}"aws-opentelemetry-distro", + "bedrock-agentcore[a2a] >= 1.0.3", + "botocore[crt] >= 1.35.0", + {{#if (eq modelProvider "Gemini")}}"google-genai >= 1.0.0", + {{/if}}{{#if (eq modelProvider "OpenAI")}}"openai >= 1.0.0", + {{/if}}"strands-agents >= 1.13.0", +] + +[tool.hatch.build.targets.wheel] +packages = ["."] diff --git a/src/assets/python/googleadk/base/mcp_client/__init__.py b/src/assets/python/a2a/strands/capabilities/memory/__init__.py similarity index 100% rename from src/assets/python/googleadk/base/mcp_client/__init__.py rename to src/assets/python/a2a/strands/capabilities/memory/__init__.py diff --git a/src/assets/python/a2a/strands/capabilities/memory/session.py b/src/assets/python/a2a/strands/capabilities/memory/session.py new file mode 100644 index 00000000..9661243b --- /dev/null +++ b/src/assets/python/a2a/strands/capabilities/memory/session.py @@ -0,0 +1,38 @@ +import os +from typing import Optional + +from bedrock_agentcore.memory.integrations.strands.config import AgentCoreMemoryConfig{{#if memoryProviders.[0].strategies.length}}, RetrievalConfig{{/if}} +from bedrock_agentcore.memory.integrations.strands.session_manager import AgentCoreMemorySessionManager + +MEMORY_ID = os.getenv("{{memoryProviders.[0].envVarName}}") +REGION = os.getenv("AWS_REGION") + +def get_memory_session_manager(session_id: str, actor_id: str) -> Optional[AgentCoreMemorySessionManager]: + if not MEMORY_ID: + return None + +{{#if memoryProviders.[0].strategies.length}} + retrieval_config = { +{{#if (includes memoryProviders.[0].strategies "SEMANTIC")}} + f"/users/{actor_id}/facts": RetrievalConfig(top_k=3, relevance_score=0.5), +{{/if}} +{{#if (includes memoryProviders.[0].strategies "USER_PREFERENCE")}} + f"/users/{actor_id}/preferences": RetrievalConfig(top_k=3, relevance_score=0.5), +{{/if}} +{{#if (includes memoryProviders.[0].strategies "SUMMARIZATION")}} + f"/summaries/{actor_id}/{session_id}": RetrievalConfig(top_k=3, relevance_score=0.5), +{{/if}} + } +{{/if}} + + return AgentCoreMemorySessionManager( + AgentCoreMemoryConfig( + memory_id=MEMORY_ID, + session_id=session_id, + actor_id=actor_id, +{{#if memoryProviders.[0].strategies.length}} + retrieval_config=retrieval_config, +{{/if}} + ), + REGION + ) diff --git a/src/assets/python/autogen/base/README.md b/src/assets/python/http/autogen/base/README.md similarity index 100% rename from src/assets/python/autogen/base/README.md rename to src/assets/python/http/autogen/base/README.md diff --git a/src/assets/python/langchain_langgraph/base/gitignore.template b/src/assets/python/http/autogen/base/gitignore.template similarity index 100% rename from src/assets/python/langchain_langgraph/base/gitignore.template rename to src/assets/python/http/autogen/base/gitignore.template diff --git a/src/assets/python/autogen/base/main.py b/src/assets/python/http/autogen/base/main.py similarity index 100% rename from src/assets/python/autogen/base/main.py rename to src/assets/python/http/autogen/base/main.py diff --git a/src/assets/python/googleadk/base/model/__init__.py b/src/assets/python/http/autogen/base/mcp_client/__init__.py similarity index 100% rename from src/assets/python/googleadk/base/model/__init__.py rename to src/assets/python/http/autogen/base/mcp_client/__init__.py diff --git a/src/assets/python/autogen/base/mcp_client/client.py b/src/assets/python/http/autogen/base/mcp_client/client.py similarity index 100% rename from src/assets/python/autogen/base/mcp_client/client.py rename to src/assets/python/http/autogen/base/mcp_client/client.py diff --git a/src/assets/python/langchain_langgraph/base/mcp_client/__init__.py b/src/assets/python/http/autogen/base/model/__init__.py similarity index 100% rename from src/assets/python/langchain_langgraph/base/mcp_client/__init__.py rename to src/assets/python/http/autogen/base/model/__init__.py diff --git a/src/assets/python/autogen/base/model/load.py b/src/assets/python/http/autogen/base/model/load.py similarity index 100% rename from src/assets/python/autogen/base/model/load.py rename to src/assets/python/http/autogen/base/model/load.py diff --git a/src/assets/python/autogen/base/pyproject.toml b/src/assets/python/http/autogen/base/pyproject.toml similarity index 100% rename from src/assets/python/autogen/base/pyproject.toml rename to src/assets/python/http/autogen/base/pyproject.toml diff --git a/src/assets/python/crewai/base/README.md b/src/assets/python/http/crewai/base/README.md similarity index 100% rename from src/assets/python/crewai/base/README.md rename to src/assets/python/http/crewai/base/README.md diff --git a/src/assets/python/openaiagents/base/gitignore.template b/src/assets/python/http/crewai/base/gitignore.template similarity index 100% rename from src/assets/python/openaiagents/base/gitignore.template rename to src/assets/python/http/crewai/base/gitignore.template diff --git a/src/assets/python/crewai/base/main.py b/src/assets/python/http/crewai/base/main.py similarity index 100% rename from src/assets/python/crewai/base/main.py rename to src/assets/python/http/crewai/base/main.py diff --git a/src/assets/python/langchain_langgraph/base/model/__init__.py b/src/assets/python/http/crewai/base/model/__init__.py similarity index 100% rename from src/assets/python/langchain_langgraph/base/model/__init__.py rename to src/assets/python/http/crewai/base/model/__init__.py diff --git a/src/assets/python/crewai/base/model/load.py b/src/assets/python/http/crewai/base/model/load.py similarity index 100% rename from src/assets/python/crewai/base/model/load.py rename to src/assets/python/http/crewai/base/model/load.py diff --git a/src/assets/python/crewai/base/pyproject.toml b/src/assets/python/http/crewai/base/pyproject.toml similarity index 100% rename from src/assets/python/crewai/base/pyproject.toml rename to src/assets/python/http/crewai/base/pyproject.toml diff --git a/src/assets/python/googleadk/base/README.md b/src/assets/python/http/googleadk/base/README.md similarity index 100% rename from src/assets/python/googleadk/base/README.md rename to src/assets/python/http/googleadk/base/README.md diff --git a/src/assets/python/http/googleadk/base/gitignore.template b/src/assets/python/http/googleadk/base/gitignore.template new file mode 100644 index 00000000..fa1c60ae --- /dev/null +++ b/src/assets/python/http/googleadk/base/gitignore.template @@ -0,0 +1,41 @@ +# Environment variables +.env + +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# Virtual environments +.venv/ +venv/ +ENV/ +env/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db diff --git a/src/assets/python/googleadk/base/main.py b/src/assets/python/http/googleadk/base/main.py similarity index 100% rename from src/assets/python/googleadk/base/main.py rename to src/assets/python/http/googleadk/base/main.py diff --git a/src/assets/python/openaiagents/base/mcp_client/__init__.py b/src/assets/python/http/googleadk/base/mcp_client/__init__.py similarity index 100% rename from src/assets/python/openaiagents/base/mcp_client/__init__.py rename to src/assets/python/http/googleadk/base/mcp_client/__init__.py diff --git a/src/assets/python/googleadk/base/mcp_client/client.py b/src/assets/python/http/googleadk/base/mcp_client/client.py similarity index 100% rename from src/assets/python/googleadk/base/mcp_client/client.py rename to src/assets/python/http/googleadk/base/mcp_client/client.py diff --git a/src/assets/python/openaiagents/base/model/__init__.py b/src/assets/python/http/googleadk/base/model/__init__.py similarity index 100% rename from src/assets/python/openaiagents/base/model/__init__.py rename to src/assets/python/http/googleadk/base/model/__init__.py diff --git a/src/assets/python/http/googleadk/base/model/load.py b/src/assets/python/http/googleadk/base/model/load.py new file mode 100644 index 00000000..7c74e1ce --- /dev/null +++ b/src/assets/python/http/googleadk/base/model/load.py @@ -0,0 +1,41 @@ +import os +from bedrock_agentcore.identity.auth import requires_api_key + +IDENTITY_PROVIDER_NAME = "{{identityProviders.[0].name}}" +IDENTITY_ENV_VAR = "{{identityProviders.[0].envVarName}}" + + +@requires_api_key(provider_name=IDENTITY_PROVIDER_NAME) +def _agentcore_identity_api_key_provider(api_key: str) -> str: + """Fetch API key from AgentCore Identity.""" + return api_key + + +def _get_api_key() -> str: + """ + Uses AgentCore Identity for API key management in deployed environments. + For local development, run via 'agentcore dev' which loads agentcore/.env. + """ + if os.getenv("LOCAL_DEV") == "1": + api_key = os.getenv(IDENTITY_ENV_VAR) + if not api_key: + raise RuntimeError( + f"{IDENTITY_ENV_VAR} not found. Add {IDENTITY_ENV_VAR}=your-key to .env.local" + ) + return api_key + return _agentcore_identity_api_key_provider() + + +def load_model() -> None: + """ + Set up Gemini API key authentication. + Uses AgentCore Identity for API key management in deployed environments, + and falls back to .env file for local development. + Sets the GOOGLE_API_KEY environment variable for the Google ADK. + """ + api_key = _get_api_key() + # Use Google AI Studios API Key Authentication. + # https://google.github.io/adk-docs/agents/models/#google-ai-studio + os.environ["GOOGLE_API_KEY"] = api_key + # Set to TRUE is using Google Vertex AI, Set to FALSE for Google AI Studio + os.environ["GOOGLE_GENAI_USE_VERTEXAI"] = "FALSE" diff --git a/src/assets/python/googleadk/base/pyproject.toml b/src/assets/python/http/googleadk/base/pyproject.toml similarity index 100% rename from src/assets/python/googleadk/base/pyproject.toml rename to src/assets/python/http/googleadk/base/pyproject.toml diff --git a/src/assets/python/langchain_langgraph/base/README.md b/src/assets/python/http/langchain_langgraph/base/README.md similarity index 100% rename from src/assets/python/langchain_langgraph/base/README.md rename to src/assets/python/http/langchain_langgraph/base/README.md diff --git a/src/assets/python/http/langchain_langgraph/base/gitignore.template b/src/assets/python/http/langchain_langgraph/base/gitignore.template new file mode 100644 index 00000000..fa1c60ae --- /dev/null +++ b/src/assets/python/http/langchain_langgraph/base/gitignore.template @@ -0,0 +1,41 @@ +# Environment variables +.env + +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# Virtual environments +.venv/ +venv/ +ENV/ +env/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db diff --git a/src/assets/python/langchain_langgraph/base/main.py b/src/assets/python/http/langchain_langgraph/base/main.py similarity index 100% rename from src/assets/python/langchain_langgraph/base/main.py rename to src/assets/python/http/langchain_langgraph/base/main.py diff --git a/src/assets/python/strands/base/mcp_client/__init__.py b/src/assets/python/http/langchain_langgraph/base/mcp_client/__init__.py similarity index 100% rename from src/assets/python/strands/base/mcp_client/__init__.py rename to src/assets/python/http/langchain_langgraph/base/mcp_client/__init__.py diff --git a/src/assets/python/langchain_langgraph/base/mcp_client/client.py b/src/assets/python/http/langchain_langgraph/base/mcp_client/client.py similarity index 100% rename from src/assets/python/langchain_langgraph/base/mcp_client/client.py rename to src/assets/python/http/langchain_langgraph/base/mcp_client/client.py diff --git a/src/assets/python/strands/base/model/__init__.py b/src/assets/python/http/langchain_langgraph/base/model/__init__.py similarity index 100% rename from src/assets/python/strands/base/model/__init__.py rename to src/assets/python/http/langchain_langgraph/base/model/__init__.py diff --git a/src/assets/python/http/langchain_langgraph/base/model/load.py b/src/assets/python/http/langchain_langgraph/base/model/load.py new file mode 100644 index 00000000..b8f2d71e --- /dev/null +++ b/src/assets/python/http/langchain_langgraph/base/model/load.py @@ -0,0 +1,123 @@ +{{#if (eq modelProvider "Bedrock")}} +from langchain_aws import ChatBedrock + +# Uses global inference profile for Claude Sonnet 4.5 +# https://docs.aws.amazon.com/bedrock/latest/userguide/inference-profiles-support.html +MODEL_ID = "global.anthropic.claude-sonnet-4-5-20250929-v1:0" + + +def load_model() -> ChatBedrock: + """Get Bedrock model client using IAM credentials.""" + return ChatBedrock(model_id=MODEL_ID) +{{/if}} +{{#if (eq modelProvider "Anthropic")}} +import os +from langchain_anthropic import ChatAnthropic +from bedrock_agentcore.identity.auth import requires_api_key + +IDENTITY_PROVIDER_NAME = "{{identityProviders.[0].name}}" +IDENTITY_ENV_VAR = "{{identityProviders.[0].envVarName}}" + + +@requires_api_key(provider_name=IDENTITY_PROVIDER_NAME) +def _agentcore_identity_api_key_provider(api_key: str) -> str: + """Fetch API key from AgentCore Identity.""" + return api_key + + +def _get_api_key() -> str: + """ + Uses AgentCore Identity for API key management in deployed environments. + For local development, run via 'agentcore dev' which loads agentcore/.env. + """ + if os.getenv("LOCAL_DEV") == "1": + api_key = os.getenv(IDENTITY_ENV_VAR) + if not api_key: + raise RuntimeError( + f"{IDENTITY_ENV_VAR} not found. Add {IDENTITY_ENV_VAR}=your-key to .env.local" + ) + return api_key + return _agentcore_identity_api_key_provider() + + +def load_model() -> ChatAnthropic: + """Get authenticated Anthropic model client.""" + return ChatAnthropic( + model="claude-sonnet-4-5-20250929", + api_key=_get_api_key() + ) +{{/if}} +{{#if (eq modelProvider "OpenAI")}} +import os +from langchain_openai import ChatOpenAI +from bedrock_agentcore.identity.auth import requires_api_key + +IDENTITY_PROVIDER_NAME = "{{identityProviders.[0].name}}" +IDENTITY_ENV_VAR = "{{identityProviders.[0].envVarName}}" + + +@requires_api_key(provider_name=IDENTITY_PROVIDER_NAME) +def _agentcore_identity_api_key_provider(api_key: str) -> str: + """Fetch API key from AgentCore Identity.""" + return api_key + + +def _get_api_key() -> str: + """ + Uses AgentCore Identity for API key management in deployed environments. + For local development, run via 'agentcore dev' which loads agentcore/.env. + """ + if os.getenv("LOCAL_DEV") == "1": + api_key = os.getenv(IDENTITY_ENV_VAR) + if not api_key: + raise RuntimeError( + f"{IDENTITY_ENV_VAR} not found. Add {IDENTITY_ENV_VAR}=your-key to .env.local" + ) + return api_key + return _agentcore_identity_api_key_provider() + + +def load_model() -> ChatOpenAI: + """Get authenticated OpenAI model client.""" + return ChatOpenAI( + model="gpt-4.1", + api_key=_get_api_key() + ) +{{/if}} +{{#if (eq modelProvider "Gemini")}} +import os +from langchain_google_genai import ChatGoogleGenerativeAI +from bedrock_agentcore.identity.auth import requires_api_key + +IDENTITY_PROVIDER_NAME = "{{identityProviders.[0].name}}" +IDENTITY_ENV_VAR = "{{identityProviders.[0].envVarName}}" + + +@requires_api_key(provider_name=IDENTITY_PROVIDER_NAME) +def _agentcore_identity_api_key_provider(api_key: str) -> str: + """Fetch API key from AgentCore Identity.""" + return api_key + + +def _get_api_key() -> str: + """ + Uses AgentCore Identity for API key management in deployed environments. + For local development, run via 'agentcore dev' which loads agentcore/.env. + """ + if os.getenv("LOCAL_DEV") == "1": + api_key = os.getenv(IDENTITY_ENV_VAR) + if not api_key: + raise RuntimeError( + f"{IDENTITY_ENV_VAR} not found. Add {IDENTITY_ENV_VAR}=your-key to .env.local" + ) + return api_key + return _agentcore_identity_api_key_provider() + + +def load_model() -> ChatGoogleGenerativeAI: + """Get authenticated Gemini model client.""" + return ChatGoogleGenerativeAI( + model="gemini-2.5-flash", + api_key=_get_api_key() + ) +{{/if}} diff --git a/src/assets/python/langchain_langgraph/base/pyproject.toml b/src/assets/python/http/langchain_langgraph/base/pyproject.toml similarity index 100% rename from src/assets/python/langchain_langgraph/base/pyproject.toml rename to src/assets/python/http/langchain_langgraph/base/pyproject.toml diff --git a/src/assets/python/openaiagents/base/README.md b/src/assets/python/http/openaiagents/base/README.md similarity index 100% rename from src/assets/python/openaiagents/base/README.md rename to src/assets/python/http/openaiagents/base/README.md diff --git a/src/assets/python/http/openaiagents/base/gitignore.template b/src/assets/python/http/openaiagents/base/gitignore.template new file mode 100644 index 00000000..fa1c60ae --- /dev/null +++ b/src/assets/python/http/openaiagents/base/gitignore.template @@ -0,0 +1,41 @@ +# Environment variables +.env + +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# Virtual environments +.venv/ +venv/ +ENV/ +env/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db diff --git a/src/assets/python/openaiagents/base/main.py b/src/assets/python/http/openaiagents/base/main.py similarity index 100% rename from src/assets/python/openaiagents/base/main.py rename to src/assets/python/http/openaiagents/base/main.py diff --git a/src/assets/python/strands/capabilities/memory/__init__.py b/src/assets/python/http/openaiagents/base/mcp_client/__init__.py similarity index 100% rename from src/assets/python/strands/capabilities/memory/__init__.py rename to src/assets/python/http/openaiagents/base/mcp_client/__init__.py diff --git a/src/assets/python/openaiagents/base/mcp_client/client.py b/src/assets/python/http/openaiagents/base/mcp_client/client.py similarity index 100% rename from src/assets/python/openaiagents/base/mcp_client/client.py rename to src/assets/python/http/openaiagents/base/mcp_client/client.py diff --git a/src/assets/python/http/openaiagents/base/model/__init__.py b/src/assets/python/http/openaiagents/base/model/__init__.py new file mode 100644 index 00000000..0e632e10 --- /dev/null +++ b/src/assets/python/http/openaiagents/base/model/__init__.py @@ -0,0 +1 @@ +# Package marker diff --git a/src/assets/python/openaiagents/base/model/load.py b/src/assets/python/http/openaiagents/base/model/load.py similarity index 100% rename from src/assets/python/openaiagents/base/model/load.py rename to src/assets/python/http/openaiagents/base/model/load.py diff --git a/src/assets/python/openaiagents/base/pyproject.toml b/src/assets/python/http/openaiagents/base/pyproject.toml similarity index 100% rename from src/assets/python/openaiagents/base/pyproject.toml rename to src/assets/python/http/openaiagents/base/pyproject.toml diff --git a/src/assets/python/strands/base/README.md b/src/assets/python/http/strands/base/README.md similarity index 100% rename from src/assets/python/strands/base/README.md rename to src/assets/python/http/strands/base/README.md diff --git a/src/assets/python/strands/base/gitignore.template b/src/assets/python/http/strands/base/gitignore.template similarity index 100% rename from src/assets/python/strands/base/gitignore.template rename to src/assets/python/http/strands/base/gitignore.template diff --git a/src/assets/python/strands/base/main.py b/src/assets/python/http/strands/base/main.py similarity index 100% rename from src/assets/python/strands/base/main.py rename to src/assets/python/http/strands/base/main.py diff --git a/src/assets/python/http/strands/base/mcp_client/__init__.py b/src/assets/python/http/strands/base/mcp_client/__init__.py new file mode 100644 index 00000000..0e632e10 --- /dev/null +++ b/src/assets/python/http/strands/base/mcp_client/__init__.py @@ -0,0 +1 @@ +# Package marker diff --git a/src/assets/python/strands/base/mcp_client/client.py b/src/assets/python/http/strands/base/mcp_client/client.py similarity index 100% rename from src/assets/python/strands/base/mcp_client/client.py rename to src/assets/python/http/strands/base/mcp_client/client.py diff --git a/src/assets/python/http/strands/base/model/__init__.py b/src/assets/python/http/strands/base/model/__init__.py new file mode 100644 index 00000000..0e632e10 --- /dev/null +++ b/src/assets/python/http/strands/base/model/__init__.py @@ -0,0 +1 @@ +# Package marker diff --git a/src/assets/python/http/strands/base/model/load.py b/src/assets/python/http/strands/base/model/load.py new file mode 100644 index 00000000..8954269e --- /dev/null +++ b/src/assets/python/http/strands/base/model/load.py @@ -0,0 +1,123 @@ +{{#if (eq modelProvider "Bedrock")}} +from strands.models.bedrock import BedrockModel + + +def load_model() -> BedrockModel: + """Get Bedrock model client using IAM credentials.""" + return BedrockModel(model_id="global.anthropic.claude-sonnet-4-5-20250929-v1:0") +{{/if}} +{{#if (eq modelProvider "Anthropic")}} +import os + +from strands.models.anthropic import AnthropicModel +from bedrock_agentcore.identity.auth import requires_api_key + +IDENTITY_PROVIDER_NAME = "{{identityProviders.[0].name}}" +IDENTITY_ENV_VAR = "{{identityProviders.[0].envVarName}}" + + +@requires_api_key(provider_name=IDENTITY_PROVIDER_NAME) +def _agentcore_identity_api_key_provider(api_key: str) -> str: + """Fetch API key from AgentCore Identity.""" + return api_key + + +def _get_api_key() -> str: + """ + Uses AgentCore Identity for API key management in deployed environments. + For local development, run via 'agentcore dev' which loads agentcore/.env. + """ + if os.getenv("LOCAL_DEV") == "1": + api_key = os.getenv(IDENTITY_ENV_VAR) + if not api_key: + raise RuntimeError( + f"{IDENTITY_ENV_VAR} not found. Add {IDENTITY_ENV_VAR}=your-key to .env.local" + ) + return api_key + return _agentcore_identity_api_key_provider() + + +def load_model() -> AnthropicModel: + """Get authenticated Anthropic model client.""" + return AnthropicModel( + client_args={"api_key": _get_api_key()}, + model_id="claude-sonnet-4-5-20250929", + max_tokens=5000, + ) +{{/if}} +{{#if (eq modelProvider "OpenAI")}} +import os + +from strands.models.openai import OpenAIModel +from bedrock_agentcore.identity.auth import requires_api_key + +IDENTITY_PROVIDER_NAME = "{{identityProviders.[0].name}}" +IDENTITY_ENV_VAR = "{{identityProviders.[0].envVarName}}" + + +@requires_api_key(provider_name=IDENTITY_PROVIDER_NAME) +def _agentcore_identity_api_key_provider(api_key: str) -> str: + """Fetch API key from AgentCore Identity.""" + return api_key + + +def _get_api_key() -> str: + """ + Uses AgentCore Identity for API key management in deployed environments. + For local development, run via 'agentcore dev' which loads agentcore/.env. + """ + if os.getenv("LOCAL_DEV") == "1": + api_key = os.getenv(IDENTITY_ENV_VAR) + if not api_key: + raise RuntimeError( + f"{IDENTITY_ENV_VAR} not found. Add {IDENTITY_ENV_VAR}=your-key to .env.local" + ) + return api_key + return _agentcore_identity_api_key_provider() + + +def load_model() -> OpenAIModel: + """Get authenticated OpenAI model client.""" + return OpenAIModel( + client_args={"api_key": _get_api_key()}, + model_id="gpt-4.1", + ) +{{/if}} +{{#if (eq modelProvider "Gemini")}} +import os + +from strands.models.gemini import GeminiModel +from bedrock_agentcore.identity.auth import requires_api_key + +IDENTITY_PROVIDER_NAME = "{{identityProviders.[0].name}}" +IDENTITY_ENV_VAR = "{{identityProviders.[0].envVarName}}" + + +@requires_api_key(provider_name=IDENTITY_PROVIDER_NAME) +def _agentcore_identity_api_key_provider(api_key: str) -> str: + """Fetch API key from AgentCore Identity.""" + return api_key + + +def _get_api_key() -> str: + """ + Uses AgentCore Identity for API key management in deployed environments. + For local development, run via 'agentcore dev' which loads agentcore/.env. + """ + if os.getenv("LOCAL_DEV") == "1": + api_key = os.getenv(IDENTITY_ENV_VAR) + if not api_key: + raise RuntimeError( + f"{IDENTITY_ENV_VAR} not found. Add {IDENTITY_ENV_VAR}=your-key to .env.local" + ) + return api_key + return _agentcore_identity_api_key_provider() + + +def load_model() -> GeminiModel: + """Get authenticated Gemini model client.""" + return GeminiModel( + client_args={"api_key": _get_api_key()}, + model_id="gemini-2.5-flash", + ) +{{/if}} diff --git a/src/assets/python/strands/base/pyproject.toml b/src/assets/python/http/strands/base/pyproject.toml similarity index 100% rename from src/assets/python/strands/base/pyproject.toml rename to src/assets/python/http/strands/base/pyproject.toml diff --git a/src/assets/python/http/strands/capabilities/memory/__init__.py b/src/assets/python/http/strands/capabilities/memory/__init__.py new file mode 100644 index 00000000..0e632e10 --- /dev/null +++ b/src/assets/python/http/strands/capabilities/memory/__init__.py @@ -0,0 +1 @@ +# Package marker diff --git a/src/assets/python/strands/capabilities/memory/session.py b/src/assets/python/http/strands/capabilities/memory/session.py similarity index 100% rename from src/assets/python/strands/capabilities/memory/session.py rename to src/assets/python/http/strands/capabilities/memory/session.py diff --git a/src/assets/python/mcp/standalone/base/README.md b/src/assets/python/mcp/standalone/base/README.md new file mode 100644 index 00000000..6e4dc8a0 --- /dev/null +++ b/src/assets/python/mcp/standalone/base/README.md @@ -0,0 +1,36 @@ +# {{ name }} + +An MCP (Model Context Protocol) server deployed on Amazon Bedrock AgentCore. + +## Overview + +This project implements an MCP server using FastMCP. MCP servers expose tools that can be consumed by MCP clients (other agents or applications). + +## Local Development + +```bash +# Install dependencies +uv sync + +# Run the MCP server locally +uv run python main.py +``` + +The server starts on port 8000 with Streamable HTTP transport. + +## Adding Tools + +Define tools using the `@mcp.tool()` decorator in `main.py`: + +```python +@mcp.tool() +def my_tool(param: str) -> str: + """Description of what the tool does.""" + return f"Result: {param}" +``` + +## Deploy + +```bash +agentcore deploy +``` diff --git a/src/assets/python/mcp/standalone/base/gitignore.template b/src/assets/python/mcp/standalone/base/gitignore.template new file mode 100644 index 00000000..fa1c60ae --- /dev/null +++ b/src/assets/python/mcp/standalone/base/gitignore.template @@ -0,0 +1,41 @@ +# Environment variables +.env + +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# Virtual environments +.venv/ +venv/ +ENV/ +env/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db diff --git a/src/assets/python/mcp/standalone/base/main.py b/src/assets/python/mcp/standalone/base/main.py new file mode 100644 index 00000000..f91415ec --- /dev/null +++ b/src/assets/python/mcp/standalone/base/main.py @@ -0,0 +1,24 @@ +from mcp.server.fastmcp import FastMCP +import uvicorn + +mcp = FastMCP("{{ name }}") + + +@mcp.tool() +def add_numbers(a: int, b: int) -> int: + """Return the sum of two numbers.""" + return a + b + + +@mcp.tool() +def greet(name: str) -> str: + """Return a greeting for the given name.""" + return f"Hello, {name}!" + + +if __name__ == "__main__": + uvicorn.run( + mcp.streamable_http_app(), + host="0.0.0.0", + port=8000, + ) diff --git a/src/assets/python/mcp/standalone/base/pyproject.toml b/src/assets/python/mcp/standalone/base/pyproject.toml new file mode 100644 index 00000000..599a5cef --- /dev/null +++ b/src/assets/python/mcp/standalone/base/pyproject.toml @@ -0,0 +1,19 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "{{ name }}" +version = "0.1.0" +description = "AgentCore MCP Server" +readme = "README.md" +requires-python = ">=3.10" +dependencies = [ + "aws-opentelemetry-distro", + "bedrock-agentcore >= 1.0.3", + "mcp >= 1.19.0", + "uvicorn >= 0.30.0", +] + +[tool.hatch.build.targets.wheel] +packages = ["."] diff --git a/src/cli/commands/add/__tests__/validate.test.ts b/src/cli/commands/add/__tests__/validate.test.ts index 9a24db86..62c803b3 100644 --- a/src/cli/commands/add/__tests__/validate.test.ts +++ b/src/cli/commands/add/__tests__/validate.test.ts @@ -937,6 +937,116 @@ describe('validate', () => { }); }); + describe('validateAddAgentOptions protocol validation', () => { + it('MCP: succeeds with just name and language', () => { + const result = validateAddAgentOptions({ + name: 'McpAgent', + type: 'create', + language: 'Python', + protocol: 'MCP', + }); + expect(result.valid).toBe(true); + }); + + it('MCP: fails with --framework', () => { + const result = validateAddAgentOptions({ + name: 'McpAgent', + type: 'create', + language: 'Python', + protocol: 'MCP', + framework: 'Strands', + }); + expect(result.valid).toBe(false); + expect(result.error).toContain('not applicable for MCP protocol'); + }); + + it('MCP: fails with --model-provider', () => { + const result = validateAddAgentOptions({ + name: 'McpAgent', + type: 'create', + language: 'Python', + protocol: 'MCP', + modelProvider: 'Bedrock', + }); + expect(result.valid).toBe(false); + expect(result.error).toContain('not applicable for MCP protocol'); + }); + + it('MCP: fails with --memory (non-none)', () => { + const result = validateAddAgentOptions({ + name: 'McpAgent', + type: 'create', + language: 'Python', + protocol: 'MCP', + memory: 'shortTerm', + }); + expect(result.valid).toBe(false); + expect(result.error).toContain('not applicable for MCP protocol'); + }); + + it('A2A: succeeds with --framework Strands', () => { + const result = validateAddAgentOptions({ + name: 'A2aAgent', + type: 'byo', + language: 'Python', + protocol: 'A2A', + framework: 'Strands', + modelProvider: 'Bedrock', + codeLocation: '/path/to/code', + }); + expect(result.valid).toBe(true); + }); + + it('A2A: fails with --framework CrewAI', () => { + const result = validateAddAgentOptions({ + name: 'A2aAgent', + type: 'byo', + language: 'Python', + protocol: 'A2A', + framework: 'CrewAI', + modelProvider: 'Bedrock', + codeLocation: '/path/to/code', + }); + expect(result.valid).toBe(false); + expect(result.error).toContain('does not support A2A protocol'); + }); + + it('A2A: fails with --framework OpenAIAgents', () => { + const result = validateAddAgentOptions({ + name: 'A2aAgent', + type: 'byo', + language: 'Python', + protocol: 'A2A', + framework: 'OpenAIAgents', + modelProvider: 'OpenAI', + codeLocation: '/path/to/code', + }); + expect(result.valid).toBe(false); + expect(result.error).toContain('does not support A2A protocol'); + }); + + it('invalid protocol fails validation', () => { + const result = validateAddAgentOptions({ + name: 'BadAgent', + type: 'create', + language: 'Python', + protocol: 'GRPC' as any, + framework: 'Strands', + modelProvider: 'Bedrock', + }); + expect(result.valid).toBe(false); + expect(result.error).toContain('Invalid protocol'); + }); + + it('default (no --protocol) works as before (HTTP)', () => { + const result = validateAddAgentOptions({ + ...validAgentOptionsByo, + protocol: undefined, + }); + expect(result.valid).toBe(true); + }); + }); + describe('validateAddIdentityOptions OAuth', () => { it('passes for valid OAuth identity', () => { const result = validateAddIdentityOptions({ diff --git a/src/cli/commands/add/types.ts b/src/cli/commands/add/types.ts index 6f39c224..216e7a41 100644 --- a/src/cli/commands/add/types.ts +++ b/src/cli/commands/add/types.ts @@ -1,4 +1,4 @@ -import type { GatewayAuthorizerType, ModelProvider, SDKFramework, TargetLanguage } from '../../../schema'; +import type { GatewayAuthorizerType, ModelProvider, ProtocolMode, SDKFramework, TargetLanguage } from '../../../schema'; import type { MemoryOption } from '../../tui/screens/generate/types'; // Agent types @@ -11,6 +11,7 @@ export interface AddAgentOptions { modelProvider?: ModelProvider; apiKey?: string; memory?: MemoryOption; + protocol?: ProtocolMode; codeLocation?: string; entrypoint?: string; json?: boolean; diff --git a/src/cli/commands/add/validate.ts b/src/cli/commands/add/validate.ts index 8c77476d..0c1e2a7f 100644 --- a/src/cli/commands/add/validate.ts +++ b/src/cli/commands/add/validate.ts @@ -5,9 +5,11 @@ import { GatewayExceptionLevelSchema, GatewayNameSchema, ModelProviderSchema, + ProtocolModeSchema, SDKFrameworkSchema, TARGET_TYPE_AUTH_CONFIG, TargetLanguageSchema, + getSupportedFrameworksForProtocol, getSupportedModelProviders, matchEnumValue, } from '../../../schema'; @@ -63,6 +65,9 @@ async function validateCredentialExists(credentialName: string): Promise = []; let strategy: Awaited> | undefined; - if (modelProvider !== 'Bedrock') { + const isMcp = protocol === 'MCP'; + + if (!isMcp && modelProvider !== 'Bedrock') { strategy = await credentialPrimitive.resolveCredentialStrategy( name, agentName, diff --git a/src/cli/commands/create/command.tsx b/src/cli/commands/create/command.tsx index ada69dd8..56a30626 100644 --- a/src/cli/commands/create/command.tsx +++ b/src/cli/commands/create/command.tsx @@ -1,5 +1,5 @@ import { getWorkingDirectory } from '../../../lib'; -import type { BuildType, ModelProvider, SDKFramework, TargetLanguage } from '../../../schema'; +import type { BuildType, ModelProvider, ProtocolMode, SDKFramework, TargetLanguage } from '../../../schema'; import { getErrorMessage } from '../../errors'; import { COMMAND_DESCRIPTIONS } from '../../tui/copy'; import { CreateScreen } from '../../tui/screens/create'; @@ -116,10 +116,12 @@ async function handleCreateCLI(options: CreateOptions): Promise { cwd, buildType: (options.build as BuildType) ?? 'CodeZip', language: options.language as TargetLanguage, - framework: options.framework as SDKFramework, - modelProvider: options.modelProvider as ModelProvider, + // Defaults are harmless for MCP: schema-mapper excludes modelProvider for MCP agents + framework: (options.framework as SDKFramework) ?? ('Strands' as SDKFramework), + modelProvider: (options.modelProvider as ModelProvider) ?? ('Bedrock' as ModelProvider), apiKey: options.apiKey, - memory: options.memory as 'none' | 'shortTerm' | 'longAndShortTerm', + memory: (options.memory as 'none' | 'shortTerm' | 'longAndShortTerm') ?? 'none', + protocol: options.protocol as ProtocolMode | undefined, skipGit: options.skipGit, skipPythonSetup: options.skipPythonSetup, onProgress, @@ -152,6 +154,7 @@ export const registerCreate = (program: Command) => { .option('--model-provider ', 'Model provider (Bedrock, Anthropic, OpenAI, Gemini) [non-interactive]') .option('--api-key ', 'API key for non-Bedrock providers [non-interactive]') .option('--memory