@aws/agentcore 1.0.0-preview.17 → 1.0.0-preview.18

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 (33) hide show
  1. package/dist/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap +271 -7
  2. package/dist/assets/python/http/strands/base/main.py +13 -3
  3. package/dist/assets/python/http/strands/base/mcp_client/client.py +4 -1
  4. package/dist/assets/python/http/strands/base/model/load.py +116 -0
  5. package/dist/assets/python/http/strands/base/model/mantle_compat.py +21 -0
  6. package/dist/assets/python/http/strands/base/pyproject.toml +3 -0
  7. package/dist/assets/typescript/http/strands/base/main.ts +48 -0
  8. package/dist/assets/typescript/http/strands/base/package.json +3 -2
  9. package/dist/assets/typescript/http/strands/capabilities/memory/memory.ts +52 -0
  10. package/dist/cli/index.mjs +523 -557
  11. package/dist/schema/constants.d.ts +3 -2
  12. package/dist/schema/constants.d.ts.map +1 -1
  13. package/dist/schema/constants.js +7 -4
  14. package/dist/schema/constants.js.map +1 -1
  15. package/dist/schema/schemas/agent-env.d.ts +43 -0
  16. package/dist/schema/schemas/agent-env.d.ts.map +1 -1
  17. package/dist/schema/schemas/agent-env.js +11 -0
  18. package/dist/schema/schemas/agent-env.js.map +1 -1
  19. package/dist/schema/schemas/agentcore-project.d.ts +43 -0
  20. package/dist/schema/schemas/agentcore-project.d.ts.map +1 -1
  21. package/dist/schema/schemas/connections.d.ts +185 -0
  22. package/dist/schema/schemas/connections.d.ts.map +1 -0
  23. package/dist/schema/schemas/connections.js +176 -0
  24. package/dist/schema/schemas/connections.js.map +1 -0
  25. package/dist/schema/schemas/index.d.ts +1 -0
  26. package/dist/schema/schemas/index.d.ts.map +1 -1
  27. package/dist/schema/schemas/index.js +1 -0
  28. package/dist/schema/schemas/index.js.map +1 -1
  29. package/dist/schema/schemas/primitives/harness.d.ts +42 -0
  30. package/dist/schema/schemas/primitives/harness.d.ts.map +1 -1
  31. package/dist/schema/schemas/primitives/harness.js +4 -0
  32. package/dist/schema/schemas/primitives/harness.js.map +1 -1
  33. package/package.json +1 -1
