@aws/agentcore 1.0.0-preview.23 → 1.0.0-preview.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/assets/README.md +5 -0
  2. package/dist/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap +146 -28
  3. package/dist/assets/__tests__/input-validation.test.ts +44 -0
  4. package/dist/assets/agents/AGENTS.md +2 -0
  5. package/dist/assets/evaluators/autoevals-lambda/execution-role-policy.json +15 -0
  6. package/dist/assets/evaluators/autoevals-lambda/lambda_function.py +37 -0
  7. package/dist/assets/evaluators/autoevals-lambda/pyproject.toml +22 -0
  8. package/dist/assets/evaluators/deepeval-lambda/execution-role-policy.json +15 -0
  9. package/dist/assets/evaluators/deepeval-lambda/lambda_function.py +29 -0
  10. package/dist/assets/evaluators/deepeval-lambda/pyproject.toml +19 -0
  11. package/dist/assets/mcp/python/pyproject.toml +1 -1
  12. package/dist/assets/python/a2a/googleadk/base/main.py +8 -2
  13. package/dist/assets/python/a2a/googleadk/base/pyproject.toml +4 -4
  14. package/dist/assets/python/a2a/langchain_langgraph/base/main.py +11 -5
  15. package/dist/assets/python/a2a/langchain_langgraph/base/pyproject.toml +2 -2
  16. package/dist/assets/python/a2a/strands/base/pyproject.toml +1 -1
  17. package/dist/assets/python/http/autogen/base/README.md +5 -0
  18. package/dist/assets/python/http/autogen/base/main.py +2 -0
  19. package/dist/assets/python/http/googleadk/base/README.md +5 -0
  20. package/dist/assets/python/http/googleadk/base/main.py +2 -0
  21. package/dist/assets/python/http/langchain_langgraph/base/README.md +5 -0
  22. package/dist/assets/python/http/langchain_langgraph/base/main.py +4 -0
  23. package/dist/assets/python/http/langchain_langgraph/base/pyproject.toml +2 -2
  24. package/dist/assets/python/http/openaiagents/base/README.md +5 -0
  25. package/dist/assets/python/http/openaiagents/base/main.py +2 -0
  26. package/dist/assets/python/http/strands/base/README.md +6 -0
  27. package/dist/assets/python/http/strands/base/main.py +40 -4
  28. package/dist/assets/python/http/strands/base/pyproject.toml +1 -1
  29. package/dist/assets/python/mcp/standalone/base/pyproject.toml +1 -1
  30. package/dist/assets/typescript/http/strands/base/README.md +5 -0
  31. package/dist/assets/typescript/http/strands/base/main.ts +8 -3
  32. package/dist/assets/typescript/http/vercelai/base/README.md +5 -0
  33. package/dist/assets/typescript/http/vercelai/base/main.ts +8 -2
  34. package/dist/cli/index.mjs +507 -502
  35. package/npm-shrinkwrap.json +234 -376
  36. package/package.json +4 -1
  37. package/scripts/extract-cli-model.mjs +47 -6
  38. package/scripts/extract-cli-model.test.mjs +43 -0
  39. package/scripts/render_adoc.py +81 -5
  40. package/scripts/safe-junit-reporter.ts +7 -0
  41. package/scripts/sanitize-test-artifacts.ts +65 -0
  42. package/scripts/test_render_adoc.py +38 -0
@@ -37,6 +37,11 @@ Run your agent locally:
37
37
  agentcore dev
38
38
  ```
39
39
 
40
+ ### Validate Invocation Input
41
+
42
+ Validate runtime invocation payloads before forwarding them to an agent framework. Keep user prompts typed as strings
43
+ and pass only prompt text to the agent.
44
+
40
45
  ### Deployment
41
46
 
42
47
  Deploy to AWS:
@@ -742,6 +742,12 @@ exports[`Assets Directory Snapshots > File listing > should match the expected f
742
742
  "container/typescript/dockerignore.template",
743
743
  "datasets/predefined-v1.jsonl",
744
744
  "datasets/simulated-v1.jsonl",
745
+ "evaluators/autoevals-lambda/execution-role-policy.json",
746
+ "evaluators/autoevals-lambda/lambda_function.py",
747
+ "evaluators/autoevals-lambda/pyproject.toml",
748
+ "evaluators/deepeval-lambda/execution-role-policy.json",
749
+ "evaluators/deepeval-lambda/lambda_function.py",
750
+ "evaluators/deepeval-lambda/pyproject.toml",
745
751
  "evaluators/python-lambda/execution-role-policy.json",
746
752
  "evaluators/python-lambda/lambda_function.py",
747
753
  "evaluators/python-lambda/pyproject.toml",
@@ -904,7 +910,7 @@ description = "MCP Server demonstrating HTTP tool patterns"
904
910
  readme = "README.md"
