@aws/nx-plugin 1.0.0-rc.72 → 1.0.0-rc.73

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 (120) hide show
  1. package/LICENSE-THIRD-PARTY +210 -730
  2. package/migrations.json +34 -24
  3. package/package.json +4 -4
  4. package/src/agentcore-gateway/__snapshots__/generator.spec.ts.snap +1 -1
  5. package/src/infra/app/__snapshots__/generator.spec.ts.snap +3 -3
  6. package/src/license/config.d.ts +9 -0
  7. package/src/license/config.js +28 -0
  8. package/src/license/config.js.map +1 -1
  9. package/src/license/dependency-check/collectors/collector.js.map +1 -1
  10. package/src/license/generator.js +3 -17
  11. package/src/license/generator.js.map +1 -1
  12. package/src/license/known-exceptions.d.ts +0 -1
  13. package/src/license/known-exceptions.js +1 -23
  14. package/src/license/known-exceptions.js.map +1 -1
  15. package/src/migrations/latest/apt-security-updates-in-container-images/__snapshots__/migration.spec.ts.snap +90 -0
  16. package/src/migrations/latest/apt-security-updates-in-container-images/metadata.json +3 -0
  17. package/src/migrations/latest/apt-security-updates-in-container-images/migration.js +90 -0
  18. package/src/migrations/latest/apt-security-updates-in-container-images/migration.js.map +1 -0
  19. package/src/migrations/latest/remove-mcp-inspector-license-exceptions/metadata.json +3 -0
  20. package/src/migrations/latest/remove-mcp-inspector-license-exceptions/migration.d.ts +6 -0
  21. package/src/migrations/latest/remove-mcp-inspector-license-exceptions/migration.js +38 -0
  22. package/src/migrations/latest/remove-mcp-inspector-license-exceptions/migration.js.map +1 -0
  23. package/src/migrations/v1.0.0-rc.67/0001-smithy-server-package-rename/migration.js.map +1 -0
  24. package/src/migrations/v1.0.0-rc.68/0001-translate-script-whole-file-writes/migration.js.map +1 -0
  25. package/src/migrations/{latest/py-agent-a2a-lifespan-construction → v1.0.0-rc.70/0001-py-agent-a2a-lifespan-construction}/migration.js +14 -2
  26. package/src/migrations/v1.0.0-rc.70/0001-py-agent-a2a-lifespan-construction/migration.js.map +1 -0
  27. package/src/migrations/v1.0.0-rc.71/0001-py-agent-a2a-httpx-client-per-call/migration.d.ts +6 -0
  28. package/src/migrations/v1.0.0-rc.71/0001-py-agent-a2a-httpx-client-per-call/migration.js.map +1 -0
  29. package/src/migrations/v1.0.0-rc.72/0001-py-agent-session-management-support/migration.js.map +1 -0
  30. package/src/preset/__snapshots__/generator.spec.ts.snap +2 -2
  31. package/src/py/agent/__snapshots__/generator.constructs.spec.ts.snap +11 -4
  32. package/src/py/agent/__snapshots__/generator.frameworks.spec.ts.snap +8 -8
  33. package/src/py/agent/__snapshots__/generator.protocols.spec.ts.snap +2 -2
  34. package/src/py/agent/a2a-connection/generator.js +5 -11
  35. package/src/py/agent/a2a-connection/generator.js.map +1 -1
  36. package/src/py/agent/files/deploy/Dockerfile.template +7 -0
  37. package/src/py/mcp-server/__snapshots__/generator.spec.ts.snap +5 -5
  38. package/src/py/mcp-server/files/deploy/Dockerfile.template +7 -0
  39. package/src/py/mcp-server/generator.js +8 -3
  40. package/src/py/mcp-server/generator.js.map +1 -1
  41. package/src/py/rdb/__snapshots__/generator.spec.ts.snap +7 -5
  42. package/src/py/rdb/generator.js +36 -2
  43. package/src/py/rdb/generator.js.map +1 -1
  44. package/src/smithy/project/__snapshots__/generator.spec.ts.snap +14 -14
  45. package/src/smithy/ts/api/__snapshots__/generator.spec.ts.snap +1 -1
  46. package/src/ts/agent/__snapshots__/generator.spec.ts.snap +9 -2
  47. package/src/ts/agent/a2a-connection/generator.js +4 -2
  48. package/src/ts/agent/a2a-connection/generator.js.map +1 -1
  49. package/src/ts/agent/files/deploy/Dockerfile.template +7 -0
  50. package/src/ts/mcp-server/__snapshots__/generator.spec.ts.snap +2 -2
  51. package/src/ts/mcp-server/files/Dockerfile.template +7 -0
  52. package/src/ts/mcp-server/generator.js +8 -3
  53. package/src/ts/mcp-server/generator.js.map +1 -1
  54. package/src/ts/rdb/__snapshots__/generator.spec.ts.snap +8 -6
  55. package/src/ts/rdb/generator.js +29 -2
  56. package/src/ts/rdb/generator.js.map +1 -1
  57. package/src/ts/react-website/app/__snapshots__/generator.spec.ts.snap +15 -15
  58. package/src/ts/react-website/app/generator.js +4 -2
  59. package/src/ts/react-website/app/generator.js.map +1 -1
  60. package/src/ts/react-website/cognito-auth/__snapshots__/generator.terraform.spec.ts.snap +1 -1
  61. package/src/utils/agent-connection/agent-connection.js +5 -9
  62. package/src/utils/agent-connection/agent-connection.js.map +1 -1
  63. package/src/utils/ast.d.ts +29 -0
  64. package/src/utils/ast.js +31 -0
  65. package/src/utils/ast.js.map +1 -1
  66. package/src/utils/metrics.js +2 -2
  67. package/src/utils/metrics.js.map +1 -1
  68. package/src/utils/versions.d.ts +73 -73
  69. package/src/utils/versions.js +70 -70
  70. package/src/utils/versions.js.map +1 -1
  71. package/src/migrations/latest/py-agent-a2a-httpx-client-per-call/migration.js.map +0 -1
  72. package/src/migrations/latest/py-agent-a2a-lifespan-construction/migration.js.map +0 -1
  73. package/src/migrations/latest/py-agent-session-management-support/migration.js.map +0 -1
  74. package/src/migrations/latest/smithy-server-package-rename/migration.js.map +0 -1
  75. package/src/migrations/latest/translate-script-whole-file-writes/migration.js.map +0 -1
  76. package/src/py/rdb/files/__name__/__init__.py.template +0 -3
  77. package/src/py/rdb/files/config.json.template +0 -14
  78. package/src/ts/rdb/files/config.json.template +0 -14
  79. package/src/ts/rdb/files/src/index.ts.template +0 -1
  80. /package/src/migrations/latest/{py-agent-a2a-httpx-client-per-call → apt-security-updates-in-container-images}/migration.d.ts +0 -0
  81. /package/src/migrations/{latest/smithy-server-package-rename → v1.0.0-rc.67/0001-smithy-server-package-rename}/metadata.json +0 -0
  82. /package/src/migrations/{latest/py-agent-a2a-lifespan-construction → v1.0.0-rc.67/0001-smithy-server-package-rename}/migration.d.ts +0 -0
  83. /package/src/migrations/{latest/smithy-server-package-rename → v1.0.0-rc.67/0001-smithy-server-package-rename}/migration.js +0 -0
  84. /package/src/migrations/{latest/translate-script-whole-file-writes → v1.0.0-rc.68/0001-translate-script-whole-file-writes}/files/build-system-prompt.ts.fixture +0 -0
  85. /package/src/migrations/{latest/translate-script-whole-file-writes → v1.0.0-rc.68/0001-translate-script-whole-file-writes}/files/build-user-prompt.ts.fixture +0 -0
  86. /package/src/migrations/{latest/translate-script-whole-file-writes → v1.0.0-rc.68/0001-translate-script-whole-file-writes}/files/config-path.ts.fixture +0 -0
  87. /package/src/migrations/{latest/translate-script-whole-file-writes → v1.0.0-rc.68/0001-translate-script-whole-file-writes}/files/file-to-translate.ts.fixture +0 -0
  88. /package/src/migrations/{latest/translate-script-whole-file-writes → v1.0.0-rc.68/0001-translate-script-whole-file-writes}/files/get-files-to-translate.ts.fixture +0 -0
  89. /package/src/migrations/{latest/translate-script-whole-file-writes → v1.0.0-rc.68/0001-translate-script-whole-file-writes}/files/header.ts.fixture +0 -0
  90. /package/src/migrations/{latest/translate-script-whole-file-writes → v1.0.0-rc.68/0001-translate-script-whole-file-writes}/files/log.ts.fixture +0 -0
  91. /package/src/migrations/{latest/translate-script-whole-file-writes → v1.0.0-rc.68/0001-translate-script-whole-file-writes}/files/main-call.ts.fixture +0 -0
  92. /package/src/migrations/{latest/translate-script-whole-file-writes → v1.0.0-rc.68/0001-translate-script-whole-file-writes}/files/main.ts.fixture +0 -0
  93. /package/src/migrations/{latest/translate-script-whole-file-writes → v1.0.0-rc.68/0001-translate-script-whole-file-writes}/files/project-root.ts.fixture +0 -0
  94. /package/src/migrations/{latest/translate-script-whole-file-writes → v1.0.0-rc.68/0001-translate-script-whole-file-writes}/files/run-with-concurrency.ts.fixture +0 -0
  95. /package/src/migrations/{latest/translate-script-whole-file-writes → v1.0.0-rc.68/0001-translate-script-whole-file-writes}/files/scripts-dir.ts.fixture +0 -0
  96. /package/src/migrations/{latest/translate-script-whole-file-writes → v1.0.0-rc.68/0001-translate-script-whole-file-writes}/files/translate-file-for-language.ts.fixture +0 -0
  97. /package/src/migrations/{latest/translate-script-whole-file-writes → v1.0.0-rc.68/0001-translate-script-whole-file-writes}/grit/already-migrated.grit +0 -0
  98. /package/src/migrations/{latest/translate-script-whole-file-writes → v1.0.0-rc.68/0001-translate-script-whole-file-writes}/grit/build-system-prompt.grit +0 -0
  99. /package/src/migrations/{latest/translate-script-whole-file-writes → v1.0.0-rc.68/0001-translate-script-whole-file-writes}/grit/build-user-prompt.grit +0 -0
  100. /package/src/migrations/{latest/translate-script-whole-file-writes → v1.0.0-rc.68/0001-translate-script-whole-file-writes}/grit/config-path.grit +0 -0
  101. /package/src/migrations/{latest/translate-script-whole-file-writes → v1.0.0-rc.68/0001-translate-script-whole-file-writes}/grit/file-to-translate.grit +0 -0
  102. /package/src/migrations/{latest/translate-script-whole-file-writes → v1.0.0-rc.68/0001-translate-script-whole-file-writes}/grit/get-files-to-translate.grit +0 -0
  103. /package/src/migrations/{latest/translate-script-whole-file-writes → v1.0.0-rc.68/0001-translate-script-whole-file-writes}/grit/log.grit +0 -0
  104. /package/src/migrations/{latest/translate-script-whole-file-writes → v1.0.0-rc.68/0001-translate-script-whole-file-writes}/grit/main-call.grit +0 -0
  105. /package/src/migrations/{latest/translate-script-whole-file-writes → v1.0.0-rc.68/0001-translate-script-whole-file-writes}/grit/main.grit +0 -0
  106. /package/src/migrations/{latest/translate-script-whole-file-writes → v1.0.0-rc.68/0001-translate-script-whole-file-writes}/grit/project-root.grit +0 -0
  107. /package/src/migrations/{latest/translate-script-whole-file-writes → v1.0.0-rc.68/0001-translate-script-whole-file-writes}/grit/run-with-concurrency.grit +0 -0
  108. /package/src/migrations/{latest/translate-script-whole-file-writes → v1.0.0-rc.68/0001-translate-script-whole-file-writes}/grit/translate-file-for-language.grit +0 -0
  109. /package/src/migrations/{latest/translate-script-whole-file-writes → v1.0.0-rc.68/0001-translate-script-whole-file-writes}/metadata.json +0 -0
  110. /package/src/migrations/{latest/py-agent-session-management-support → v1.0.0-rc.68/0001-translate-script-whole-file-writes}/migration.d.ts +0 -0
  111. /package/src/migrations/{latest/translate-script-whole-file-writes → v1.0.0-rc.68/0001-translate-script-whole-file-writes}/migration.js +0 -0
  112. /package/src/migrations/{latest/translate-script-whole-file-writes → v1.0.0-rc.68/0001-translate-script-whole-file-writes}/test-fixtures/released-script-first.ts.fixture +0 -0
  113. /package/src/migrations/{latest/translate-script-whole-file-writes → v1.0.0-rc.68/0001-translate-script-whole-file-writes}/test-fixtures/released-script.ts.fixture +0 -0
  114. /package/src/migrations/{latest/py-agent-a2a-lifespan-construction → v1.0.0-rc.70/0001-py-agent-a2a-lifespan-construction}/metadata.json +0 -0
  115. /package/src/migrations/{latest/smithy-server-package-rename → v1.0.0-rc.70/0001-py-agent-a2a-lifespan-construction}/migration.d.ts +0 -0
  116. /package/src/migrations/{latest/py-agent-a2a-httpx-client-per-call → v1.0.0-rc.71/0001-py-agent-a2a-httpx-client-per-call}/metadata.json +0 -0
  117. /package/src/migrations/{latest/py-agent-a2a-httpx-client-per-call → v1.0.0-rc.71/0001-py-agent-a2a-httpx-client-per-call}/migration.js +0 -0
  118. /package/src/migrations/{latest/py-agent-session-management-support → v1.0.0-rc.72/0001-py-agent-session-management-support}/metadata.json +0 -0
  119. /package/src/migrations/{latest/translate-script-whole-file-writes → v1.0.0-rc.72/0001-py-agent-session-management-support}/migration.d.ts +0 -0
  120. /package/src/migrations/{latest/py-agent-session-management-support → v1.0.0-rc.72/0001-py-agent-session-management-support}/migration.js +0 -0
