@aws/nx-plugin 1.0.0-rc.70 → 1.0.0-rc.72
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.
- package/migrations.json +11 -1
- package/package.json +1 -1
- package/src/agentcore-gateway/__snapshots__/generator.spec.ts.snap +1 -1
- package/src/agentcore-harness/generator.d.ts +1 -1
- package/src/infra/app/generator.d.ts +1 -1
- package/src/init/generator.d.ts +1 -1
- package/src/license/dependency-check/evaluator.js +55 -10
- package/src/license/dependency-check/evaluator.js.map +1 -1
- package/src/migrations/latest/py-agent-a2a-httpx-client-per-call/metadata.json +3 -0
- package/src/migrations/latest/py-agent-a2a-httpx-client-per-call/migration.d.ts +6 -0
- package/src/migrations/latest/py-agent-a2a-httpx-client-per-call/migration.js +329 -0
- package/src/migrations/latest/py-agent-a2a-httpx-client-per-call/migration.js.map +1 -0
- package/src/migrations/latest/py-agent-session-management-support/metadata.json +3 -0
- package/src/migrations/latest/py-agent-session-management-support/migration.d.ts +2 -0
- package/src/migrations/latest/py-agent-session-management-support/migration.js +308 -0
- package/src/migrations/latest/py-agent-session-management-support/migration.js.map +1 -0
- package/src/preset/generator.d.ts +1 -1
- package/src/py/agent/__snapshots__/generator.constructs.spec.ts.snap +615 -6
- package/src/py/agent/__snapshots__/generator.core.spec.ts.snap +186 -0
- package/src/py/agent/__snapshots__/generator.frameworks.spec.ts.snap +47 -3
- package/src/py/agent/__snapshots__/generator.protocols.spec.ts.snap +668 -3
- package/src/py/agent/a2a-connection/__snapshots__/generator.spec.ts.snap +55 -14
- package/src/py/agent/a2a-connection/files/agent-connection/app/__targetAgentSnakeCase___client_strands.py.template +1 -3
- package/src/py/agent/files/langchain/common/__init__.py.template +0 -0
- package/src/py/agent/files/langchain/common/agent.py.template +3 -3
- package/src/py/agent/files/langchain/common/session.py.template +61 -0
- package/src/py/agent/files/strands/ag-ui/main.py.template +5 -1
- package/src/py/agent/files/strands/common/agent.py.template +9 -2
- package/src/py/agent/files/strands/common/session.py.template +36 -0
- package/src/py/agent/generator.d.ts +11 -1
- package/src/py/agent/generator.js +78 -28
- package/src/py/agent/generator.js.map +1 -1
- package/src/py/agent/mcp-connection/__snapshots__/generator.spec.ts.snap +2 -0
- package/src/py/agent/react-connection/generator.d.ts +1 -1
- package/src/py/agent/schema.d.js.map +1 -1
- package/src/py/agent/schema.d.ts +1 -1
- package/src/py/agent/schema.json +3 -3
- package/src/py/dynamodb/__snapshots__/generator.spec.ts.snap +2 -2
- package/src/py/fast-api/__snapshots__/generator.terraform.spec.ts.snap +12 -12
- package/src/py/fast-api/react/generator.d.ts +1 -1
- package/src/py/lambda-function/__snapshots__/generator.spec.ts.snap +2 -2
- package/src/py/mcp-server/__snapshots__/generator.spec.ts.snap +1 -1
- package/src/py/rdb/__snapshots__/generator.spec.ts.snap +1 -1
- package/src/smithy/project/generator.d.ts +1 -1
- package/src/smithy/react-connection/generator.d.ts +1 -1
- package/src/smithy/ts/api/__snapshots__/generator.spec.ts.snap +7 -7
- package/src/terraform/project/generator.d.ts +1 -1
- package/src/trpc/backend/__snapshots__/generator.spec.ts.snap +12 -12
- package/src/trpc/react/generator.d.ts +1 -1
- package/src/ts/agent/__snapshots__/generator.spec.ts.snap +28 -7
- package/src/ts/agent/a2a-connection/generator.d.ts +1 -1
- package/src/ts/agent/gateway-connection/generator.d.ts +1 -1
- package/src/ts/agent/mcp-connection/generator.d.ts +1 -1
- package/src/ts/agent/react-connection/generator.d.ts +1 -1
- package/src/ts/astro-docs/generator.d.ts +1 -1
- package/src/ts/dcr-proxy/__snapshots__/generator.spec.ts.snap +1 -1
- package/src/ts/dcr-proxy/generator.d.ts +1 -1
- package/src/ts/dynamodb/__snapshots__/generator.spec.ts.snap +2 -2
- package/src/ts/dynamodb/generator.d.ts +1 -1
- package/src/ts/lambda-function/__snapshots__/generator.spec.ts.snap +2 -2
- package/src/ts/lambda-function/generator.d.ts +1 -1
- package/src/ts/lib/generator.d.ts +1 -1
- package/src/ts/mcp-server/__snapshots__/generator.spec.ts.snap +1 -1
- package/src/ts/nx-generator/generator.d.ts +1 -1
- package/src/ts/nx-migration/generator.d.ts +1 -1
- package/src/ts/nx-plugin/generator.d.ts +1 -1
- package/src/ts/rdb/__snapshots__/generator.spec.ts.snap +4 -4
- package/src/ts/rdb/generator.d.ts +1 -1
- package/src/ts/react-website/agui/generator.d.ts +1 -1
- package/src/ts/react-website/app/__snapshots__/generator.spec.ts.snap +2 -2
- package/src/ts/react-website/cognito-auth/__snapshots__/generator.terraform.spec.ts.snap +1 -1
- package/src/utils/agent-connection/agent-connection.d.ts +10 -0
- package/src/utils/agent-connection/agent-connection.js +15 -0
- package/src/utils/agent-connection/agent-connection.js.map +1 -1
- package/src/utils/agent-connection/files/core-runtime-config/runtime-config.ts.template +5 -3
- package/src/utils/agent-connection/files/py-core-langchain/a2a/agentcore_a2a_client_langchain.py.template +26 -20
- package/src/utils/agent-connection/files/py-core-langchain/s3/s3_checkpoint_saver_langchain.py.template +360 -0
- package/src/utils/agent-connection/files/py-core-runtime-config/runtime_config.py.template +2 -4
- package/src/utils/agent-connection/files/py-core-strands/a2a/agentcore_a2a_client_strands.py.template +55 -10
- package/src/utils/agent-connection/files/py-core-strands/base/tool_errors_strands.py.template +24 -0
- package/src/utils/agent-core-constructs/agent-core-constructs.d.ts +1 -1
- package/src/utils/agent-core-constructs/agent-core-constructs.js.map +1 -1
- package/src/utils/agent-core-constructs/files/cdk/app/agent-core/__nameKebabCase__/__nameKebabCase__.ts.template +113 -13
- package/src/utils/agent-core-constructs/files/terraform/app/agent-core/__nameKebabCase__/__nameKebabCase__.tf.template +76 -7
- package/src/utils/versions.d.ts +5 -2
- package/src/utils/versions.js +4 -1
- package/src/utils/versions.js.map +1 -1
|
@@ -42,21 +42,64 @@ class AgentCoreA2aClientConfig:
|
|
|
42
42
|
`;
|
|
43
43
|
|
|
44
44
|
exports[`py#agent#a2a-connection generator > should match snapshot for agent-connection core files > agentcore_a2a_client_strands.py 1`] = `
|
|
45
|
-
"
|
|
45
|
+
"import asyncio
|
|
46
|
+
from collections.abc import Callable
|
|
47
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
46
48
|
|
|
49
|
+
import httpx
|
|
50
|
+
from a2a.client import ClientConfig
|
|
47
51
|
from strands.agent.a2a_agent import A2AAgent
|
|
52
|
+
from strands.agent.agent_result import AgentResult
|
|
48
53
|
|
|
49
54
|
from .agentcore_a2a_client_config import AgentCoreA2aClientConfig
|
|
50
55
|
|
|
51
56
|
|
|
52
|
-
def
|
|
57
|
+
def _run_sync(coro):
|
|
58
|
+
# The tool is invoked from sync agent code, which under uvicorn runs inside a
|
|
59
|
+
# live event loop where asyncio.run() would raise — fall back to a worker.
|
|
60
|
+
try:
|
|
61
|
+
asyncio.get_running_loop()
|
|
62
|
+
except RuntimeError:
|
|
63
|
+
return asyncio.run(coro)
|
|
64
|
+
with ThreadPoolExecutor(max_workers=1) as pool:
|
|
65
|
+
return pool.submit(asyncio.run, coro).result()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class _A2AClient:
|
|
69
|
+
def __init__(
|
|
70
|
+
self,
|
|
71
|
+
url: str,
|
|
72
|
+
client_config: ClientConfig,
|
|
73
|
+
*,
|
|
74
|
+
name: str | None,
|
|
75
|
+
description: str | None,
|
|
76
|
+
):
|
|
77
|
+
shared_client = client_config.httpx_client
|
|
78
|
+
if shared_client is None:
|
|
79
|
+
raise RuntimeError("A2A client config is missing an httpx client")
|
|
80
|
+
self._url = url
|
|
81
|
+
self._auth = shared_client.auth
|
|
82
|
+
self._timeout = shared_client.timeout
|
|
83
|
+
self._name = name
|
|
84
|
+
self._description = description
|
|
85
|
+
|
|
86
|
+
def __call__(self, prompt: str) -> AgentResult:
|
|
87
|
+
return _run_sync(self._invoke(prompt))
|
|
88
|
+
|
|
89
|
+
async def _invoke(self, prompt: str) -> AgentResult:
|
|
90
|
+
async with httpx.AsyncClient(auth=self._auth, timeout=self._timeout) as httpx_client:
|
|
91
|
+
agent = A2AAgent(
|
|
92
|
+
endpoint=self._url,
|
|
93
|
+
name=self._name,
|
|
94
|
+
description=self._description,
|
|
95
|
+
client_config=ClientConfig(httpx_client=httpx_client, streaming=False),
|
|
96
|
+
)
|
|
97
|
+
return await agent.invoke_async(prompt)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _build(config: tuple, *, name: str | None, description: str | None) -> _A2AClient:
|
|
53
101
|
url, client_config = config
|
|
54
|
-
|
|
55
|
-
if name:
|
|
56
|
-
kwargs["name"] = name
|
|
57
|
-
if description:
|
|
58
|
-
kwargs["description"] = description
|
|
59
|
-
return A2AAgent(**kwargs)
|
|
102
|
+
return _A2AClient(url, client_config, name=name, description=description)
|
|
60
103
|
|
|
61
104
|
|
|
62
105
|
class AgentCoreA2aClientStrands:
|
|
@@ -68,7 +111,7 @@ class AgentCoreA2aClientStrands:
|
|
|
68
111
|
*,
|
|
69
112
|
name: str | None = None,
|
|
70
113
|
description: str | None = None,
|
|
71
|
-
) ->
|
|
114
|
+
) -> _A2AClient:
|
|
72
115
|
"""SigV4-authenticated client for a Bedrock AgentCore runtime."""
|
|
73
116
|
return _build(
|
|
74
117
|
AgentCoreA2aClientConfig.with_iam_auth(agent_runtime_arn),
|
|
@@ -83,7 +126,7 @@ class AgentCoreA2aClientStrands:
|
|
|
83
126
|
*,
|
|
84
127
|
name: str | None = None,
|
|
85
128
|
description: str | None = None,
|
|
86
|
-
) ->
|
|
129
|
+
) -> _A2AClient:
|
|
87
130
|
"""Bearer-authenticated client for a Bedrock AgentCore runtime."""
|
|
88
131
|
return _build(
|
|
89
132
|
AgentCoreA2aClientConfig.with_jwt_auth(agent_runtime_arn, access_token_provider),
|
|
@@ -97,7 +140,7 @@ class AgentCoreA2aClientStrands:
|
|
|
97
140
|
*,
|
|
98
141
|
name: str | None = None,
|
|
99
142
|
description: str | None = None,
|
|
100
|
-
) ->
|
|
143
|
+
) -> _A2AClient:
|
|
101
144
|
"""Plain-HTTP client — for local dev."""
|
|
102
145
|
return _build(
|
|
103
146
|
AgentCoreA2aClientConfig.without_auth(url),
|
|
@@ -110,8 +153,6 @@ class AgentCoreA2aClientStrands:
|
|
|
110
153
|
exports[`py#agent#a2a-connection generator > should match snapshot for agent-connection core files > remote_client_strands.py 1`] = `
|
|
111
154
|
"import os
|
|
112
155
|
|
|
113
|
-
from strands.agent.a2a_agent import A2AAgent
|
|
114
|
-
|
|
115
156
|
from test_agent_connection.core.agentcore_a2a_client_strands import (
|
|
116
157
|
AgentCoreA2aClientStrands,
|
|
117
158
|
)
|
|
@@ -124,7 +165,7 @@ class RemoteClientStrands:
|
|
|
124
165
|
"""Strands client for the Remote A2A agent."""
|
|
125
166
|
|
|
126
167
|
@staticmethod
|
|
127
|
-
def create()
|
|
168
|
+
def create():
|
|
128
169
|
if os.environ.get("LOCAL_DEV") == "true":
|
|
129
170
|
return AgentCoreA2aClientStrands.without_auth("http://localhost:9001/")
|
|
130
171
|
config = get_agentcore_runtime_config()
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import os
|
|
2
2
|
|
|
3
|
-
from strands.agent.a2a_agent import A2AAgent
|
|
4
|
-
|
|
5
3
|
from <%- agentConnectionModuleName %>.core.agentcore_a2a_client_strands import (
|
|
6
4
|
AgentCoreA2aClientStrands,
|
|
7
5
|
)
|
|
@@ -14,7 +12,7 @@ class <%- targetAgentClassName %>ClientStrands:
|
|
|
14
12
|
"""Strands client for the <%- targetAgentClassName %> A2A agent."""
|
|
15
13
|
|
|
16
14
|
@staticmethod
|
|
17
|
-
def create()
|
|
15
|
+
def create():
|
|
18
16
|
if os.environ.get("LOCAL_DEV") == "true":
|
|
19
17
|
return AgentCoreA2aClientStrands.without_auth(
|
|
20
18
|
"http://localhost:<%- targetAgentPort %>/"
|
|
File without changes
|
|
@@ -3,7 +3,8 @@ import os
|
|
|
3
3
|
from langchain.agents import create_agent
|
|
4
4
|
from langchain_aws import ChatBedrockConverse
|
|
5
5
|
from langchain_core.tools import tool
|
|
6
|
-
|
|
6
|
+
|
|
7
|
+
from .session import get_checkpointer
|
|
7
8
|
|
|
8
9
|
REGION = os.environ.get("AWS_REGION", "us-east-1")
|
|
9
10
|
MODEL_ID = os.environ.get("MODEL_ID", "global.anthropic.claude-haiku-4-5-20251001-v1:0")
|
|
@@ -17,7 +18,6 @@ def subtract(a: int, b: int) -> int:
|
|
|
17
18
|
|
|
18
19
|
def get_agent():
|
|
19
20
|
model = ChatBedrockConverse(model=MODEL_ID, region_name=REGION)
|
|
20
|
-
# Swap InMemorySaver for a durable checkpointer in a multi-replica deployment.
|
|
21
21
|
return create_agent(
|
|
22
22
|
model=model,
|
|
23
23
|
tools=[subtract],
|
|
@@ -26,5 +26,5 @@ You are a mathematical wizard.
|
|
|
26
26
|
Use your tools for mathematical tasks.
|
|
27
27
|
Refer to tools as your 'spellbook'.
|
|
28
28
|
""",
|
|
29
|
-
checkpointer=
|
|
29
|
+
checkpointer=get_checkpointer(),
|
|
30
30
|
)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
<%_ if (session === 's3') { _%>
|
|
4
|
+
import boto3
|
|
5
|
+
<%_ } _%>
|
|
6
|
+
import aiosqlite
|
|
7
|
+
from langgraph.checkpoint.base import BaseCheckpointSaver
|
|
8
|
+
<%_ if (session === 'in-memory') { _%>
|
|
9
|
+
from langgraph.checkpoint.memory import InMemorySaver
|
|
10
|
+
<%_ } _%>
|
|
11
|
+
from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver
|
|
12
|
+
<%_ if (session === 's3') { _%>
|
|
13
|
+
from <%- agentConnectionModuleName %>.core.s3_checkpoint_saver_langchain import S3CheckpointSaver
|
|
14
|
+
<%_ } else if (session === 'dynamodb-s3') { _%>
|
|
15
|
+
from langgraph_checkpoint_aws import DynamoDBSaver
|
|
16
|
+
<%_ } _%>
|
|
17
|
+
<%_ if (session === 's3' || session === 'dynamodb-s3') { _%>
|
|
18
|
+
from <%- agentConnectionModuleName %>.core.runtime_config import get_agentcore_runtime_config
|
|
19
|
+
<%_ } _%>
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_checkpointer() -> BaseCheckpointSaver:
|
|
23
|
+
"""Returns a LangGraph checkpointer for persisting conversation state across
|
|
24
|
+
invocations. Local development always uses a local SQLite database for
|
|
25
|
+
convenience, regardless of the configured session option.
|
|
26
|
+
"""
|
|
27
|
+
if os.environ.get("LOCAL_DEV") == "true":
|
|
28
|
+
os.makedirs("<%- localSessionsDir %>", exist_ok=True)
|
|
29
|
+
conn = aiosqlite.connect(os.path.join("<%- localSessionsDir %>", "checkpoints.sqlite"))
|
|
30
|
+
return AsyncSqliteSaver(conn)
|
|
31
|
+
|
|
32
|
+
<%_ if (session === 's3') { _%>
|
|
33
|
+
config = get_agentcore_runtime_config()
|
|
34
|
+
session_config = (config.get("agentRuntimes") or {}).get("<%- agentNameClassName %>", {}).get("session", {})
|
|
35
|
+
bucket_name = session_config.get("bucketName")
|
|
36
|
+
if not bucket_name:
|
|
37
|
+
raise RuntimeError(
|
|
38
|
+
"No S3 checkpoint bucket configured for this agent in runtime configuration."
|
|
39
|
+
)
|
|
40
|
+
return S3CheckpointSaver(
|
|
41
|
+
bucket_name=bucket_name,
|
|
42
|
+
prefix="checkpoints/",
|
|
43
|
+
s3_client=boto3.client("s3"),
|
|
44
|
+
)
|
|
45
|
+
<%_ } else if (session === 'dynamodb-s3') { _%>
|
|
46
|
+
config = get_agentcore_runtime_config()
|
|
47
|
+
session_config = (config.get("agentRuntimes") or {}).get("<%- agentNameClassName %>", {}).get("session", {})
|
|
48
|
+
table_name = session_config.get("tableName")
|
|
49
|
+
bucket_name = session_config.get("bucketName")
|
|
50
|
+
if not table_name or not bucket_name:
|
|
51
|
+
raise RuntimeError(
|
|
52
|
+
"No DynamoDB checkpoint table or S3 offload bucket configured for this agent in runtime configuration."
|
|
53
|
+
)
|
|
54
|
+
return DynamoDBSaver(
|
|
55
|
+
table_name=table_name,
|
|
56
|
+
region_name=os.environ.get("AWS_REGION"),
|
|
57
|
+
s3_offload_config={"bucket_name": bucket_name},
|
|
58
|
+
)
|
|
59
|
+
<%_ } else { _%>
|
|
60
|
+
return InMemorySaver()
|
|
61
|
+
<%_ } _%>
|
|
@@ -4,7 +4,7 @@ from contextlib import asynccontextmanager
|
|
|
4
4
|
|
|
5
5
|
from ag_ui.core import EventType, RunAgentInput, RunErrorEvent
|
|
6
6
|
from ag_ui.encoder import EventEncoder
|
|
7
|
-
from ag_ui_strands import StrandsAgent
|
|
7
|
+
from ag_ui_strands import StrandsAgent, StrandsAgentConfig
|
|
8
8
|
from fastapi import FastAPI, Request
|
|
9
9
|
from fastapi.middleware.cors import CORSMiddleware
|
|
10
10
|
from fastapi.responses import StreamingResponse
|
|
@@ -12,6 +12,7 @@ from <%- agentConnectionModuleName %> import get_current_session_id, session_id_
|
|
|
12
12
|
from starlette.middleware.base import BaseHTTPMiddleware
|
|
13
13
|
|
|
14
14
|
from .agent import get_agent
|
|
15
|
+
from .session import get_session_manager
|
|
15
16
|
|
|
16
17
|
logging.basicConfig(level=logging.INFO)
|
|
17
18
|
|
|
@@ -25,6 +26,9 @@ async def lifespan(app: FastAPI):
|
|
|
25
26
|
agent=agent,
|
|
26
27
|
name="<%= agentNameClassName %>",
|
|
27
28
|
description="A Strands Agent exposed via the AG-UI protocol.",
|
|
29
|
+
# A per-thread session manager, not the template Agent's own, since
|
|
30
|
+
# AG-UI caches one Strands agent per thread_id.
|
|
31
|
+
config=StrandsAgentConfig(session_manager_provider=lambda _input_data: get_session_manager()),
|
|
28
32
|
)
|
|
29
33
|
yield
|
|
30
34
|
|
|
@@ -2,7 +2,11 @@ from contextlib import contextmanager
|
|
|
2
2
|
|
|
3
3
|
from strands import Agent, tool
|
|
4
4
|
from strands_tools import current_time
|
|
5
|
-
from <%- agentConnectionModuleName %> import log_model_errors
|
|
5
|
+
from <%- agentConnectionModuleName %> import log_model_errors, log_tool_errors
|
|
6
|
+
<%_ if (protocol !== 'ag-ui') { _%>
|
|
7
|
+
|
|
8
|
+
from .session import get_session_manager
|
|
9
|
+
<%_ } _%>
|
|
6
10
|
|
|
7
11
|
|
|
8
12
|
@tool
|
|
@@ -21,5 +25,8 @@ Use your tools for mathematical tasks.
|
|
|
21
25
|
Refer to tools as your 'spellbook'.
|
|
22
26
|
""",
|
|
23
27
|
tools=[subtract, current_time],
|
|
24
|
-
hooks=[log_model_errors],
|
|
28
|
+
hooks=[log_model_errors, log_tool_errors],
|
|
29
|
+
<%_ if (protocol !== 'ag-ui') { _%>
|
|
30
|
+
session_manager=get_session_manager(),
|
|
31
|
+
<%_ } _%>
|
|
25
32
|
)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from strands.session import FileSessionManager, SessionManager<% if (session === 's3') { %>, S3SessionManager<% } %>
|
|
4
|
+
|
|
5
|
+
from <%- agentConnectionModuleName %> import get_current_session_id
|
|
6
|
+
<%_ if (session === 's3') { _%>
|
|
7
|
+
from <%- agentConnectionModuleName %>.core.runtime_config import get_agentcore_runtime_config
|
|
8
|
+
<%_ } _%>
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_session_manager() -> SessionManager | None:
|
|
12
|
+
"""Returns a SessionManager for persisting conversation state across
|
|
13
|
+
invocations. Local development always uses local file storage for
|
|
14
|
+
convenience, regardless of the configured session option. Without a
|
|
15
|
+
configured session option, conversation state is kept in memory only and
|
|
16
|
+
does not survive process restarts.
|
|
17
|
+
"""
|
|
18
|
+
session_id = get_current_session_id()
|
|
19
|
+
if not session_id:
|
|
20
|
+
raise RuntimeError(
|
|
21
|
+
"No current session id — cannot resolve a SessionManager outside of a request scope."
|
|
22
|
+
)
|
|
23
|
+
if os.environ.get("LOCAL_DEV") == "true":
|
|
24
|
+
return FileSessionManager(session_id=session_id, storage_dir="<%- localSessionsDir %>")
|
|
25
|
+
|
|
26
|
+
<%_ if (session === 's3') { _%>
|
|
27
|
+
config = get_agentcore_runtime_config()
|
|
28
|
+
bucket_name = (
|
|
29
|
+
(config.get("agentRuntimes") or {}).get("<%- agentNameClassName %>", {}).get("session", {}).get("bucketName")
|
|
30
|
+
)
|
|
31
|
+
if not bucket_name:
|
|
32
|
+
raise RuntimeError("No S3 bucket configured for this agent's session in runtime configuration.")
|
|
33
|
+
return S3SessionManager(session_id=session_id, bucket=bucket_name)
|
|
34
|
+
<%_ } else { _%>
|
|
35
|
+
return None
|
|
36
|
+
<%_ } _%>
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { type GeneratorCallback, type Tree } from '@nx/devkit';
|
|
6
6
|
import { type NxGeneratorInfo } from '../../utils/nx';
|
|
7
7
|
import type { IacMetadata } from '../../utils/shared-constructs-constants';
|
|
8
|
-
import type { AgentProtocol, PyAgentFramework, PyAgentGeneratorSchema } from './schema';
|
|
8
|
+
import type { AgentProtocol, PyAgentFramework, PyAgentGeneratorSchema, PyAgentSession } from './schema';
|
|
9
9
|
/** The metadata this generator records, which its predicates read. */
|
|
10
10
|
export interface PyAgentMetadata extends IacMetadata {
|
|
11
11
|
readonly port: number;
|
|
@@ -17,6 +17,7 @@ export interface PyAgentMetadata extends IacMetadata {
|
|
|
17
17
|
* Strands vs LangChain Layer-2 client + agent.py transform.
|
|
18
18
|
*/
|
|
19
19
|
readonly framework: PyAgentFramework;
|
|
20
|
+
readonly session: PyAgentSession;
|
|
20
21
|
}
|
|
21
22
|
export declare const DEPENDENCIES: import("../../utils/declared-dependencies").DependencyDeclaration<readonly [{
|
|
22
23
|
readonly name: "agent-chat-cli";
|
|
@@ -120,6 +121,15 @@ export declare const DEPENDENCIES: import("../../utils/declared-dependencies").D
|
|
|
120
121
|
}, {
|
|
121
122
|
readonly name: "langgraph";
|
|
122
123
|
readonly when: (m: PyAgentMetadata) => boolean;
|
|
124
|
+
}, {
|
|
125
|
+
readonly name: "langgraph-checkpoint-sqlite";
|
|
126
|
+
readonly when: (m: PyAgentMetadata) => boolean;
|
|
127
|
+
}, {
|
|
128
|
+
readonly name: "aiosqlite";
|
|
129
|
+
readonly when: (m: PyAgentMetadata) => boolean;
|
|
130
|
+
}, {
|
|
131
|
+
readonly name: "langgraph-checkpoint-aws";
|
|
132
|
+
readonly when: (m: PyAgentMetadata) => boolean;
|
|
123
133
|
}, {
|
|
124
134
|
readonly name: "ag-ui-protocol";
|
|
125
135
|
readonly when: (m: PyAgentMetadata) => boolean;
|
|
@@ -6,7 +6,7 @@ import { ensureLicenseExceptions } from "../../license/config.js";
|
|
|
6
6
|
import { AG_UI_LANGGRAPH_EXCEPTIONS } from "../../license/known-exceptions.js";
|
|
7
7
|
import { addPyDependencies, addTsDependencies } from "../../utils/add-dependencies.js";
|
|
8
8
|
import { addAgentChatScripts } from "../../utils/agent-chat/agent-chat.js";
|
|
9
|
-
import { AGENT_CONNECTION_PY_DEPENDENCIES, addPythonFrameworkBase, ensurePythonAgentConnectionProject, getPythonAgentConnectionModuleName, getPythonAgentConnectionProject } from "../../utils/agent-connection/agent-connection.js";
|
|
9
|
+
import { AGENT_CONNECTION_PY_DEPENDENCIES, addPythonFrameworkBase, ensureLangchainS3CheckpointSaver, ensurePythonAgentConnectionProject, getPythonAgentConnectionModuleName, getPythonAgentConnectionProject } from "../../utils/agent-connection/agent-connection.js";
|
|
10
10
|
import { addAgentInfra } from "../../utils/agent-core-constructs/agent-core-constructs.js";
|
|
11
11
|
import { addPythonBundleTarget } from "../../utils/bundle/bundle.js";
|
|
12
12
|
import { resolveContainers } from "../../utils/containers.js";
|
|
@@ -22,7 +22,7 @@ import { kebabCase, toClassName, toSnakeCase } from "../../utils/names.js";
|
|
|
22
22
|
import { getNpmScope } from "../../utils/npm-scope.js";
|
|
23
23
|
import { addComponentDevTarget, addComponentGeneratorMetadata, addDependencyToTargetIfNotPresent, getGeneratorInfo, normalizeTargetKeyOrder, readProjectConfigurationUnqualified } from "../../utils/nx.js";
|
|
24
24
|
import { sortObjectKeys } from "../../utils/object.js";
|
|
25
|
-
import { toProjectRelativePath } from "../../utils/paths.js";
|
|
25
|
+
import { getRelativePathToRootByDirectory, toProjectRelativePath } from "../../utils/paths.js";
|
|
26
26
|
import { assignPort } from "../../utils/port.js";
|
|
27
27
|
import { addWorkspaceDependencyToPyProject } from "../../utils/py.js";
|
|
28
28
|
import { SHARED_CONSTRUCTS_DEPENDENCIES, sharedConstructsGenerator } from "../../utils/shared-constructs.js";
|
|
@@ -117,6 +117,22 @@ export const DEPENDENCIES = declareDependencies()({
|
|
|
117
117
|
name: 'langgraph',
|
|
118
118
|
when: (m)=>m.framework === 'langchain'
|
|
119
119
|
},
|
|
120
|
+
// Local dev uses a SQLite-backed checkpointer for convenience (parity
|
|
121
|
+
// with the strands framework's local FileSessionManager).
|
|
122
|
+
{
|
|
123
|
+
name: 'langgraph-checkpoint-sqlite',
|
|
124
|
+
when: (m)=>m.framework === 'langchain'
|
|
125
|
+
},
|
|
126
|
+
// AsyncSqliteSaver's driver, required alongside langgraph-checkpoint-sqlite.
|
|
127
|
+
{
|
|
128
|
+
name: 'aiosqlite',
|
|
129
|
+
when: (m)=>m.framework === 'langchain'
|
|
130
|
+
},
|
|
131
|
+
// Provides DynamoDBSaver (with S3 offloading for large checkpoints).
|
|
132
|
+
{
|
|
133
|
+
name: 'langgraph-checkpoint-aws',
|
|
134
|
+
when: (m)=>m.framework === 'langchain' && m.session === 'dynamodb-s3'
|
|
135
|
+
},
|
|
120
136
|
{
|
|
121
137
|
name: 'ag-ui-protocol',
|
|
122
138
|
when: (m)=>m.framework === 'langchain' && m.protocol === 'ag-ui'
|
|
@@ -131,25 +147,25 @@ export const DEPENDENCIES = declareDependencies()({
|
|
|
131
147
|
},
|
|
132
148
|
{
|
|
133
149
|
name: 'strands-agents[a2a]',
|
|
134
|
-
when: (m)=>m.framework
|
|
150
|
+
when: (m)=>m.framework === 'strands' && m.protocol === 'a2a'
|
|
135
151
|
},
|
|
136
152
|
{
|
|
137
153
|
name: 'strands-agents',
|
|
138
|
-
when: (m)=>m.framework
|
|
154
|
+
when: (m)=>m.framework === 'strands' && m.protocol !== 'a2a'
|
|
139
155
|
},
|
|
140
156
|
{
|
|
141
157
|
name: 'strands-agents-tools',
|
|
142
|
-
when: (m)=>m.framework
|
|
158
|
+
when: (m)=>m.framework === 'strands'
|
|
143
159
|
},
|
|
144
160
|
// Declared again here so a Strands AG-UI agent lists it in the same order
|
|
145
161
|
// the generated pyproject.toml had before.
|
|
146
162
|
{
|
|
147
163
|
name: 'ag-ui-protocol',
|
|
148
|
-
when: (m)=>m.framework
|
|
164
|
+
when: (m)=>m.framework === 'strands' && m.protocol === 'ag-ui'
|
|
149
165
|
},
|
|
150
166
|
{
|
|
151
167
|
name: 'ag-ui-strands',
|
|
152
|
-
when: (m)=>m.framework
|
|
168
|
+
when: (m)=>m.framework === 'strands' && m.protocol === 'ag-ui'
|
|
153
169
|
},
|
|
154
170
|
{
|
|
155
171
|
name: 'uvicorn'
|
|
@@ -193,9 +209,41 @@ export const pyAgentGenerator = async (tree, options)=>{
|
|
|
193
209
|
console.warn('Warning: auth is ignored when no infrastructure is configured (no infrastructure is generated)');
|
|
194
210
|
}
|
|
195
211
|
const auth = options.auth ?? 'iam';
|
|
196
|
-
//
|
|
197
|
-
//
|
|
198
|
-
|
|
212
|
+
// Session backends are framework-specific: Strands' SessionManager only has
|
|
213
|
+
// an S3-backed implementation here (via strands.session.S3SessionManager),
|
|
214
|
+
// while LangChain uses dedicated S3 and DynamoDB checkpointer libraries.
|
|
215
|
+
// 'in-memory' is valid for both.
|
|
216
|
+
const SESSIONS_BY_FRAMEWORK = {
|
|
217
|
+
strands: [
|
|
218
|
+
's3',
|
|
219
|
+
'in-memory'
|
|
220
|
+
],
|
|
221
|
+
langchain: [
|
|
222
|
+
's3',
|
|
223
|
+
'dynamodb-s3',
|
|
224
|
+
'in-memory'
|
|
225
|
+
]
|
|
226
|
+
};
|
|
227
|
+
if (options.session && !SESSIONS_BY_FRAMEWORK[framework].includes(options.session)) {
|
|
228
|
+
throw new Error(`Unsupported combination: session '${options.session}' is not implemented for the ${framework} framework (supported: ${SESSIONS_BY_FRAMEWORK[framework].join(', ')}).`);
|
|
229
|
+
}
|
|
230
|
+
const session = options.session ?? 's3';
|
|
231
|
+
// With infra=none there's no CDK/Terraform construct to provision a bucket
|
|
232
|
+
// or table or set RUNTIME_CONFIG_APP_ID, so session.py's non-local-dev
|
|
233
|
+
// branch has nothing to read from AppConfig at runtime — the generated code
|
|
234
|
+
// still honors `session`, but only works outside local dev if the caller
|
|
235
|
+
// wires up matching infra/runtime config themselves.
|
|
236
|
+
if (infra === 'none' && session !== 'in-memory') {
|
|
237
|
+
console.warn(`Warning: session '${session}' requires infrastructure to configure it automatically (no infrastructure is generated for infra=none) — outside local dev this will fail unless you configure matching runtime config yourself`);
|
|
238
|
+
}
|
|
239
|
+
// Local-dev session storage lives at the workspace root
|
|
240
|
+
// (`tmp/agents/<framework>/<agent-name>`), not inside the project, so each
|
|
241
|
+
// agent gets its own storage directory — shared by both frameworks
|
|
242
|
+
// (Strands' FileSessionManager, LangChain's SqliteSaver). The
|
|
243
|
+
// `-dev`/`-serve` targets run with cwd={projectRoot}, so compute that
|
|
244
|
+
// directory relative to the project root here rather than resolving it at
|
|
245
|
+
// runtime.
|
|
246
|
+
const localSessionsDir = joinPathFragments(getRelativePathToRootByDirectory(project.root), `tmp/agents/${framework}/${name}`);
|
|
199
247
|
// Ensure the shared agent-connection project exists so the server entry
|
|
200
248
|
// point can import `session_id_context` and propagate the AgentCore
|
|
201
249
|
// session ID to any downstream MCP / A2A clients a later connection
|
|
@@ -203,8 +251,8 @@ export const pyAgentGenerator = async (tree, options)=>{
|
|
|
203
251
|
await ensurePythonAgentConnectionProject(tree, DEPENDENCIES);
|
|
204
252
|
// The agent server imports the framework base helpers (session cache + model
|
|
205
253
|
// error logging) regardless of whether a connection client is wired in. The
|
|
206
|
-
//
|
|
207
|
-
// framework-agnostic session context), so this is a no-op for
|
|
254
|
+
// LangChain framework has no base layer (its AG-UI foundation reuses only the
|
|
255
|
+
// framework-agnostic session context), so this is a no-op for LangChain.
|
|
208
256
|
await addPythonFrameworkBase(tree, DEPENDENCIES, framework);
|
|
209
257
|
const agentConnectionModuleName = getPythonAgentConnectionModuleName(tree);
|
|
210
258
|
addWorkspaceDependencyToPyProject(tree, project, getPythonAgentConnectionProject(tree));
|
|
@@ -214,27 +262,28 @@ export const pyAgentGenerator = async (tree, options)=>{
|
|
|
214
262
|
agentNameClassName,
|
|
215
263
|
moduleName,
|
|
216
264
|
agentConnectionModuleName,
|
|
217
|
-
framework
|
|
265
|
+
framework,
|
|
266
|
+
protocol,
|
|
267
|
+
session,
|
|
268
|
+
localSessionsDir
|
|
218
269
|
};
|
|
219
|
-
//
|
|
220
|
-
//
|
|
221
|
-
//
|
|
222
|
-
//
|
|
270
|
+
// Files live under a per-framework dir (files/strands, files/langchain),
|
|
271
|
+
// mirrored one-for-one — including files that don't vary by framework
|
|
272
|
+
// (e.g. the empty package __init__.py) — so there's a single, uniform
|
|
273
|
+
// <framework>/<protocol> layout with no cross-framework fallbacks.
|
|
274
|
+
// `framework` is itself the directory name.
|
|
275
|
+
// Common files shared by both protocols: the agent module (Strands yields a
|
|
276
|
+
// contextmanaged Agent with a session.py sibling; LangChain returns a
|
|
277
|
+
// compiled create_agent graph) and the package __init__.py.
|
|
223
278
|
generateFiles(tree, joinPathFragments(import.meta.dirname, 'files', framework, 'common'), targetSourceDir, templateContext, {
|
|
224
279
|
overwriteStrategy: OverwriteStrategy.KeepExisting
|
|
225
280
|
});
|
|
226
|
-
if (framework === 'langchain') {
|
|
227
|
-
|
|
228
|
-
// framework-agnostic, so emit it from the strands `common` dir.
|
|
229
|
-
generateFiles(tree, joinPathFragments(import.meta.dirname, 'files', 'strands', 'common'), targetSourceDir, templateContext, {
|
|
230
|
-
overwriteStrategy: OverwriteStrategy.KeepExisting
|
|
231
|
-
});
|
|
281
|
+
if (framework === 'langchain' && session === 's3') {
|
|
282
|
+
ensureLangchainS3CheckpointSaver(tree);
|
|
232
283
|
}
|
|
233
|
-
//
|
|
234
|
-
//
|
|
235
|
-
//
|
|
236
|
-
// a compiled create_agent graph), so it comes from a per-framework,
|
|
237
|
-
// per-protocol dir: `files/<framework>/<protocol>`.
|
|
284
|
+
// Protocol-specific files. Each protocol's server entry point is
|
|
285
|
+
// framework-specific (Strands yields a contextmanaged Agent; LangChain
|
|
286
|
+
// drives a compiled create_agent graph).
|
|
238
287
|
const protocolLower = protocol.toLowerCase();
|
|
239
288
|
generateFiles(tree, joinPathFragments(import.meta.dirname, 'files', framework, protocolLower), targetSourceDir, templateContext, {
|
|
240
289
|
overwriteStrategy: OverwriteStrategy.KeepExisting
|
|
@@ -325,6 +374,7 @@ export const pyAgentGenerator = async (tree, options)=>{
|
|
|
325
374
|
auth,
|
|
326
375
|
protocol,
|
|
327
376
|
framework,
|
|
377
|
+
session,
|
|
328
378
|
...iac ? {
|
|
329
379
|
iac
|
|
330
380
|
} : {}
|