905
911
  requires-python = ">=3.10"
906
912
  dependencies = [
907
- "mcp[cli] >= 1.2.0",
913
+ "mcp[cli] ~= 1.24.0",
908
914
  "httpx >= 0.27.0",
909
915
  "opentelemetry-distro",
910
916
  "opentelemetry-exporter-otlp",
@@ -1311,7 +1317,7 @@ from google.adk.agents import Agent
1311
1317
  from google.adk.a2a.executor.a2a_agent_executor import A2aAgentExecutor
1312
1318
  from google.adk.runners import Runner
1313
1319
  from google.adk.sessions import InMemorySessionService
1314
- from a2a.types import AgentCapabilities, AgentCard, AgentSkill
1320
+ from a2a.types import AgentCapabilities, AgentCard, AgentInterface, AgentSkill
1315
1321
  from bedrock_agentcore.runtime import serve_a2a
1316
1322
  from model.load import load_model
1317
1323
 
@@ -1405,7 +1411,6 @@ runner = Runner(
1405
1411
  card = AgentCard(
1406
1412
  name=agent.name,
1407
1413
  description=agent.description,
1408
- url="http://localhost:9000/",
1409
1414
  version="0.1.0",
1410
1415
  capabilities=AgentCapabilities(streaming=True),
1411
1416
  skills=[
@@ -1418,6 +1423,13 @@ card = AgentCard(
1418
1423
  ],
1419
1424
  default_input_modes=["text"],
1420
1425
  default_output_modes=["text"],
1426
+ supported_interfaces=[
1427
+ AgentInterface(
1428
+ protocol_binding="JSONRPC",
1429
+ protocol_version="1.0",
1430
+ url="http://localhost:9000/",
1431
+ )
1432
+ ],
1421
1433
  )
1422
1434
 
1423
1435
  if __name__ == "__main__":
@@ -1487,11 +1499,11 @@ description = "AgentCore A2A Agent using Google ADK"
1487
1499
  readme = "README.md"
1488
1500
  requires-python = ">=3.10"
1489
1501
  dependencies = [
1490
- "a2a-sdk >= 0.2.0, < 1.0.0",
1502
+ "a2a-sdk[http-server] >= 1.0.1, < 2.0.0",
1491
1503
  "aws-opentelemetry-distro",
1492
- "bedrock-agentcore[a2a] >= 1.0.3",
1493
- "google-adk >= 1.0.0, < 2.0.0",
1494
- "google-genai >= 1.0.0, < 2.0.0",
1504
+ "bedrock-agentcore[a2a-v1] >= 1.19.0",
1505
+ "google-adk[a2a] >= 2.5.0, < 3.0.0",
1506
+ "google-genai >= 2.9.0, < 3.0.0",
1495
1507
  # 1.13.0 was yanked for broken imports.
1496
1508
  "opentelemetry-resourcedetector-gcp >= 1.9.0a0, < 2.0.0, != 1.13.0",
1497
1509
  ]
@@ -1579,11 +1591,11 @@ import os
1579
1591
  from langchain_core.tools import tool
1580
1592
  from langgraph.prebuilt import create_react_agent
1581
1593
  from opentelemetry.instrumentation.langchain import LangchainInstrumentor
1594
+ from a2a.helpers import new_task_from_user_message
1582
1595
  from a2a.server.agent_execution import AgentExecutor, RequestContext
1583
1596
  from a2a.server.events import EventQueue
1584
1597
  from a2a.server.tasks import TaskUpdater
1585
- from a2a.types import AgentCapabilities, AgentCard, AgentSkill, Part, TextPart
1586
- from a2a.utils import new_task
1598
+ from a2a.types import AgentCapabilities, AgentCard, AgentInterface, AgentSkill, Part
1587
1599
  from bedrock_agentcore.runtime import serve_a2a
1588
1600
  from model.load import load_model
1589
1601
 
@@ -1675,7 +1687,7 @@ class LangGraphA2AExecutor(AgentExecutor):
1675
1687
  self.graph = graph
1676
1688
 
1677
1689
  async def execute(self, context: RequestContext, event_queue: EventQueue) -> None:
1678
- task = context.current_task or new_task(context.message)
1690
+ task = context.current_task or new_task_from_user_message(context.message)
1679
1691
  if not context.current_task:
1680
1692
  await event_queue.enqueue_event(task)
1681
1693
  updater = TaskUpdater(event_queue, task.id, task.context_id)
@@ -1684,7 +1696,7 @@ class LangGraphA2AExecutor(AgentExecutor):
1684
1696
  result = await self.graph.ainvoke({"messages": [("user", user_text)]})
1685
1697
  response = result["messages"][-1].content
1686
1698
 
1687
- await updater.add_artifact([Part(root=TextPart(text=response))])
1699
+ await updater.add_artifact([Part(text=response)])
1688
1700
  await updater.complete()
1689
1701
 
1690
1702
  async def cancel(self, context: RequestContext, event_queue: EventQueue) -> None:
@@ -1694,7 +1706,6 @@ class LangGraphA2AExecutor(AgentExecutor):
1694
1706
  card = AgentCard(
1695
1707
  name="{{ name }}",
1696
1708
  description="A LangGraph agent on Bedrock AgentCore",
1697
- url="http://localhost:9000/",
1698
1709
  version="0.1.0",
1699
1710
  capabilities=AgentCapabilities(streaming=True),
1700
1711
  skills=[
@@ -1707,6 +1718,13 @@ card = AgentCard(
1707
1718
  ],
1708
1719
  default_input_modes=["text"],
1709
1720
  default_output_modes=["text"],
1721
+ supported_interfaces=[
1722
+ AgentInterface(
1723
+ protocol_binding="JSONRPC",
1724
+ protocol_version="1.0",
1725
+ url="http://localhost:9000/",
1726
+ )
1727
+ ],
1710
1728
  )
1711
1729
 
1712
1730
  if __name__ == "__main__":
@@ -1858,7 +1876,7 @@ description = "AgentCore A2A Agent using LangChain + LangGraph"
1858
1876
  readme = "README.md"
1859
1877
  requires-python = ">=3.10"
1860
1878
  dependencies = [
1861
- "a2a-sdk >= 0.2.0, < 1.0.0",
1879
+ "a2a-sdk[http-server] >= 1.0.1, < 2.0.0",
1862
1880
  {{#if (eq modelProvider "Anthropic")}}"langchain-anthropic >= 0.3.0",
1863
1881
  {{/if}}{{#if (eq modelProvider "Bedrock")}}"langchain-aws >= 0.2.0",
1864
1882
  {{/if}}{{#if (eq modelProvider "Gemini")}}"langchain-google-genai >= 2.0.0",
@@ -1866,7 +1884,7 @@ dependencies = [
1866
1884
  {{/if}}{{#if (eq modelProvider "OpenAI")}}"langchain-openai >= 0.2.0",
1867
1885
  {{/if}}"aws-opentelemetry-distro",
1868
1886
  "opentelemetry-instrumentation-langchain >= 0.59.0",
1869
- "bedrock-agentcore[a2a] >= 1.8.0",
1887
+ "bedrock-agentcore[a2a-v1] >= 1.19.0",
1870
1888
  "botocore[crt] >= 1.35.0",
1871
1889
  "langgraph >= 0.2.0",
1872
1890
  ]
@@ -2209,7 +2227,7 @@ readme = "README.md"
2209
2227
  requires-python = ">=3.10"
2210
2228
  dependencies = [
2211
2229
  {{#if (eq modelProvider "Anthropic")}}"anthropic >= 0.30.0",
2212
- {{/if}}"a2a-sdk[all] >= 0.2.0, < 1.0.0",
2230
+ {{/if}}"a2a-sdk[all] >= 0.3.0, < 0.4.0",
2213
2231
  "aws-opentelemetry-distro",
2214
2232
  "bedrock-agentcore[a2a] >= 1.9.1",
2215
2233
  "botocore[crt] >= 1.35.0",
@@ -3317,6 +3335,11 @@ file defines a Starlette ASGI app with the AutoGen framework running within.
3317
3335
 
3318
3336
  \`model/load.py\` instantiates your chosen model provider.
3319
3337
 
3338
+ ## Input Validation
3339
+
3340
+ Validate invocation input before forwarding it to the agent framework. Keep plain prompts typed as strings and pass
3341
+ only prompt text to the agent.
3342
+
3320
3343
  ## Environment Variables
3321
3344
 
3322
3345
  | Variable | Required | Description |
@@ -3520,6 +3543,8 @@ async def invoke(payload, context):
3520
3543
 
3521
3544
  # Process the user prompt
3522
3545
  prompt = payload.get("prompt", "What can you help me with?")
3546
+ if not isinstance(prompt, str):
3547
+ raise ValueError("prompt must be a string")
3523
3548
  session_id = getattr(context, "session_id", "default-session")
3524
3549
 
3525
3550
  # Reuse the per-session agent (preserves conversation history)
@@ -3773,6 +3798,11 @@ file defines a Starlette ASGI app with the Google ADK framework running within.
3773
3798
 
3774
3799
  \`model/load.py\` instantiates your chosen model provider (Gemini).
3775
3800
 
3801
+ ## Input Validation
3802
+
3803
+ Validate invocation input before forwarding it to the agent framework. Keep plain prompts typed as strings and pass
3804
+ only prompt text to the agent.
3805
+
3776
3806
  ## Environment Variables
3777
3807
 
3778
3808
  | Variable | Required | Description |
@@ -4040,6 +4070,8 @@ async def invoke(payload, context):
4040
4070
 
4041
4071
  # Process the user prompt
4042
4072
  prompt = payload.get("prompt", "What can you help me with?")
4073
+ if not isinstance(prompt, str):
4074
+ raise ValueError("prompt must be a string")
4043
4075
  session_id = getattr(context, "session_id", "default_session")
4044
4076
  user_id = payload.get("user_id", "default_user")
4045
4077
 
@@ -4233,6 +4265,11 @@ file defines a Starlette ASGI app with the LangChain/LangGraph framework running
4233
4265
 
4234
4266
  \`model/load.py\` instantiates your chosen model provider.
4235
4267
 
4268
+ ## Input Validation
4269
+
4270
+ Validate invocation input before forwarding it to the agent framework. Keep plain prompts typed as strings and pass
4271
+ only prompt text to the agent.
4272
+
4236
4273
  ## Environment Variables
4237
4274
 
4238
4275
  | Variable | Required | Description |
@@ -4492,6 +4529,8 @@ async def invoke(payload, context):
4492
4529
 
4493
4530
  # Process the user prompt
4494
4531
  prompt = payload.get("prompt", "What can you help me with?")
4532
+ if not isinstance(prompt, str):
4533
+ raise ValueError("prompt must be a string")
4495
4534
  session_id = getattr(context, "session_id", "default-session")
4496
4535
  touch_thread(session_id)
4497
4536
  log.info(f"Agent input: {prompt}")
@@ -4511,6 +4550,8 @@ async def invoke(payload, context):
4511
4550
 
4512
4551
  # Process the user prompt
4513
4552
  prompt = payload.get("prompt", "What can you help me with?")
4553
+ if not isinstance(prompt, str):
4554
+ raise ValueError("prompt must be a string")
4514
4555
  session_id = getattr(context, "session_id", "default-session")
4515
4556
  touch_thread(session_id)
4516
4557
  log.info(f"Agent input: {prompt}")
@@ -4768,8 +4809,8 @@ dependencies = [
4768
4809
  "aws-opentelemetry-distro",
4769
4810
  "opentelemetry-instrumentation-langchain >= 0.59.0",
4770
4811
  "langgraph >= 1.0.2",
4771
- "mcp >= 1.19.0",
4772
- "langchain-mcp-adapters >= 0.2.0",
4812
+ "mcp ~= 1.24.0",
4813
+ "langchain-mcp-adapters >= 0.3.1",
4773
4814
  "langchain >= 1.0.3",
4774
4815
  "bedrock-agentcore >= 1.8.0",
4775
4816
  "botocore[crt] >= 1.35.0",
@@ -4811,6 +4852,11 @@ file defines a Starlette ASGI app with the OpenAI Agents SDK framework running w
4811
4852
 
4812
4853
  \`model/load.py\` instantiates your chosen model provider (OpenAI).
4813
4854
 
4855
+ ## Input Validation
4856
+
4857
+ Validate invocation input before forwarding it to the agent framework. Keep plain prompts typed as strings and pass
4858
+ only prompt text to the agent.
4859
+
4814
4860
  ## Environment Variables
4815
4861
 
4816
4862
  | Variable | Required | Description |
@@ -5071,6 +5117,8 @@ async def invoke(payload, context):
5071
5117
 
5072
5118
  # Process the user prompt
5073
5119
  prompt = payload.get("prompt", "What can you help me with?")
5120
+ if not isinstance(prompt, str):
5121
+ raise ValueError("prompt must be a string")
5074
5122
  session_id = getattr(context, "session_id", "default-session")
5075
5123
  session = get_session(session_id)
5076
5124
 
@@ -5256,6 +5304,12 @@ file defines a Starlette ASGI app with the chosen Agent framework SDK running wi
5256
5304
 
5257
5305
  \`model/load.py\` instantiates your chosen model provider.
5258
5306
 
5307
+ ## Input Validation
5308
+
5309
+ Validate invocation input before forwarding it to Strands. Keep plain prompts typed as strings. If the app accepts a
5310
+ caller-supplied message history, retain \`strip_trailing_tool_use()\`, which normalizes the history tail before
5311
+ invoking the agent.
5312
+
5259
5313
  ## Environment Variables
5260
5314
 
5261
5315
  | Variable | Required | Description |
@@ -5812,17 +5866,53 @@ get_or_create_agent = agent_factory()
5812
5866
  {{/if}}
5813
5867
 
5814
5868
 
5869
+ def strip_trailing_tool_use(messages: Any) -> list[dict]:
5870
+ """Strip toolUse blocks from the tail until the last message has none."""
5871
+ if not isinstance(messages, list):
5872
+ raise ValueError("messages must be a list")
5873
+
5874
+ messages = list(messages)
5875
+ while messages:
5876
+ last = messages[-1]
5877
+ if not isinstance(last, dict):
5878
+ raise ValueError("each message must be an object")
5879
+ original_content = last.get("content", [])
5880
+ if not isinstance(original_content, list) or not all(isinstance(block, dict) for block in original_content):
5881
+ raise ValueError("each message content value must be a list of content blocks")
5882
+
5883
+ content = [block for block in original_content if "toolUse" not in block]
5884
+ if len(content) == len(original_content):
5885
+ break
5886
+ if content:
5887
+ messages[-1] = {**last, "content": content}
5888
+ break
5889
+ messages.pop()
5890
+
5891
+ return messages
5892
+
5893
+
5815
5894
  def _extract_prompt(payload: dict):
5816
- """Accept harness-style messages[], tool_results[], or plain prompt string payloads."""
5895
+ """Accept validated harness messages, tool results, or a plain prompt string."""
5896
+ if not isinstance(payload, dict):
5897
+ raise ValueError("payload must be a JSON object")
5817
5898
  if "messages" in payload:
5818
- return payload["messages"]
5899
+ return strip_trailing_tool_use(payload["messages"])
5819
5900
  if "tool_results" in payload:
5901
+ tool_results = payload["tool_results"]
5902
+ if not isinstance(tool_results, list) or not all(
5903
+ isinstance(tool_result, dict) and isinstance(tool_result.get("toolUseId"), str)
5904
+ for tool_result in tool_results
5905
+ ):
5906
+ raise ValueError("tool_results must contain objects with a toolUseId string")
5820
5907
  return [{"role": "user", "content": [{"toolResult": {
5821
5908
  "toolUseId": tr["toolUseId"],
5822
5909
  "status": tr.get("status", "success"),
5823
5910
  "content": tr.get("content", []),
5824
- }} for tr in payload["tool_results"]]}]
5825
- return payload.get("prompt", "")
5911
+ }} for tr in tool_results]}]
5912
+ prompt = payload.get("prompt", "")
5913
+ if not isinstance(prompt, str):
5914
+ raise ValueError("prompt must be a string")
5915
+ return prompt
5826
5916
 
5827
5917
 
5828
5918
  def _has_inline_function_call(messages) -> bool:
@@ -6425,7 +6515,7 @@ dependencies = [
6425
6515
  "bedrock-agentcore >= 1.9.1",
6426
6516
  "botocore[crt] >= 1.35.0",
6427
6517
  {{#if (eq modelProvider "Gemini")}}"google-genai >= 1.0.0",
6428
- {{/if}}"mcp >= 1.19.0",
6518
+ {{/if}}"mcp ~= 1.24.0",
6429
6519
  {{#if (eq modelProvider "OpenAI")}}"openai >= 1.0.0",
6430
6520
  {{/if}}{{#if (eq modelProvider "LiteLLM")}}"litellm >= 1.0.0",
6431
6521
  {{/if}}{{#if bedrockMantle}}"openai >= 1.0.0",
@@ -7178,7 +7268,7 @@ description = "AgentCore MCP Server"
7178
7268
  readme = "README.md"
7179
7269
  requires-python = ">=3.10"
7180
7270
  dependencies = [
7181
- "mcp >= 1.19.0",
7271
+ "mcp ~= 1.24.0",
7182
7272
  ]
7183
7273
 
7184
7274
  [tool.hatch.build.targets.wheel]
@@ -7290,6 +7380,11 @@ Run your agent locally:
7290
7380
  agentcore dev
7291
7381
  \`\`\`
7292
7382
 
7383
+ ### Validate Invocation Input
7384
+
7385
+ Validate runtime invocation payloads before forwarding them to an agent framework. Keep user prompts typed as strings
7386
+ and pass only prompt text to the agent.
7387
+
7293
7388
  ### Deployment
7294
7389
 
7295
7390
  Deploy to AWS:
@@ -7384,6 +7479,8 @@ Tags defined in \`agentcore.json\` flow through to deployed CloudFormation resou
7384
7479
  \`agentcore validate\` to check.
7385
7480
  4. **Resource Removal:** Use \`agentcore remove\` to remove resources. Run \`agentcore deploy\` after removal to tear down
7386
7481
  deployed infrastructure.
7482
+ 5. **Invocation Input:** Validate runtime payloads and require text prompts to be strings. If a Strands app accepts a
7483
+ caller-supplied message history, normalize the history tail with \`strip_trailing_tool_use()\` before invocation.
7387
7484
 
7388
7485
  ## Directory Structure
7389
7486
 
@@ -7597,6 +7694,11 @@ defines an HTTP server that streams tokens from your chosen Agent framework SDK.
7597
7694
 
7598
7695
  \`model/load.ts\` instantiates your chosen model provider.
7599
7696
 
7697
+ ## Input Validation
7698
+
7699
+ The generated Zod request schema keeps plain prompts typed as strings before forwarding them to Strands. Retain this
7700
+ validation when extending the request shape, and pass only prompt text to the agent.
7701
+
7600
7702
  ## Environment Variables
7601
7703
 
7602
7704
  | Variable | Required | Description |
@@ -7685,6 +7787,10 @@ const SYSTEM_PROMPT = \`
7685
7787
  You are a helpful assistant. Use tools when appropriate.
7686
7788
  \`;
7687
7789
 
7790
+ const requestSchema = z.object({
7791
+ prompt: z.string().default(''),
7792
+ });
7793
+
7688
7794
  {{#if hasMemory}}
7689
7795
  const agentCache = new Map<string, Agent>();
7690
7796
 
@@ -7738,7 +7844,8 @@ async function getOrCreateAgent(sessionId: string): Promise<Agent> {
7738
7844
 
7739
7845
  const app = new BedrockAgentCoreApp({
7740
7846
  invocationHandler: {
7741
- async *process(payload: any, context: any) {
7847
+ requestSchema,
7848
+ async *process(payload, context) {
7742
7849
  {{#if hasMemory}}
7743
7850
  const sessionId = context?.sessionId ?? 'default-session';
7744
7851
  const actorId = getActorId(payload, context);
@@ -7750,7 +7857,7 @@ const app = new BedrockAgentCoreApp({
7750
7857
 
7751
7858
  {{#if hasMemory}}
7752
7859
  try {
7753
- for await (const event of agent.stream(payload.prompt ?? '')) {
7860
+ for await (const event of agent.stream(payload.prompt)) {
7754
7861
  if (
7755
7862
  event.type === 'modelStreamUpdateEvent' &&
7756
7863
  event.event?.type === 'modelContentBlockDeltaEvent' &&
@@ -7774,7 +7881,7 @@ const app = new BedrockAgentCoreApp({
7774
7881
  // e.g. Anthropic). Restoring on error keeps the session reusable.
7775
7882
  const snapshot = agent.takeSnapshot({ include: ['messages'] });
7776
7883
  try {
7777
- for await (const event of agent.stream(payload.prompt ?? '')) {
7884
+ for await (const event of agent.stream(payload.prompt)) {
7778
7885
  if (
7779
7886
  event.type === 'modelStreamUpdateEvent' &&
7780
7887
  event.event?.type === 'modelContentBlockDeltaEvent' &&
@@ -8054,6 +8161,11 @@ defines an HTTP app that streams tokens using the Vercel AI SDK's \`streamText\`
8054
8161
 
8055
8162
  \`model/load.ts\` instantiates your chosen model provider.
8056
8163
 
8164
+ ## Input Validation
8165
+
8166
+ The generated Zod request schema keeps plain prompts typed as strings before forwarding them to the agent framework.
8167
+ Retain this validation when extending the request shape, and pass only prompt text to the agent.
8168
+
8057
8169
  ## Environment Variables
8058
8170
 
8059
8171
  | Variable | Required | Description |
@@ -8108,10 +8220,15 @@ Thumbs.db
8108
8220
  exports[`Assets Directory Snapshots > TypeScript assets > typescript/typescript/http/vercelai/base/main.ts should match snapshot 1`] = `
8109
8221
  "import { BedrockAgentCoreApp } from 'bedrock-agentcore/runtime';
8110
8222
  import { streamText, type ModelMessage } from 'ai';
8223
+ import { z } from 'zod';
8111
8224
  import { loadModel } from './model/load.js';
8112
8225
 
8113
8226
  const SYSTEM_PROMPT = \`You are a helpful assistant.\`;
8114
8227
 
8228
+ const requestSchema = z.object({
8229
+ prompt: z.string().default(''),
8230
+ });
8231
+
8115
8232
  const HISTORY_LIMIT = 128;
8116
8233
 
8117
8234
  // Keeps one message history per sessionId so each session remembers its own
@@ -8140,10 +8257,11 @@ function getHistory(sessionId: string): ModelMessage[] {
8140
8257
 
8141
8258
  const app = new BedrockAgentCoreApp({
8142
8259
  invocationHandler: {
8143
- async *process(payload: any, context: any) {
8260
+ requestSchema,
8261
+ async *process(payload, context) {
8144
8262
  const sessionId = context?.sessionId ?? 'default-session';
8145
8263
  const history = getHistory(sessionId);
8146
- const userMessage: ModelMessage = { role: 'user', content: payload.prompt ?? '' };
8264
+ const userMessage: ModelMessage = { role: 'user', content: payload.prompt };
8147
8265
 
8148
8266
  const model = await loadModel();
8149
8267
  const result = streamText({
@@ -0,0 +1,44 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import { describe, expect, it } from 'vitest';
4
+
5
+ const ASSETS_DIR = resolve(__dirname, '..');
6
+
7
+ const PYTHON_HTTP_ENTRYPOINTS = [
8
+ 'python/http/autogen/base/main.py',
9
+ 'python/http/googleadk/base/main.py',
10
+ 'python/http/langchain_langgraph/base/main.py',
11
+ 'python/http/openaiagents/base/main.py',
12
+ 'python/http/strands/base/main.py',
13
+ ];
14
+
15
+ const TYPESCRIPT_HTTP_ENTRYPOINTS = [
16
+ 'typescript/http/strands/base/main.ts',
17
+ 'typescript/http/vercelai/base/main.ts',
18
+ ];
19
+
20
+ describe('HTTP agent template input validation', () => {
21
+ it.each(PYTHON_HTTP_ENTRYPOINTS)('%s rejects non-string prompts', templatePath => {
22
+ const template = readFileSync(resolve(ASSETS_DIR, templatePath), 'utf8');
23
+
24
+ expect(template).toContain('if not isinstance(prompt, str):');
25
+ expect(template).toContain('raise ValueError("prompt must be a string")');
26
+ });
27
+
28
+ it.each(TYPESCRIPT_HTTP_ENTRYPOINTS)('%s validates prompts with Zod', templatePath => {
29
+ const template = readFileSync(resolve(ASSETS_DIR, templatePath), 'utf8');
30
+
31
+ expect(template).toContain("prompt: z.string().default('')");
32
+ expect(template).toContain('requestSchema,');
33
+ });
34
+
35
+ it('strips toolUse blocks from the Python Strands message-history tail', () => {
36
+ const template = readFileSync(resolve(ASSETS_DIR, 'python/http/strands/base/main.py'), 'utf8');
37
+
38
+ expect(template).toContain('def strip_trailing_tool_use(messages: Any) -> list[dict]:');
39
+ expect(template).toContain('while messages:');
40
+ expect(template).toContain('content = [block for block in original_content if "toolUse" not in block]');
41
+ expect(template).toContain('messages.pop()');
42
+ expect(template).toContain('return strip_trailing_tool_use(payload["messages"])');
43
+ });
44
+ });
@@ -23,6 +23,8 @@ Tags defined in `agentcore.json` flow through to deployed CloudFormation resourc
23
23
  `agentcore validate` to check.
24
24
  4. **Resource Removal:** Use `agentcore remove` to remove resources. Run `agentcore deploy` after removal to tear down
25
25
  deployed infrastructure.
26
+ 5. **Invocation Input:** Validate runtime payloads and require text prompts to be strings. If a Strands app accepts a
27
+ caller-supplied message history, normalize the history tail with `strip_trailing_tool_use()` before invocation.
26
28
 
27
29
  ## Directory Structure
28
30
 
@@ -0,0 +1,15 @@
1
+ {
2
+ "Version": "2012-10-17",
3
+ "Statement": [
4
+ {
5
+ "Effect": "Allow",
6
+ "Action": ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"],
7
+ "Resource": "arn:*:logs:*:*:log-group:/aws/lambda/*"
8
+ },
9
+ {
10
+ "Effect": "Allow",
11
+ "Action": ["bedrock:InvokeModel"],
12
+ "Resource": "*"
13
+ }
14
+ ]
15
+ }
@@ -0,0 +1,37 @@
1
+ {{#if ModelProviderBedrock}}
2
+ import os
3
+
4
+ # litellm's Bedrock provider reads AWS_REGION_NAME; Lambda only sets AWS_REGION/AWS_DEFAULT_REGION.
5
+ os.environ.setdefault("AWS_REGION_NAME", os.environ.get("AWS_REGION", "us-west-2"))
6
+
7
+ from autoevals import {{ EvaluatorClass }}, init
8
+ from autoevals.litellm import LiteLLMClient
9
+
10
+ from bedrock_agentcore.evaluation.custom_code_based_evaluators import (
11
+ EvaluatorInput,
12
+ EvaluatorOutput,
13
+ custom_code_based_evaluator,
14
+ )
15
+ from bedrock_agentcore.evaluation.custom_code_based_evaluators.third_party.autoevals import AutoEvalsAdapter
16
+
17
+ client = LiteLLMClient()
18
+ init(client=client, default_model="{{ Model }}")
19
+
20
+ adapter = AutoEvalsAdapter(metric={{ EvaluatorClass }}(client=client, model="{{ Model }}"){{#if EvaluatorParams}}, {{{ EvaluatorParams }}}{{/if}})
21
+ {{else}}
22
+ from autoevals import {{ EvaluatorClass }}
23
+
24
+ from bedrock_agentcore.evaluation.custom_code_based_evaluators import (
25
+ EvaluatorInput,
26
+ EvaluatorOutput,
27
+ custom_code_based_evaluator,
28
+ )
29
+ from bedrock_agentcore.evaluation.custom_code_based_evaluators.third_party.autoevals import AutoEvalsAdapter
30
+
31
+ adapter = AutoEvalsAdapter(metric={{ EvaluatorClass }}({{#if Model}}model="{{ Model }}"{{/if}}){{#if EvaluatorParams}}, {{{ EvaluatorParams }}}{{/if}})
32
+ {{/if}}
33
+
34
+
35
+ @custom_code_based_evaluator()
36
+ def handler(evaluator_input: EvaluatorInput, context) -> EvaluatorOutput:
37
+ return adapter(evaluator_input, context)
@@ -0,0 +1,22 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "{{ Name }}"
7
+ version = "0.1.0"
8
+ description = "AgentCore Code-Based Evaluator (Autoevals)"
9
+ requires-python = ">=3.10"
10
+ dependencies = [
11
+ "bedrock-agentcore[autoevals]",
12
+ "autoevals>=0.0.80,<1.0.0",
13
+ {{#if ModelProviderBedrock}}
14
+ # autoevals grades via LiteLLMClient -> Bedrock (Converse); litellm replaces the openai judge
15
+ "litellm>=1.60,<1.85",
16
+ {{else}}
17
+ "openai>=1.0.0",
18
+ {{/if}}
19
+ ]
20
+
21
+ [tool.hatch.build.targets.wheel]
22
+ packages = ["."]
@@ -0,0 +1,15 @@
1
+ {
2
+ "Version": "2012-10-17",
3
+ "Statement": [
4
+ {
5
+ "Effect": "Allow",
6
+ "Action": ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"],
7
+ "Resource": "arn:*:logs:*:*:log-group:/aws/lambda/*"
8
+ },
9
+ {
10
+ "Effect": "Allow",
11
+ "Action": ["bedrock:InvokeModel"],
12
+ "Resource": "*"
13
+ }
14
+ ]
15
+ }
@@ -0,0 +1,29 @@
1
+ import os
2
+
3
+ os.environ.setdefault("DEEPEVAL_RESULTS_FOLDER", "/tmp/.deepeval")
4
+ os.environ.setdefault("DEEPEVAL_TELEMETRY_OPT_OUT", "YES")
5
+ os.chdir("/tmp")
6
+
7
+ {{#if ModelProviderBedrock}}
8
+ from deepeval.models import AmazonBedrockModel
9
+ {{/if}}
10
+ from deepeval.metrics import {{ EvaluatorClass }}
11
+
12
+ from bedrock_agentcore.evaluation.custom_code_based_evaluators import (
13
+ EvaluatorInput,
14
+ EvaluatorOutput,
15
+ custom_code_based_evaluator,
16
+ )
17
+ from bedrock_agentcore.evaluation.custom_code_based_evaluators.third_party.deepeval import DeepEvalAdapter
18
+
19
+ {{#if ModelProviderBedrock}}
20
+ model = AmazonBedrockModel(model="{{ Model }}", region=os.environ.get("AWS_REGION", "us-west-2"))
21
+ adapter = DeepEvalAdapter(metric={{ EvaluatorClass }}(model=model{{#if EvaluatorParams}}, {{{ EvaluatorParams }}}{{/if}}))
22
+ {{else}}
23
+ adapter = DeepEvalAdapter(metric={{ EvaluatorClass }}({{{ EvaluatorParams }}}))
24
+ {{/if}}
25
+
26
+
27
+ @custom_code_based_evaluator()
28
+ def handler(evaluator_input: EvaluatorInput, context) -> EvaluatorOutput:
29
+ return adapter(evaluator_input, context)
@@ -0,0 +1,19 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "{{ Name }}"
7
+ version = "0.1.0"
8
+ description = "AgentCore Code-Based Evaluator (DeepEval)"
9
+ requires-python = ">=3.10"
10
+ dependencies = [
11
+ "bedrock-agentcore[deepeval]",
12
+ "deepeval>=2.0.0,<3.0.0",
13
+ {{#if ModelProviderBedrock}}
14
+ "aiobotocore>=2.13.0",
15
+ {{/if}}
16
+ ]
17
+
18
+ [tool.hatch.build.targets.wheel]
19
+ packages = ["."]
@@ -9,7 +9,7 @@ description = "MCP Server demonstrating HTTP tool patterns"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
11
11
  dependencies = [
12
- "mcp[cli] >= 1.2.0",
12
+ "mcp[cli] ~= 1.24.0",
13
13
  "httpx >= 0.27.0",
14
14
  "opentelemetry-distro",
15
15
  "opentelemetry-exporter-otlp",