@@ -831,6 +831,7 @@ exports[`Assets Directory Snapshots > File listing > should match the expected f
831
831
  "python/http/strands/base/mcp_client/client.py",
832
832
  "python/http/strands/base/model/__init__.py",
833
833
  "python/http/strands/base/model/load.py",
834
+ "python/http/strands/base/model/mantle_compat.py",
834
835
  "python/http/strands/base/pyproject.toml",
835
836
  "python/http/strands/base/skills/fetcher.py",
836
837
  "python/http/strands/capabilities/execution-limits/hooks/execution_limits.py",
@@ -850,6 +851,7 @@ exports[`Assets Directory Snapshots > File listing > should match the expected f
850
851
  "typescript/http/strands/base/model/load.ts",
851
852
  "typescript/http/strands/base/package.json",
852
853
  "typescript/http/strands/base/tsconfig.json",
854
+ "typescript/http/strands/capabilities/memory/memory.ts",
853
855
  "typescript/http/vercelai/base/README.md",
854
856
  "typescript/http/vercelai/base/gitignore.template",
855
857
  "typescript/http/vercelai/base/main.ts",
@@ -5276,7 +5278,7 @@ from mcp_client.client import get_streamable_http_mcp_client
5276
5278
  from memory.session import get_memory_session_manager
5277
5279
  {{/if}}
5278
5280
  {{#unless hasFileOperations}}
5279
- {{#if (or needsOs (some gitSkills "credentialArn"))}}
5281
+ {{#if (or needsOs browserIdentifierEnvVar codeInterpreterIdentifierEnvVar (some gitSkills "credentialArn"))}}
5280
5282
  import os
5281
5283
  {{/if}}
5282
5284
  {{/unless}}
@@ -5362,10 +5364,20 @@ tools.append(add_numbers)
5362
5364
  {{/unless}}
5363
5365
  {{/if}}
5364
5366
  {{#if hasBrowser}}
5365
- tools.append(AgentCoreBrowser({{#if browserIdentifier}}identifier="{{browserIdentifier}}"{{/if}}).browser)
5367
+ {{#if browserIdentifierEnvVar}}
5368
+ _browser_id = os.getenv("{{browserIdentifierEnvVar}}")
5369
+ tools.append(AgentCoreBrowser(**({"identifier": _browser_id} if _browser_id else {})).browser)
5370
+ {{else}}
5371
+ tools.append(AgentCoreBrowser().browser)
5372
+ {{/if}}
5366
5373
  {{/if}}
5367
5374
  {{#if hasCodeInterpreter}}
5368
- tools.append(AgentCoreCodeInterpreter({{#if codeInterpreterIdentifier}}identifier="{{codeInterpreterIdentifier}}"{{/if}}).code_interpreter)
5375
+ {{#if codeInterpreterIdentifierEnvVar}}
5376
+ _code_interpreter_id = os.getenv("{{codeInterpreterIdentifierEnvVar}}")
5377
+ tools.append(AgentCoreCodeInterpreter(**({"identifier": _code_interpreter_id} if _code_interpreter_id else {})).code_interpreter)
5378
+ {{else}}
5379
+ tools.append(AgentCoreCodeInterpreter().code_interpreter)
5380
+ {{/if}}
5369
5381
  {{/if}}
5370
5382
  {{#if hasShell}}
5371
5383
  @tool
@@ -5908,7 +5920,10 @@ from bedrock_agentcore.identity import requires_access_token
5908
5920
  @requires_access_token(
5909
5921
  provider_name="{{credentialProviderName}}",
5910
5922
  scopes=[{{#if scopes}}"{{scopes}}"{{/if}}],
5911
- auth_flow="M2M",
5923
+ auth_flow="{{#if authFlow}}{{authFlow}}{{else}}M2M{{/if}}",
5924
+ {{#if customParameters}}
5925
+ custom_parameters={{safeJson customParameters}},
5926
+ {{/if}}
5912
5927
  )
5913
5928
  def _get_bearer_token_{{snakeCase name}}(*, access_token: str):
5914
5929
  """Obtain OAuth access token via AgentCore Identity for {{name}}."""
@@ -6011,6 +6026,67 @@ exports[`Assets Directory Snapshots > Python framework assets > python/python/ht
6011
6026
 
6012
6027
  exports[`Assets Directory Snapshots > Python framework assets > python/python/http/strands/base/model/load.py should match snapshot 1`] = `
6013
6028
  "{{#if (eq modelProvider "Bedrock")}}
6029
+ {{#if bedrockMantle}}
6030
+ import os
6031
+
6032
+ from aws_bedrock_token_generator import provide_token
6033
+ {{#if (eq mantleApiFormat "chat_completions")}}
6034
+ from strands.models.openai import OpenAIModel
6035
+ {{else}}
6036
+ {{#if mantleProprietary}}
6037
+ from strands.models.openai_responses import OpenAIResponsesModel
6038
+ {{else}}
6039
+ from model.mantle_compat import MantleCompatResponsesModel
6040
+ {{/if}}
6041
+ {{/if}}
6042
+
6043
+ MODEL_ID = "{{modelId}}"
6044
+
6045
+
6046
+ def load_model():
6047
+ """
6048
+ Get a Bedrock Mantle model client. These OpenAI-compatible models (e.g. openai.gpt-5.5,
6049
+ openai.gpt-oss-120b) are served via the Bedrock Mantle endpoint, NOT the Converse API — so they
6050
+ are invoked through an OpenAI-style client authenticated with a short-lived Bedrock bearer token.
6051
+ Region is read from AWS_REGION (set by the AgentCore runtime).
6052
+ """
6053
+ region = os.environ.get("AWS_REGION", os.environ.get("AWS_DEFAULT_REGION", "us-east-1"))
6054
+ token = provide_token(region=region)
6055
+ {{#if mantleProprietary}}
6056
+ # Proprietary OpenAI models only work on the /openai/v1 Mantle path.
6057
+ base_url = f"https://bedrock-mantle.{region}.api.aws/openai/v1"
6058
+ {{else}}
6059
+ # Open-source OpenAI models (gpt-oss-*) only work on the /v1 Mantle path.
6060
+ base_url = f"https://bedrock-mantle.{region}.api.aws/v1"
6061
+ {{/if}}
6062
+ client_args = {"api_key": token, "base_url": base_url}
6063
+
6064
+ params = {}
6065
+ {{#if modelMaxTokens}}
6066
+ {{#if (eq mantleApiFormat "chat_completions")}}
6067
+ params["max_completion_tokens"] = {{modelMaxTokens}}
6068
+ {{else}}
6069
+ params["max_output_tokens"] = {{modelMaxTokens}}
6070
+ {{/if}}
6071
+ {{/if}}
6072
+ {{#if modelTemperature}}
6073
+ params["temperature"] = {{modelTemperature}}
6074
+ {{/if}}
6075
+ {{#if modelTopP}}
6076
+ params["top_p"] = {{modelTopP}}
6077
+ {{/if}}
6078
+ {{#if (eq mantleApiFormat "chat_completions")}}
6079
+ return OpenAIModel(client_args=client_args, model_id=MODEL_ID, params=params)
6080
+ {{else}}
6081
+ # Responses API: Mantle does not persist responses, so disable server-side storage.
6082
+ params["store"] = False
6083
+ {{#if mantleProprietary}}
6084
+ return OpenAIResponsesModel(client_args=client_args, model_id=MODEL_ID, params=params)
6085
+ {{else}}
6086
+ return MantleCompatResponsesModel(client_args=client_args, model_id=MODEL_ID, params=params)
6087
+ {{/if}}
6088
+ {{/if}}
6089
+ {{else}}
6014
6090
  from strands.models.bedrock import BedrockModel
6015
6091
 
6016
6092
 
@@ -6018,6 +6094,7 @@ def load_model() -> BedrockModel:
6018
6094
  """Get Bedrock model client using IAM credentials."""
6019
6095
  return BedrockModel(model_id="{{#if modelId}}{{modelId}}{{else}}global.anthropic.claude-sonnet-4-5-20250929-v1:0{{/if}}")
6020
6096
  {{/if}}
6097
+ {{/if}}
6021
6098
  {{#if (eq modelProvider "Anthropic")}}
6022
6099
  import os
6023
6100
 
@@ -6133,6 +6210,85 @@ def load_model() -> GeminiModel:
6133
6210
  model_id="{{#if modelId}}{{modelId}}{{else}}gemini-2.5-flash{{/if}}",
6134
6211
  )
6135
6212
  {{/if}}
6213
+ {{#if (eq modelProvider "LiteLLM")}}
6214
+ import os
6215
+ {{#if litellmAdditionalParams}}
6216
+ import json
6217
+ {{/if}}
6218
+
6219
+ from strands.models.litellm import LiteLLMModel
6220
+ {{#if identityProviders.[0].name}}
6221
+ from bedrock_agentcore.identity.auth import requires_api_key
6222
+
6223
+ IDENTITY_PROVIDER_NAME = "{{identityProviders.[0].name}}"
6224
+ IDENTITY_ENV_VAR = "{{identityProviders.[0].envVarName}}"
6225
+
6226
+
6227
+ @requires_api_key(provider_name=IDENTITY_PROVIDER_NAME)
6228
+ def _agentcore_identity_api_key_provider(api_key: str) -> str:
6229
+ """Fetch API key from AgentCore Identity."""
6230
+ return api_key
6231
+
6232
+
6233
+ def _get_api_key() -> str:
6234
+ """
6235
+ Uses AgentCore Identity for API key management in deployed environments.
6236
+ For local development, run via 'agentcore dev' which loads agentcore/.env.
6237
+ """
6238
+ if os.getenv("LOCAL_DEV") == "1":
6239
+ api_key = os.getenv(IDENTITY_ENV_VAR)
6240
+ if not api_key:
6241
+ raise RuntimeError(
6242
+ f"{IDENTITY_ENV_VAR} not found. Add {IDENTITY_ENV_VAR}=your-key to .env.local"
6243
+ )
6244
+ return api_key
6245
+ return _agentcore_identity_api_key_provider()
6246
+ {{/if}}
6247
+
6248
+
6249
+
6250
+
6251
+ def load_model() -> LiteLLMModel:
6252
+ """Get a LiteLLM model client (proxies to the provider encoded in model_id)."""
6253
+ client_args = {}
6254
+ {{#if identityProviders.[0].name}}
6255
+ client_args["api_key"] = _get_api_key()
6256
+ {{/if}}
6257
+ {{#if litellmApiBase}}
6258
+ client_args["api_base"] = {{safeJson litellmApiBase}}
6259
+ {{/if}}
6260
+ params = {{#if litellmAdditionalParams}}json.loads({{pyJsonStr litellmAdditionalParams}}){{else}}{}{{/if}}
6261
+ return LiteLLMModel(
6262
+ client_args=client_args,
6263
+ model_id="{{#if modelId}}{{modelId}}{{else}}bedrock/us.anthropic.claude-sonnet-4-5-20250514-v1:0{{/if}}",
6264
+ params=params,
6265
+ )
6266
+ {{/if}}
6267
+ "
6268
+ `;
6269
+
6270
+ exports[`Assets Directory Snapshots > Python framework assets > python/python/http/strands/base/model/mantle_compat.py should match snapshot 1`] = `
6271
+ "from strands.models.openai_responses import OpenAIResponsesModel
6272
+
6273
+
6274
+ class MantleCompatResponsesModel(OpenAIResponsesModel):
6275
+ """Workaround for Bedrock Mantle rejecting output_text in EasyInputMessage content arrays.
6276
+
6277
+ Mantle's Pydantic validation only accepts content as a plain string for assistant messages, while
6278
+ real OpenAI accepts both formats. Flatten assistant content arrays to strings so multi-turn works.
6279
+ Used for open-source OpenAI models (gpt-oss-*) on the /v1 Mantle path; proprietary models use the
6280
+ plain OpenAIResponsesModel on /openai/v1.
6281
+ """
6282
+
6283
+ @classmethod
6284
+ def _format_request_messages(cls, messages):
6285
+ formatted = super()._format_request_messages(messages)
6286
+ for msg in formatted:
6287
+ if msg.get("role") == "assistant" and isinstance(msg.get("content"), list):
6288
+ msg["content"] = "".join(
6289
+ part.get("text", "") for part in msg["content"] if part.get("type") == "output_text"
6290
+ )
6291
+ return formatted
6136
6292
  "
6137
6293
  `;
6138
6294
 
@@ -6155,6 +6311,9 @@ dependencies = [
6155
6311
  {{#if (eq modelProvider "Gemini")}}"google-genai >= 1.0.0",
6156
6312
  {{/if}}"mcp >= 1.19.0",
6157
6313
  {{#if (eq modelProvider "OpenAI")}}"openai >= 1.0.0",
6314
+ {{/if}}{{#if (eq modelProvider "LiteLLM")}}"litellm >= 1.0.0",
6315
+ {{/if}}{{#if bedrockMantle}}"openai >= 1.0.0",
6316
+ "aws-bedrock-token-generator >= 1.0.0",
6158
6317
  {{/if}}"strands-agents >= 1.15.0",
6159
6318
  {{#if (or hasBrowser hasCodeInterpreter)}}"strands-agents-tools >= 0.1.0",
6160
6319
  {{/if}}{{#if hasBrowser}}"nest-asyncio >= 1.5.0",
@@ -7379,6 +7538,9 @@ import { Agent, McpClient, tool, type ToolList } from '@strands-agents/sdk';
7379
7538
  import { z } from 'zod';
7380
7539
  import { loadModel } from './model/load.js';
7381
7540
  import { getStreamableHttpMcpClient } from './mcp_client/client.js';
7541
+ {{#if hasMemory}}
7542
+ import { getActorId, getOrCreateMemoryManager } from './memory/memory.js';
7543
+ {{/if}}
7382
7544
 
7383
7545
  // Define a collection of MCP clients (filter out anything that failed to initialize)
7384
7546
  const mcpClients: McpClient[] = [getStreamableHttpMcpClient()].filter(
@@ -7407,6 +7569,25 @@ const SYSTEM_PROMPT = \`
7407
7569
  You are a helpful assistant. Use tools when appropriate.
7408
7570
  \`;
7409
7571
 
7572
+ {{#if hasMemory}}
7573
+ const agentCache = new Map<string, Agent>();
7574
+
7575
+ async function getOrCreateAgent(sessionId: string, actorId: string): Promise<Agent> {
7576
+ const key = \`\${actorId}:\${sessionId}\`;
7577
+ let agent = agentCache.get(key);
7578
+ if (agent) return agent;
7579
+
7580
+ const model = await loadModel();
7581
+ agent = new Agent({
7582
+ model,
7583
+ systemPrompt: SYSTEM_PROMPT,
7584
+ tools,
7585
+ memoryManager: getOrCreateMemoryManager(sessionId, actorId) ?? undefined,
7586
+ });
7587
+ agentCache.set(key, agent);
7588
+ return agent;
7589
+ }
7590
+ {{else}}
7410
7591
  let cachedAgent: Agent | null = null;
7411
7592
 
7412
7593
  async function getOrCreateAgent(): Promise<Agent> {
@@ -7420,12 +7601,37 @@ async function getOrCreateAgent(): Promise<Agent> {
7420
7601
  }
7421
7602
  return cachedAgent;
7422
7603
  }
7604
+ {{/if}}
7423
7605
 
7424
7606
  const app = new BedrockAgentCoreApp({
7425
7607
  invocationHandler: {
7426
7608
  async *process(payload: any, context: any) {
7609
+ {{#if hasMemory}}
7610
+ const sessionId = context?.sessionId ?? 'default-session';
7611
+ const actorId = getActorId(payload, context);
7612
+ const agent = await getOrCreateAgent(sessionId, actorId);
7613
+ {{else}}
7427
7614
  const agent = await getOrCreateAgent();
7428
-
7615
+ {{/if}}
7616
+
7617
+ {{#if hasMemory}}
7618
+ try {
7619
+ for await (const event of agent.stream(payload.prompt ?? '')) {
7620
+ if (
7621
+ event.type === 'modelStreamUpdateEvent' &&
7622
+ event.event?.type === 'modelContentBlockDeltaEvent' &&
7623
+ event.event.delta?.type === 'textDelta'
7624
+ ) {
7625
+ yield { data: event.event.delta.text };
7626
+ }
7627
+ }
7628
+ } finally {
7629
+ // Drain in-flight createEvent calls before the runtime can reclaim
7630
+ // the session microVM. flush() is the durability mechanism — without
7631
+ // it, an idle reclamation can lose the tail of the conversation.
7632
+ await agent.memoryManager?.flush();
7633
+ }
7634
+ {{else}}
7429
7635
  for await (const event of agent.stream(payload.prompt ?? '')) {
7430
7636
  if (
7431
7637
  event.type === 'modelStreamUpdateEvent' &&
@@ -7435,6 +7641,7 @@ const app = new BedrockAgentCoreApp({
7435
7641
  yield { data: event.event.delta.text };
7436
7642
  }
7437
7643
  }
7644
+ {{/if}}
7438
7645
  },
7439
7646
  },
7440
7647
  });
@@ -7587,8 +7794,9 @@ exports[`Assets Directory Snapshots > TypeScript assets > typescript/typescript/
7587
7794
  "@google/genai": "^1.40.0",
7588
7795
  {{/if}}
7589
7796
  "@modelcontextprotocol/sdk": "^1.25.2",
7590
- "@strands-agents/sdk": "1.0.0-rc.4",
7591
- "bedrock-agentcore": "^0.2.4",
7797
+ "@opentelemetry/api": "^1.9.0",
7798
+ "@strands-agents/sdk": "^1.5.0",
7799
+ "bedrock-agentcore": "^0.3.0",
7592
7800
  "tsx": "^4.19.0",
7593
7801
  "zod": "^4.4.3"
7594
7802
  },
@@ -7628,6 +7836,62 @@ exports[`Assets Directory Snapshots > TypeScript assets > typescript/typescript/
7628
7836
  "
7629
7837
  `;
7630
7838
 
7839
+ exports[`Assets Directory Snapshots > TypeScript assets > typescript/typescript/http/strands/capabilities/memory/memory.ts should match snapshot 1`] = `
7840
+ "import { randomUUID } from 'node:crypto';
7841
+ import { MemoryManager } from '@strands-agents/sdk';
7842
+ import { createAgentCoreMemoryStores } from 'bedrock-agentcore/experimental/memory/strands';
7843
+
7844
+ const MEMORY_ID = process.env.{{memoryProviders.[0].envVarName}};
7845
+
7846
+ const CUSTOM_ACTOR_ID_HEADER = 'x-amzn-bedrock-agentcore-runtime-custom-actor-id';
7847
+
7848
+ export function getActorId(payload: any, context: any): string {
7849
+ const raw =
7850
+ context?.headers?.[CUSTOM_ACTOR_ID_HEADER] ||
7851
+ payload?.userId ||
7852
+ context?.sessionId;
7853
+ return typeof raw === 'string' && raw.trim().length > 0 ? raw.trim() : randomUUID();
7854
+ }
7855
+
7856
+ const memoryManagerCache = new Map<string, MemoryManager>();
7857
+
7858
+ export function getOrCreateMemoryManager(sessionId: string, actorId: string): MemoryManager | null {
7859
+ if (!MEMORY_ID) return null;
7860
+
7861
+ const key = \`\${actorId}:\${sessionId}\`;
7862
+ let manager = memoryManagerCache.get(key);
7863
+ if (manager) return manager;
7864
+
7865
+ const stores = createAgentCoreMemoryStores({
7866
+ memoryId: MEMORY_ID,
7867
+ actorId,
7868
+ sessionId,
7869
+ namespaces: [
7870
+ {{#if (includes memoryProviders.[0].strategies "SEMANTIC")}}
7871
+ { namespace: '/users/{actorId}/facts' },
7872
+ {{/if}}
7873
+ {{#if (includes memoryProviders.[0].strategies "USER_PREFERENCE")}}
7874
+ { namespace: '/users/{actorId}/preferences' },
7875
+ {{/if}}
7876
+ {{#if (includes memoryProviders.[0].strategies "EPISODIC")}}
7877
+ { namespace: '/episodes/{actorId}/{sessionId}' },
7878
+ {{/if}}
7879
+ {{#if (includes memoryProviders.[0].strategies "SUMMARIZATION")}}
7880
+ { namespace: '/summaries/{actorId}/{sessionId}' },
7881
+ {{/if}}
7882
+ ],
7883
+ // readMode defaults to 'per-namespace' (one retrieve call per namespace).
7884
+ // Switch to 'subtree' to consolidate to a single hierarchical recall call.
7885
+ extraction: true,
7886
+ });
7887
+
7888
+ manager = new MemoryManager({ stores });
7889
+ memoryManagerCache.set(key, manager);
7890
+ return manager;
7891
+ }
7892
+ "
7893
+ `;
7894
+
7631
7895
  exports[`Assets Directory Snapshots > TypeScript assets > typescript/typescript/http/vercelai/base/README.md should match snapshot 1`] = `
7632
7896
  "This is a project generated by the AgentCore CLI!
7633
7897
 
@@ -66,7 +66,7 @@ from mcp_client.client import get_streamable_http_mcp_client
66
66
  from memory.session import get_memory_session_manager
67
67
  {{/if}}
68
68
  {{#unless hasFileOperations}}
69
- {{#if (or needsOs (some gitSkills "credentialArn"))}}
69
+ {{#if (or needsOs browserIdentifierEnvVar codeInterpreterIdentifierEnvVar (some gitSkills "credentialArn"))}}
70
70
  import os
71
71
  {{/if}}
72
72
  {{/unless}}
@@ -152,10 +152,20 @@ tools.append(add_numbers)
152
152
  {{/unless}}
153
153
  {{/if}}
154
154
  {{#if hasBrowser}}
155
- tools.append(AgentCoreBrowser({{#if browserIdentifier}}identifier="{{browserIdentifier}}"{{/if}}).browser)
155
+ {{#if browserIdentifierEnvVar}}
156
+ _browser_id = os.getenv("{{browserIdentifierEnvVar}}")
157
+ tools.append(AgentCoreBrowser(**({"identifier": _browser_id} if _browser_id else {})).browser)
158
+ {{else}}
159
+ tools.append(AgentCoreBrowser().browser)
160
+ {{/if}}
156
161
  {{/if}}
157
162
  {{#if hasCodeInterpreter}}
158
- tools.append(AgentCoreCodeInterpreter({{#if codeInterpreterIdentifier}}identifier="{{codeInterpreterIdentifier}}"{{/if}}).code_interpreter)
163
+ {{#if codeInterpreterIdentifierEnvVar}}
164
+ _code_interpreter_id = os.getenv("{{codeInterpreterIdentifierEnvVar}}")
165
+ tools.append(AgentCoreCodeInterpreter(**({"identifier": _code_interpreter_id} if _code_interpreter_id else {})).code_interpreter)
166
+ {{else}}
167
+ tools.append(AgentCoreCodeInterpreter().code_interpreter)
168
+ {{/if}}
159
169
  {{/if}}
160
170
  {{#if hasShell}}
161
171
  @tool
@@ -18,7 +18,10 @@ from bedrock_agentcore.identity import requires_access_token
18
18
  @requires_access_token(
19
19
  provider_name="{{credentialProviderName}}",
20
20
  scopes=[{{#if scopes}}"{{scopes}}"{{/if}}],
21
- auth_flow="M2M",
21
+ auth_flow="{{#if authFlow}}{{authFlow}}{{else}}M2M{{/if}}",
22
+ {{#if customParameters}}
23
+ custom_parameters={{safeJson customParameters}},
24
+ {{/if}}
22
25
  )
23
26
  def _get_bearer_token_{{snakeCase name}}(*, access_token: str):
24
27
  """Obtain OAuth access token via AgentCore Identity for {{name}}."""
@@ -1,4 +1,65 @@
1
1
  {{#if (eq modelProvider "Bedrock")}}
2
+ {{#if bedrockMantle}}
3
+ import os
4
+
5
+ from aws_bedrock_token_generator import provide_token
6
+ {{#if (eq mantleApiFormat "chat_completions")}}
7
+ from strands.models.openai import OpenAIModel
8
+ {{else}}
9
+ {{#if mantleProprietary}}
10
+ from strands.models.openai_responses import OpenAIResponsesModel
11
+ {{else}}
12
+ from model.mantle_compat import MantleCompatResponsesModel
13
+ {{/if}}
14
+ {{/if}}
15
+
16
+ MODEL_ID = "{{modelId}}"
17
+
18
+
19
+ def load_model():
20
+ """
21
+ Get a Bedrock Mantle model client. These OpenAI-compatible models (e.g. openai.gpt-5.5,
22
+ openai.gpt-oss-120b) are served via the Bedrock Mantle endpoint, NOT the Converse API — so they
23
+ are invoked through an OpenAI-style client authenticated with a short-lived Bedrock bearer token.
24
+ Region is read from AWS_REGION (set by the AgentCore runtime).
25
+ """
26
+ region = os.environ.get("AWS_REGION", os.environ.get("AWS_DEFAULT_REGION", "us-east-1"))
27
+ token = provide_token(region=region)
28
+ {{#if mantleProprietary}}
29
+ # Proprietary OpenAI models only work on the /openai/v1 Mantle path.
30
+ base_url = f"https://bedrock-mantle.{region}.api.aws/openai/v1"
31
+ {{else}}
32
+ # Open-source OpenAI models (gpt-oss-*) only work on the /v1 Mantle path.
33
+ base_url = f"https://bedrock-mantle.{region}.api.aws/v1"
34
+ {{/if}}
35
+ client_args = {"api_key": token, "base_url": base_url}
36
+
37
+ params = {}
38
+ {{#if modelMaxTokens}}
39
+ {{#if (eq mantleApiFormat "chat_completions")}}
40
+ params["max_completion_tokens"] = {{modelMaxTokens}}
41
+ {{else}}
42
+ params["max_output_tokens"] = {{modelMaxTokens}}
43
+ {{/if}}
44
+ {{/if}}
45
+ {{#if modelTemperature}}
46
+ params["temperature"] = {{modelTemperature}}
47
+ {{/if}}
48
+ {{#if modelTopP}}
49
+ params["top_p"] = {{modelTopP}}
50
+ {{/if}}
51
+ {{#if (eq mantleApiFormat "chat_completions")}}
52
+ return OpenAIModel(client_args=client_args, model_id=MODEL_ID, params=params)
53
+ {{else}}
54
+ # Responses API: Mantle does not persist responses, so disable server-side storage.
55
+ params["store"] = False
56
+ {{#if mantleProprietary}}
57
+ return OpenAIResponsesModel(client_args=client_args, model_id=MODEL_ID, params=params)
58
+ {{else}}
59
+ return MantleCompatResponsesModel(client_args=client_args, model_id=MODEL_ID, params=params)
60
+ {{/if}}
61
+ {{/if}}
62
+ {{else}}
2
63
  from strands.models.bedrock import BedrockModel
3
64
 
4
65
 
@@ -6,6 +67,7 @@ def load_model() -> BedrockModel:
6
67
  """Get Bedrock model client using IAM credentials."""
7
68
  return BedrockModel(model_id="{{#if modelId}}{{modelId}}{{else}}global.anthropic.claude-sonnet-4-5-20250929-v1:0{{/if}}")
8
69
  {{/if}}
70
+ {{/if}}
9
71
  {{#if (eq modelProvider "Anthropic")}}
10
72
  import os
11
73
 
@@ -121,3 +183,57 @@ def load_model() -> GeminiModel:
121
183
  model_id="{{#if modelId}}{{modelId}}{{else}}gemini-2.5-flash{{/if}}",
122
184
  )
123
185
  {{/if}}
186
+ {{#if (eq modelProvider "LiteLLM")}}
187
+ import os
188
+ {{#if litellmAdditionalParams}}
189
+ import json
190
+ {{/if}}
191
+
192
+ from strands.models.litellm import LiteLLMModel
193
+ {{#if identityProviders.[0].name}}
194
+ from bedrock_agentcore.identity.auth import requires_api_key
195
+
196
+ IDENTITY_PROVIDER_NAME = "{{identityProviders.[0].name}}"
197
+ IDENTITY_ENV_VAR = "{{identityProviders.[0].envVarName}}"
198
+
199
+
200
+ @requires_api_key(provider_name=IDENTITY_PROVIDER_NAME)
201
+ def _agentcore_identity_api_key_provider(api_key: str) -> str:
202
+ """Fetch API key from AgentCore Identity."""
203
+ return api_key
204
+
205
+
206
+ def _get_api_key() -> str:
207
+ """
208
+ Uses AgentCore Identity for API key management in deployed environments.
209
+ For local development, run via 'agentcore dev' which loads agentcore/.env.
210
+ """
211
+ if os.getenv("LOCAL_DEV") == "1":
212
+ api_key = os.getenv(IDENTITY_ENV_VAR)
213
+ if not api_key:
214
+ raise RuntimeError(
215
+ f"{IDENTITY_ENV_VAR} not found. Add {IDENTITY_ENV_VAR}=your-key to .env.local"
216
+ )
217
+ return api_key
218
+ return _agentcore_identity_api_key_provider()
219
+ {{/if}}
220
+
221
+
222
+
223
+
224
+ def load_model() -> LiteLLMModel:
225
+ """Get a LiteLLM model client (proxies to the provider encoded in model_id)."""
226
+ client_args = {}
227
+ {{#if identityProviders.[0].name}}
228
+ client_args["api_key"] = _get_api_key()
229
+ {{/if}}
230
+ {{#if litellmApiBase}}
231
+ client_args["api_base"] = {{safeJson litellmApiBase}}
232
+ {{/if}}
233
+ params = {{#if litellmAdditionalParams}}json.loads({{pyJsonStr litellmAdditionalParams}}){{else}}{}{{/if}}
234
+ return LiteLLMModel(
235
+ client_args=client_args,
236
+ model_id="{{#if modelId}}{{modelId}}{{else}}bedrock/us.anthropic.claude-sonnet-4-5-20250514-v1:0{{/if}}",
237
+ params=params,
238
+ )
239
+ {{/if}}
@@ -0,0 +1,21 @@
1
+ from strands.models.openai_responses import OpenAIResponsesModel
2
+
3
+
4
+ class MantleCompatResponsesModel(OpenAIResponsesModel):
5
+ """Workaround for Bedrock Mantle rejecting output_text in EasyInputMessage content arrays.
6
+
7
+ Mantle's Pydantic validation only accepts content as a plain string for assistant messages, while
8
+ real OpenAI accepts both formats. Flatten assistant content arrays to strings so multi-turn works.
9
+ Used for open-source OpenAI models (gpt-oss-*) on the /v1 Mantle path; proprietary models use the
10
+ plain OpenAIResponsesModel on /openai/v1.
11
+ """
12
+
13
+ @classmethod
14
+ def _format_request_messages(cls, messages):
15
+ formatted = super()._format_request_messages(messages)
16
+ for msg in formatted:
17
+ if msg.get("role") == "assistant" and isinstance(msg.get("content"), list):
18
+ msg["content"] = "".join(
19
+ part.get("text", "") for part in msg["content"] if part.get("type") == "output_text"
20
+ )
21
+ return formatted
@@ -16,6 +16,9 @@ dependencies = [
16
16
  {{#if (eq modelProvider "Gemini")}}"google-genai >= 1.0.0",
17
17
  {{/if}}"mcp >= 1.19.0",
18
18
  {{#if (eq modelProvider "OpenAI")}}"openai >= 1.0.0",
19
+ {{/if}}{{#if (eq modelProvider "LiteLLM")}}"litellm >= 1.0.0",
20
+ {{/if}}{{#if bedrockMantle}}"openai >= 1.0.0",
21
+ "aws-bedrock-token-generator >= 1.0.0",
19
22
  {{/if}}"strands-agents >= 1.15.0",
20
23
  {{#if (or hasBrowser hasCodeInterpreter)}}"strands-agents-tools >= 0.1.0",
21
24
  {{/if}}{{#if hasBrowser}}"nest-asyncio >= 1.5.0",
@@ -3,6 +3,9 @@ import { Agent, McpClient, tool, type ToolList } from '@strands-agents/sdk';
3
3
  import { z } from 'zod';
4
4
  import { loadModel } from './model/load.js';
5
5
  import { getStreamableHttpMcpClient } from './mcp_client/client.js';
6
+ {{#if hasMemory}}
7
+ import { getActorId, getOrCreateMemoryManager } from './memory/memory.js';
8
+ {{/if}}
6
9
 
7
10
  // Define a collection of MCP clients (filter out anything that failed to initialize)
8
11
  const mcpClients: McpClient[] = [getStreamableHttpMcpClient()].filter(
@@ -31,6 +34,25 @@ const SYSTEM_PROMPT = `
31
34
  You are a helpful assistant. Use tools when appropriate.
32
35
  `;
33
36
 
37
+ {{#if hasMemory}}
38
+ const agentCache = new Map<string, Agent>();
39
+
40
+ async function getOrCreateAgent(sessionId: string, actorId: string): Promise<Agent> {
41
+ const key = `${actorId}:${sessionId}`;
42
+ let agent = agentCache.get(key);
43
+ if (agent) return agent;
44
+
45
+ const model = await loadModel();
46
+ agent = new Agent({
47
+ model,
48
+ systemPrompt: SYSTEM_PROMPT,
49
+ tools,
50
+ memoryManager: getOrCreateMemoryManager(sessionId, actorId) ?? undefined,
51
+ });
52
+ agentCache.set(key, agent);
53
+ return agent;
54
+ }
55
+ {{else}}
34
56
  let cachedAgent: Agent | null = null;
35
57
 
36
58
  async function getOrCreateAgent(): Promise<Agent> {
@@ -44,12 +66,37 @@ async function getOrCreateAgent(): Promise<Agent> {
44
66
  }
45
67
  return cachedAgent;
46
68
  }
69
+ {{/if}}
47
70
 
48
71
  const app = new BedrockAgentCoreApp({
49
72
  invocationHandler: {
50
73
  async *process(payload: any, context: any) {
74
+ {{#if hasMemory}}
75
+ const sessionId = context?.sessionId ?? 'default-session';
76
+ const actorId = getActorId(payload, context);
77
+ const agent = await getOrCreateAgent(sessionId, actorId);
78
+ {{else}}
51
79
  const agent = await getOrCreateAgent();
80
+ {{/if}}
52
81
 
82
+ {{#if hasMemory}}
83
+ try {
84
+ for await (const event of agent.stream(payload.prompt ?? '')) {
85
+ if (
86
+ event.type === 'modelStreamUpdateEvent' &&
87
+ event.event?.type === 'modelContentBlockDeltaEvent' &&
88
+ event.event.delta?.type === 'textDelta'
89
+ ) {
90
+ yield { data: event.event.delta.text };
91
+ }
92
+ }
93
+ } finally {
94
+ // Drain in-flight createEvent calls before the runtime can reclaim
95
+ // the session microVM. flush() is the durability mechanism — without
96
+ // it, an idle reclamation can lose the tail of the conversation.
97
+ await agent.memoryManager?.flush();
98
+ }
99
+ {{else}}
53
100
  for await (const event of agent.stream(payload.prompt ?? '')) {
54
101
  if (
55
102
  event.type === 'modelStreamUpdateEvent' &&
@@ -59,6 +106,7 @@ const app = new BedrockAgentCoreApp({
59
106
  yield { data: event.event.delta.text };
60
107
  }
61
108
  }
109
+ {{/if}}
62
110
  },
63
111
  },
64
112
  });