@@ -0,0 +1,90 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`apt-security-updates-in-container-images migration > adds the security updates to a py#agent Dockerfile 1`] = `
4
+ "FROM public.ecr.aws/docker/library/python:3.14-slim
5
+
6
+ # Apply the distribution's security updates: base image tags lag behind the
7
+ # security suite, so its OS packages carry fixed HIGH/CRITICAL vulnerabilities
8
+ # flagged by the image scan.
9
+ RUN apt-get update && \\
10
+ apt-get upgrade -y --no-install-recommends && \\
11
+ rm -rf /var/lib/apt/lists/*
12
+
13
+ # Remove the base image's pip: the agent runs from the bundled environment and
14
+ # never needs it at runtime, and pip's vendored packages carry known
15
+ # HIGH/CRITICAL vulnerabilities flagged by the image scan.
16
+ RUN python -m pip uninstall -y pip && \\
17
+ rm -rf /usr/local/lib/python*/site-packages/pip \\
18
+ /usr/local/lib/python*/site-packages/pip-*.dist-info
19
+
20
+ WORKDIR /app
21
+
22
+ # Copy bundled package
23
+ COPY . /app
24
+
25
+ EXPOSE 8080
26
+
27
+ ENV PYTHONPATH=/app
28
+ ENV PATH="/app/bin:\${PATH}"
29
+
30
+ CMD ["python", "bin/opentelemetry-instrument", "python", "-m", "proj_py_project.my_agent.main"]
31
+ "
32
+ `;
33
+
34
+ exports[`apt-security-updates-in-container-images migration > adds the security updates to a py#mcp-server Dockerfile 1`] = `
35
+ "FROM public.ecr.aws/docker/library/python:3.14-slim
36
+
37
+ # Apply the distribution's security updates: base image tags lag behind the
38
+ # security suite, so its OS packages carry fixed HIGH/CRITICAL vulnerabilities
39
+ # flagged by the image scan.
40
+ RUN apt-get update && \\
41
+ apt-get upgrade -y --no-install-recommends && \\
42
+ rm -rf /var/lib/apt/lists/*
43
+
44
+ # Remove the base image's pip: the server runs from the bundled environment and
45
+ # never needs it at runtime, and pip's vendored packages carry known
46
+ # HIGH/CRITICAL vulnerabilities flagged by the image scan.
47
+ RUN python -m pip uninstall -y pip && \\
48
+ rm -rf /usr/local/lib/python*/site-packages/pip \\
49
+ /usr/local/lib/python*/site-packages/pip-*.dist-info
50
+
51
+ WORKDIR /app
52
+
53
+ # Copy bundled package
54
+ COPY . /app
55
+
56
+ EXPOSE 8000
57
+
58
+ ENV PYTHONPATH=/app
59
+
60
+ CMD ["python", "bin/opentelemetry-instrument", "python", "-m", "uvicorn", "proj_py_project.my_mcp.http:app", "--host", "0.0.0.0", "--port", "8000"]
61
+ "
62
+ `;
63
+
64
+ exports[`apt-security-updates-in-container-images migration > adds the security updates to a ts#agent Dockerfile 1`] = `
65
+ "FROM public.ecr.aws/docker/library/node:lts-slim
66
+
67
+ # Apply the distribution's security updates: base image tags lag behind the
68
+ # security suite, so its OS packages carry fixed HIGH/CRITICAL vulnerabilities
69
+ # flagged by the image scan.
70
+ RUN apt-get update && \\
71
+ apt-get upgrade -y --no-install-recommends && \\
72
+ rm -rf /var/lib/apt/lists/*
73
+
74
+ # Upgrade npm to a version free of known HIGH/CRITICAL vulnerabilities
75
+ RUN npm install -g npm@11.0.0
76
+
77
+ WORKDIR /app
78
+
79
+ RUN npm init -y \\
80
+ && npm install @aws/aws-distro-opentelemetry-node-autoinstrumentation@0.12.0 \\
81
+ && npm uninstall -g npm
82
+
83
+ # Copy bundled agent
84
+ COPY index.js /app
85
+
86
+ EXPOSE 8080
87
+
88
+ CMD [ "node", "--require", "@aws/aws-distro-opentelemetry-node-autoinstrumentation/register", "index.js" ]
89
+ "
90
+ `;
@@ -0,0 +1,3 @@
1
+ {
2
+ "description": "Apply the distribution's security updates in vended agent / MCP server container images"
3
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */ import { getProjects, joinPathFragments } from "@nx/devkit";
5
+ import { formatFilesInSubtree } from "../../../utils/format.js";
6
+ /**
7
+ * Apply the distribution's security updates in the vended agent / MCP server
8
+ * images.
9
+ *
10
+ * A base image tag lags behind its distribution's security suite, so the OS
11
+ * packages it ships carry vulnerabilities that already have a fixed version
12
+ * published — which the image scan flags as HIGH/CRITICAL. Upgrading them in the
13
+ * image build clears those findings whenever the scan runs, rather than waiting
14
+ * for the base image to be rebuilt. New workspaces get this from the generator
15
+ * templates; this brings existing ones up to date.
16
+ *
17
+ * Only the Dockerfiles of the components whose images the generators vend are
18
+ * touched, located from their project metadata. The Dockerfile is generated
19
+ * `KeepExisting`, so it is edited in place: the upgrade is inserted right after
20
+ * the `FROM` line, ahead of every other instruction, so later layers install
21
+ * against the upgraded packages. One that already runs `apt-get upgrade`, or
22
+ * which has been re-based onto an image `apt-get` can't upgrade, is left alone.
23
+ */ /**
24
+ * The generators whose runtime images the vended Dockerfiles build. Hardcoded
25
+ * rather than imported from the generators: a migration matches the ids
26
+ * workspaces recorded at generation time, which must stay fixed even if the
27
+ * generators' current ids change later.
28
+ */ const RUNTIME_IMAGE_GENERATOR_IDS = [
29
+ 'py#agent',
30
+ 'py#mcp-server',
31
+ 'ts#agent',
32
+ 'ts#mcp-server'
33
+ ];
34
+ const APT_UPGRADE = `# Apply the distribution's security updates: base image tags lag behind the
35
+ # security suite, so its OS packages carry fixed HIGH/CRITICAL vulnerabilities
36
+ # flagged by the image scan.
37
+ RUN apt-get update && \\
38
+ apt-get upgrade -y --no-install-recommends && \\
39
+ rm -rf /var/lib/apt/lists/*`;
40
+ /** The apt command this migration adds; used to detect an applied fix. */ const APT_UPGRADE_COMMAND = 'apt-get upgrade';
41
+ /**
42
+ * The Debian-derived base images the generators vend, whose packages `apt-get`
43
+ * upgrades. A user who has re-based onto another image (Alpine, distroless,
44
+ * Amazon Linux) needs a different command, so theirs is left as they wrote it.
45
+ */ const DEBIAN_BASE_IMAGE = /^FROM\s+\S*(?:node|python):\S*\n/m;
46
+ /**
47
+ * The Dockerfiles of every runtime image component, located from each project's
48
+ * recorded component metadata (`path` is the component directory relative to
49
+ * the project root).
50
+ */ const runtimeImageDockerfiles = (tree)=>{
51
+ const paths = [];
52
+ for (const project of getProjects(tree).values()){
53
+ const components = project.metadata?.components ?? [];
54
+ for (const component of components){
55
+ if (!RUNTIME_IMAGE_GENERATOR_IDS.includes(component.generator) || !component.path) {
56
+ continue;
57
+ }
58
+ const dockerfile = joinPathFragments(projectRoot(project), component.path, 'Dockerfile');
59
+ if (tree.exists(dockerfile)) {
60
+ paths.push(dockerfile);
61
+ }
62
+ }
63
+ }
64
+ return paths;
65
+ };
66
+ /** A project's root, falling back to sourceRoot for the TS-solution layout. */ const projectRoot = (project)=>project.root ?? project.sourceRoot ?? '';
67
+ export default async function migration(tree) {
68
+ for (const filePath of runtimeImageDockerfiles(tree)){
69
+ const content = tree.read(filePath, 'utf-8');
70
+ if (!content) {
71
+ continue;
72
+ }
73
+ if (content.includes(APT_UPGRADE_COMMAND)) {
74
+ continue; // Already upgrading (migrated or user-added).
75
+ }
76
+ const from = content.match(DEBIAN_BASE_IMAGE);
77
+ if (from?.index === undefined) {
78
+ continue; // The user has re-based the image; theirs to patch, not ours.
79
+ }
80
+ // Insert the upgrade after the FROM line, ahead of every other instruction
81
+ // so later layers install against the upgraded packages, with the spacing a
82
+ // freshly generated Dockerfile has.
83
+ const insertAt = from.index + from[0].length;
84
+ tree.write(filePath, `${content.slice(0, insertAt)}\n${APT_UPGRADE}\n${content.slice(insertAt)}`);
85
+ }
86
+ await formatFilesInSubtree(tree);
87
+ return {};
88
+ }
89
+
90
+ //# sourceMappingURL=migration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/latest/apt-security-updates-in-container-images/migration.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport {\n getProjects,\n joinPathFragments,\n type MigrationReturnObject,\n type ProjectConfiguration,\n type Tree,\n} from '@nx/devkit';\nimport { formatFilesInSubtree } from '../../../utils/format';\nimport type { ComponentMetadata } from '../../../utils/nx';\n\n/**\n * Apply the distribution's security updates in the vended agent / MCP server\n * images.\n *\n * A base image tag lags behind its distribution's security suite, so the OS\n * packages it ships carry vulnerabilities that already have a fixed version\n * published — which the image scan flags as HIGH/CRITICAL. Upgrading them in the\n * image build clears those findings whenever the scan runs, rather than waiting\n * for the base image to be rebuilt. New workspaces get this from the generator\n * templates; this brings existing ones up to date.\n *\n * Only the Dockerfiles of the components whose images the generators vend are\n * touched, located from their project metadata. The Dockerfile is generated\n * `KeepExisting`, so it is edited in place: the upgrade is inserted right after\n * the `FROM` line, ahead of every other instruction, so later layers install\n * against the upgraded packages. One that already runs `apt-get upgrade`, or\n * which has been re-based onto an image `apt-get` can't upgrade, is left alone.\n */\n\n/**\n * The generators whose runtime images the vended Dockerfiles build. Hardcoded\n * rather than imported from the generators: a migration matches the ids\n * workspaces recorded at generation time, which must stay fixed even if the\n * generators' current ids change later.\n */\nconst RUNTIME_IMAGE_GENERATOR_IDS = [\n 'py#agent',\n 'py#mcp-server',\n 'ts#agent',\n 'ts#mcp-server',\n];\n\nconst APT_UPGRADE = `# Apply the distribution's security updates: base image tags lag behind the\n# security suite, so its OS packages carry fixed HIGH/CRITICAL vulnerabilities\n# flagged by the image scan.\nRUN apt-get update && \\\\\n apt-get upgrade -y --no-install-recommends && \\\\\n rm -rf /var/lib/apt/lists/*`;\n\n/** The apt command this migration adds; used to detect an applied fix. */\nconst APT_UPGRADE_COMMAND = 'apt-get upgrade';\n\n/**\n * The Debian-derived base images the generators vend, whose packages `apt-get`\n * upgrades. A user who has re-based onto another image (Alpine, distroless,\n * Amazon Linux) needs a different command, so theirs is left as they wrote it.\n */\nconst DEBIAN_BASE_IMAGE = /^FROM\\s+\\S*(?:node|python):\\S*\\n/m;\n\n/**\n * The Dockerfiles of every runtime image component, located from each project's\n * recorded component metadata (`path` is the component directory relative to\n * the project root).\n */\nconst runtimeImageDockerfiles = (tree: Tree): string[] => {\n const paths: string[] = [];\n for (const project of getProjects(tree).values()) {\n const components = ((\n project.metadata as { components?: ComponentMetadata[] }\n )?.components ?? []) as ComponentMetadata[];\n for (const component of components) {\n if (\n !RUNTIME_IMAGE_GENERATOR_IDS.includes(component.generator) ||\n !component.path\n ) {\n continue;\n }\n const dockerfile = joinPathFragments(\n projectRoot(project),\n component.path,\n 'Dockerfile',\n );\n if (tree.exists(dockerfile)) {\n paths.push(dockerfile);\n }\n }\n }\n return paths;\n};\n\n/** A project's root, falling back to sourceRoot for the TS-solution layout. */\nconst projectRoot = (project: ProjectConfiguration): string =>\n project.root ?? project.sourceRoot ?? '';\n\nexport default async function migration(\n tree: Tree,\n): Promise<MigrationReturnObject> {\n for (const filePath of runtimeImageDockerfiles(tree)) {\n const content = tree.read(filePath, 'utf-8');\n if (!content) {\n continue;\n }\n if (content.includes(APT_UPGRADE_COMMAND)) {\n continue; // Already upgrading (migrated or user-added).\n }\n\n const from = content.match(DEBIAN_BASE_IMAGE);\n if (from?.index === undefined) {\n continue; // The user has re-based the image; theirs to patch, not ours.\n }\n\n // Insert the upgrade after the FROM line, ahead of every other instruction\n // so later layers install against the upgraded packages, with the spacing a\n // freshly generated Dockerfile has.\n const insertAt = from.index + from[0].length;\n tree.write(\n filePath,\n `${content.slice(0, insertAt)}\\n${APT_UPGRADE}\\n${content.slice(insertAt)}`,\n );\n }\n\n await formatFilesInSubtree(tree);\n\n return {};\n}\n"],"names":["getProjects","joinPathFragments","formatFilesInSubtree","RUNTIME_IMAGE_GENERATOR_IDS","APT_UPGRADE","APT_UPGRADE_COMMAND","DEBIAN_BASE_IMAGE","runtimeImageDockerfiles","tree","paths","project","values","components","metadata","component","includes","generator","path","dockerfile","projectRoot","exists","push","root","sourceRoot","migration","filePath","content","read","from","match","index","undefined","insertAt","length","write","slice"],"mappings":"AAAA;;;CAGC,GACD,SACEA,WAAW,EACXC,iBAAiB,QAIZ,aAAa;AACpB,SAASC,oBAAoB,QAAQ,2BAAwB;AAG7D;;;;;;;;;;;;;;;;;CAiBC,GAED;;;;;CAKC,GACD,MAAMC,8BAA8B;IAClC;IACA;IACA;IACA;CACD;AAED,MAAMC,cAAc,CAAC;;;;;+BAKU,CAAC;AAEhC,wEAAwE,GACxE,MAAMC,sBAAsB;AAE5B;;;;CAIC,GACD,MAAMC,oBAAoB;AAE1B;;;;CAIC,GACD,MAAMC,0BAA0B,CAACC;IAC/B,MAAMC,QAAkB,EAAE;IAC1B,KAAK,MAAMC,WAAWV,YAAYQ,MAAMG,MAAM,GAAI;QAChD,MAAMC,aAAc,AAClBF,QAAQG,QAAQ,EACfD,cAAc,EAAE;QACnB,KAAK,MAAME,aAAaF,WAAY;YAClC,IACE,CAACT,4BAA4BY,QAAQ,CAACD,UAAUE,SAAS,KACzD,CAACF,UAAUG,IAAI,EACf;gBACA;YACF;YACA,MAAMC,aAAajB,kBACjBkB,YAAYT,UACZI,UAAUG,IAAI,EACd;YAEF,IAAIT,KAAKY,MAAM,CAACF,aAAa;gBAC3BT,MAAMY,IAAI,CAACH;YACb;QACF;IACF;IACA,OAAOT;AACT;AAEA,6EAA6E,GAC7E,MAAMU,cAAc,CAACT,UACnBA,QAAQY,IAAI,IAAIZ,QAAQa,UAAU,IAAI;AAExC,eAAe,eAAeC,UAC5BhB,IAAU;IAEV,KAAK,MAAMiB,YAAYlB,wBAAwBC,MAAO;QACpD,MAAMkB,UAAUlB,KAAKmB,IAAI,CAACF,UAAU;QACpC,IAAI,CAACC,SAAS;YACZ;QACF;QACA,IAAIA,QAAQX,QAAQ,CAACV,sBAAsB;YACzC,UAAU,8CAA8C;QAC1D;QAEA,MAAMuB,OAAOF,QAAQG,KAAK,CAACvB;QAC3B,IAAIsB,MAAME,UAAUC,WAAW;YAC7B,UAAU,8DAA8D;QAC1E;QAEA,2EAA2E;QAC3E,4EAA4E;QAC5E,oCAAoC;QACpC,MAAMC,WAAWJ,KAAKE,KAAK,GAAGF,IAAI,CAAC,EAAE,CAACK,MAAM;QAC5CzB,KAAK0B,KAAK,CACRT,UACA,GAAGC,QAAQS,KAAK,CAAC,GAAGH,UAAU,EAAE,EAAE5B,YAAY,EAAE,EAAEsB,QAAQS,KAAK,CAACH,WAAW;IAE/E;IAEA,MAAM9B,qBAAqBM;IAE3B,OAAO,CAAC;AACV"}
@@ -0,0 +1,3 @@
1
+ {
2
+ "description": "Remove the obsolete MCP Inspector license exceptions"
3
+ }
@@ -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 Tree } from '@nx/devkit';
6
+ export default function migration(tree: Tree): Promise<void>;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */ import { readJson } from "@nx/devkit";
5
+ import { removeLicenseExceptions } from "../../../license/config.js";
6
+ import { registerPnpmBuiltDependencies } from "../../../utils/pnpm-workspace.js";
7
+ const INSPECTOR = '@modelcontextprotocol/inspector';
8
+ /**
9
+ * MCP Inspector v1 declared `SEE LICENSE IN LICENSE`, which resolves to no SPDX
10
+ * identifier, so the license check needed an exception per published package.
11
+ * v2 ships as a single package declaring `MIT`, which the allowlist already
12
+ * covers, and the stale exceptions would override that with `Apache-2.0`.
13
+ */ const OBSOLETE_EXCEPTIONS = [
14
+ INSPECTOR,
15
+ `${INSPECTOR}-cli`,
16
+ `${INSPECTOR}-server`,
17
+ `${INSPECTOR}-client`
18
+ ];
19
+ /** Whether the workspace root declares the inspector as a devDependency. */ const hasInspector = (tree)=>{
20
+ if (!tree.exists('package.json')) {
21
+ return false;
22
+ }
23
+ const packageJson = readJson(tree, 'package.json');
24
+ return Boolean(packageJson.devDependencies?.[INSPECTOR] ?? packageJson.dependencies?.[INSPECTOR]);
25
+ };
26
+ export default async function migration(tree) {
27
+ await removeLicenseExceptions(tree, OBSOLETE_EXCEPTIONS);
28
+ // The version sync bumps the inspector to v2, which added a postinstall
29
+ // script. Register it as an explicitly-rejected build so the upgrade's
30
+ // install isn't failed by pnpm 11's default `strictDepBuilds=true`.
31
+ if (hasInspector(tree)) {
32
+ registerPnpmBuiltDependencies(tree, {
33
+ [INSPECTOR]: false
34
+ });
35
+ }
36
+ }
37
+
38
+ //# sourceMappingURL=migration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/latest/remove-mcp-inspector-license-exceptions/migration.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport { readJson, type Tree } from '@nx/devkit';\nimport { removeLicenseExceptions } from '../../../license/config';\nimport { registerPnpmBuiltDependencies } from '../../../utils/pnpm-workspace';\n\nconst INSPECTOR = '@modelcontextprotocol/inspector';\n\n/**\n * MCP Inspector v1 declared `SEE LICENSE IN LICENSE`, which resolves to no SPDX\n * identifier, so the license check needed an exception per published package.\n * v2 ships as a single package declaring `MIT`, which the allowlist already\n * covers, and the stale exceptions would override that with `Apache-2.0`.\n */\nconst OBSOLETE_EXCEPTIONS = [\n INSPECTOR,\n `${INSPECTOR}-cli`,\n `${INSPECTOR}-server`,\n `${INSPECTOR}-client`,\n];\n\n/** Whether the workspace root declares the inspector as a devDependency. */\nconst hasInspector = (tree: Tree): boolean => {\n if (!tree.exists('package.json')) {\n return false;\n }\n const packageJson = readJson(tree, 'package.json');\n return Boolean(\n packageJson.devDependencies?.[INSPECTOR] ??\n packageJson.dependencies?.[INSPECTOR],\n );\n};\n\nexport default async function migration(tree: Tree): Promise<void> {\n await removeLicenseExceptions(tree, OBSOLETE_EXCEPTIONS);\n\n // The version sync bumps the inspector to v2, which added a postinstall\n // script. Register it as an explicitly-rejected build so the upgrade's\n // install isn't failed by pnpm 11's default `strictDepBuilds=true`.\n if (hasInspector(tree)) {\n registerPnpmBuiltDependencies(tree, { [INSPECTOR]: false });\n }\n}\n"],"names":["readJson","removeLicenseExceptions","registerPnpmBuiltDependencies","INSPECTOR","OBSOLETE_EXCEPTIONS","hasInspector","tree","exists","packageJson","Boolean","devDependencies","dependencies","migration"],"mappings":"AAAA;;;CAGC,GACD,SAASA,QAAQ,QAAmB,aAAa;AACjD,SAASC,uBAAuB,QAAQ,6BAA0B;AAClE,SAASC,6BAA6B,QAAQ,mCAAgC;AAE9E,MAAMC,YAAY;AAElB;;;;;CAKC,GACD,MAAMC,sBAAsB;IAC1BD;IACA,GAAGA,UAAU,IAAI,CAAC;IAClB,GAAGA,UAAU,OAAO,CAAC;IACrB,GAAGA,UAAU,OAAO,CAAC;CACtB;AAED,0EAA0E,GAC1E,MAAME,eAAe,CAACC;IACpB,IAAI,CAACA,KAAKC,MAAM,CAAC,iBAAiB;QAChC,OAAO;IACT;IACA,MAAMC,cAAcR,SAASM,MAAM;IACnC,OAAOG,QACLD,YAAYE,eAAe,EAAE,CAACP,UAAU,IACtCK,YAAYG,YAAY,EAAE,CAACR,UAAU;AAE3C;AAEA,eAAe,eAAeS,UAAUN,IAAU;IAChD,MAAML,wBAAwBK,MAAMF;IAEpC,wEAAwE;IACxE,uEAAuE;IACvE,oEAAoE;IACpE,IAAIC,aAAaC,OAAO;QACtBJ,8BAA8BI,MAAM;YAAE,CAACH,UAAU,EAAE;QAAM;IAC3D;AACF"}
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/v1.0.0-rc.67/0001-smithy-server-package-rename/migration.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport {\n getProjects,\n joinPathFragments,\n type MigrationReturnObject,\n readJson,\n type Tree,\n updateJson,\n} from '@nx/devkit';\nimport { applyGritQL, matchGritQL } from '../../../utils/ast';\nimport { formatFilesInSubtree } from '../../../utils/format';\n\n/**\n * Move a Smithy API onto the renamed `@smithy/server-*` packages.\n *\n * The Smithy server runtime was published as `@aws-smithy/server-*`, stopped at\n * `1.0.0-alpha.10`, and was renamed to `@smithy/server-*`. The Server SDK codegen\n * this release vends generates against the renamed packages, importing\n * `AuthScheme` and `ServerInterceptor` — types the deprecated packages never\n * exported — so a workspace left on them fails to bundle its SSDK.\n *\n * Both the imports and the declarations move: the renamed packages are no longer\n * the same names, so the version sync cannot recognise the old ones and would\n * leave them installed alongside the new.\n *\n * Guardrails:\n * - Scoped to a Smithy API project this plugin generated, found through the\n * metadata the generator records. A file of the user's own that happens to\n * import these packages is left alone.\n * - Imports are rewritten by module specifier, preserving whatever the file\n * imports from it, so a project that imports more than the generated shape\n * keeps its own bindings. Only the two packages the generators vend are\n * matched: `@aws-smithy/server-common` is a transitive dependency of those and\n * is never declared or imported directly.\n * - A manifest is only rewritten where it declares the old package, and the new\n * name takes the field the old one occupied. A workspace that already moved\n * itself is left alone.\n * - Idempotent: nothing matches the old names once they are gone.\n *\n * Every value here is hardcoded rather than read from the generators: this runs\n * once, for the release that renamed the packages, so it has to keep applying\n * that exact change however far the vended versions and generators move\n * afterwards.\n */\n\n/** The id the `ts#smithy-api` generator records on the projects it creates. */\nconst SMITHY_API_GENERATOR_ID = 'ts#smithy-api';\n\n/** The renamed packages, old name to new, with the version this release vends. */\nconst RENAMES = [\n {\n old: '@aws-smithy/server-apigateway',\n renamed: '@smithy/server-apigateway',\n version: '0.2.0',\n },\n {\n old: '@aws-smithy/server-node',\n renamed: '@smithy/server-node',\n version: '0.2.0',\n },\n] as const;\n\n/** The source files the generators give these imports. */\nconst SOURCE_FILES = ['src/handler.ts', 'src/local-server.ts'];\n\n/**\n * Rewrite an import's module specifier, keeping its bindings — `$bindings` holds\n * whatever the file imports, so a binding the project added survives the move.\n */\nconst importPattern = (from: string, to: string): string =>\n `\\`import { $bindings } from '${from}'\\` => \\`import { $bindings } from '${to}'\\``;\n\n/** Whether the file still imports the old package under any other form. */\nconst hasRemainingImport = (from: string): string =>\n `\\`import $_ from '${from}'\\``;\n\n/** Rename the packages a manifest declares, reporting whether anything changed. */\nconst renameInManifest = (tree: Tree, path: string): boolean => {\n if (!tree.exists(path)) {\n return false;\n }\n const json = readJson(tree, path);\n const fields = (['dependencies', 'devDependencies'] as const).filter(\n (field) => RENAMES.some(({ old }) => json[field]?.[old]),\n );\n if (fields.length === 0) {\n return false;\n }\n updateJson(tree, path, (manifest) => {\n for (const field of fields) {\n const declared = manifest[field] as Record<string, string>;\n for (const { old, renamed, version } of RENAMES) {\n if (!declared[old]) {\n continue;\n }\n delete declared[old];\n // The vended pin rather than the old specifier: the renamed line\n // restarted at 0.x, so carrying `1.0.0-alpha.10` across resolves nothing.\n declared[renamed] = version;\n }\n }\n return manifest;\n });\n return true;\n};\n\nconst divergedNextStep = (path: string): string =>\n `${path}: still imports a deprecated '@aws-smithy/server-*' package in a form this migration does not rewrite - update the import to the matching '@smithy/server-*' package by hand.`;\n\nexport default async function migration(\n tree: Tree,\n): Promise<MigrationReturnObject> {\n const nextSteps: string[] = [];\n\n // Only a Smithy API project this plugin generated carries these packages.\n const projects = [...getProjects(tree).values()].filter(\n (project) =>\n (project.metadata as { generator?: string } | undefined)?.generator ===\n SMITHY_API_GENERATOR_ID,\n );\n\n for (const project of projects) {\n for (const sourceFile of SOURCE_FILES) {\n const sourcePath = joinPathFragments(project.root, sourceFile);\n if (!tree.exists(sourcePath)) {\n continue;\n }\n for (const { old, renamed } of RENAMES) {\n await applyGritQL(tree, sourcePath, importPattern(old, renamed));\n // A namespace or default import of the same package is left as it is —\n // reported rather than rewritten, so a diverged file is never clobbered.\n if (await matchGritQL(tree, sourcePath, hasRemainingImport(old))) {\n nextSteps.push(divergedNextStep(sourcePath));\n }\n }\n }\n\n renameInManifest(tree, joinPathFragments(project.root, 'package.json'));\n }\n\n // A workspace may hoist these to the root manifest, which is not an nx project.\n if (projects.length > 0) {\n renameInManifest(tree, 'package.json');\n }\n\n await formatFilesInSubtree(tree);\n\n return { nextSteps };\n}\n"],"names":["getProjects","joinPathFragments","readJson","updateJson","applyGritQL","matchGritQL","formatFilesInSubtree","SMITHY_API_GENERATOR_ID","RENAMES","old","renamed","version","SOURCE_FILES","importPattern","from","to","hasRemainingImport","renameInManifest","tree","path","exists","json","fields","filter","field","some","length","manifest","declared","divergedNextStep","migration","nextSteps","projects","values","project","metadata","generator","sourceFile","sourcePath","root","push"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/v1.0.0-rc.68/0001-translate-script-whole-file-writes/migration.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport {\n getProjects,\n joinPathFragments,\n type MigrationReturnObject,\n type Tree,\n} from '@nx/devkit';\nimport { TS_ASTRO_DOCS_GENERATOR_INFO } from '../../../ts/astro-docs/generator';\nimport { insertViaGritQL, matchGritQL } from '../../../utils/ast';\nimport { formatFilesInSubtree } from '../../../utils/format';\nimport { isEsmWorkspace } from '../../../utils/module-format';\nimport { getPackageManagerDisplayCommands } from '../../../utils/pkg-manager';\n\n/**\n * Rewrite the docs translate script so it writes whole translated files.\n *\n * The script used to tell the agent to update an existing translation with\n * `str_replace` edits. A replacement that did not match cleanly duplicated or\n * spliced sections, and nothing checked the result, so the damage was committed.\n * The rewrite also fixes incremental runs, which never matched a changed file in\n * a generated workspace, and raises the Bedrock socket timeout that made every\n * large file fail.\n *\n * Most of the script changed, so it is rewritten a declaration at a time. Each\n * pattern in `grit/` matches one declaration as a previous release wrote it and\n * rewrites it to a placeholder, which `insertViaGritQL` swaps for today's version\n * from `files/`. Routing the replacement through a placeholder keeps it out of the\n * pattern: the script is full of template literals, and a GritQL snippet\n * containing a backtick cannot be parsed.\n *\n * Each pattern recognises its declaration by name *and* by statements the released\n * body contained, so a declaration the user has edited no longer matches. The\n * rewrite is all or nothing — every pattern must match before anything is written\n * — so an edited script keeps its whole file and is reported through `nextSteps`\n * rather than being half-rewritten from two different versions.\n */\n\n/**\n * The declarations that changed, in the order they appear in the script. Each name\n * pairs a `grit/<name>.grit` pattern matching its previous form with a\n * `files/<name>.ts.fixture` holding its replacement, which carries any new\n * declarations that follow it.\n *\n * `grit/already-migrated.grit` stands apart: it recognises today's script rather\n * than rewriting anything.\n */\nconst REWRITES = [\n 'project-root',\n 'config-path',\n 'file-to-translate',\n 'log',\n 'get-files-to-translate',\n 'build-system-prompt',\n 'build-user-prompt',\n 'translate-file-for-language',\n 'run-with-concurrency',\n 'main',\n 'main-call',\n] as const;\n\n/** A leading block comment, which GritQL does not match. */\nconst HEADER_COMMENT = /^\\/\\*\\*[\\s\\S]*?\\*\\/\\n/;\n\nconst readGritPattern = (name: string): string =>\n `language js\\n${readFileSync(join(import.meta.dirname, 'grit', `${name}.grit`), 'utf-8').trim()}`;\n\nconst readReplacement = (name: string): string =>\n readFileSync(\n join(import.meta.dirname, 'files', `${name}.ts.fixture`),\n 'utf-8',\n );\n\n/** Fills in the values the generator interpolates when it vends the script. */\nconst render = (\n tree: Tree,\n source: string,\n fullyQualifiedName: string,\n): string =>\n source\n .replace(\n /<% if \\(esm\\) \\{ %>(.*?)<% \\} else \\{ %>(.*?)<% \\} %>/g,\n isEsmWorkspace(tree) ? '$1' : '$2',\n )\n .replace(/<%= pkgMgrCmd %>/g, getPackageManagerDisplayCommands().exec)\n .replace(/<%= fullyQualifiedName %>/g, fullyQualifiedName);\n\nconst editedNextStep = (projectName: string): string =>\n `${projectName}: its scripts/translate.ts has been customised, so it was left as it is. The script now writes each translation as a whole file rather than editing it in place — see https://awslabs.github.io/nx-plugin-for-aws/guides/astro-docs/ for the version it expects.`;\n\nexport default async function migration(\n tree: Tree,\n): Promise<MigrationReturnObject> {\n const nextSteps: string[] = [];\n\n for (const [name, project] of getProjects(tree)) {\n const metadata = project.metadata as { generator?: string } | undefined;\n if (metadata?.generator !== TS_ASTRO_DOCS_GENERATOR_INFO.id) {\n continue;\n }\n\n // Absent when the docs site was generated with --noTranslation.\n const scriptPath = joinPathFragments(\n project.root,\n 'scripts',\n 'translate.ts',\n );\n if (!tree.exists(scriptPath)) {\n continue;\n }\n\n // Already migrated: matched by a declaration only today's script has, so a\n // second run is a no-op rather than a rewrite.\n if (\n await matchGritQL(tree, scriptPath, readGritPattern('already-migrated'))\n ) {\n continue;\n }\n\n // All or nothing: every declaration must still be the one a release vended,\n // so a partly edited script is never left half-rewritten.\n const matches = await Promise.all(\n REWRITES.map((rewrite) =>\n matchGritQL(tree, scriptPath, readGritPattern(rewrite)),\n ),\n );\n if (!matches.every(Boolean)) {\n nextSteps.push(editedNextStep(name));\n continue;\n }\n\n // The header is a leading comment, which GritQL does not reach. Anchored to\n // the start of the file so the replacement cannot match anything else.\n tree.write(\n scriptPath,\n (tree.read(scriptPath, 'utf-8') ?? '').replace(\n HEADER_COMMENT,\n render(tree, readReplacement('header'), name),\n ),\n );\n\n // The first release resolved paths from `__dirname` directly, with no\n // `SCRIPTS_DIR` for the rewrites below to build on.\n const script = tree.read(scriptPath, 'utf-8') ?? '';\n if (!/^const SCRIPTS_DIR /m.test(script)) {\n tree.write(\n scriptPath,\n script.replace(\n /^const PROJECT_ROOT /m,\n `${render(tree, readReplacement('scripts-dir'), name).trimEnd()}\\nconst PROJECT_ROOT `,\n ),\n );\n }\n\n for (const rewrite of REWRITES) {\n await insertViaGritQL(\n tree,\n scriptPath,\n readGritPattern(rewrite),\n render(tree, readReplacement(rewrite), name).trimEnd(),\n );\n }\n }\n\n await formatFilesInSubtree(tree);\n\n return { nextSteps };\n}\n"],"names":["readFileSync","join","getProjects","joinPathFragments","TS_ASTRO_DOCS_GENERATOR_INFO","insertViaGritQL","matchGritQL","formatFilesInSubtree","isEsmWorkspace","getPackageManagerDisplayCommands","REWRITES","HEADER_COMMENT","readGritPattern","name","dirname","trim","readReplacement","render","tree","source","fullyQualifiedName","replace","exec","editedNextStep","projectName","migration","nextSteps","project","metadata","generator","id","scriptPath","root","exists","matches","Promise","all","map","rewrite","every","Boolean","push","write","read","script","test","trimEnd"],"mappings":"AAAA;;;CAGC,GACD,SAASA,YAAY,QAAQ,UAAU;AACvC,SAASC,IAAI,QAAQ,YAAY;AACjC,SACEC,WAAW,EACXC,iBAAiB,QAGZ,aAAa;AACpB,SAASC,4BAA4B,QAAQ,sCAAmC;AAChF,SAASC,eAAe,EAAEC,WAAW,QAAQ,wBAAqB;AAClE,SAASC,oBAAoB,QAAQ,2BAAwB;AAC7D,SAASC,cAAc,QAAQ,kCAA+B;AAC9D,SAASC,gCAAgC,QAAQ,gCAA6B;AAE9E;;;;;;;;;;;;;;;;;;;;;;CAsBC,GAED;;;;;;;;CAQC,GACD,MAAMC,WAAW;IACf;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD;AAED,0DAA0D,GAC1D,MAAMC,iBAAiB;AAEvB,MAAMC,kBAAkB,CAACC,OACvB,CAAC,aAAa,EAAEb,aAAaC,KAAK,YAAYa,OAAO,EAAE,QAAQ,GAAGD,KAAK,KAAK,CAAC,GAAG,SAASE,IAAI,IAAI;AAEnG,MAAMC,kBAAkB,CAACH,OACvBb,aACEC,KAAK,YAAYa,OAAO,EAAE,SAAS,GAAGD,KAAK,WAAW,CAAC,GACvD;AAGJ,6EAA6E,GAC7E,MAAMI,SAAS,CACbC,MACAC,QACAC,qBAEAD,OACGE,OAAO,CACN,0DACAb,eAAeU,QAAQ,OAAO,MAE/BG,OAAO,CAAC,qBAAqBZ,mCAAmCa,IAAI,EACpED,OAAO,CAAC,8BAA8BD;AAE3C,MAAMG,iBAAiB,CAACC,cACtB,GAAGA,YAAY,gQAAgQ,CAAC;AAElR,eAAe,eAAeC,UAC5BP,IAAU;IAEV,MAAMQ,YAAsB,EAAE;IAE9B,KAAK,MAAM,CAACb,MAAMc,QAAQ,IAAIzB,YAAYgB,MAAO;QAC/C,MAAMU,WAAWD,QAAQC,QAAQ;QACjC,IAAIA,UAAUC,cAAczB,6BAA6B0B,EAAE,EAAE;YAC3D;QACF;QAEA,gEAAgE;QAChE,MAAMC,aAAa5B,kBACjBwB,QAAQK,IAAI,EACZ,WACA;QAEF,IAAI,CAACd,KAAKe,MAAM,CAACF,aAAa;YAC5B;QACF;QAEA,2EAA2E;QAC3E,+CAA+C;QAC/C,IACE,MAAMzB,YAAYY,MAAMa,YAAYnB,gBAAgB,sBACpD;YACA;QACF;QAEA,4EAA4E;QAC5E,0DAA0D;QAC1D,MAAMsB,UAAU,MAAMC,QAAQC,GAAG,CAC/B1B,SAAS2B,GAAG,CAAC,CAACC,UACZhC,YAAYY,MAAMa,YAAYnB,gBAAgB0B;QAGlD,IAAI,CAACJ,QAAQK,KAAK,CAACC,UAAU;YAC3Bd,UAAUe,IAAI,CAAClB,eAAeV;YAC9B;QACF;QAEA,4EAA4E;QAC5E,uEAAuE;QACvEK,KAAKwB,KAAK,CACRX,YACA,AAACb,CAAAA,KAAKyB,IAAI,CAACZ,YAAY,YAAY,EAAC,EAAGV,OAAO,CAC5CV,gBACAM,OAAOC,MAAMF,gBAAgB,WAAWH;QAI5C,sEAAsE;QACtE,oDAAoD;QACpD,MAAM+B,SAAS1B,KAAKyB,IAAI,CAACZ,YAAY,YAAY;QACjD,IAAI,CAAC,uBAAuBc,IAAI,CAACD,SAAS;YACxC1B,KAAKwB,KAAK,CACRX,YACAa,OAAOvB,OAAO,CACZ,yBACA,GAAGJ,OAAOC,MAAMF,gBAAgB,gBAAgBH,MAAMiC,OAAO,GAAG,qBAAqB,CAAC;QAG5F;QAEA,KAAK,MAAMR,WAAW5B,SAAU;YAC9B,MAAML,gBACJa,MACAa,YACAnB,gBAAgB0B,UAChBrB,OAAOC,MAAMF,gBAAgBsB,UAAUzB,MAAMiC,OAAO;QAExD;IACF;IAEA,MAAMvC,qBAAqBW;IAE3B,OAAO;QAAEQ;IAAU;AACrB"}
@@ -168,13 +168,25 @@ const migrateLangchainA2AAgent = async (tree, mainPath, nextSteps)=>{
168
168
  // --- Iterator -> Generator (with_session_id_strands.py / session_context.py) ---
169
169
  const WITH_SESSION_ID_IMPORT_OLD = 'from collections.abc import Callable, Iterator';
170
170
  const WITH_SESSION_ID_IMPORT_NEW = 'from collections.abc import Callable, Generator';
171
+ /**
172
+ * Whether every `Iterator` reference in the file is one this migration is about
173
+ * to convert, so dropping the import cannot leave an undefined name behind. A
174
+ * user's own `Iterator[...]` annotation elsewhere in the file is not rewritten
175
+ * (the rewrite targets one exact annotation), so removing the import would leave
176
+ * the module raising `NameError` on import.
177
+ */ const onlyIteratorUseIs = (contents, annotation)=>{
178
+ const withoutImport = contents.replace(/^from collections\.abc import .*$/m, '');
179
+ const uses = withoutImport.match(/\bIterator\b/g) ?? [];
180
+ const converted = withoutImport.match(new RegExp(annotation.replace(/[[\]]/g, '\\$&'), 'g'));
181
+ return uses.length === (converted?.length ?? 0);
182
+ };
171
183
  const migrateWithSessionId = async (tree, filePath, nextSteps)=>{
172
184
  const contents = tree.read(filePath, 'utf-8') ?? '';
173
185
  if (!contents.includes('Iterator[Any]')) return;
174
186
  const ready = await allMatch(tree, filePath, [
175
187
  pyMatch(WITH_SESSION_ID_IMPORT_OLD),
176
188
  pyMatch('Iterator[Any]')
177
- ]);
189
+ ]) && onlyIteratorUseIs(contents, 'Iterator[Any]');
178
190
  if (!ready) {
179
191
  nextSteps.push(`${filePath}: diverged from the generated shape - left untouched. Manually change the \`Iterator\` import and \`Iterator[Any]\` return annotation on \`with_session_id\` to \`Generator\`/\`Generator[Any]\` (see the agent-connection generator's with_session_id_strands.py template).`);
180
192
  return;
@@ -190,7 +202,7 @@ const migrateSessionContext = async (tree, filePath, nextSteps)=>{
190
202
  const ready = await allMatch(tree, filePath, [
191
203
  pyMatch(SESSION_CONTEXT_IMPORT_OLD),
192
204
  pyMatch('Iterator[None]')
193
- ]);
205
+ ]) && onlyIteratorUseIs(contents, 'Iterator[None]');
194
206
  if (!ready) {
195
207
  nextSteps.push(`${filePath}: diverged from the generated shape - left untouched. Manually change the \`Iterator\` import and \`Iterator[None]\` return annotation on \`session_id_context\` to \`Generator\`/\`Generator[None]\` (see the agent-connection generator's session_context.py template).`);
196
208
  return;
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/v1.0.0-rc.70/0001-py-agent-a2a-lifespan-construction/migration.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport {\n getProjects,\n joinPathFragments,\n type MigrationReturnObject,\n type Tree,\n visitNotIgnoredFiles,\n} from '@nx/devkit';\nimport { PY_AGENT_GENERATOR_INFO } from '../../../py/agent/generator';\nimport {\n addPythonDestructuredImport,\n applyGritQL,\n captureGritQLVariable,\n matchGritQL,\n} from '../../../utils/ast';\nimport { formatFilesInSubtree } from '../../../utils/format';\nimport type { ComponentMetadata } from '../../../utils/nx';\n\n/**\n * Move py#agent A2A agent construction (Strands and LangChain) out of module\n * import time and into a FastAPI `lifespan` handler, stored on `app.state`.\n *\n * The `v1.0.0-rc.61/0001-py-agent-lifespan-construction` migration (HTTP/AG-UI)\n * deliberately left A2A alone, reasoning that `with_session_id` only touches\n * its agent factory lazily on first use, so the eager `_agent_ctx.__enter__()`\n * was never actually eager in practice. That held for `with_session_id`'s own\n * laziness, but missed a separate trigger: `strands.multiagent.a2a.executor\n * .StrandsA2AExecutor.__init__`, in its deprecated single-`agent=` mode, does\n * `getattr(agent, \"_session_manager\", None)` - which the session-routing\n * proxy's `__getattr__` happily serves by building the real underlying Agent\n * right then, under a bogus \"default\" session, since no request has bound a\n * real session id yet. That call happens inside `A2AServer.__init__`, which\n * the old template constructed at plain module import time - so the \"lazy\"\n * proxy was forced eager anyway, just one layer further down than the\n * previous migration checked.\n *\n * Strands: `agent_factory=` mode sidesteps this - `StrandsA2AExecutor` takes\n * an entirely different branch when given a factory (no `_session_manager`\n * probe at all), and the one placeholder call `A2AServer.__init__` makes\n * before `lifespan` has run returns a bare `SimpleNamespace` with just\n * `name`/`description` - no `__getattr__`, nothing to force construction.\n *\n * LangChain: the compiled graph from `get_agent()` has no such trap, but was\n * still built eagerly at import time (`_graph = get_agent()`). Moved into\n * `lifespan` for the same reason HTTP/AG-UI were: it can require env vars\n * (model id, region) or do network setup that isn't guaranteed available at\n * import - e.g. under test collection or OpenAPI spec generation tooling.\n *\n * Also folds in an unrelated but adjacent fix: Pylance (Pyright) deprecates\n * annotating a `@contextmanager`-decorated function's return type as\n * `Iterator[T]`, in favor of the single-argument `Generator[T]` form enabled\n * by PEP 696 default type parameters (Python 3.13+ - generated projects\n * target >=3.14). `with_session_id_strands.py` and `session_context.py` both\n * use this pattern. Both files are shared, protocol-agnostic files copied\n * once per workspace (not per py#agent component), so that part scans the\n * whole tree by basename rather than going through component metadata.\n */\nconst pyMatch = (snippet: string) => `language python\\n\\`${snippet}\\``;\n\nconst pyDelete = (snippet: string) => `${pyMatch(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 AGENT_ENTER = '_agent = _agent_ctx.__enter__()';\n\nconst OLD_AGENT_CONTEXT = `_agent_ctx = with_session_id(\n get_agent,\n name=\"$name\",\n description=\"A Strands Agent exposed via the Agent-to-Agent (A2A) protocol.\",\n)`;\n\nconst NEW_CONSTANTS_AND_LIFESPAN = `AGENT_NAME = \"$name\"\nAGENT_DESCRIPTION = \"A Strands Agent exposed via the Agent-to-Agent (A2A) protocol.\"\n\n# A2AServer.__init__ calls agent_factory once, synchronously, before the lifespan\n# below has run - this is what it reads name/description from at that point.\n# The cast is a lie about runtime shape, not behavior: only .name/.description\n# are ever read off this value.\n_card_placeholder = cast(\n Agent, SimpleNamespace(name=AGENT_NAME, description=AGENT_DESCRIPTION)\n)\n\n\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n with with_session_id(\n get_agent,\n name=AGENT_NAME,\n description=AGENT_DESCRIPTION,\n ) as agent:\n app.state.agent = agent\n yield`;\n\nconst A2A_SERVER_STATEMENT = 'a2a_server = A2AServer($args)';\n\nconst OLD_MOUNT_STATEMENT = 'app.mount(\"/\", a2a_server.to_fastapi_app())';\n\nconst OLD_FASTAPI_APP = 'app = FastAPI()';\n\nconst NEW_FASTAPI_APP = 'app = FastAPI(lifespan=lifespan)';\n\nconst SERVER_MATCH =\n 'language python\\n`A2AServer($args)` where { $args <: contains `agent=_agent` }';\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\nconst migrateStrandsA2AAgent = async (\n tree: Tree,\n mainPath: string,\n nextSteps: string[],\n): Promise<void> => {\n if (!tree.exists(mainPath)) return;\n\n const contents = tree.read(mainPath, 'utf-8') ?? '';\n if (!contents.includes('_agent_ctx = with_session_id(')) return;\n\n const ready = await allMatch(tree, mainPath, [\n pyMatch('import logging'),\n pyMatch('from $mod import session_id_context, with_session_id'),\n pyMatch(OLD_AGENT_CONTEXT),\n pyMatch(AGENT_ENTER),\n pyMatch(OLD_FASTAPI_APP),\n pyMatch(OLD_MOUNT_STATEMENT),\n SERVER_MATCH,\n ]);\n\n const diverged = () => {\n nextSteps.push(\n `${mainPath}: diverged from the generated Strands A2A shape - left untouched. Manually rebuild the agent inside the FastAPI \\`lifespan\\` and have \\`agent_factory\\` read it off \\`app.state\\` (see the py#agent generator's strands/a2a template).`,\n );\n };\n\n if (!ready) {\n diverged();\n return;\n }\n\n await addPythonDestructuredImport(\n tree,\n mainPath,\n ['asynccontextmanager'],\n 'contextlib',\n );\n await addPythonDestructuredImport(\n tree,\n mainPath,\n ['SimpleNamespace'],\n 'types',\n );\n await addPythonDestructuredImport(tree, mainPath, ['cast'], 'typing');\n await addPythonDestructuredImport(tree, mainPath, ['Agent'], 'strands');\n await applyGritQL(\n tree,\n mainPath,\n pyRewrite(OLD_AGENT_CONTEXT, NEW_CONSTANTS_AND_LIFESPAN),\n );\n await applyGritQL(tree, mainPath, pyDelete(AGENT_ENTER));\n\n // Swap the kwarg while the call is still in its original spot (any extra\n // kwargs a user added are left alone), then capture the whole arg list so\n // it can be reinserted at the call's new home below.\n await applyGritQL(\n tree,\n mainPath,\n pyRewrite(\n 'agent=_agent',\n 'agent_factory=lambda _context_id: getattr(app.state, \"agent\", _card_placeholder)',\n ),\n );\n const capturedArgs = await captureGritQLVariable(\n tree,\n mainPath,\n `language python\\n\\`${A2A_SERVER_STATEMENT}\\``,\n 'args',\n );\n if (capturedArgs === undefined) {\n diverged();\n return;\n }\n\n await applyGritQL(tree, mainPath, pyDelete(A2A_SERVER_STATEMENT));\n await applyGritQL(\n tree,\n mainPath,\n pyRewrite(OLD_FASTAPI_APP, NEW_FASTAPI_APP),\n );\n await applyGritQL(\n tree,\n mainPath,\n pyRewrite(\n OLD_MOUNT_STATEMENT,\n `a2a_server = A2AServer(${capturedArgs})\\n\\n${OLD_MOUNT_STATEMENT}`,\n ),\n );\n};\n\n// --- LangChain A2A -----------------------------------------------------------\n\nconst LANGCHAIN_GRAPH_OLD = '_graph = get_agent()';\n\nconst LANGCHAIN_LIFESPAN_NEW = `@asynccontextmanager\nasync def lifespan(app: FastAPI):\n app.state.agent = get_agent()\n yield`;\n\nconst LANGCHAIN_GRAPH_INVOKE_OLD = '_graph.ainvoke($args)';\n\nconst LANGCHAIN_GRAPH_INVOKE_NEW = 'app.state.agent.ainvoke($args)';\n\nconst LANGCHAIN_APP_OLD = 'app = FastAPI(title=\"$name\")';\n\nconst LANGCHAIN_APP_NEW = 'app = FastAPI(title=\"$name\", lifespan=lifespan)';\n\nconst migrateLangchainA2AAgent = async (\n tree: Tree,\n mainPath: string,\n nextSteps: string[],\n): Promise<void> => {\n if (!tree.exists(mainPath)) return;\n\n const contents = tree.read(mainPath, 'utf-8') ?? '';\n if (!contents.includes(LANGCHAIN_GRAPH_OLD)) return;\n\n const ready = await allMatch(tree, mainPath, [\n pyMatch(LANGCHAIN_GRAPH_OLD),\n pyMatch('from .agent import get_agent'),\n pyMatch(LANGCHAIN_GRAPH_INVOKE_OLD),\n pyMatch(LANGCHAIN_APP_OLD),\n ]);\n\n if (!ready) {\n nextSteps.push(\n `${mainPath}: diverged from the generated LangChain A2A shape - left untouched. Manually wrap \\`get_agent()\\` in a \\`lifespan\\` handler storing the graph on \\`app.state.agent\\` (see the py#agent generator's langchain/a2a template).`,\n );\n return;\n }\n\n await addPythonDestructuredImport(\n tree,\n mainPath,\n ['asynccontextmanager'],\n 'contextlib',\n );\n await applyGritQL(\n tree,\n mainPath,\n pyRewrite(LANGCHAIN_GRAPH_OLD, LANGCHAIN_LIFESPAN_NEW),\n );\n await applyGritQL(\n tree,\n mainPath,\n pyRewrite(LANGCHAIN_GRAPH_INVOKE_OLD, LANGCHAIN_GRAPH_INVOKE_NEW),\n );\n await applyGritQL(\n tree,\n mainPath,\n pyRewrite(LANGCHAIN_APP_OLD, LANGCHAIN_APP_NEW),\n );\n};\n\n// --- Iterator -> Generator (with_session_id_strands.py / session_context.py) ---\n\nconst WITH_SESSION_ID_IMPORT_OLD =\n 'from collections.abc import Callable, Iterator';\nconst WITH_SESSION_ID_IMPORT_NEW =\n 'from collections.abc import Callable, Generator';\n\n/**\n * Whether every `Iterator` reference in the file is one this migration is about\n * to convert, so dropping the import cannot leave an undefined name behind. A\n * user's own `Iterator[...]` annotation elsewhere in the file is not rewritten\n * (the rewrite targets one exact annotation), so removing the import would leave\n * the module raising `NameError` on import.\n */\nconst onlyIteratorUseIs = (contents: string, annotation: string): boolean => {\n const withoutImport = contents.replace(\n /^from collections\\.abc import .*$/m,\n '',\n );\n const uses = withoutImport.match(/\\bIterator\\b/g) ?? [];\n const converted = withoutImport.match(\n new RegExp(annotation.replace(/[[\\]]/g, '\\\\$&'), 'g'),\n );\n return uses.length === (converted?.length ?? 0);\n};\n\nconst migrateWithSessionId = 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('Iterator[Any]')) return;\n\n const ready =\n (await allMatch(tree, filePath, [\n pyMatch(WITH_SESSION_ID_IMPORT_OLD),\n pyMatch('Iterator[Any]'),\n ])) && onlyIteratorUseIs(contents, 'Iterator[Any]');\n\n if (!ready) {\n nextSteps.push(\n `${filePath}: diverged from the generated shape - left untouched. Manually change the \\`Iterator\\` import and \\`Iterator[Any]\\` return annotation on \\`with_session_id\\` to \\`Generator\\`/\\`Generator[Any]\\` (see the agent-connection generator's with_session_id_strands.py template).`,\n );\n return;\n }\n\n await applyGritQL(\n tree,\n filePath,\n pyRewrite(WITH_SESSION_ID_IMPORT_OLD, WITH_SESSION_ID_IMPORT_NEW),\n );\n await applyGritQL(\n tree,\n filePath,\n pyRewrite('Iterator[Any]', 'Generator[Any]'),\n );\n};\n\nconst SESSION_CONTEXT_IMPORT_OLD = 'from collections.abc import Iterator';\nconst SESSION_CONTEXT_IMPORT_NEW = 'from collections.abc import Generator';\n\nconst migrateSessionContext = 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('Iterator[None]')) return;\n\n const ready =\n (await allMatch(tree, filePath, [\n pyMatch(SESSION_CONTEXT_IMPORT_OLD),\n pyMatch('Iterator[None]'),\n ])) && onlyIteratorUseIs(contents, 'Iterator[None]');\n\n if (!ready) {\n nextSteps.push(\n `${filePath}: diverged from the generated shape - left untouched. Manually change the \\`Iterator\\` import and \\`Iterator[None]\\` return annotation on \\`session_id_context\\` to \\`Generator\\`/\\`Generator[None]\\` (see the agent-connection generator's session_context.py template).`,\n );\n return;\n }\n\n await applyGritQL(\n tree,\n filePath,\n pyRewrite(SESSION_CONTEXT_IMPORT_OLD, SESSION_CONTEXT_IMPORT_NEW),\n );\n await applyGritQL(\n tree,\n filePath,\n pyRewrite('Iterator[None]', 'Generator[None]'),\n );\n};\n\nconst findAgentComponents = (\n components: ComponentMetadata[] | undefined,\n): ComponentMetadata[] =>\n (components ?? []).filter(\n (component) => component.generator === PY_AGENT_GENERATOR_INFO.id,\n );\n\nexport default async function migration(\n tree: Tree,\n): Promise<MigrationReturnObject> {\n const nextSteps: string[] = [];\n\n for (const project of getProjects(tree).values()) {\n const components = findAgentComponents(\n (project.metadata as { components?: ComponentMetadata[] })?.components,\n );\n\n for (const component of components) {\n if (!component.path || component.protocol !== 'a2a') {\n continue;\n }\n\n const componentDir = component.path.endsWith('/agent.py')\n ? component.path.slice(0, -'/agent.py'.length)\n : component.path;\n const mainPath = joinPathFragments(project.root, componentDir, 'main.py');\n\n if ((component.framework ?? 'strands') === 'langchain') {\n await migrateLangchainA2AAgent(tree, mainPath, nextSteps);\n } else {\n await migrateStrandsA2AAgent(tree, mainPath, nextSteps);\n }\n }\n }\n\n const filePaths: string[] = [];\n visitNotIgnoredFiles(tree, '', (filePath) => filePaths.push(filePath));\n\n for (const filePath of filePaths) {\n if (filePath.endsWith('/with_session_id_strands.py')) {\n await migrateWithSessionId(tree, filePath, nextSteps);\n } else if (filePath.endsWith('/session_context.py')) {\n await migrateSessionContext(tree, filePath, nextSteps);\n }\n }\n\n await formatFilesInSubtree(tree);\n\n return { nextSteps };\n}\n"],"names":["getProjects","joinPathFragments","visitNotIgnoredFiles","PY_AGENT_GENERATOR_INFO","addPythonDestructuredImport","applyGritQL","captureGritQLVariable","matchGritQL","formatFilesInSubtree","pyMatch","snippet","pyDelete","pyRewrite","from","to","replacement","includes","AGENT_ENTER","OLD_AGENT_CONTEXT","NEW_CONSTANTS_AND_LIFESPAN","A2A_SERVER_STATEMENT","OLD_MOUNT_STATEMENT","OLD_FASTAPI_APP","NEW_FASTAPI_APP","SERVER_MATCH","allMatch","tree","filePath","patterns","pattern","migrateStrandsA2AAgent","mainPath","nextSteps","exists","contents","read","ready","diverged","push","capturedArgs","undefined","LANGCHAIN_GRAPH_OLD","LANGCHAIN_LIFESPAN_NEW","LANGCHAIN_GRAPH_INVOKE_OLD","LANGCHAIN_GRAPH_INVOKE_NEW","LANGCHAIN_APP_OLD","LANGCHAIN_APP_NEW","migrateLangchainA2AAgent","WITH_SESSION_ID_IMPORT_OLD","WITH_SESSION_ID_IMPORT_NEW","onlyIteratorUseIs","annotation","withoutImport","replace","uses","match","converted","RegExp","length","migrateWithSessionId","SESSION_CONTEXT_IMPORT_OLD","SESSION_CONTEXT_IMPORT_NEW","migrateSessionContext","findAgentComponents","components","filter","component","generator","id","migration","project","values","metadata","path","protocol","componentDir","endsWith","slice","root","framework","filePaths"],"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 @@
1
+ {"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/v1.0.0-rc.71/0001-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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"}
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/v1.0.0-rc.72/0001-py-agent-session-management-support/migration.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport {\n getProjects,\n joinPathFragments,\n type MigrationReturnObject,\n type ProjectConfiguration,\n type Tree,\n visitNotIgnoredFiles,\n} from '@nx/devkit';\nimport { PY_AGENT_GENERATOR_INFO } from '../../../py/agent/generator';\nimport { addPyDependencies } from '../../../utils/add-dependencies';\nimport {\n addPythonReExport,\n getPythonAgentConnectionModuleName,\n getPythonAgentConnectionProjectDir,\n} from '../../../utils/agent-connection/agent-connection';\nimport {\n applyGritQL,\n captureGritQLVariable,\n matchGritQL,\n} from '../../../utils/ast';\nimport { declareDependencies } from '../../../utils/declared-dependencies';\nimport { formatFilesInSubtree } from '../../../utils/format';\nimport { kebabCase } from '../../../utils/names';\nimport type { ComponentMetadata } from '../../../utils/nx';\nimport {\n getRelativePathToRootByDirectory,\n toProjectRelativePath,\n} from '../../../utils/paths';\n\n/**\n * Add session management support to py#agent.\n *\n * How to write a migration:\n * - https://nx.dev/docs/kb/migration-generators\n * - What `nextSteps` means: https://nx.dev/docs/reference/devkit/MigrationReturnObject\n *\n * Guardrails:\n * - Pattern-match before writing: skip files that have diverged from the shape\n * your generators produce and report them via `nextSteps`, rather than\n * clobbering the user's changes.\n * - Idempotent: re-running must be a no-op.\n * - Format what you write: finish with `formatFilesInSubtree` so the files\n * this migration wrote are formatted correctly.\n */\n\n// Captures the agent-connection module a file imports `log_model_errors` (or\n// `session_id_context`) from, and gates every rewrite below on the import\n// actually being present. Generic over `$names` since a connection generator\n// may have merged its own import into the same line.\nconst LOG_MODEL_ERRORS_IMPORT_CAPTURE_PATTERN =\n 'language python\\n`from $mod import $names` where { $names <: contains `log_model_errors` }';\nconst SESSION_ID_CONTEXT_IMPORT_CAPTURE_PATTERN =\n 'language python\\n`from $mod import $names` where { $names <: contains `session_id_context` }';\n\nconst DEPENDENCIES = declareDependencies()({\n py: [{ name: 'langgraph-checkpoint-sqlite' }, { name: 'aiosqlite' }],\n ts: [],\n});\n\nconst LANGCHAIN_AGENT_MATCH_PATTERN =\n 'language python\\n`create_agent($args)` where { $args <: contains `system_prompt=$_`, $args <: contains `checkpointer=InMemorySaver()` }';\nconst LANGCHAIN_CHECKPOINTER_PATTERN =\n 'language python\\n`checkpointer=InMemorySaver()` => `checkpointer=get_checkpointer()`';\nconst LANGCHAIN_IN_MEMORY_IMPORT_PATTERN =\n 'language python\\n`from langgraph.checkpoint.memory import InMemorySaver` => .';\nconst langChainSessionImportPattern =\n 'language python\\n`from langchain.agents import create_agent` => raw`from langchain.agents import create_agent\\n\\nfrom .session import get_checkpointer`';\n\n// Every strands agent.py already hooks log_model_errors regardless of\n// protocol (hooks, unlike session_manager, carry over per-thread for AG-UI\n// without special wiring — see StrandsAgent's kwargs-forwarding), so\n// log_tool_errors is retrofitted the same way for every protocol.\nconst LOG_TOOL_ERRORS_HOOKS_PATTERN =\n 'language python\\n`hooks=[$hooks]` where { $hooks <: contains `log_model_errors`, $hooks <: not contains `log_tool_errors` }';\nconst logToolErrorsImportPattern = (mod: string) =>\n `language python\\n\\`from contextlib import contextmanager\\` => raw\\`from contextlib import contextmanager\\nfrom ${mod} import log_tool_errors\\``;\nconst LOG_TOOL_ERRORS_WRITE_PATTERN =\n 'language python\\n`hooks=[log_model_errors]` => `hooks=[log_model_errors, log_tool_errors]`';\n\n// Existing agent-connection projects predate tool_errors_strands.py. This has\n// no EJS templating, so it's read verbatim from the generator's own template\n// rather than kept as a second, hand-copied source of truth.\nconst TOOL_ERRORS_STRANDS_PY_CONTENT = readFileSync(\n join(\n import.meta.dirname,\n '../../../utils/agent-connection/files/py-core-strands/base/tool_errors_strands.py.template',\n ),\n 'utf-8',\n);\n\n/**\n * Ensures the shared Python agent-connection project has tool_errors_strands.py\n * and re-exports log_tool_errors, backfilling projects generated before this\n * helper existed. Returns false (and leaves the tree untouched) if the\n * agent-connection project itself can't be found.\n */\nasync function ensureToolErrorsBase(tree: Tree): Promise<boolean> {\n const projectDir = getPythonAgentConnectionProjectDir(tree);\n if (!tree.exists(joinPathFragments(projectDir, 'project.json'))) {\n return false;\n }\n const moduleDir = joinPathFragments(\n projectDir,\n getPythonAgentConnectionModuleName(tree),\n );\n const toolErrorsPath = joinPathFragments(\n moduleDir,\n 'core',\n 'tool_errors_strands.py',\n );\n if (!tree.exists(toolErrorsPath)) {\n tree.write(toolErrorsPath, TOOL_ERRORS_STRANDS_PY_CONTENT);\n }\n await addPythonReExport(\n tree,\n joinPathFragments(moduleDir, '__init__.py'),\n '.core.tool_errors_strands',\n 'log_tool_errors',\n );\n return true;\n}\n\n// HTTP/A2A agents build one Agent per session (via with_session_id), so\n// wiring `session_manager` directly into the constructor is safe. Anchored\n// on a `tools=` keyword argument so this only matches an inline kwargs call\n// (as the generator produces), not e.g. `Agent(**agent_kwargs)`.\nconst AGENT_PY_CONSTRUCTOR_MATCH_PATTERN =\n 'language python\\n`Agent($args)` where { $args <: contains `tools=$_` }';\nconst AGENT_PY_SESSION_MANAGER_PATTERN =\n 'language python\\n`yield Agent($args)` => `yield Agent(session_manager=get_session_manager(), $args)` where { $args <: not contains `session_manager=$_` }';\nconst AGENT_PY_SESSION_IMPORT_PATTERN =\n 'language python\\n`from contextlib import contextmanager` => raw`from contextlib import contextmanager\\n\\nfrom .session import get_session_manager`';\n\n// AG-UI's StrandsAgent adapter needs a session_manager_provider (called once\n// per thread_id) rather than a plain session_manager.\nconst AGUI_MAIN_IMPORT_PATTERN =\n 'language python\\n`from ag_ui_strands import $names` => `from ag_ui_strands import StrandsAgentConfig, $names` where { $names <: contains `StrandsAgent`, $names <: not contains `StrandsAgentConfig` }';\nconst AGUI_MAIN_CONSTRUCTOR_MATCH_PATTERN =\n 'language python\\n`StrandsAgent($args)` where { $args <: contains `agent=$_` }';\nconst AGUI_MAIN_CONSTRUCTOR_PATTERN = `${AGUI_MAIN_CONSTRUCTOR_MATCH_PATTERN} => \\`StrandsAgent(config=StrandsAgentConfig(session_manager_provider=lambda _input_data: get_session_manager()), $args)\\` where { $args <: not contains \\`config=\\` }`;\nconst AGUI_MAIN_SESSION_IMPORT_PATTERN =\n 'language python\\n`from .agent import get_agent` => raw`from .agent import get_agent\\nfrom .session import get_session_manager` where { $program <: not contains `from .session import get_session_manager` }';\n\n// Existing agents predate session.py, so there's no prior storage to\n// preserve — default to in-memory, mirroring the ts#agent migration.\nconst legacySessionManagerContent = (\n agentConnectionModule: string,\n localSessionsDir: string,\n): string =>\n `import os\n\nfrom strands.session import FileSessionManager, SessionManager\n\nfrom ${agentConnectionModule} import get_current_session_id\n\n\ndef get_session_manager() -> SessionManager | None:\n \"\"\"Returns a SessionManager for persisting conversation state across\n invocations. Local development always uses local file storage for\n convenience, regardless of the configured session option. Without a\n configured session option, conversation state is kept in memory only and\n does not survive process restarts.\n \"\"\"\n session_id = get_current_session_id()\n if not session_id:\n raise RuntimeError(\n \"No current session id — cannot resolve a SessionManager outside of a request scope.\"\n )\n if os.environ.get(\"LOCAL_DEV\") == \"true\":\n return FileSessionManager(session_id=session_id, storage_dir=\"${localSessionsDir}\")\n return None\n`;\n\nconst legacyLangChainSessionContent = (localSessionsDir: string): string =>\n `import os\n\nimport aiosqlite\nfrom langgraph.checkpoint.base import BaseCheckpointSaver\nfrom langgraph.checkpoint.memory import InMemorySaver\nfrom langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver\n\n\ndef get_checkpointer() -> BaseCheckpointSaver:\n \"\"\"Returns the checkpointer used to persist conversation state.\"\"\"\n if os.environ.get(\"LOCAL_DEV\") == \"true\":\n os.makedirs(\"${localSessionsDir}\", exist_ok=True)\n conn = aiosqlite.connect(os.path.join(\"${localSessionsDir}\", \"checkpoints.sqlite\"))\n return AsyncSqliteSaver(conn)\n return InMemorySaver()\n`;\n\n/** The Nx project owning `dirPath`, if any (the longest-matching project.root). */\nconst findOwningProject = (\n tree: Tree,\n dirPath: string,\n): ProjectConfiguration | undefined => {\n let best: ProjectConfiguration | undefined;\n for (const project of getProjects(tree).values()) {\n if (\n (dirPath === project.root || dirPath.startsWith(`${project.root}/`)) &&\n (!best || project.root.length > best.root.length)\n ) {\n best = project;\n }\n }\n return best;\n};\n\n/** This agent's metadata, accepting both current and legacy component paths. */\nconst findAgentComponentMetadata = (\n project: ProjectConfiguration,\n dirRelativeToProjectRoot: string,\n): ComponentMetadata | undefined =>\n (project.metadata as { components?: ComponentMetadata[] })?.components?.find(\n (component) =>\n component.generator === PY_AGENT_GENERATOR_INFO.id &&\n (component.path === dirRelativeToProjectRoot ||\n component.path === `${dirRelativeToProjectRoot}/agent.py`),\n );\n\n/**\n * This agent's real kebab-case name — the generator's own target-prefix\n * (ComponentMetadata's `name` field), used directly rather than\n * reconstructing it by kebab-casing the class name (`rc`). That round trip\n * loses word boundaries for names with single-letter segments, e.g.\n * `kebabCase(\"OldSHttp\")` => \"old-shttp\", not the real \"old-s-http\".\n *\n * When no `--name` was given at generation time, the generator falls back\n * to `${project}-agent` for the real name, but records a literal 'agent' as\n * the target-prefix — indistinguishable from an agent explicitly named\n * \"agent\" — so re-derive the fallback from the project name in that case,\n * exactly as the generator itself does.\n */\nconst agentTmpNameFor = (\n project: ProjectConfiguration,\n dir: string,\n): string | undefined => {\n const dirRelativeToProjectRoot = toProjectRelativePath(project, dir);\n const targetPrefix = findAgentComponentMetadata(\n project,\n dirRelativeToProjectRoot,\n )?.name;\n if (!targetPrefix) return undefined;\n if (targetPrefix !== 'agent') return targetPrefix;\n return kebabCase(`${project.name.split('.').pop() ?? project.name}-agent`);\n};\n\n/** This agent's protocol + framework, from its ComponentMetadata entry. */\nconst agentComponentFor = (\n project: ProjectConfiguration,\n dir: string,\n): ComponentMetadata | undefined => {\n const dirRelativeToProjectRoot = toProjectRelativePath(project, dir);\n return findAgentComponentMetadata(project, dirRelativeToProjectRoot);\n};\n\n/** The relative path from `projectRoot` up to this agent's workspace-root-level local session storage. */\nconst localSessionsDirFor = (\n projectRoot: string,\n agentTmpName: string,\n framework: 'strands' | 'langchain' = 'strands',\n): string =>\n joinPathFragments(\n getRelativePathToRootByDirectory(projectRoot),\n `tmp/agents/${framework}/${agentTmpName}`,\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('/agent.py')) {\n // log_tool_errors applies to every protocol (unlike session_manager),\n // so retrofit it independently of whether the owning project can be\n // resolved below. LOG_TOOL_ERRORS_HOOKS_PATTERN's own `where` clause\n // (contains log_model_errors, not already containing log_tool_errors)\n // doubles as the \"still needs retrofitting\" check.\n if (await matchGritQL(tree, filePath, LOG_TOOL_ERRORS_HOOKS_PATTERN)) {\n const mod = await captureGritQLVariable(\n tree,\n filePath,\n LOG_MODEL_ERRORS_IMPORT_CAPTURE_PATTERN,\n 'mod',\n );\n if ((await ensureToolErrorsBase(tree)) && mod) {\n await applyGritQL(tree, filePath, logToolErrorsImportPattern(mod));\n await applyGritQL(tree, filePath, LOG_TOOL_ERRORS_WRITE_PATTERN);\n } else {\n nextSteps.push(\n `${filePath}: could not find the shared agent-connection project — manually add \\`log_tool_errors\\` to the hooks list (see the py#agent generator's template).`,\n );\n }\n }\n\n const dir = filePath.split('/').slice(0, -1).join('/');\n const project = findOwningProject(tree, dir);\n\n // Without a registered project there's no ComponentMetadata, so this\n // agent's protocol/framework/agent-connection module can't be\n // resolved — and a pre-migration AG-UI agent.py is textually identical\n // to an HTTP/A2A one, so there's nothing left to safely guess from.\n if (!project) {\n if (\n await matchGritQL(\n tree,\n filePath,\n LOG_MODEL_ERRORS_IMPORT_CAPTURE_PATTERN,\n )\n ) {\n nextSteps.push(\n `${filePath}: could not determine the project root — manually verify whether this is an AG-UI or HTTP/A2A Strands agent and wire session_manager in accordingly (see the py#agent generator's template).`,\n );\n }\n continue;\n }\n\n const component = agentComponentFor(project, dir);\n\n if (component?.framework === 'langchain') {\n const content = tree.read(filePath, 'utf-8') ?? '';\n if (content.includes('checkpointer=get_checkpointer()')) {\n continue;\n }\n if (\n !(await matchGritQL(tree, filePath, LANGCHAIN_AGENT_MATCH_PATTERN))\n ) {\n nextSteps.push(\n `${filePath}: the create_agent call has diverged from the generated shape — manually create ${dir}/session.py and pass \\`checkpointer=get_checkpointer()\\` to \\`create_agent\\` (see the py#agent generator's template).`,\n );\n continue;\n }\n\n const agentName = agentTmpNameFor(project, dir);\n const sessionPath = `${dir}/session.py`;\n if (!agentName) {\n nextSteps.push(\n `${filePath}: could not determine this agent's name from its ComponentMetadata — manually create ${sessionPath} and pass \\`checkpointer=get_checkpointer()\\` to \\`create_agent\\` (see the py#agent generator's template).`,\n );\n continue;\n }\n\n if (!tree.exists(sessionPath)) {\n tree.write(\n sessionPath,\n legacyLangChainSessionContent(\n localSessionsDirFor(project.root, agentName, 'langchain'),\n ),\n );\n }\n addPyDependencies(tree, DEPENDENCIES, { projectRoot: project.root });\n await applyGritQL(tree, filePath, langChainSessionImportPattern);\n await applyGritQL(tree, filePath, LANGCHAIN_IN_MEMORY_IMPORT_PATTERN);\n await applyGritQL(tree, filePath, LANGCHAIN_CHECKPOINTER_PATTERN);\n const migratedContent = tree.read(filePath, 'utf-8') ?? '';\n if (\n !migratedContent.includes('from .session import get_checkpointer') ||\n !migratedContent.includes('checkpointer=get_checkpointer()')\n ) {\n nextSteps.push(\n `${filePath}: session.py was created but the checkpointer could not be wired automatically — import \\`get_checkpointer\\` from it and pass \\`checkpointer=get_checkpointer()\\` to \\`create_agent\\`.`,\n );\n }\n continue;\n }\n\n // AG-UI wires session_manager_provider on the StrandsAgent adapter in\n // main.py instead — handled by the branch below.\n if (component?.protocol === 'ag-ui') {\n continue;\n }\n\n if (!component?.protocol) {\n if (\n await matchGritQL(\n tree,\n filePath,\n LOG_MODEL_ERRORS_IMPORT_CAPTURE_PATTERN,\n )\n ) {\n nextSteps.push(\n `${filePath}: could not determine this agent's protocol/framework from its ComponentMetadata — manually verify whether it's AG-UI or HTTP/A2A Strands and wire session_manager in accordingly (see the py#agent generator's template).`,\n );\n }\n continue;\n }\n\n // HTTP/A2A: wire session_manager into the Agent constructor and create\n // the sibling session.py if it doesn't exist yet.\n if (\n !(await matchGritQL(\n tree,\n filePath,\n LOG_MODEL_ERRORS_IMPORT_CAPTURE_PATTERN,\n ))\n ) {\n continue;\n }\n\n const sessionPath = `${dir}/session.py`;\n\n if (\n !(await matchGritQL(tree, filePath, AGENT_PY_CONSTRUCTOR_MATCH_PATTERN))\n ) {\n nextSteps.push(\n `${filePath}: the Agent is not constructed with an inline \\`Agent(...)\\` call, so session_manager could not be wired in automatically. Manually add \\`session_manager=get_session_manager()\\` to its constructor (see the py#agent generator's template), creating ${sessionPath} first if it doesn't already exist.`,\n );\n continue;\n }\n\n if (!tree.exists(sessionPath)) {\n const mod = await captureGritQLVariable(\n tree,\n filePath,\n LOG_MODEL_ERRORS_IMPORT_CAPTURE_PATTERN,\n 'mod',\n );\n const agentName = agentTmpNameFor(project, dir);\n\n if (mod && agentName) {\n tree.write(\n sessionPath,\n legacySessionManagerContent(\n mod,\n localSessionsDirFor(project.root, agentName),\n ),\n );\n } else {\n nextSteps.push(\n `${filePath}: could not determine the agent-connection module or this agent's name from its ComponentMetadata — manually create ${sessionPath} (see the py#agent generator's template) and wire \\`session_manager=get_session_manager()\\` into the Agent constructor.`,\n );\n continue;\n }\n }\n\n const agentContent = tree.read(filePath, 'utf-8') ?? '';\n if (!agentContent.includes('from .session import get_session_manager')) {\n await applyGritQL(tree, filePath, AGENT_PY_SESSION_IMPORT_PATTERN);\n await applyGritQL(tree, filePath, AGENT_PY_SESSION_MANAGER_PATTERN);\n }\n\n if (\n !(tree.read(filePath, 'utf-8') ?? '').includes('get_session_manager')\n ) {\n nextSteps.push(\n `${filePath}: found ${sessionPath} but couldn't confirm the get_session_manager import — wire \\`session_manager=get_session_manager()\\` into the Agent constructor manually.`,\n );\n continue;\n }\n\n continue;\n }\n\n if (\n filePath.endsWith('/main.py') &&\n (tree.read(filePath, 'utf-8') ?? '').includes('ag_ui_strands')\n ) {\n const alreadyWired = (tree.read(filePath, 'utf-8') ?? '').includes(\n 'StrandsAgentConfig',\n );\n\n if (!alreadyWired) {\n const needsWiring = await matchGritQL(\n tree,\n filePath,\n AGUI_MAIN_CONSTRUCTOR_MATCH_PATTERN,\n );\n if (!needsWiring) {\n nextSteps.push(\n `${filePath}: the StrandsAgent constructor has diverged from the generated shape - left as-is. Manually add \\`config=StrandsAgentConfig(session_manager_provider=lambda _input_data: get_session_manager())\\` to it (see the py#agent generator's template).`,\n );\n continue;\n }\n\n await applyGritQL(tree, filePath, AGUI_MAIN_IMPORT_PATTERN);\n await applyGritQL(tree, filePath, AGUI_MAIN_CONSTRUCTOR_PATTERN);\n }\n\n const dir = filePath.split('/').slice(0, -1).join('/');\n const sessionPath = `${dir}/session.py`;\n\n if (!tree.exists(sessionPath)) {\n const project = findOwningProject(tree, dir);\n const mod = await captureGritQLVariable(\n tree,\n filePath,\n SESSION_ID_CONTEXT_IMPORT_CAPTURE_PATTERN,\n 'mod',\n );\n const agentName = project && agentTmpNameFor(project, dir);\n\n if (project && mod && agentName) {\n tree.write(\n sessionPath,\n legacySessionManagerContent(\n mod,\n localSessionsDirFor(project.root, agentName),\n ),\n );\n } else {\n nextSteps.push(\n `${filePath}: could not determine the project root, agent-connection module, or this agent's name from its ComponentMetadata — manually create ${sessionPath} (see the py#agent generator's template) and wire \\`config=StrandsAgentConfig(session_manager_provider=lambda _input_data: get_session_manager())\\` into the StrandsAgent constructor.`,\n );\n continue;\n }\n }\n\n await applyGritQL(tree, filePath, AGUI_MAIN_SESSION_IMPORT_PATTERN);\n\n if (\n !(tree.read(filePath, 'utf-8') ?? '').includes('get_session_manager')\n ) {\n nextSteps.push(\n `${filePath}: found ${sessionPath} but couldn't confirm the get_session_manager import — wire it into the StrandsAgent constructor manually.`,\n );\n }\n }\n }\n\n await formatFilesInSubtree(tree);\n\n return { nextSteps };\n}\n"],"names":["readFileSync","join","getProjects","joinPathFragments","visitNotIgnoredFiles","PY_AGENT_GENERATOR_INFO","addPyDependencies","addPythonReExport","getPythonAgentConnectionModuleName","getPythonAgentConnectionProjectDir","applyGritQL","captureGritQLVariable","matchGritQL","declareDependencies","formatFilesInSubtree","kebabCase","getRelativePathToRootByDirectory","toProjectRelativePath","LOG_MODEL_ERRORS_IMPORT_CAPTURE_PATTERN","SESSION_ID_CONTEXT_IMPORT_CAPTURE_PATTERN","DEPENDENCIES","py","name","ts","LANGCHAIN_AGENT_MATCH_PATTERN","LANGCHAIN_CHECKPOINTER_PATTERN","LANGCHAIN_IN_MEMORY_IMPORT_PATTERN","langChainSessionImportPattern","LOG_TOOL_ERRORS_HOOKS_PATTERN","logToolErrorsImportPattern","mod","LOG_TOOL_ERRORS_WRITE_PATTERN","TOOL_ERRORS_STRANDS_PY_CONTENT","dirname","ensureToolErrorsBase","tree","projectDir","exists","moduleDir","toolErrorsPath","write","AGENT_PY_CONSTRUCTOR_MATCH_PATTERN","AGENT_PY_SESSION_MANAGER_PATTERN","AGENT_PY_SESSION_IMPORT_PATTERN","AGUI_MAIN_IMPORT_PATTERN","AGUI_MAIN_CONSTRUCTOR_MATCH_PATTERN","AGUI_MAIN_CONSTRUCTOR_PATTERN","AGUI_MAIN_SESSION_IMPORT_PATTERN","legacySessionManagerContent","agentConnectionModule","localSessionsDir","legacyLangChainSessionContent","findOwningProject","dirPath","best","project","values","root","startsWith","length","findAgentComponentMetadata","dirRelativeToProjectRoot","metadata","components","find","component","generator","id","path","agentTmpNameFor","dir","targetPrefix","undefined","split","pop","agentComponentFor","localSessionsDirFor","projectRoot","agentTmpName","framework","migration","nextSteps","filePaths","filePath","push","endsWith","slice","content","read","includes","agentName","sessionPath","migratedContent","protocol","agentContent","alreadyWired","needsWiring"],"mappings":"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"}
@@ -252,7 +252,7 @@ export default {
252
252
 
253
253
  exports[`preset generator > should run successfully > biome.json 1`] = `
254
254
  "{
255
- "$schema": "https://biomejs.dev/schemas/2.5.7/schema.json",
255
+ "$schema": "https://biomejs.dev/schemas/2.5.8/schema.json",
256
256
  "root": true,
257
257
  "formatter": {
258
258
  "enabled": true,
@@ -418,7 +418,7 @@ catalog:
418
418
  '@nx/js': 23.1.1
419
419
  '@nx/workspace': 23.1.1
420
420
  typescript: ~6.0.3
421
- '@biomejs/biome': 2.5.7
421
+ '@biomejs/biome': 2.5.8
422
422
  '@aws/nx-plugin': ^0.0.0
423
423
  '@aws/nx-plugin-mcp': ^0.0.0
424
424
  "
@@ -326,6 +326,13 @@ export class DynamodbAgent
326
326
  exports[`py#agent generator > should match snapshot for BedrockAgentCoreRuntime generated constructs files > agent-Dockerfile 1`] = `
327
327
  "FROM public.ecr.aws/docker/library/python:3.14-slim
328
328
 
329
+ # Apply the distribution's security updates: base image tags lag behind the
330
+ # security suite, so its OS packages carry fixed HIGH/CRITICAL vulnerabilities
331
+ # flagged by the image scan.
332
+ RUN apt-get update && \\
333
+ apt-get upgrade -y --no-install-recommends && \\
334
+ rm -rf /var/lib/apt/lists/*
335
+
329
336
  # Remove the base image's pip: the agent runs from the bundled environment and
330
337
  # never needs it at runtime, and pip's vendored packages carry known
331
338
  # HIGH/CRITICAL vulnerabilities flagged by the image scan.
@@ -767,15 +774,15 @@ name = "proj.test_project"
767
774
  version = "0.1.0"
768
775
  dependencies = [
769
776
  "proj-agent-connection",
770
- "aws-lambda-powertools==3.31.1",
777
+ "aws-lambda-powertools==3.34.0",
771
778
  "aws-opentelemetry-distro==0.19.0",
772
779
  "bedrock-agentcore==1.21.0",
773
- "boto3==1.43.67",
780
+ "boto3==1.43.72",
774
781
  "fastapi==0.141.1",
775
782
  "mcp==1.28.1",
776
- "strands-agents==1.51.0",
783
+ "strands-agents==1.52.0",
777
784
  "strands-agents-tools==0.8.6",
778
- "uvicorn==0.52.1"
785
+ "uvicorn==0.52.3"
779
786
  ]
780
787
 
781
788
  [dependency-groups]