@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
package/migrations.json
CHANGED
|
@@ -17,6 +17,16 @@
|
|
|
17
17
|
"description": "Move py#agent A2A agent construction (Strands and LangChain) out of module import time and into a FastAPI lifespan handler, stored on app.state; also updates with_session_id/session_id_context to use Generator instead of the deprecated Iterator return type",
|
|
18
18
|
"implementation": "./src/migrations/latest/py-agent-a2a-lifespan-construction/migration"
|
|
19
19
|
},
|
|
20
|
+
"latest-py-agent-a2a-httpx-client-per-call": {
|
|
21
|
+
"version": "1.0.0-rc.71",
|
|
22
|
+
"description": "Rebuild the httpx.AsyncClient (and Strands A2AAgent) per A2A delegate call instead of reusing one built at factory time, fixing a RuntimeError: Event loop is closed on the second call through a shared client",
|
|
23
|
+
"implementation": "./src/migrations/latest/py-agent-a2a-httpx-client-per-call/migration"
|
|
24
|
+
},
|
|
25
|
+
"latest-py-agent-session-management-support": {
|
|
26
|
+
"version": "1.0.0-rc.72",
|
|
27
|
+
"description": "Add session management support to py#agent",
|
|
28
|
+
"implementation": "./src/migrations/latest/py-agent-session-management-support/migration"
|
|
29
|
+
},
|
|
20
30
|
"v1.0.0-rc.50-0001-modernize-function-props-cast": {
|
|
21
31
|
"version": "1.0.0-rc.50",
|
|
22
32
|
"description": "Replace the legacy angle-bracket FunctionProps type assertion with the modern as syntax in generated API constructs",
|
|
@@ -113,7 +123,7 @@
|
|
|
113
123
|
"implementation": "./src/migrations/v1.0.0-rc.65/0002-rolldown-code-splitting/migration"
|
|
114
124
|
},
|
|
115
125
|
"sync-vended-versions": {
|
|
116
|
-
"version": "1.0.0-rc.
|
|
126
|
+
"version": "1.0.0-rc.72",
|
|
117
127
|
"description": "Sync vended dependency versions and the tracked plugin version to those vended by this release",
|
|
118
128
|
"implementation": "./src/utils/version-upgrade-migration/migration"
|
|
119
129
|
}
|
package/package.json
CHANGED
|
@@ -40,7 +40,7 @@ export declare const DEPENDENCIES: import("../utils/declared-dependencies").Depe
|
|
|
40
40
|
readonly name: "@types/node";
|
|
41
41
|
} & {
|
|
42
42
|
versionOnly: true;
|
|
43
|
-
}], readonly import("../utils/declared-dependencies").DeclaredPyDependency<"a2a-sdk" | "ag-ui-langgraph" | "ag-ui-protocol" | "ag-ui-strands" | "aws-lambda-powertools" | "aws-lambda-powertools[tracer]" | "aws-lambda-powertools[parser]" | "aws-opentelemetry-distro" | "bedrock-agentcore" | "boto3" | "checkov" | "fastapi" | "fastapi[standard]" | "httpx" | "langchain" | "langchain-aws" | "langchain-mcp-adapters" | "langgraph" | "mcp" | "pip-check-updates" | "pip-licenses" | "ruff" | "strands-agents" | "strands-agents[a2a]" | "strands-agents-tools" | "ty" | "pynamodb" | "uvicorn" | "sqlmodel" | "alembic" | "aiomysql" | "asyncpg" | "greenlet", AgentCoreHarnessMetadata>[]>;
|
|
43
|
+
}], readonly import("../utils/declared-dependencies").DeclaredPyDependency<"a2a-sdk" | "ag-ui-langgraph" | "ag-ui-protocol" | "ag-ui-strands" | "aiosqlite" | "aws-lambda-powertools" | "aws-lambda-powertools[tracer]" | "aws-lambda-powertools[parser]" | "aws-opentelemetry-distro" | "bedrock-agentcore" | "boto3" | "checkov" | "fastapi" | "fastapi[standard]" | "httpx" | "langchain" | "langchain-aws" | "langchain-mcp-adapters" | "langgraph" | "langgraph-checkpoint-aws" | "langgraph-checkpoint-sqlite" | "mcp" | "pip-check-updates" | "pip-licenses" | "ruff" | "strands-agents" | "strands-agents[a2a]" | "strands-agents-tools" | "ty" | "pynamodb" | "uvicorn" | "sqlmodel" | "alembic" | "aiomysql" | "asyncpg" | "greenlet", AgentCoreHarnessMetadata>[]>;
|
|
44
44
|
export declare const AGENTCORE_HARNESS_GENERATOR_INFO: NxGeneratorInfo;
|
|
45
45
|
export declare const agentcoreHarnessGenerator: (tree: Tree, options: AgentcoreHarnessGeneratorSchema) => Promise<GeneratorCallback>;
|
|
46
46
|
/**
|
|
@@ -47,7 +47,7 @@ export declare const DEPENDENCIES: import("../../utils/declared-dependencies").D
|
|
|
47
47
|
readonly when: (m: import("../../utils/shared-constructs-constants").IacMetadata) => boolean;
|
|
48
48
|
} & {
|
|
49
49
|
versionOnly: true;
|
|
50
|
-
}], readonly import("../../utils/declared-dependencies").DeclaredPyDependency<"a2a-sdk" | "ag-ui-langgraph" | "ag-ui-protocol" | "ag-ui-strands" | "aws-lambda-powertools" | "aws-lambda-powertools[tracer]" | "aws-lambda-powertools[parser]" | "aws-opentelemetry-distro" | "bedrock-agentcore" | "boto3" | "checkov" | "fastapi" | "fastapi[standard]" | "httpx" | "langchain" | "langchain-aws" | "langchain-mcp-adapters" | "langgraph" | "mcp" | "pip-check-updates" | "pip-licenses" | "ruff" | "strands-agents" | "strands-agents[a2a]" | "strands-agents-tools" | "ty" | "pynamodb" | "uvicorn" | "sqlmodel" | "alembic" | "aiomysql" | "asyncpg" | "greenlet", Record<string, never>>[]>;
|
|
50
|
+
}], readonly import("../../utils/declared-dependencies").DeclaredPyDependency<"a2a-sdk" | "ag-ui-langgraph" | "ag-ui-protocol" | "ag-ui-strands" | "aiosqlite" | "aws-lambda-powertools" | "aws-lambda-powertools[tracer]" | "aws-lambda-powertools[parser]" | "aws-opentelemetry-distro" | "bedrock-agentcore" | "boto3" | "checkov" | "fastapi" | "fastapi[standard]" | "httpx" | "langchain" | "langchain-aws" | "langchain-mcp-adapters" | "langgraph" | "langgraph-checkpoint-aws" | "langgraph-checkpoint-sqlite" | "mcp" | "pip-check-updates" | "pip-licenses" | "ruff" | "strands-agents" | "strands-agents[a2a]" | "strands-agents-tools" | "ty" | "pynamodb" | "uvicorn" | "sqlmodel" | "alembic" | "aiomysql" | "asyncpg" | "greenlet", Record<string, never>>[]>;
|
|
51
51
|
export declare const INFRA_APP_GENERATOR_INFO: NxGeneratorInfo;
|
|
52
52
|
export declare function tsInfraGenerator(tree: Tree, schema: TsInfraGeneratorSchema): Promise<GeneratorCallback>;
|
|
53
53
|
export default tsInfraGenerator;
|
package/src/init/generator.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export declare const DEPENDENCIES: import("../utils/declared-dependencies").Depe
|
|
|
17
17
|
readonly name: "typescript";
|
|
18
18
|
}, {
|
|
19
19
|
readonly name: "@biomejs/biome";
|
|
20
|
-
}], readonly import("../utils/declared-dependencies").DeclaredPyDependency<"a2a-sdk" | "ag-ui-langgraph" | "ag-ui-protocol" | "ag-ui-strands" | "aws-lambda-powertools" | "aws-lambda-powertools[tracer]" | "aws-lambda-powertools[parser]" | "aws-opentelemetry-distro" | "bedrock-agentcore" | "boto3" | "checkov" | "fastapi" | "fastapi[standard]" | "httpx" | "langchain" | "langchain-aws" | "langchain-mcp-adapters" | "langgraph" | "mcp" | "pip-check-updates" | "pip-licenses" | "ruff" | "strands-agents" | "strands-agents[a2a]" | "strands-agents-tools" | "ty" | "pynamodb" | "uvicorn" | "sqlmodel" | "alembic" | "aiomysql" | "asyncpg" | "greenlet", Record<string, never>>[]>;
|
|
20
|
+
}], readonly import("../utils/declared-dependencies").DeclaredPyDependency<"a2a-sdk" | "ag-ui-langgraph" | "ag-ui-protocol" | "ag-ui-strands" | "aiosqlite" | "aws-lambda-powertools" | "aws-lambda-powertools[tracer]" | "aws-lambda-powertools[parser]" | "aws-opentelemetry-distro" | "bedrock-agentcore" | "boto3" | "checkov" | "fastapi" | "fastapi[standard]" | "httpx" | "langchain" | "langchain-aws" | "langchain-mcp-adapters" | "langgraph" | "langgraph-checkpoint-aws" | "langgraph-checkpoint-sqlite" | "mcp" | "pip-check-updates" | "pip-licenses" | "ruff" | "strands-agents" | "strands-agents[a2a]" | "strands-agents-tools" | "ty" | "pynamodb" | "uvicorn" | "sqlmodel" | "alembic" | "aiomysql" | "asyncpg" | "greenlet", Record<string, never>>[]>;
|
|
21
21
|
export declare const INIT_GENERATOR_INFO: NxGeneratorInfo;
|
|
22
22
|
/**
|
|
23
23
|
* Configure an existing Nx workspace to use the @aws/nx-plugin.
|
|
@@ -53,6 +53,45 @@ const isUnknownToken = (raw)=>{
|
|
|
53
53
|
}
|
|
54
54
|
return m;
|
|
55
55
|
};
|
|
56
|
+
/**
|
|
57
|
+
* Split a free-text license list on a separator, treating the operands as
|
|
58
|
+
* alternatives (OR).
|
|
59
|
+
*
|
|
60
|
+
* Commas need care: several allowed license names contain one themselves (e.g.
|
|
61
|
+
* "Apache License, Version 2.0"), so the string is left intact when it already
|
|
62
|
+
* resolves as a single license, and fragments are otherwise rejoined
|
|
63
|
+
* longest-match-first. That keeps "MIT License, Apache License, Version 2.0"
|
|
64
|
+
* parsing as MIT + Apache rather than three unresolvable fragments.
|
|
65
|
+
*
|
|
66
|
+
* Returns `undefined` when the value shouldn't be treated as a list.
|
|
67
|
+
*/ const splitAlternatives = (value, separator, isKnown)=>{
|
|
68
|
+
if (!value.includes(separator)) return undefined;
|
|
69
|
+
if (isKnown(value)) return undefined;
|
|
70
|
+
const parts = value.split(separator).map((p)=>p.trim()).filter((p)=>p.length > 0);
|
|
71
|
+
if (parts.length < 2) return undefined;
|
|
72
|
+
// Rejoin fragments that only make sense together, so a license name
|
|
73
|
+
// containing the separator survives the split. At each position take the
|
|
74
|
+
// longest run of fragments that forms a known license; failing that, emit the
|
|
75
|
+
// single fragment and let the caller report it.
|
|
76
|
+
const merged = [];
|
|
77
|
+
let i = 0;
|
|
78
|
+
while(i < parts.length){
|
|
79
|
+
let matchedTo = -1;
|
|
80
|
+
let candidate = parts[i];
|
|
81
|
+
for(let j = i; j < parts.length; j++){
|
|
82
|
+
if (j > i) candidate = `${candidate}${separator} ${parts[j]}`;
|
|
83
|
+
if (isKnown(candidate)) matchedTo = j;
|
|
84
|
+
}
|
|
85
|
+
if (matchedTo >= 0) {
|
|
86
|
+
merged.push(parts.slice(i, matchedTo + 1).join(`${separator} `));
|
|
87
|
+
i = matchedTo + 1;
|
|
88
|
+
} else {
|
|
89
|
+
merged.push(parts[i]);
|
|
90
|
+
i += 1;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return merged.length > 1 ? merged : undefined;
|
|
94
|
+
};
|
|
56
95
|
const evaluateToken = (token, spdxIds, aliasLookup)=>{
|
|
57
96
|
const cleaned = normalise(token);
|
|
58
97
|
if (isUnknownToken(cleaned)) return 'UNKNOWN';
|
|
@@ -74,17 +113,23 @@ export const createEvaluator = (options)=>{
|
|
|
74
113
|
if (!rawLicense || rawLicense.trim().length === 0) return 'UNKNOWN';
|
|
75
114
|
const normalised = normalise(rawLicense);
|
|
76
115
|
if (isUnknownToken(normalised)) return 'UNKNOWN';
|
|
77
|
-
// Python tooling emits
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
116
|
+
// Python tooling emits free-text license lists rather than an SPDX
|
|
117
|
+
// expression: "Apache Software License; BSD License" (setuptools
|
|
118
|
+
// classifiers) or "MIT License, Apache License, Version 2.0" (a dual
|
|
119
|
+
// license declared in one `License:` field). Both mean "any of these
|
|
120
|
+
// applies", so treat the operands as OR.
|
|
121
|
+
const isKnown = (token)=>evaluateToken(token, spdxIds, aliasLookup) === 'PRE_APPROVED';
|
|
122
|
+
for (const separator of [
|
|
123
|
+
';',
|
|
124
|
+
','
|
|
125
|
+
]){
|
|
126
|
+
const parts = splitAlternatives(normalised, separator, isKnown);
|
|
127
|
+
if (!parts) continue;
|
|
128
|
+
for (const part of parts){
|
|
129
|
+
if (evaluateToken(part, spdxIds, aliasLookup) === 'PRE_APPROVED') return 'PRE_APPROVED';
|
|
87
130
|
}
|
|
131
|
+
if (parts.every((p)=>isUnknownToken(normalise(p)))) return 'UNKNOWN';
|
|
132
|
+
return 'NOT_APPROVED';
|
|
88
133
|
}
|
|
89
134
|
return evaluateToken(normalised, spdxIds, aliasLookup);
|
|
90
135
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../packages/nx-plugin/src/license/dependency-check/evaluator.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport spdxSatisfies from 'spdx-satisfies';\nimport type { AllowlistEntry, LicenseStatus } from './types';\n\nexport interface EvaluatorOptions {\n allow: AllowlistEntry[];\n}\n\ninterface Evaluator {\n evaluate(rawLicense: string | undefined | null): LicenseStatus;\n}\n\nconst isUnknownToken = (raw: string): boolean => {\n if (!raw) return true;\n const trimmed = raw.trim();\n if (!trimmed) return true;\n if (trimmed.toUpperCase() === 'UNKNOWN') return true;\n if (trimmed.toLowerCase().startsWith('custom:')) return true;\n if (trimmed.toLowerCase().startsWith('see license in')) return true;\n return false;\n};\n\n/**\n * Strip the trailing `*` that license-checker adds when a license is guessed\n * from file content rather than declared in package.json metadata.\n */\nconst normalise = (raw: string): string => {\n let s = raw.trim();\n if (s.endsWith('*')) s = s.slice(0, -1).trim();\n return s;\n};\n\n/**\n * Build a flat list of valid SPDX IDs from the allowlist for use with\n * spdx-satisfies. Entries that aren't parseable as SPDX identifiers are\n * excluded (they're still matched via the alias lookup).\n */\nconst buildSpdxIds = (allow: AllowlistEntry[]): string[] => {\n const ids: string[] = [];\n for (const entry of allow) {\n if (!entry.spdxId) continue;\n try {\n spdxSatisfies(entry.spdxId, [entry.spdxId]);\n ids.push(entry.spdxId);\n } catch {\n // Not a valid SPDX identifier — skip for spdx-satisfies, still\n // matched via alias lookup\n }\n }\n return ids;\n};\n\n/**\n * Build a case-insensitive lookup for matching by full name or alias\n * (for licenses that aren't valid SPDX but appear in package metadata).\n */\nconst buildAliasLookup = (allow: AllowlistEntry[]): Map<string, true> => {\n const m = new Map<string, true>();\n for (const e of allow) {\n if (e.spdxId) m.set(e.spdxId.toLowerCase(), true);\n if (e.fullName) m.set(e.fullName.toLowerCase(), true);\n for (const a of e.aliases ?? []) {\n if (a) m.set(a.toLowerCase(), true);\n }\n }\n return m;\n};\n\nconst evaluateToken = (\n token: string,\n spdxIds: string[],\n aliasLookup: Map<string, true>,\n): LicenseStatus => {\n const cleaned = normalise(token);\n if (isUnknownToken(cleaned)) return 'UNKNOWN';\n\n // Try spdx-satisfies for valid SPDX expressions\n try {\n if (spdxSatisfies(cleaned, spdxIds)) return 'PRE_APPROVED';\n } catch {\n // Not a valid SPDX expression — fall through to alias matching\n }\n\n // Fall back to alias/fullName matching for non-SPDX strings\n if (aliasLookup.has(cleaned.toLowerCase())) return 'PRE_APPROVED';\n\n return 'NOT_APPROVED';\n};\n\nexport const createEvaluator = (options: EvaluatorOptions): Evaluator => {\n const spdxIds = buildSpdxIds(options.allow);\n const aliasLookup = buildAliasLookup(options.allow);\n\n return {\n evaluate(rawLicense) {\n if (!rawLicense || rawLicense.trim().length === 0) return 'UNKNOWN';\n\n const normalised = normalise(rawLicense);\n if (isUnknownToken(normalised)) return 'UNKNOWN';\n\n // Python tooling emits \"Apache Software License; BSD License\"
|
|
1
|
+
{"version":3,"sources":["../../../../../../packages/nx-plugin/src/license/dependency-check/evaluator.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport spdxSatisfies from 'spdx-satisfies';\nimport type { AllowlistEntry, LicenseStatus } from './types';\n\nexport interface EvaluatorOptions {\n allow: AllowlistEntry[];\n}\n\ninterface Evaluator {\n evaluate(rawLicense: string | undefined | null): LicenseStatus;\n}\n\nconst isUnknownToken = (raw: string): boolean => {\n if (!raw) return true;\n const trimmed = raw.trim();\n if (!trimmed) return true;\n if (trimmed.toUpperCase() === 'UNKNOWN') return true;\n if (trimmed.toLowerCase().startsWith('custom:')) return true;\n if (trimmed.toLowerCase().startsWith('see license in')) return true;\n return false;\n};\n\n/**\n * Strip the trailing `*` that license-checker adds when a license is guessed\n * from file content rather than declared in package.json metadata.\n */\nconst normalise = (raw: string): string => {\n let s = raw.trim();\n if (s.endsWith('*')) s = s.slice(0, -1).trim();\n return s;\n};\n\n/**\n * Build a flat list of valid SPDX IDs from the allowlist for use with\n * spdx-satisfies. Entries that aren't parseable as SPDX identifiers are\n * excluded (they're still matched via the alias lookup).\n */\nconst buildSpdxIds = (allow: AllowlistEntry[]): string[] => {\n const ids: string[] = [];\n for (const entry of allow) {\n if (!entry.spdxId) continue;\n try {\n spdxSatisfies(entry.spdxId, [entry.spdxId]);\n ids.push(entry.spdxId);\n } catch {\n // Not a valid SPDX identifier — skip for spdx-satisfies, still\n // matched via alias lookup\n }\n }\n return ids;\n};\n\n/**\n * Build a case-insensitive lookup for matching by full name or alias\n * (for licenses that aren't valid SPDX but appear in package metadata).\n */\nconst buildAliasLookup = (allow: AllowlistEntry[]): Map<string, true> => {\n const m = new Map<string, true>();\n for (const e of allow) {\n if (e.spdxId) m.set(e.spdxId.toLowerCase(), true);\n if (e.fullName) m.set(e.fullName.toLowerCase(), true);\n for (const a of e.aliases ?? []) {\n if (a) m.set(a.toLowerCase(), true);\n }\n }\n return m;\n};\n\n/**\n * Split a free-text license list on a separator, treating the operands as\n * alternatives (OR).\n *\n * Commas need care: several allowed license names contain one themselves (e.g.\n * \"Apache License, Version 2.0\"), so the string is left intact when it already\n * resolves as a single license, and fragments are otherwise rejoined\n * longest-match-first. That keeps \"MIT License, Apache License, Version 2.0\"\n * parsing as MIT + Apache rather than three unresolvable fragments.\n *\n * Returns `undefined` when the value shouldn't be treated as a list.\n */\nconst splitAlternatives = (\n value: string,\n separator: string,\n isKnown: (token: string) => boolean,\n): string[] | undefined => {\n if (!value.includes(separator)) return undefined;\n if (isKnown(value)) return undefined;\n\n const parts = value\n .split(separator)\n .map((p) => p.trim())\n .filter((p) => p.length > 0);\n if (parts.length < 2) return undefined;\n\n // Rejoin fragments that only make sense together, so a license name\n // containing the separator survives the split. At each position take the\n // longest run of fragments that forms a known license; failing that, emit the\n // single fragment and let the caller report it.\n const merged: string[] = [];\n let i = 0;\n while (i < parts.length) {\n let matchedTo = -1;\n let candidate = parts[i];\n for (let j = i; j < parts.length; j++) {\n if (j > i) candidate = `${candidate}${separator} ${parts[j]}`;\n if (isKnown(candidate)) matchedTo = j;\n }\n if (matchedTo >= 0) {\n merged.push(parts.slice(i, matchedTo + 1).join(`${separator} `));\n i = matchedTo + 1;\n } else {\n merged.push(parts[i]);\n i += 1;\n }\n }\n\n return merged.length > 1 ? merged : undefined;\n};\n\nconst evaluateToken = (\n token: string,\n spdxIds: string[],\n aliasLookup: Map<string, true>,\n): LicenseStatus => {\n const cleaned = normalise(token);\n if (isUnknownToken(cleaned)) return 'UNKNOWN';\n\n // Try spdx-satisfies for valid SPDX expressions\n try {\n if (spdxSatisfies(cleaned, spdxIds)) return 'PRE_APPROVED';\n } catch {\n // Not a valid SPDX expression — fall through to alias matching\n }\n\n // Fall back to alias/fullName matching for non-SPDX strings\n if (aliasLookup.has(cleaned.toLowerCase())) return 'PRE_APPROVED';\n\n return 'NOT_APPROVED';\n};\n\nexport const createEvaluator = (options: EvaluatorOptions): Evaluator => {\n const spdxIds = buildSpdxIds(options.allow);\n const aliasLookup = buildAliasLookup(options.allow);\n\n return {\n evaluate(rawLicense) {\n if (!rawLicense || rawLicense.trim().length === 0) return 'UNKNOWN';\n\n const normalised = normalise(rawLicense);\n if (isUnknownToken(normalised)) return 'UNKNOWN';\n\n // Python tooling emits free-text license lists rather than an SPDX\n // expression: \"Apache Software License; BSD License\" (setuptools\n // classifiers) or \"MIT License, Apache License, Version 2.0\" (a dual\n // license declared in one `License:` field). Both mean \"any of these\n // applies\", so treat the operands as OR.\n const isKnown = (token: string) =>\n evaluateToken(token, spdxIds, aliasLookup) === 'PRE_APPROVED';\n for (const separator of [';', ',']) {\n const parts = splitAlternatives(normalised, separator, isKnown);\n if (!parts) continue;\n for (const part of parts) {\n if (evaluateToken(part, spdxIds, aliasLookup) === 'PRE_APPROVED')\n return 'PRE_APPROVED';\n }\n if (parts.every((p) => isUnknownToken(normalise(p)))) return 'UNKNOWN';\n return 'NOT_APPROVED';\n }\n\n return evaluateToken(normalised, spdxIds, aliasLookup);\n },\n };\n};\n"],"names":["spdxSatisfies","isUnknownToken","raw","trimmed","trim","toUpperCase","toLowerCase","startsWith","normalise","s","endsWith","slice","buildSpdxIds","allow","ids","entry","spdxId","push","buildAliasLookup","m","Map","e","set","fullName","a","aliases","splitAlternatives","value","separator","isKnown","includes","undefined","parts","split","map","p","filter","length","merged","i","matchedTo","candidate","j","join","evaluateToken","token","spdxIds","aliasLookup","cleaned","has","createEvaluator","options","evaluate","rawLicense","normalised","part","every"],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
import { type MigrationReturnObject, type Tree } from '@nx/devkit';
|
|
6
|
+
export default function migration(tree: Tree): Promise<MigrationReturnObject>;
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/ import { visitNotIgnoredFiles } from "@nx/devkit";
|
|
5
|
+
import { applyGritQL, matchGritQL } from "../../../utils/ast.js";
|
|
6
|
+
import { formatFilesInSubtree } from "../../../utils/format.js";
|
|
7
|
+
/**
|
|
8
|
+
* Rebuild the `httpx.AsyncClient` (and, for Strands, the `A2AAgent`) fresh on
|
|
9
|
+
* every A2A delegate call, instead of reusing the one built once at factory
|
|
10
|
+
* time - fixes a reproducible `RuntimeError: Event loop is closed` on the
|
|
11
|
+
* second call through a shared client.
|
|
12
|
+
*
|
|
13
|
+
* Both frameworks' A2A clients call out through a synchronous wrapper
|
|
14
|
+
* (`_run_sync`) that runs the async call via `asyncio.run(...)` - a brand new
|
|
15
|
+
* event loop per call, closed when the call returns. `AgentCoreA2aClientConfig`
|
|
16
|
+
* builds one `httpx.AsyncClient` a single time, at factory call time, and that
|
|
17
|
+
* client's connection pool binds to whichever event loop first touches it. So
|
|
18
|
+
* call #1 creates a loop, uses the shared client, closes the loop; call #2
|
|
19
|
+
* creates a *new* loop, but the shared client's pool is still bound to the
|
|
20
|
+
* old, now-closed one.
|
|
21
|
+
*
|
|
22
|
+
* Strands hits the same failure one layer down: `A2AAgent.__call__` is itself
|
|
23
|
+
* synchronous and does its own `asyncio.run(...)` per call internally, while
|
|
24
|
+
* reusing the same factory-built `httpx.AsyncClient` via `client_config`.
|
|
25
|
+
*
|
|
26
|
+
* The fix in both `AgentCoreA2aClientLangChain`/`AgentCoreA2aClientStrands` is
|
|
27
|
+
* to stop handing out a client wired to one long-lived `httpx.AsyncClient`.
|
|
28
|
+
* Instead each call opens a fresh `httpx.AsyncClient` (reusing only the
|
|
29
|
+
* `auth`/`timeout` off the once-built shared config) scoped to that call via
|
|
30
|
+
* `async with`, so its connection pool lives and dies with the same loop that
|
|
31
|
+
* created it. For Strands this also means `AgentCoreA2aClientStrands`'s
|
|
32
|
+
* factories no longer return a raw `A2AAgent` (dropped in favour of a private
|
|
33
|
+
* `_A2AClient` built per call), so the per-target `<Agent>ClientStrands.create`
|
|
34
|
+
* wrapper's `-> A2AAgent` return annotation and its now-unused `A2AAgent`
|
|
35
|
+
* import go stale too.
|
|
36
|
+
*/ const pyMatch = (snippet)=>`language python\n\`${snippet}\``;
|
|
37
|
+
const pyRewrite = (from, to)=>{
|
|
38
|
+
const replacement = to.includes('\n') ? `raw\`${to}\`` : `\`${to}\``;
|
|
39
|
+
return `${pyMatch(from)} => ${replacement}`;
|
|
40
|
+
};
|
|
41
|
+
const allMatch = async (tree, filePath, patterns)=>{
|
|
42
|
+
for (const pattern of patterns){
|
|
43
|
+
if (!await matchGritQL(tree, filePath, pattern)) return false;
|
|
44
|
+
}
|
|
45
|
+
return true;
|
|
46
|
+
};
|
|
47
|
+
// --- agentcore_a2a_client_langchain.py (framework-agnostic core, shared) ---
|
|
48
|
+
const LANGCHAIN_IMPORT_OLD = 'from a2a.client import A2ACardResolver, ClientConfig, ClientFactory';
|
|
49
|
+
const LANGCHAIN_IMPORT_NEW = `import httpx
|
|
50
|
+
from a2a.client import A2ACardResolver, ClientConfig, ClientFactory`;
|
|
51
|
+
const LANGCHAIN_INVOKE_OLD = ` async def _invoke(self, prompt: str) -> str:
|
|
52
|
+
# AgentCoreA2aClientConfig always sets the signed httpx client.
|
|
53
|
+
httpx_client = self._config.httpx_client
|
|
54
|
+
if httpx_client is None:
|
|
55
|
+
raise RuntimeError("A2A client config is missing an httpx client")
|
|
56
|
+
card = await A2ACardResolver(
|
|
57
|
+
httpx_client=httpx_client, base_url=self._url
|
|
58
|
+
).get_agent_card()
|
|
59
|
+
client = ClientFactory(self._config).create(card)
|
|
60
|
+
message = Message(
|
|
61
|
+
kind="message",
|
|
62
|
+
role=Role.user,
|
|
63
|
+
message_id=uuid4().hex,
|
|
64
|
+
parts=[Part(TextPart(kind="text", text=prompt))],
|
|
65
|
+
)
|
|
66
|
+
# The client config uses streaming=False, so the last event carries the
|
|
67
|
+
# complete response.
|
|
68
|
+
reply = ""
|
|
69
|
+
async for event in client.send_message(message):
|
|
70
|
+
text = _text(event)
|
|
71
|
+
if text:
|
|
72
|
+
reply = text
|
|
73
|
+
return reply`;
|
|
74
|
+
const LANGCHAIN_INVOKE_NEW = `async def _invoke(self, prompt: str) -> str:
|
|
75
|
+
# AgentCoreA2aClientConfig always sets the signed httpx client.
|
|
76
|
+
shared_client = self._config.httpx_client
|
|
77
|
+
if shared_client is None:
|
|
78
|
+
raise RuntimeError("A2A client config is missing an httpx client")
|
|
79
|
+
async with httpx.AsyncClient(
|
|
80
|
+
auth=shared_client.auth, timeout=shared_client.timeout
|
|
81
|
+
) as httpx_client:
|
|
82
|
+
card = await A2ACardResolver(
|
|
83
|
+
httpx_client=httpx_client, base_url=self._url
|
|
84
|
+
).get_agent_card()
|
|
85
|
+
client = ClientFactory(
|
|
86
|
+
ClientConfig(httpx_client=httpx_client, streaming=False)
|
|
87
|
+
).create(card)
|
|
88
|
+
message = Message(
|
|
89
|
+
kind="message",
|
|
90
|
+
role=Role.user,
|
|
91
|
+
message_id=uuid4().hex,
|
|
92
|
+
parts=[Part(TextPart(kind="text", text=prompt))],
|
|
93
|
+
)
|
|
94
|
+
# The client config uses streaming=False, so the last event carries
|
|
95
|
+
# the complete response.
|
|
96
|
+
reply = ""
|
|
97
|
+
async for event in client.send_message(message):
|
|
98
|
+
text = _text(event)
|
|
99
|
+
if text:
|
|
100
|
+
reply = text
|
|
101
|
+
return reply`;
|
|
102
|
+
const migrateLangchainCoreClient = async (tree, filePath, nextSteps)=>{
|
|
103
|
+
const contents = tree.read(filePath, 'utf-8') ?? '';
|
|
104
|
+
if (!contents.includes('client = ClientFactory(self._config).create(card)')) return;
|
|
105
|
+
const ready = await allMatch(tree, filePath, [
|
|
106
|
+
pyMatch(LANGCHAIN_IMPORT_OLD),
|
|
107
|
+
pyMatch(LANGCHAIN_INVOKE_OLD)
|
|
108
|
+
]);
|
|
109
|
+
if (!ready) {
|
|
110
|
+
nextSteps.push(`${filePath}: diverged from the generated shape - left untouched. Manually rebuild a fresh httpx.AsyncClient per call in _invoke instead of reusing the one built at factory time (see the agent-connection generator's agentcore_a2a_client_langchain.py template).`);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
await applyGritQL(tree, filePath, pyRewrite(LANGCHAIN_IMPORT_OLD, LANGCHAIN_IMPORT_NEW));
|
|
114
|
+
await applyGritQL(tree, filePath, pyRewrite(LANGCHAIN_INVOKE_OLD, LANGCHAIN_INVOKE_NEW));
|
|
115
|
+
};
|
|
116
|
+
// --- agentcore_a2a_client_strands.py (Strands core client) ------------------
|
|
117
|
+
const STRANDS_IMPORT1_OLD = 'from collections.abc import Callable';
|
|
118
|
+
const STRANDS_IMPORT1_NEW = `import asyncio
|
|
119
|
+
from collections.abc import Callable
|
|
120
|
+
from concurrent.futures import ThreadPoolExecutor`;
|
|
121
|
+
const STRANDS_IMPORT2_OLD = 'from strands.agent.a2a_agent import A2AAgent';
|
|
122
|
+
const STRANDS_IMPORT2_NEW = `import httpx
|
|
123
|
+
from a2a.client import ClientConfig
|
|
124
|
+
from strands.agent.a2a_agent import A2AAgent
|
|
125
|
+
from strands.agent.agent_result import AgentResult`;
|
|
126
|
+
const STRANDS_BUILD_OLD = `def _build(
|
|
127
|
+
config: tuple, *, name: str | None, description: str | None
|
|
128
|
+
) -> A2AAgent:
|
|
129
|
+
url, client_config = config
|
|
130
|
+
kwargs: dict = {"endpoint": url, "client_config": client_config}
|
|
131
|
+
if name:
|
|
132
|
+
kwargs["name"] = name
|
|
133
|
+
if description:
|
|
134
|
+
kwargs["description"] = description
|
|
135
|
+
return A2AAgent(**kwargs)`;
|
|
136
|
+
const STRANDS_BUILD_NEW = `def _run_sync(coro):
|
|
137
|
+
# The tool is invoked from sync agent code, which under uvicorn runs inside a
|
|
138
|
+
# live event loop where asyncio.run() would raise — fall back to a worker.
|
|
139
|
+
try:
|
|
140
|
+
asyncio.get_running_loop()
|
|
141
|
+
except RuntimeError:
|
|
142
|
+
return asyncio.run(coro)
|
|
143
|
+
with ThreadPoolExecutor(max_workers=1) as pool:
|
|
144
|
+
return pool.submit(asyncio.run, coro).result()
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class _A2AClient:
|
|
148
|
+
def __init__(
|
|
149
|
+
self,
|
|
150
|
+
url: str,
|
|
151
|
+
client_config: ClientConfig,
|
|
152
|
+
*,
|
|
153
|
+
name: str | None,
|
|
154
|
+
description: str | None,
|
|
155
|
+
):
|
|
156
|
+
shared_client = client_config.httpx_client
|
|
157
|
+
if shared_client is None:
|
|
158
|
+
raise RuntimeError("A2A client config is missing an httpx client")
|
|
159
|
+
self._url = url
|
|
160
|
+
self._auth = shared_client.auth
|
|
161
|
+
self._timeout = shared_client.timeout
|
|
162
|
+
self._name = name
|
|
163
|
+
self._description = description
|
|
164
|
+
|
|
165
|
+
def __call__(self, prompt: str) -> AgentResult:
|
|
166
|
+
return _run_sync(self._invoke(prompt))
|
|
167
|
+
|
|
168
|
+
async def _invoke(self, prompt: str) -> AgentResult:
|
|
169
|
+
async with httpx.AsyncClient(
|
|
170
|
+
auth=self._auth, timeout=self._timeout
|
|
171
|
+
) as httpx_client:
|
|
172
|
+
agent = A2AAgent(
|
|
173
|
+
endpoint=self._url,
|
|
174
|
+
name=self._name,
|
|
175
|
+
description=self._description,
|
|
176
|
+
client_config=ClientConfig(httpx_client=httpx_client, streaming=False),
|
|
177
|
+
)
|
|
178
|
+
return await agent.invoke_async(prompt)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _build(
|
|
182
|
+
config: tuple, *, name: str | None, description: str | None
|
|
183
|
+
) -> _A2AClient:
|
|
184
|
+
url, client_config = config
|
|
185
|
+
return _A2AClient(url, client_config, name=name, description=description)`;
|
|
186
|
+
const STRANDS_IAM_OLD = ` @staticmethod
|
|
187
|
+
def with_iam_auth(
|
|
188
|
+
agent_runtime_arn: str,
|
|
189
|
+
*,
|
|
190
|
+
name: str | None = None,
|
|
191
|
+
description: str | None = None,
|
|
192
|
+
) -> A2AAgent:
|
|
193
|
+
"""SigV4-authenticated client for a Bedrock AgentCore runtime."""
|
|
194
|
+
return _build(
|
|
195
|
+
AgentCoreA2aClientConfig.with_iam_auth(agent_runtime_arn),
|
|
196
|
+
name=name,
|
|
197
|
+
description=description,
|
|
198
|
+
)`;
|
|
199
|
+
const STRANDS_IAM_NEW = `@staticmethod
|
|
200
|
+
def with_iam_auth(
|
|
201
|
+
agent_runtime_arn: str,
|
|
202
|
+
*,
|
|
203
|
+
name: str | None = None,
|
|
204
|
+
description: str | None = None,
|
|
205
|
+
) -> _A2AClient:
|
|
206
|
+
"""SigV4-authenticated client for a Bedrock AgentCore runtime."""
|
|
207
|
+
return _build(
|
|
208
|
+
AgentCoreA2aClientConfig.with_iam_auth(agent_runtime_arn),
|
|
209
|
+
name=name,
|
|
210
|
+
description=description,
|
|
211
|
+
)`;
|
|
212
|
+
const STRANDS_JWT_OLD = ` @staticmethod
|
|
213
|
+
def with_jwt_auth(
|
|
214
|
+
agent_runtime_arn: str,
|
|
215
|
+
access_token_provider: Callable[[], str],
|
|
216
|
+
*,
|
|
217
|
+
name: str | None = None,
|
|
218
|
+
description: str | None = None,
|
|
219
|
+
) -> A2AAgent:
|
|
220
|
+
"""Bearer-authenticated client for a Bedrock AgentCore runtime."""
|
|
221
|
+
return _build(
|
|
222
|
+
AgentCoreA2aClientConfig.with_jwt_auth(
|
|
223
|
+
agent_runtime_arn, access_token_provider
|
|
224
|
+
),
|
|
225
|
+
name=name,
|
|
226
|
+
description=description,
|
|
227
|
+
)`;
|
|
228
|
+
const STRANDS_JWT_NEW = `@staticmethod
|
|
229
|
+
def with_jwt_auth(
|
|
230
|
+
agent_runtime_arn: str,
|
|
231
|
+
access_token_provider: Callable[[], str],
|
|
232
|
+
*,
|
|
233
|
+
name: str | None = None,
|
|
234
|
+
description: str | None = None,
|
|
235
|
+
) -> _A2AClient:
|
|
236
|
+
"""Bearer-authenticated client for a Bedrock AgentCore runtime."""
|
|
237
|
+
return _build(
|
|
238
|
+
AgentCoreA2aClientConfig.with_jwt_auth(
|
|
239
|
+
agent_runtime_arn, access_token_provider
|
|
240
|
+
),
|
|
241
|
+
name=name,
|
|
242
|
+
description=description,
|
|
243
|
+
)`;
|
|
244
|
+
const STRANDS_NOAUTH_OLD = ` @staticmethod
|
|
245
|
+
def without_auth(
|
|
246
|
+
url: str,
|
|
247
|
+
*,
|
|
248
|
+
name: str | None = None,
|
|
249
|
+
description: str | None = None,
|
|
250
|
+
) -> A2AAgent:
|
|
251
|
+
"""Plain-HTTP client — for local dev."""
|
|
252
|
+
return _build(
|
|
253
|
+
AgentCoreA2aClientConfig.without_auth(url),
|
|
254
|
+
name=name,
|
|
255
|
+
description=description,
|
|
256
|
+
)`;
|
|
257
|
+
const STRANDS_NOAUTH_NEW = `@staticmethod
|
|
258
|
+
def without_auth(
|
|
259
|
+
url: str,
|
|
260
|
+
*,
|
|
261
|
+
name: str | None = None,
|
|
262
|
+
description: str | None = None,
|
|
263
|
+
) -> _A2AClient:
|
|
264
|
+
"""Plain-HTTP client — for local dev."""
|
|
265
|
+
return _build(
|
|
266
|
+
AgentCoreA2aClientConfig.without_auth(url),
|
|
267
|
+
name=name,
|
|
268
|
+
description=description,
|
|
269
|
+
)`;
|
|
270
|
+
const migrateStrandsCoreClient = async (tree, filePath, nextSteps)=>{
|
|
271
|
+
const contents = tree.read(filePath, 'utf-8') ?? '';
|
|
272
|
+
if (!contents.includes('return A2AAgent(**kwargs)')) return;
|
|
273
|
+
const ready = await allMatch(tree, filePath, [
|
|
274
|
+
pyMatch(STRANDS_IMPORT1_OLD),
|
|
275
|
+
pyMatch(STRANDS_IMPORT2_OLD),
|
|
276
|
+
pyMatch(STRANDS_BUILD_OLD),
|
|
277
|
+
pyMatch(STRANDS_IAM_OLD),
|
|
278
|
+
pyMatch(STRANDS_JWT_OLD),
|
|
279
|
+
pyMatch(STRANDS_NOAUTH_OLD)
|
|
280
|
+
]);
|
|
281
|
+
if (!ready) {
|
|
282
|
+
nextSteps.push(`${filePath}: diverged from the generated shape - left untouched. Manually rebuild a fresh httpx.AsyncClient (and A2AAgent) per call instead of reusing the ones built at factory time (see the agent-connection generator's agentcore_a2a_client_strands.py template).`);
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
await applyGritQL(tree, filePath, pyRewrite(STRANDS_IMPORT1_OLD, STRANDS_IMPORT1_NEW));
|
|
286
|
+
await applyGritQL(tree, filePath, pyRewrite(STRANDS_IMPORT2_OLD, STRANDS_IMPORT2_NEW));
|
|
287
|
+
await applyGritQL(tree, filePath, pyRewrite(STRANDS_BUILD_OLD, STRANDS_BUILD_NEW));
|
|
288
|
+
await applyGritQL(tree, filePath, pyRewrite(STRANDS_IAM_OLD, STRANDS_IAM_NEW));
|
|
289
|
+
await applyGritQL(tree, filePath, pyRewrite(STRANDS_JWT_OLD, STRANDS_JWT_NEW));
|
|
290
|
+
await applyGritQL(tree, filePath, pyRewrite(STRANDS_NOAUTH_OLD, STRANDS_NOAUTH_NEW));
|
|
291
|
+
};
|
|
292
|
+
// --- <target>_client_strands.py (per-target wrapper, one per connection) ---
|
|
293
|
+
const WRAPPER_IMPORT_OLD = 'from strands.agent.a2a_agent import A2AAgent';
|
|
294
|
+
const WRAPPER_CREATE_OLD = 'def create() -> A2AAgent:\n $body';
|
|
295
|
+
const WRAPPER_CREATE_NEW = 'def create():\n $body';
|
|
296
|
+
const migrateStrandsWrapperClient = async (tree, filePath, nextSteps)=>{
|
|
297
|
+
const contents = tree.read(filePath, 'utf-8') ?? '';
|
|
298
|
+
if (!contents.includes('def create() -> A2AAgent:')) return;
|
|
299
|
+
const ready = await allMatch(tree, filePath, [
|
|
300
|
+
pyMatch(WRAPPER_IMPORT_OLD),
|
|
301
|
+
pyMatch(WRAPPER_CREATE_OLD)
|
|
302
|
+
]);
|
|
303
|
+
if (!ready) {
|
|
304
|
+
nextSteps.push(`${filePath}: diverged from the generated shape - left untouched. Manually drop the A2AAgent import and its -> A2AAgent return annotation on create() (see the py#agent#a2a-connection generator's per-target client_strands.py template).`);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
await applyGritQL(tree, filePath, `${pyMatch(WRAPPER_IMPORT_OLD)} => .`);
|
|
308
|
+
await applyGritQL(tree, filePath, pyRewrite(WRAPPER_CREATE_OLD, WRAPPER_CREATE_NEW));
|
|
309
|
+
};
|
|
310
|
+
export default async function migration(tree) {
|
|
311
|
+
const nextSteps = [];
|
|
312
|
+
const filePaths = [];
|
|
313
|
+
visitNotIgnoredFiles(tree, '', (filePath)=>filePaths.push(filePath));
|
|
314
|
+
for (const filePath of filePaths){
|
|
315
|
+
if (filePath.endsWith('/agentcore_a2a_client_langchain.py')) {
|
|
316
|
+
await migrateLangchainCoreClient(tree, filePath, nextSteps);
|
|
317
|
+
} else if (filePath.endsWith('/agentcore_a2a_client_strands.py')) {
|
|
318
|
+
await migrateStrandsCoreClient(tree, filePath, nextSteps);
|
|
319
|
+
} else if (filePath.endsWith('_client_strands.py')) {
|
|
320
|
+
await migrateStrandsWrapperClient(tree, filePath, nextSteps);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
await formatFilesInSubtree(tree);
|
|
324
|
+
return {
|
|
325
|
+
nextSteps
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
//# sourceMappingURL=migration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/latest/py-agent-a2a-httpx-client-per-call/migration.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport {\n type MigrationReturnObject,\n type Tree,\n visitNotIgnoredFiles,\n} from '@nx/devkit';\nimport { applyGritQL, matchGritQL } from '../../../utils/ast';\nimport { formatFilesInSubtree } from '../../../utils/format';\n\n/**\n * Rebuild the `httpx.AsyncClient` (and, for Strands, the `A2AAgent`) fresh on\n * every A2A delegate call, instead of reusing the one built once at factory\n * time - fixes a reproducible `RuntimeError: Event loop is closed` on the\n * second call through a shared client.\n *\n * Both frameworks' A2A clients call out through a synchronous wrapper\n * (`_run_sync`) that runs the async call via `asyncio.run(...)` - a brand new\n * event loop per call, closed when the call returns. `AgentCoreA2aClientConfig`\n * builds one `httpx.AsyncClient` a single time, at factory call time, and that\n * client's connection pool binds to whichever event loop first touches it. So\n * call #1 creates a loop, uses the shared client, closes the loop; call #2\n * creates a *new* loop, but the shared client's pool is still bound to the\n * old, now-closed one.\n *\n * Strands hits the same failure one layer down: `A2AAgent.__call__` is itself\n * synchronous and does its own `asyncio.run(...)` per call internally, while\n * reusing the same factory-built `httpx.AsyncClient` via `client_config`.\n *\n * The fix in both `AgentCoreA2aClientLangChain`/`AgentCoreA2aClientStrands` is\n * to stop handing out a client wired to one long-lived `httpx.AsyncClient`.\n * Instead each call opens a fresh `httpx.AsyncClient` (reusing only the\n * `auth`/`timeout` off the once-built shared config) scoped to that call via\n * `async with`, so its connection pool lives and dies with the same loop that\n * created it. For Strands this also means `AgentCoreA2aClientStrands`'s\n * factories no longer return a raw `A2AAgent` (dropped in favour of a private\n * `_A2AClient` built per call), so the per-target `<Agent>ClientStrands.create`\n * wrapper's `-> A2AAgent` return annotation and its now-unused `A2AAgent`\n * import go stale too.\n */\nconst pyMatch = (snippet: string) => `language python\\n\\`${snippet}\\``;\n\nconst pyRewrite = (from: string, to: string): string => {\n const replacement = to.includes('\\n') ? `raw\\`${to}\\`` : `\\`${to}\\``;\n return `${pyMatch(from)} => ${replacement}`;\n};\n\nconst allMatch = async (\n tree: Tree,\n filePath: string,\n patterns: string[],\n): Promise<boolean> => {\n for (const pattern of patterns) {\n if (!(await matchGritQL(tree, filePath, pattern))) return false;\n }\n return true;\n};\n\n// --- agentcore_a2a_client_langchain.py (framework-agnostic core, shared) ---\n\nconst LANGCHAIN_IMPORT_OLD =\n 'from a2a.client import A2ACardResolver, ClientConfig, ClientFactory';\nconst LANGCHAIN_IMPORT_NEW = `import httpx\nfrom a2a.client import A2ACardResolver, ClientConfig, ClientFactory`;\n\nconst LANGCHAIN_INVOKE_OLD = ` async def _invoke(self, prompt: str) -> str:\n # AgentCoreA2aClientConfig always sets the signed httpx client.\n httpx_client = self._config.httpx_client\n if httpx_client is None:\n raise RuntimeError(\"A2A client config is missing an httpx client\")\n card = await A2ACardResolver(\n httpx_client=httpx_client, base_url=self._url\n ).get_agent_card()\n client = ClientFactory(self._config).create(card)\n message = Message(\n kind=\"message\",\n role=Role.user,\n message_id=uuid4().hex,\n parts=[Part(TextPart(kind=\"text\", text=prompt))],\n )\n # The client config uses streaming=False, so the last event carries the\n # complete response.\n reply = \"\"\n async for event in client.send_message(message):\n text = _text(event)\n if text:\n reply = text\n return reply`;\n\nconst LANGCHAIN_INVOKE_NEW = `async def _invoke(self, prompt: str) -> str:\n # AgentCoreA2aClientConfig always sets the signed httpx client.\n shared_client = self._config.httpx_client\n if shared_client is None:\n raise RuntimeError(\"A2A client config is missing an httpx client\")\n async with httpx.AsyncClient(\n auth=shared_client.auth, timeout=shared_client.timeout\n ) as httpx_client:\n card = await A2ACardResolver(\n httpx_client=httpx_client, base_url=self._url\n ).get_agent_card()\n client = ClientFactory(\n ClientConfig(httpx_client=httpx_client, streaming=False)\n ).create(card)\n message = Message(\n kind=\"message\",\n role=Role.user,\n message_id=uuid4().hex,\n parts=[Part(TextPart(kind=\"text\", text=prompt))],\n )\n # The client config uses streaming=False, so the last event carries\n # the complete response.\n reply = \"\"\n async for event in client.send_message(message):\n text = _text(event)\n if text:\n reply = text\n return reply`;\n\nconst migrateLangchainCoreClient = async (\n tree: Tree,\n filePath: string,\n nextSteps: string[],\n): Promise<void> => {\n const contents = tree.read(filePath, 'utf-8') ?? '';\n if (!contents.includes('client = ClientFactory(self._config).create(card)'))\n return;\n\n const ready = await allMatch(tree, filePath, [\n pyMatch(LANGCHAIN_IMPORT_OLD),\n pyMatch(LANGCHAIN_INVOKE_OLD),\n ]);\n\n if (!ready) {\n nextSteps.push(\n `${filePath}: diverged from the generated shape - left untouched. Manually rebuild a fresh httpx.AsyncClient per call in _invoke instead of reusing the one built at factory time (see the agent-connection generator's agentcore_a2a_client_langchain.py template).`,\n );\n return;\n }\n\n await applyGritQL(\n tree,\n filePath,\n pyRewrite(LANGCHAIN_IMPORT_OLD, LANGCHAIN_IMPORT_NEW),\n );\n await applyGritQL(\n tree,\n filePath,\n pyRewrite(LANGCHAIN_INVOKE_OLD, LANGCHAIN_INVOKE_NEW),\n );\n};\n\n// --- agentcore_a2a_client_strands.py (Strands core client) ------------------\n\nconst STRANDS_IMPORT1_OLD = 'from collections.abc import Callable';\nconst STRANDS_IMPORT1_NEW = `import asyncio\nfrom collections.abc import Callable\nfrom concurrent.futures import ThreadPoolExecutor`;\n\nconst STRANDS_IMPORT2_OLD = 'from strands.agent.a2a_agent import A2AAgent';\nconst STRANDS_IMPORT2_NEW = `import httpx\nfrom a2a.client import ClientConfig\nfrom strands.agent.a2a_agent import A2AAgent\nfrom strands.agent.agent_result import AgentResult`;\n\nconst STRANDS_BUILD_OLD = `def _build(\n config: tuple, *, name: str | None, description: str | None\n) -> A2AAgent:\n url, client_config = config\n kwargs: dict = {\"endpoint\": url, \"client_config\": client_config}\n if name:\n kwargs[\"name\"] = name\n if description:\n kwargs[\"description\"] = description\n return A2AAgent(**kwargs)`;\n\nconst STRANDS_BUILD_NEW = `def _run_sync(coro):\n # The tool is invoked from sync agent code, which under uvicorn runs inside a\n # live event loop where asyncio.run() would raise — fall back to a worker.\n try:\n asyncio.get_running_loop()\n except RuntimeError:\n return asyncio.run(coro)\n with ThreadPoolExecutor(max_workers=1) as pool:\n return pool.submit(asyncio.run, coro).result()\n\n\nclass _A2AClient:\n def __init__(\n self,\n url: str,\n client_config: ClientConfig,\n *,\n name: str | None,\n description: str | None,\n ):\n shared_client = client_config.httpx_client\n if shared_client is None:\n raise RuntimeError(\"A2A client config is missing an httpx client\")\n self._url = url\n self._auth = shared_client.auth\n self._timeout = shared_client.timeout\n self._name = name\n self._description = description\n\n def __call__(self, prompt: str) -> AgentResult:\n return _run_sync(self._invoke(prompt))\n\n async def _invoke(self, prompt: str) -> AgentResult:\n async with httpx.AsyncClient(\n auth=self._auth, timeout=self._timeout\n ) as httpx_client:\n agent = A2AAgent(\n endpoint=self._url,\n name=self._name,\n description=self._description,\n client_config=ClientConfig(httpx_client=httpx_client, streaming=False),\n )\n return await agent.invoke_async(prompt)\n\n\ndef _build(\n config: tuple, *, name: str | None, description: str | None\n) -> _A2AClient:\n url, client_config = config\n return _A2AClient(url, client_config, name=name, description=description)`;\n\nconst STRANDS_IAM_OLD = ` @staticmethod\n def with_iam_auth(\n agent_runtime_arn: str,\n *,\n name: str | None = None,\n description: str | None = None,\n ) -> A2AAgent:\n \"\"\"SigV4-authenticated client for a Bedrock AgentCore runtime.\"\"\"\n return _build(\n AgentCoreA2aClientConfig.with_iam_auth(agent_runtime_arn),\n name=name,\n description=description,\n )`;\n\nconst STRANDS_IAM_NEW = `@staticmethod\ndef with_iam_auth(\n agent_runtime_arn: str,\n *,\n name: str | None = None,\n description: str | None = None,\n) -> _A2AClient:\n \"\"\"SigV4-authenticated client for a Bedrock AgentCore runtime.\"\"\"\n return _build(\n AgentCoreA2aClientConfig.with_iam_auth(agent_runtime_arn),\n name=name,\n description=description,\n )`;\n\nconst STRANDS_JWT_OLD = ` @staticmethod\n def with_jwt_auth(\n agent_runtime_arn: str,\n access_token_provider: Callable[[], str],\n *,\n name: str | None = None,\n description: str | None = None,\n ) -> A2AAgent:\n \"\"\"Bearer-authenticated client for a Bedrock AgentCore runtime.\"\"\"\n return _build(\n AgentCoreA2aClientConfig.with_jwt_auth(\n agent_runtime_arn, access_token_provider\n ),\n name=name,\n description=description,\n )`;\n\nconst STRANDS_JWT_NEW = `@staticmethod\ndef with_jwt_auth(\n agent_runtime_arn: str,\n access_token_provider: Callable[[], str],\n *,\n name: str | None = None,\n description: str | None = None,\n) -> _A2AClient:\n \"\"\"Bearer-authenticated client for a Bedrock AgentCore runtime.\"\"\"\n return _build(\n AgentCoreA2aClientConfig.with_jwt_auth(\n agent_runtime_arn, access_token_provider\n ),\n name=name,\n description=description,\n )`;\n\nconst STRANDS_NOAUTH_OLD = ` @staticmethod\n def without_auth(\n url: str,\n *,\n name: str | None = None,\n description: str | None = None,\n ) -> A2AAgent:\n \"\"\"Plain-HTTP client — for local dev.\"\"\"\n return _build(\n AgentCoreA2aClientConfig.without_auth(url),\n name=name,\n description=description,\n )`;\n\nconst STRANDS_NOAUTH_NEW = `@staticmethod\ndef without_auth(\n url: str,\n *,\n name: str | None = None,\n description: str | None = None,\n) -> _A2AClient:\n \"\"\"Plain-HTTP client — for local dev.\"\"\"\n return _build(\n AgentCoreA2aClientConfig.without_auth(url),\n name=name,\n description=description,\n )`;\n\nconst migrateStrandsCoreClient = async (\n tree: Tree,\n filePath: string,\n nextSteps: string[],\n): Promise<void> => {\n const contents = tree.read(filePath, 'utf-8') ?? '';\n if (!contents.includes('return A2AAgent(**kwargs)')) return;\n\n const ready = await allMatch(tree, filePath, [\n pyMatch(STRANDS_IMPORT1_OLD),\n pyMatch(STRANDS_IMPORT2_OLD),\n pyMatch(STRANDS_BUILD_OLD),\n pyMatch(STRANDS_IAM_OLD),\n pyMatch(STRANDS_JWT_OLD),\n pyMatch(STRANDS_NOAUTH_OLD),\n ]);\n\n if (!ready) {\n nextSteps.push(\n `${filePath}: diverged from the generated shape - left untouched. Manually rebuild a fresh httpx.AsyncClient (and A2AAgent) per call instead of reusing the ones built at factory time (see the agent-connection generator's agentcore_a2a_client_strands.py template).`,\n );\n return;\n }\n\n await applyGritQL(\n tree,\n filePath,\n pyRewrite(STRANDS_IMPORT1_OLD, STRANDS_IMPORT1_NEW),\n );\n await applyGritQL(\n tree,\n filePath,\n pyRewrite(STRANDS_IMPORT2_OLD, STRANDS_IMPORT2_NEW),\n );\n await applyGritQL(\n tree,\n filePath,\n pyRewrite(STRANDS_BUILD_OLD, STRANDS_BUILD_NEW),\n );\n await applyGritQL(\n tree,\n filePath,\n pyRewrite(STRANDS_IAM_OLD, STRANDS_IAM_NEW),\n );\n await applyGritQL(\n tree,\n filePath,\n pyRewrite(STRANDS_JWT_OLD, STRANDS_JWT_NEW),\n );\n await applyGritQL(\n tree,\n filePath,\n pyRewrite(STRANDS_NOAUTH_OLD, STRANDS_NOAUTH_NEW),\n );\n};\n\n// --- <target>_client_strands.py (per-target wrapper, one per connection) ---\n\nconst WRAPPER_IMPORT_OLD = 'from strands.agent.a2a_agent import A2AAgent';\n\nconst WRAPPER_CREATE_OLD = 'def create() -> A2AAgent:\\n $body';\nconst WRAPPER_CREATE_NEW = 'def create():\\n $body';\n\nconst migrateStrandsWrapperClient = async (\n tree: Tree,\n filePath: string,\n nextSteps: string[],\n): Promise<void> => {\n const contents = tree.read(filePath, 'utf-8') ?? '';\n if (!contents.includes('def create() -> A2AAgent:')) return;\n\n const ready = await allMatch(tree, filePath, [\n pyMatch(WRAPPER_IMPORT_OLD),\n pyMatch(WRAPPER_CREATE_OLD),\n ]);\n\n if (!ready) {\n nextSteps.push(\n `${filePath}: diverged from the generated shape - left untouched. Manually drop the A2AAgent import and its -> A2AAgent return annotation on create() (see the py#agent#a2a-connection generator's per-target client_strands.py template).`,\n );\n return;\n }\n\n await applyGritQL(tree, filePath, `${pyMatch(WRAPPER_IMPORT_OLD)} => .`);\n await applyGritQL(\n tree,\n filePath,\n pyRewrite(WRAPPER_CREATE_OLD, WRAPPER_CREATE_NEW),\n );\n};\n\nexport default async function migration(\n tree: Tree,\n): Promise<MigrationReturnObject> {\n const nextSteps: string[] = [];\n\n const filePaths: string[] = [];\n visitNotIgnoredFiles(tree, '', (filePath) => filePaths.push(filePath));\n\n for (const filePath of filePaths) {\n if (filePath.endsWith('/agentcore_a2a_client_langchain.py')) {\n await migrateLangchainCoreClient(tree, filePath, nextSteps);\n } else if (filePath.endsWith('/agentcore_a2a_client_strands.py')) {\n await migrateStrandsCoreClient(tree, filePath, nextSteps);\n } else if (filePath.endsWith('_client_strands.py')) {\n await migrateStrandsWrapperClient(tree, filePath, nextSteps);\n }\n }\n\n await formatFilesInSubtree(tree);\n\n return { nextSteps };\n}\n"],"names":["visitNotIgnoredFiles","applyGritQL","matchGritQL","formatFilesInSubtree","pyMatch","snippet","pyRewrite","from","to","replacement","includes","allMatch","tree","filePath","patterns","pattern","LANGCHAIN_IMPORT_OLD","LANGCHAIN_IMPORT_NEW","LANGCHAIN_INVOKE_OLD","LANGCHAIN_INVOKE_NEW","migrateLangchainCoreClient","nextSteps","contents","read","ready","push","STRANDS_IMPORT1_OLD","STRANDS_IMPORT1_NEW","STRANDS_IMPORT2_OLD","STRANDS_IMPORT2_NEW","STRANDS_BUILD_OLD","STRANDS_BUILD_NEW","STRANDS_IAM_OLD","STRANDS_IAM_NEW","STRANDS_JWT_OLD","STRANDS_JWT_NEW","STRANDS_NOAUTH_OLD","STRANDS_NOAUTH_NEW","migrateStrandsCoreClient","WRAPPER_IMPORT_OLD","WRAPPER_CREATE_OLD","WRAPPER_CREATE_NEW","migrateStrandsWrapperClient","migration","filePaths","endsWith"],"mappings":"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"}
|