@aws/nx-plugin 0.64.1 → 0.66.0

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 (32) hide show
  1. package/LICENSE-THIRD-PARTY +662 -187
  2. package/README.md +6 -3
  3. package/generators.json +6 -0
  4. package/package.json +14 -14
  5. package/sdk/ts.d.ts +2 -0
  6. package/sdk/ts.js +20 -17
  7. package/sdk/ts.js.map +1 -1
  8. package/src/infra/app/__snapshots__/generator.spec.ts.snap +31 -31
  9. package/src/preset/__snapshots__/generator.spec.ts.snap +5 -3
  10. package/src/py/mcp-server/__snapshots__/generator.spec.ts.snap +3 -3
  11. package/src/py/strands-agent/__snapshots__/generator.spec.ts.snap +8 -8
  12. package/src/ts/mcp-server/__snapshots__/generator.spec.ts.snap +5 -5
  13. package/src/ts/nx-plugin/__snapshots__/generator.spec.ts.snap +1 -1
  14. package/src/ts/react-website/app/__snapshots__/generator.spec.ts.snap +93 -49
  15. package/src/ts/strands-agent/__snapshots__/generator.spec.ts.snap +1035 -0
  16. package/src/ts/strands-agent/files/app/agent-core-mcp-client.ts.template +132 -0
  17. package/src/ts/strands-agent/files/app/agent-core-trpc-client.ts.template +124 -0
  18. package/src/ts/strands-agent/files/app/agent.ts.template +20 -0
  19. package/src/ts/strands-agent/files/app/client.ts.template +28 -0
  20. package/src/ts/strands-agent/files/app/index.ts.template +54 -0
  21. package/src/ts/strands-agent/files/app/init.ts.template +8 -0
  22. package/src/ts/strands-agent/files/app/router.ts.template +34 -0
  23. package/src/ts/strands-agent/files/app/schema/z-async-iterable.ts.template +77 -0
  24. package/src/ts/strands-agent/files/deploy/Dockerfile.template +16 -0
  25. package/src/ts/strands-agent/generator.d.ts +10 -0
  26. package/src/ts/strands-agent/generator.js +115 -0
  27. package/src/ts/strands-agent/generator.js.map +1 -0
  28. package/src/ts/strands-agent/schema.d.ts +15 -0
  29. package/src/ts/strands-agent/schema.json +41 -0
  30. package/src/utils/versions.d.ts +62 -59
  31. package/src/utils/versions.js +61 -58
  32. package/src/utils/versions.js.map +1 -1
@@ -0,0 +1,1035 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`ts#strands-agent generator > should match snapshot for BedrockAgentCoreRuntime generated constructs files > agent-Dockerfile 1`] = `
4
+ "FROM public.ecr.aws/docker/library/node:lts-jod
5
+
6
+ WORKDIR /app
7
+
8
+ # Add AWS Distro for OpenTelemetry for observability
9
+ # https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/observability-configure.html
10
+ RUN npm install @aws/aws-distro-opentelemetry-node-autoinstrumentation@^0.7.0
11
+
12
+ # Copy bundled agent
13
+ COPY --from=workspace dist/apps/test-project/bundle/agent/snapshot-bedrock-agent/index.js /app
14
+
15
+ EXPOSE 8080
16
+
17
+ # Auto-instrument with AWS Distro for OpenTelemetry
18
+ # https://aws-otel.github.io/docs/getting-started/js-sdk/trace-metric-auto-instr
19
+ CMD [ "node", "--require", "@aws/aws-distro-opentelemetry-node-autoinstrumentation/register", "index.js" ]
20
+ "
21
+ `;
22
+
23
+ exports[`ts#strands-agent generator > should match snapshot for BedrockAgentCoreRuntime generated constructs files > agent-construct.ts 1`] = `
24
+ "import { Lazy, Names } from 'aws-cdk-lib';
25
+ import { Platform } from 'aws-cdk-lib/aws-ecr-assets';
26
+ import { Construct } from 'constructs';
27
+ import { execSync } from 'child_process';
28
+ import * as path from 'path';
29
+ import * as url from 'url';
30
+ import {
31
+ AgentRuntimeArtifact,
32
+ ProtocolType,
33
+ Runtime,
34
+ RuntimeProps,
35
+ } from '@aws-cdk/aws-bedrock-agentcore-alpha';
36
+
37
+ export type SnapshotBedrockAgentProps = Omit<
38
+ RuntimeProps,
39
+ 'runtimeName' | 'protocolConfiguration' | 'agentRuntimeArtifact'
40
+ >;
41
+
42
+ export class SnapshotBedrockAgent extends Construct {
43
+ public readonly dockerImage: AgentRuntimeArtifact;
44
+ public readonly agentCoreRuntime: Runtime;
45
+
46
+ constructor(scope: Construct, id: string, props?: SnapshotBedrockAgentProps) {
47
+ super(scope, id);
48
+
49
+ this.dockerImage = AgentRuntimeArtifact.fromAsset(
50
+ path.dirname(url.fileURLToPath(new URL(import.meta.url))),
51
+ {
52
+ platform: Platform.LINUX_ARM64,
53
+ extraHash: execSync(
54
+ \`docker inspect proj-snapshot-bedrock-agent:latest --format '{{.Id}}'\`,
55
+ { encoding: 'utf-8' },
56
+ ).trim(),
57
+ },
58
+ );
59
+
60
+ this.agentCoreRuntime = new Runtime(this, 'SnapshotBedrockAgent', {
61
+ runtimeName: Lazy.string({
62
+ produce: () =>
63
+ Names.uniqueResourceName(this.agentCoreRuntime, { maxLength: 40 }),
64
+ }),
65
+ protocolConfiguration: ProtocolType.HTTP,
66
+ agentRuntimeArtifact: this.dockerImage,
67
+ ...props,
68
+ });
69
+ }
70
+ }
71
+ "
72
+ `;
73
+
74
+ exports[`ts#strands-agent generator > should match snapshot for BedrockAgentCoreRuntime generated constructs files > agents-index.ts 1`] = `
75
+ "export * from './snapshot-bedrock-agent/snapshot-bedrock-agent.js';
76
+ "
77
+ `;
78
+
79
+ exports[`ts#strands-agent generator > should match snapshot for BedrockAgentCoreRuntime generated constructs files > app-index.ts 1`] = `
80
+ "export * from './agents/index.js';
81
+ "
82
+ `;
83
+
84
+ exports[`ts#strands-agent generator > should match snapshot for BedrockAgentCoreRuntime generated constructs files > core-index.ts 1`] = `
85
+ "export * from './app.js';
86
+ export * from './checkov.js';
87
+ export * from './runtime-config.js';
88
+ "
89
+ `;
90
+
91
+ exports[`ts#strands-agent generator > should match snapshot for Terraform generated files > terraform-agent.tf 1`] = `
92
+ "variable "env" {
93
+ description = "Environment variables to pass to the agent core runtime"
94
+ type = map(string)
95
+ default = {}
96
+ }
97
+
98
+ variable "additional_iam_policy_statements" {
99
+ description = "Additional IAM policy statements to attach to the agent core runtime role"
100
+ type = list(object({
101
+ Effect = string
102
+ Action = list(string)
103
+ Resource = list(string)
104
+ }))
105
+ default = []
106
+ }
107
+
108
+ variable "tags" {
109
+ description = "Tags to apply to resources"
110
+ type = map(string)
111
+ default = {}
112
+ }
113
+
114
+ module "agent_core_runtime" {
115
+ source = "../../../core/agent-core"
116
+ agent_runtime_name = "TerraformSnapshotAgent"
117
+ docker_image_tag = "proj-terraform-snapshot-agent:latest"
118
+ server_protocol = "HTTP"
119
+ # authorizer_configuration = {
120
+ # custom_jwt_authorizer = {
121
+ # discovery_url = "https://xxx/.well-known/openid-configuration"
122
+ # allowed_clients = [ "xxx" ]
123
+ # }
124
+ # }
125
+
126
+ env = var.env
127
+ additional_iam_policy_statements = var.additional_iam_policy_statements
128
+ tags = var.tags
129
+ }
130
+
131
+ output "agent_core_runtime_role_arn" {
132
+ description = "ARN of the agent core runtime role"
133
+ value = module.agent_core_runtime.agent_core_runtime_role_arn
134
+ }
135
+
136
+ output "agent_core_runtime_arn" {
137
+ description = "ARN of the Bedrock Agent Core runtime"
138
+ value = module.agent_core_runtime.agent_core_runtime_arn
139
+ }
140
+ "
141
+ `;
142
+
143
+ exports[`ts#strands-agent generator > should match snapshot for Terraform generated files > terraform-agent-core-runtime.tf 1`] = `
144
+ "terraform {
145
+ required_version = ">= 1.0"
146
+
147
+ required_providers {
148
+ aws = {
149
+ source = "hashicorp/aws"
150
+ version = ">= 6.23"
151
+ }
152
+ null = {
153
+ source = "hashicorp/null"
154
+ version = ">= 3.0"
155
+ }
156
+ random = {
157
+ source = "hashicorp/random"
158
+ version = ">= 3.0"
159
+ }
160
+ }
161
+ }
162
+
163
+ # Variables
164
+ variable "agent_runtime_name" {
165
+ description = "Name of the agent runtime"
166
+ type = string
167
+ validation {
168
+ condition = can(regex("^[a-zA-Z][a-zA-Z0-9_]{0,42}$", var.agent_runtime_name))
169
+ error_message = "Value must start with a letter and contain only letters, numbers, and underscores (1-43 characters)."
170
+ }
171
+ }
172
+
173
+ variable "server_protocol" {
174
+ description = "Server protocol for the agent runtime (HTTP, MCP, or A2A)"
175
+ type = string
176
+ default = "HTTP"
177
+ validation {
178
+ condition = contains(["MCP", "HTTP", "A2A"], var.server_protocol)
179
+ error_message = "Protocol type must be either 'MCP', 'HTTP', or 'A2A'."
180
+ }
181
+ }
182
+
183
+ variable "authorizer_configuration" {
184
+ description = "Authorization configuration for authenticating incoming requests"
185
+ type = object({
186
+ custom_jwt_authorizer = optional(object({
187
+ discovery_url = string
188
+ allowed_audience = optional(list(string))
189
+ allowed_clients = optional(list(string))
190
+ }))
191
+ })
192
+ default = null
193
+ }
194
+
195
+ variable "docker_image_tag" {
196
+ description = "Name of the docker image tag to use as the agent core runtime"
197
+ type = string
198
+ }
199
+
200
+ variable "env" {
201
+ description = "Environment variables to pass to the agent core runtime"
202
+ type = map(string)
203
+ default = {}
204
+ }
205
+
206
+ variable "additional_iam_policy_statements" {
207
+ description = "Additional IAM policy statements to attach to the agent core runtime role"
208
+ type = list(object({
209
+ Effect = string
210
+ Action = list(string)
211
+ Resource = list(string)
212
+ }))
213
+ default = []
214
+ }
215
+
216
+ variable "tags" {
217
+ description = "Tags to apply to resources"
218
+ type = map(string)
219
+ default = {}
220
+ }
221
+
222
+ # Data sources
223
+ data "aws_caller_identity" "current" {}
224
+ data "aws_region" "current" {}
225
+
226
+ locals {
227
+ aws_account_id = data.aws_caller_identity.current.account_id
228
+ aws_region = data.aws_region.current.id
229
+ }
230
+
231
+ # Random ID for bucket suffix to ensure uniqueness
232
+ resource "random_id" "unique_suffix" {
233
+ byte_length = 4
234
+ }
235
+
236
+ # ECR Repository
237
+ resource "aws_ecr_repository" "agent_core_repository" {
238
+ #checkov:skip=CKV_AWS_136:AES256 encryption is sufficient for ECR repositories
239
+ name = "\${lower(var.agent_runtime_name)}_repository_\${random_id.unique_suffix.hex}"
240
+
241
+ #checkov:skip=CKV_AWS_51:Image tag is reused for latest deployments
242
+ image_tag_mutability = "MUTABLE"
243
+ force_delete = true
244
+
245
+ image_scanning_configuration {
246
+ scan_on_push = true
247
+ }
248
+
249
+ tags = var.tags
250
+ }
251
+
252
+ # ECR Repository Policy
253
+ resource "aws_ecr_repository_policy" "agent_core_ecr_policy" {
254
+ repository = aws_ecr_repository.agent_core_repository.name
255
+
256
+ policy = jsonencode({
257
+ Version = "2012-10-17"
258
+ Statement = [
259
+ {
260
+ Sid = "AllowPushPull"
261
+ Effect = "Allow"
262
+ Principal = {
263
+ AWS = "arn:aws:iam::\${local.aws_account_id}:root"
264
+ }
265
+ Action = [
266
+ "ecr:GetDownloadUrlForLayer",
267
+ "ecr:BatchGetImage",
268
+ "ecr:BatchCheckLayerAvailability",
269
+ "ecr:PutImage",
270
+ "ecr:InitiateLayerUpload",
271
+ "ecr:UploadLayerPart",
272
+ "ecr:CompleteLayerUpload"
273
+ ]
274
+ }
275
+ ]
276
+ })
277
+ }
278
+
279
+ # IAM Role for Agent Core Runtime
280
+ resource "aws_iam_role" "agent_core_runtime_role" {
281
+ name = "\${var.agent_runtime_name}-AgentCoreRuntimeRole-\${random_id.unique_suffix.hex}"
282
+
283
+ assume_role_policy = jsonencode({
284
+ Version = "2012-10-17"
285
+ Statement = [
286
+ {
287
+ Sid = "AgentCoreAssumeRolePolicy"
288
+ Effect = "Allow"
289
+ Principal = {
290
+ Service = "bedrock-agentcore.amazonaws.com"
291
+ }
292
+ Action = "sts:AssumeRole"
293
+ Condition = {
294
+ StringEquals = {
295
+ "aws:SourceAccount" = local.aws_account_id
296
+ }
297
+ ArnLike = {
298
+ "aws:SourceArn" = "arn:aws:bedrock-agentcore:\${local.aws_region}:\${local.aws_account_id}:*"
299
+ }
300
+ }
301
+ }
302
+ ]
303
+ })
304
+
305
+ tags = var.tags
306
+ }
307
+
308
+ # IAM Policy for Agent Core Runtime
309
+ resource "aws_iam_policy" "agent_core_runtime_policy" {
310
+ name = "\${var.agent_runtime_name}-QueryAgentPolicy-\${random_id.unique_suffix.hex}"
311
+ description = "Restricted policy for Agent"
312
+
313
+ policy = jsonencode({
314
+ Version = "2012-10-17"
315
+ Statement = concat([
316
+ {
317
+ Sid = "ECRImageAccess"
318
+ Effect = "Allow"
319
+ Action = [
320
+ "ecr:BatchGetImage",
321
+ "ecr:GetDownloadUrlForLayer"
322
+ ]
323
+ Resource = [
324
+ aws_ecr_repository.agent_core_repository.arn
325
+ ]
326
+ },
327
+ {
328
+ Sid = "ECRTokenAccess"
329
+ Effect = "Allow"
330
+ Action = [
331
+ "ecr:GetAuthorizationToken"
332
+ ]
333
+ Resource = [
334
+ "*"
335
+ ]
336
+ },
337
+ {
338
+ "Effect" : "Allow",
339
+ "Action" : [
340
+ "logs:DescribeLogStreams",
341
+ "logs:CreateLogGroup"
342
+ ],
343
+ "Resource" : [
344
+ "arn:aws:logs:\${local.aws_region}:\${local.aws_account_id}:log-group:/aws/bedrock-agentcore/runtimes/*"
345
+ ]
346
+ },
347
+ {
348
+ "Effect" : "Allow",
349
+ "Action" : [
350
+ "logs:DescribeLogGroups"
351
+ ],
352
+ "Resource" : [
353
+ "arn:aws:logs:\${local.aws_region}:\${local.aws_account_id}:log-group:*"
354
+ ]
355
+ },
356
+ {
357
+ "Effect" : "Allow",
358
+ "Action" : [
359
+ "logs:CreateLogStream",
360
+ "logs:PutLogEvents"
361
+ ],
362
+ "Resource" : [
363
+ "arn:aws:logs:\${local.aws_region}:\${local.aws_account_id}:log-group:/aws/bedrock-agentcore/runtimes/*:log-stream:*"
364
+ ]
365
+ },
366
+ {
367
+ "Effect" : "Allow",
368
+ "Action" : [
369
+ "xray:PutTraceSegments",
370
+ "xray:PutTelemetryRecords",
371
+ "xray:GetSamplingRules",
372
+ "xray:GetSamplingTargets"
373
+ ],
374
+ "Resource" : ["*"]
375
+ },
376
+ {
377
+ "Effect" : "Allow",
378
+ "Resource" : "*",
379
+ "Action" : "cloudwatch:PutMetricData",
380
+ "Condition" : {
381
+ "StringEquals" : {
382
+ "cloudwatch:namespace" : "bedrock-agentcore"
383
+ }
384
+ }
385
+ },
386
+ {
387
+ "Sid" : "GetAgentAccessToken",
388
+ "Effect" : "Allow",
389
+ "Action" : [
390
+ "bedrock-agentcore:GetWorkloadAccessToken",
391
+ "bedrock-agentcore:GetWorkloadAccessTokenForJWT",
392
+ "bedrock-agentcore:GetWorkloadAccessTokenForUserId"
393
+ ],
394
+ "Resource" : [
395
+ "arn:aws:bedrock-agentcore:\${local.aws_region}:\${local.aws_account_id}:workload-identity-directory/default",
396
+ "arn:aws:bedrock-agentcore:\${local.aws_region}:\${local.aws_account_id}:workload-identity-directory/default/workload-identity/*"
397
+ ]
398
+ }
399
+ ], var.additional_iam_policy_statements)
400
+ })
401
+
402
+ tags = var.tags
403
+ }
404
+
405
+ # Attach the restricted policy to the role
406
+ resource "aws_iam_role_policy_attachment" "agent_core_policy" {
407
+ role = aws_iam_role.agent_core_runtime_role.name
408
+ policy_arn = aws_iam_policy.agent_core_runtime_policy.arn
409
+ }
410
+
411
+ # Data source to get Docker image digest
412
+ data "external" "docker_digest" {
413
+ program = ["sh", "-c", "echo '{\\"digest\\":\\"'$(docker inspect \${var.docker_image_tag} --format '{{.Id}}')'\\"}' "]
414
+ }
415
+
416
+ # Null resource for Docker publish
417
+ resource "null_resource" "docker_publish" {
418
+ triggers = {
419
+ docker_digest = data.external.docker_digest.result.digest
420
+ repository_url = aws_ecr_repository.agent_core_repository.repository_url
421
+ docker_image_tag = var.docker_image_tag
422
+ }
423
+
424
+ provisioner "local-exec" {
425
+ command = <<-EOT
426
+ # Get ECR login token
427
+ aws ecr get-login-password --region \${local.aws_region} | docker login --username AWS --password-stdin \${self.triggers.repository_url}
428
+
429
+ # Tag the image
430
+ docker tag \${self.triggers.docker_image_tag} \${self.triggers.repository_url}:latest
431
+
432
+ # Push the image
433
+ docker push \${self.triggers.repository_url}:latest
434
+ EOT
435
+ }
436
+
437
+ depends_on = [aws_ecr_repository_policy.agent_core_ecr_policy]
438
+ }
439
+
440
+ # Bedrock AgentCore Agent Runtime
441
+ resource "aws_bedrockagentcore_agent_runtime" "agent_runtime" {
442
+ agent_runtime_name = "\${var.agent_runtime_name}_\${random_id.unique_suffix.hex}"
443
+ description = "Agent Runtime for \${var.agent_runtime_name}"
444
+ role_arn = aws_iam_role.agent_core_runtime_role.arn
445
+
446
+ agent_runtime_artifact {
447
+ container_configuration {
448
+ container_uri = "\${aws_ecr_repository.agent_core_repository.repository_url}:latest"
449
+ }
450
+ }
451
+
452
+ environment_variables = length(var.env) > 0 ? var.env : null
453
+
454
+ dynamic "authorizer_configuration" {
455
+ for_each = var.authorizer_configuration != null && var.authorizer_configuration.custom_jwt_authorizer != null ? [var.authorizer_configuration.custom_jwt_authorizer] : []
456
+ content {
457
+ custom_jwt_authorizer {
458
+ discovery_url = authorizer_configuration.value.discovery_url
459
+ allowed_audience = authorizer_configuration.value.allowed_audience
460
+ allowed_clients = authorizer_configuration.value.allowed_clients
461
+ }
462
+ }
463
+ }
464
+
465
+ network_configuration {
466
+ network_mode = "PUBLIC"
467
+ }
468
+
469
+ protocol_configuration {
470
+ server_protocol = var.server_protocol
471
+ }
472
+
473
+ tags = var.tags
474
+
475
+ depends_on = [
476
+ null_resource.docker_publish,
477
+ aws_iam_role_policy.agent_core_runtime_policy
478
+ ]
479
+ }
480
+
481
+ # Outputs
482
+ output "agent_core_runtime_role_arn" {
483
+ description = "ARN of the Agent Core Runtime IAM role"
484
+ value = aws_iam_role.agent_core_runtime_role.arn
485
+ }
486
+
487
+ output "agent_core_runtime_role_name" {
488
+ description = "Name of the Agent Core Runtime IAM role"
489
+ value = aws_iam_role.agent_core_runtime_role.name
490
+ }
491
+
492
+ output "agent_runtime_name" {
493
+ description = "Name of the deployed agent runtime"
494
+ value = aws_bedrockagentcore_agent_runtime.agent_runtime.agent_runtime_name
495
+ }
496
+
497
+ output "agent_core_runtime_arn" {
498
+ description = "ARN of the Bedrock Agent Core runtime"
499
+ value = aws_bedrockagentcore_agent_runtime.agent_runtime.agent_runtime_arn
500
+ }
501
+
502
+ output "agent_runtime_id" {
503
+ description = "ID of the Bedrock Agent Core runtime"
504
+ value = aws_bedrockagentcore_agent_runtime.agent_runtime.agent_runtime_id
505
+ }
506
+
507
+ output "agent_runtime_version" {
508
+ description = "Version of the Bedrock Agent Core runtime"
509
+ value = aws_bedrockagentcore_agent_runtime.agent_runtime.agent_runtime_version
510
+ }
511
+ "
512
+ `;
513
+
514
+ exports[`ts#strands-agent generator > should match snapshot for generated files > strands-agent-agent.ts 1`] = `
515
+ "import { Agent, tool } from '@strands-agents/sdk';
516
+ import { z } from 'zod';
517
+
518
+ const add = tool({
519
+ name: 'Add',
520
+ description: 'Add two numbers',
521
+ inputSchema: z.object({
522
+ a: z.number(),
523
+ b: z.number(),
524
+ }),
525
+ callback: ({ a, b }) => a + b,
526
+ });
527
+
528
+ export const getAgent = () =>
529
+ new Agent({
530
+ systemPrompt: \`You are an addition wizard.
531
+ Use the add tool for addition tasks.
532
+ Refer to tools as your 'spellbook'.\`,
533
+ tools: [add],
534
+ });
535
+ "
536
+ `;
537
+
538
+ exports[`ts#strands-agent generator > should match snapshot for generated files > strands-agent-agent-core-mcp-client.ts 1`] = `
539
+ "import { McpClient } from '@strands-agents/sdk';
540
+ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
541
+ import { AwsClient } from 'aws4fetch';
542
+ import { fromNodeProviderChain } from '@aws-sdk/credential-providers';
543
+
544
+ /**
545
+ * Options for creating an AgentCore MCP client with IAM authentication
546
+ */
547
+ export interface AgentCoreMcpClientIamOptions {
548
+ agentRuntimeArn: string;
549
+ region: string;
550
+ sessionId: string;
551
+ }
552
+
553
+ /**
554
+ * Options for creating an AgentCore MCP client with JWT authentication
555
+ */
556
+ export interface AgentCoreMcpClientJwtOptions {
557
+ agentRuntimeArn: string;
558
+ accessToken: string;
559
+ region: string;
560
+ sessionId: string;
561
+ }
562
+
563
+ /**
564
+ * Internal options for creating an AgentCore MCP client
565
+ */
566
+ interface CreateOptions {
567
+ agentRuntimeArn: string;
568
+ region: string;
569
+ sessionId: string;
570
+ additionalHeaders?: Record<string, string>;
571
+ fetch?: typeof fetch;
572
+ }
573
+
574
+ /**
575
+ * Factory for clients to call MCP servers hosted on Bedrock AgentCore Runtime
576
+ */
577
+ export class AgentCoreMcpClient {
578
+ /**
579
+ * Internal method to create an MCP client
580
+ */
581
+ private static create(options: CreateOptions): McpClient {
582
+ const {
583
+ agentRuntimeArn,
584
+ region,
585
+ sessionId,
586
+ additionalHeaders = {},
587
+ fetch: customFetch,
588
+ } = options;
589
+
590
+ // Encode the ARN for URL
591
+ const encodedArn = agentRuntimeArn
592
+ .replace(/:/g, '%3A')
593
+ .replace(/\\//g, '%2F');
594
+ const url = \`https://bedrock-agentcore.\${region}.amazonaws.com/runtimes/\${encodedArn}/invocations?qualifier=DEFAULT\`;
595
+
596
+ // Create a custom fetch that adds required headers
597
+ const fetchWithHeaders: typeof fetch = async (input, init) => {
598
+ const headers = {
599
+ 'X-Amzn-Bedrock-AgentCore-Runtime-Session-Id': sessionId,
600
+ ...additionalHeaders,
601
+ };
602
+ const existingHeaders = init?.headers;
603
+
604
+ const mergedInit = {
605
+ ...init,
606
+ headers: !existingHeaders
607
+ ? headers
608
+ : existingHeaders instanceof Headers
609
+ ? (() => {
610
+ const h = new Headers(existingHeaders);
611
+ Object.entries(headers).forEach(([k, v]) => h.append(k, v));
612
+ return h;
613
+ })()
614
+ : Array.isArray(existingHeaders)
615
+ ? [...existingHeaders, ...Object.entries(headers)]
616
+ : { ...existingHeaders, ...headers },
617
+ };
618
+ return (customFetch || fetch)(input, mergedInit);
619
+ };
620
+
621
+ // Create the transport
622
+ const transport = new StreamableHTTPClientTransport(new URL(url), {
623
+ fetch: fetchWithHeaders,
624
+ });
625
+
626
+ // Create and return the MCP client
627
+ return new McpClient({ transport });
628
+ }
629
+
630
+ /**
631
+ * Create an MCP client with IAM (SigV4) authentication
632
+ */
633
+ static withIamAuth(options: AgentCoreMcpClientIamOptions): McpClient {
634
+ const { region, ...rest } = options;
635
+
636
+ // Create credential provider
637
+ const credentialProvider = fromNodeProviderChain();
638
+
639
+ // Create a SigV4 signing fetch function
640
+ const sigv4Fetch: typeof fetch = async (...args) => {
641
+ const credentials = await credentialProvider();
642
+ const client = new AwsClient({
643
+ ...credentials,
644
+ service: 'bedrock-agentcore',
645
+ region,
646
+ });
647
+ return client.fetch(...args);
648
+ };
649
+
650
+ return AgentCoreMcpClient.create({
651
+ ...rest,
652
+ region,
653
+ fetch: sigv4Fetch,
654
+ });
655
+ }
656
+
657
+ /**
658
+ * Create an MCP client with JWT authentication
659
+ */
660
+ static withJwtAuth(options: AgentCoreMcpClientJwtOptions): McpClient {
661
+ const { accessToken, ...rest } = options;
662
+
663
+ return AgentCoreMcpClient.create({
664
+ ...rest,
665
+ additionalHeaders: {
666
+ Authorization: \`Bearer \${accessToken}\`,
667
+ },
668
+ });
669
+ }
670
+ }
671
+ "
672
+ `;
673
+
674
+ exports[`ts#strands-agent generator > should match snapshot for generated files > strands-agent-agent-core-trpc-client.ts 1`] = `
675
+ "import { fromNodeProviderChain } from '@aws-sdk/credential-providers';
676
+ import {
677
+ createTRPCClient,
678
+ createWSClient,
679
+ WebSocketLinkOptions,
680
+ wsLink,
681
+ } from '@trpc/client';
682
+ import { AwsClient } from 'aws4fetch';
683
+ import { AnyTRPCRouter } from '@trpc/server';
684
+ import { WebSocket } from 'node:http';
685
+
686
+ export interface WebSocketTrpcClientOptions {
687
+ /**
688
+ * URL of the websocket server, eg http://localhost:8080/ws
689
+ */
690
+ url: string;
691
+ /**
692
+ * Factory for creating headers to include in the WebSocket handshake
693
+ */
694
+ buildHeaders?: () => Promise<{ [key: string]: string }>;
695
+ }
696
+
697
+ /**
698
+ * Client for connecting to tRPC APIs via WebSocket
699
+ */
700
+ export class WebSocketTrpcClient {
701
+ /**
702
+ * Utility for creating a tRPC client with a WebSocket link and custom headers
703
+ */
704
+ public static create = <TRouter extends AnyTRPCRouter>({
705
+ url,
706
+ buildHeaders = async () => ({}),
707
+ }: WebSocketTrpcClientOptions) => {
708
+ let headers = {};
709
+ const client = createWSClient({
710
+ url: async () => {
711
+ headers = await buildHeaders();
712
+ return url;
713
+ },
714
+ WebSocket: class extends WebSocket {
715
+ constructor(wsUrl: string | URL) {
716
+ super(wsUrl, { headers });
717
+ }
718
+ } as any,
719
+ });
720
+
721
+ return createTRPCClient<TRouter>({
722
+ links: [wsLink({ client } as WebSocketLinkOptions<TRouter>)],
723
+ });
724
+ };
725
+ }
726
+
727
+ export interface AgentCoreTrpcClientOptions {
728
+ /**
729
+ * The ARN of the Bedrock AgentCore Runtime to connect to
730
+ */
731
+ agentRuntimeArn: string;
732
+ }
733
+
734
+ export interface AgentCoreTrpcClientIamOptions extends AgentCoreTrpcClientOptions {
735
+ /**
736
+ * Optional AWS credential provider. If not provided, uses the default
737
+ * credential provider chain from the AWS SDK.
738
+ */
739
+ credentialProvider?: ReturnType<typeof fromNodeProviderChain>;
740
+ }
741
+
742
+ export interface AgentCoreTrpcClientJwtOptions extends AgentCoreTrpcClientOptions {
743
+ /**
744
+ * A function which returns the JWT access token used to authenticate
745
+ */
746
+ accessTokenProvider: () => Promise<string>;
747
+ }
748
+
749
+ /**
750
+ * Client for connecting to a tRPC API on Bedrock AgentCore Runtime via WebSocket.
751
+ */
752
+ export class AgentCoreTrpcClient {
753
+ /**
754
+ * Construct the websocket url for connecting to Bedrock AgentCore Runtime
755
+ */
756
+ private static buildUrl = (agentRuntimeArn: string) => {
757
+ const region = agentRuntimeArn.split(':')[3];
758
+ const url = \`wss://bedrock-agentcore.\${region}.amazonaws.com/runtimes/\${agentRuntimeArn.replace(/:/g, '%3A').replace(/\\//g, '%2F')}/ws\`;
759
+ return { region, url };
760
+ };
761
+
762
+ /**
763
+ * Creates a tRPC client with IAM authentication using AWS credentials.
764
+ *
765
+ * The WebSocket connection is authenticated using AWS Signature Version 4 (SigV4) signed headers.
766
+ * This method signs the WebSocket upgrade request headers directly, rather than using a presigned URL.
767
+ */
768
+ public static withIamAuth = <TRouter extends AnyTRPCRouter>(
769
+ options: AgentCoreTrpcClientIamOptions,
770
+ ) => {
771
+ const credentialProvider =
772
+ options.credentialProvider ?? fromNodeProviderChain();
773
+ const { region, url } = AgentCoreTrpcClient.buildUrl(
774
+ options.agentRuntimeArn,
775
+ );
776
+
777
+ return WebSocketTrpcClient.create<TRouter>({
778
+ url,
779
+ buildHeaders: async () =>
780
+ Object.fromEntries(
781
+ (
782
+ await new AwsClient({
783
+ ...(await credentialProvider()),
784
+ region,
785
+ service: 'bedrock-agentcore',
786
+ }).sign(url)
787
+ ).headers,
788
+ ),
789
+ });
790
+ };
791
+
792
+ /**
793
+ * Creates a tRPC client with JWT (JSON Web Token) authentication.
794
+ *
795
+ * The WebSocket connection includes the JWT as a Bearer token in the Authorization header.
796
+ */
797
+ public static withJwtAuth = <TRouter extends AnyTRPCRouter>(
798
+ options: AgentCoreTrpcClientJwtOptions,
799
+ ) => {
800
+ const { url } = AgentCoreTrpcClient.buildUrl(options.agentRuntimeArn);
801
+
802
+ return WebSocketTrpcClient.create<TRouter>({
803
+ url,
804
+ buildHeaders: async () => ({
805
+ Authorization: \`Bearer \${await options.accessTokenProvider()}\`,
806
+ }),
807
+ });
808
+ };
809
+ }
810
+ "
811
+ `;
812
+
813
+ exports[`ts#strands-agent generator > should match snapshot for generated files > strands-agent-client.ts 1`] = `
814
+ "import { AppRouter } from './router.js';
815
+ import {
816
+ AgentCoreTrpcClient,
817
+ WebSocketTrpcClient,
818
+ } from './agent-core-trpc-client.js';
819
+
820
+ /**
821
+ * Client for connecting to SnapshotAgent on Bedrock AgentCore Runtime via WebSocket from a NodeJS environment
822
+ *
823
+ * Refer to the Nx Plugin for AWS documentation for instructions to connect to this agent in a browser.
824
+ */
825
+ export class SnapshotAgentClient {
826
+ /**
827
+ * Creates a tRPC client with IAM authentication using AWS credentials.
828
+ *
829
+ * The WebSocket connection is authenticated using AWS Signature Version 4 (SigV4)
830
+ */
831
+ public static withIamAuth = AgentCoreTrpcClient.withIamAuth<AppRouter>;
832
+
833
+ /**
834
+ * Creates a tRPC client with JWT (JSON Web Token) authentication.
835
+ *
836
+ * The WebSocket connection includes the JWT as a Bearer token in the Authorization header.
837
+ */
838
+ public static withJwtAuth = AgentCoreTrpcClient.withJwtAuth<AppRouter>;
839
+
840
+ /**
841
+ * Creates a tRPC client for use with a locally running SnapshotAgent.
842
+ */
843
+ public static local = WebSocketTrpcClient.create<AppRouter>;
844
+ }
845
+ "
846
+ `;
847
+
848
+ exports[`ts#strands-agent generator > should match snapshot for generated files > strands-agent-index.ts 1`] = `
849
+ "import { createServer } from 'http';
850
+ import {
851
+ CreateHTTPContextOptions,
852
+ createHTTPHandler,
853
+ } from '@trpc/server/adapters/standalone';
854
+ import { appRouter, AppRouter } from './router.js';
855
+ import { WebSocketServer } from 'ws';
856
+ import cors from 'cors';
857
+ import {
858
+ CreateWSSContextFnOptions,
859
+ applyWSSHandler,
860
+ } from '@trpc/server/adapters/ws';
861
+ import { Context } from './init.js';
862
+
863
+ const PORT = parseInt(process.env.PORT || '8080');
864
+
865
+ const createContext = (
866
+ opts: CreateHTTPContextOptions | CreateWSSContextFnOptions,
867
+ ): Context => ({});
868
+
869
+ const handler = createHTTPHandler({
870
+ router: appRouter,
871
+ middleware: cors(),
872
+ createContext,
873
+ });
874
+
875
+ const server = createServer((req, res) => {
876
+ const url = new URL(req.url || '', \`https://\${req.headers.host}\`);
877
+
878
+ // Handle bedrock agentcore health check
879
+ if (url.pathname === '/ping') {
880
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
881
+ res.end('Healthy');
882
+ return;
883
+ }
884
+
885
+ // Handle other requests with tRPC
886
+ handler(req, res);
887
+ });
888
+
889
+ const wss = new WebSocketServer({
890
+ server,
891
+ path: '/ws',
892
+ });
893
+
894
+ applyWSSHandler<AppRouter>({
895
+ wss,
896
+ router: appRouter,
897
+ createContext,
898
+ });
899
+
900
+ server.listen(PORT);
901
+
902
+ console.log(\`TRPC server listening on port \${PORT}\`);
903
+ "
904
+ `;
905
+
906
+ exports[`ts#strands-agent generator > should match snapshot for generated files > strands-agent-init.ts 1`] = `
907
+ "import { initTRPC } from '@trpc/server';
908
+
909
+ // eslint-disable-next-line
910
+ export interface Context {}
911
+
912
+ export const t = initTRPC.context<Context>().create();
913
+
914
+ export const publicProcedure = t.procedure;
915
+ "
916
+ `;
917
+
918
+ exports[`ts#strands-agent generator > should match snapshot for generated files > strands-agent-router.ts 1`] = `
919
+ "import { publicProcedure, t } from './init.js';
920
+ import { z } from 'zod';
921
+ import { zAsyncIterable } from './schema/z-async-iterable.js';
922
+ import { getAgent } from './agent.js';
923
+
924
+ export const router = t.router;
925
+
926
+ export const appRouter = router({
927
+ invoke: publicProcedure
928
+ .input(z.object({ message: z.string() }))
929
+ .output(
930
+ zAsyncIterable({
931
+ yield: z.string(),
932
+ tracked: false,
933
+ }),
934
+ )
935
+ .subscription(async function* (opts) {
936
+ const agent = getAgent();
937
+
938
+ for await (const event of agent.stream(opts.input.message)) {
939
+ if (
940
+ event.type === 'modelContentBlockDeltaEvent' &&
941
+ event.delta.type === 'textDelta'
942
+ ) {
943
+ yield event.delta.text;
944
+ } else if (event.type === 'modelMessageStopEvent') {
945
+ yield '\\n';
946
+ }
947
+ }
948
+ return;
949
+ }),
950
+ });
951
+
952
+ export type AppRouter = typeof appRouter;
953
+ "
954
+ `;
955
+
956
+ exports[`ts#strands-agent generator > should match snapshot for generated files > strands-agent-z-async-iterable.ts 1`] = `
957
+ "import { isTrackedEnvelope, tracked, TrackedEnvelope } from '@trpc/server';
958
+ import { z } from 'zod';
959
+
960
+ function isAsyncIterable<TValue, TReturn = unknown>(
961
+ value: unknown,
962
+ ): value is AsyncIterable<TValue, TReturn> {
963
+ return !!value && typeof value === 'object' && Symbol.asyncIterator in value;
964
+ }
965
+
966
+ const trackedEnvelopeSchema =
967
+ z.custom<TrackedEnvelope<unknown>>(isTrackedEnvelope);
968
+
969
+ /**
970
+ * A Zod schema helper designed specifically for validating async iterables. This schema ensures that:
971
+ * 1. The value being validated is an async iterable.
972
+ * 2. Each item yielded by the async iterable conforms to a specified type.
973
+ * 3. The return value of the async iterable, if any, also conforms to a specified type.
974
+ */
975
+ export function zAsyncIterable<
976
+ TYieldIn,
977
+ TYieldOut,
978
+ TReturnIn = void,
979
+ TReturnOut = void,
980
+ Tracked extends boolean = false,
981
+ >(opts: {
982
+ /**
983
+ * Validate the value yielded by the async generator
984
+ */
985
+ yield: z.ZodType<TYieldOut, TYieldIn>;
986
+ /**
987
+ * Validate the return value of the async generator
988
+ * @remark not applicable for subscriptions
989
+ */
990
+ return?: z.ZodType<TReturnOut, TReturnIn>;
991
+ /**
992
+ * Whether if the yielded values are tracked
993
+ * @remark only applicable for subscriptions
994
+ */
995
+ tracked?: Tracked;
996
+ }) {
997
+ return z
998
+ .custom<
999
+ AsyncIterable<
1000
+ Tracked extends true ? TrackedEnvelope<TYieldIn> : TYieldIn,
1001
+ TReturnIn
1002
+ >
1003
+ >((val) => isAsyncIterable(val))
1004
+ .transform(async function* (iter) {
1005
+ const iterator = iter[Symbol.asyncIterator]();
1006
+ try {
1007
+ let next;
1008
+ while ((next = await iterator.next()) && !next.done) {
1009
+ if (opts.tracked) {
1010
+ const [id, data] = trackedEnvelopeSchema.parse(next.value);
1011
+ yield tracked(id, await opts.yield.parseAsync(data));
1012
+ continue;
1013
+ }
1014
+ yield opts.yield.parseAsync(next.value);
1015
+ }
1016
+ if (opts.return) {
1017
+ return await opts.return.parseAsync(next.value);
1018
+ }
1019
+ return;
1020
+ } finally {
1021
+ await iterator.return?.();
1022
+ }
1023
+ }) as z.ZodType<
1024
+ AsyncIterable<
1025
+ Tracked extends true ? TrackedEnvelope<TYieldIn> : TYieldIn,
1026
+ TReturnIn
1027
+ >,
1028
+ AsyncIterable<
1029
+ Tracked extends true ? TrackedEnvelope<TYieldOut> : TYieldOut,
1030
+ TReturnOut
1031
+ >
1032
+ >;
1033
+ }
1034
+ "
1035
+ `;