@aws/nx-plugin 1.0.0-rc.55 → 1.0.0-rc.57

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 (81) hide show
  1. package/migrations.json +13 -3
  2. package/package.json +1 -1
  3. package/src/migrations/latest/dynamodb-local-remove-optimize-flag/migration.d.ts +6 -0
  4. package/src/migrations/latest/dynamodb-local-remove-optimize-flag/migration.js +89 -0
  5. package/src/migrations/latest/dynamodb-local-remove-optimize-flag/migration.js.map +1 -0
  6. package/src/migrations/latest/modernize-function-props-cast/migration.js +0 -1
  7. package/src/migrations/latest/modernize-function-props-cast/migration.js.map +1 -1
  8. package/src/migrations/latest/order-access-log-delivery-after-bucket-policy/migration.js +0 -1
  9. package/src/migrations/latest/order-access-log-delivery-after-bucket-policy/migration.js.map +1 -1
  10. package/src/migrations/latest/restrict-cors-to-custom-domains/migration.js +0 -2
  11. package/src/migrations/latest/restrict-cors-to-custom-domains/migration.js.map +1 -1
  12. package/src/migrations/latest/terraform-bootstrap-adopt-existing-bucket/migration.js +0 -1
  13. package/src/migrations/latest/terraform-bootstrap-adopt-existing-bucket/migration.js.map +1 -1
  14. package/src/migrations/latest/ts-agent-session-management-support/migration.d.ts +6 -0
  15. package/src/migrations/latest/ts-agent-session-management-support/migration.js +449 -0
  16. package/src/migrations/latest/ts-agent-session-management-support/migration.js.map +1 -0
  17. package/src/migrations/latest/user-identity-waf-allow-localhost-callback/migration.js +0 -2
  18. package/src/migrations/latest/user-identity-waf-allow-localhost-callback/migration.js.map +1 -1
  19. package/src/py/agent/__snapshots__/generator.constructs.spec.ts.snap +3 -1
  20. package/src/py/agent/__snapshots__/generator.frameworks.spec.ts.snap +12 -12
  21. package/src/py/agent/__snapshots__/generator.protocols.spec.ts.snap +1 -1
  22. package/src/py/agent/a2a-connection/__snapshots__/generator.spec.ts.snap +2 -1
  23. package/src/py/agent/a2a-connection/files/agent-connection/app/__targetAgentSnakeCase___client_strands.py.template +2 -1
  24. package/src/py/agent/a2a-connection/files/langchain/agent-connection/app/__targetAgentSnakeCase___client_langchain.py.template +2 -1
  25. package/src/py/agent/generator.js +4 -0
  26. package/src/py/agent/generator.js.map +1 -1
  27. package/src/py/agent/mcp-connection/__snapshots__/generator.spec.ts.snap +2 -1
  28. package/src/py/agent/mcp-connection/files/agent-connection/app/__mcpServerSnakeCase___client_strands.py.template +2 -1
  29. package/src/py/agent/mcp-connection/files/langchain/agent-connection/app/__mcpServerSnakeCase___client_langchain.py.template +2 -1
  30. package/src/py/agent/react-connection/__snapshots__/generator.spec.ts.snap +6 -0
  31. package/src/py/agent/schema.d.js.map +1 -1
  32. package/src/py/agent/schema.d.ts +3 -0
  33. package/src/py/agent/schema.json +8 -0
  34. package/src/py/dynamodb/__snapshots__/generator.spec.ts.snap +0 -2
  35. package/src/py/mcp-server/__snapshots__/generator.spec.ts.snap +4 -2
  36. package/src/ts/agent/__snapshots__/generator.spec.ts.snap +663 -19
  37. package/src/ts/agent/a2a-connection/__snapshots__/generator.spec.ts.snap +2 -1
  38. package/src/ts/agent/a2a-connection/files/agent-connection/app/__targetAgentKebabCase__-client-strands.ts.template +1 -1
  39. package/src/ts/agent/files/ag-ui/index.ts.template +10 -1
  40. package/src/ts/agent/files/common/agent.ts.template +11 -3
  41. package/src/ts/agent/files/common/session.ts.template +43 -0
  42. package/src/ts/agent/gateway-connection/__snapshots__/generator.spec.ts.snap +1 -0
  43. package/src/ts/agent/generator.d.ts +4 -0
  44. package/src/ts/agent/generator.js +21 -0
  45. package/src/ts/agent/generator.js.map +1 -1
  46. package/src/ts/agent/mcp-connection/__snapshots__/generator.spec.ts.snap +2 -1
  47. package/src/ts/agent/mcp-connection/files/agent-connection/app/__mcpServerKebabCase__-client-strands.ts.template +2 -1
  48. package/src/ts/agent/react-connection/__snapshots__/generator.spec.ts.snap +8 -4
  49. package/src/ts/agent/react-connection/files/src/components/__agentNameClassName__AgentClientProvider.tsx.template +2 -1
  50. package/src/ts/agent/schema.d.js.map +1 -1
  51. package/src/ts/agent/schema.d.ts +3 -0
  52. package/src/ts/agent/schema.json +8 -0
  53. package/src/ts/dynamodb/__snapshots__/generator.spec.ts.snap +0 -2
  54. package/src/ts/mcp-server/__snapshots__/generator.spec.ts.snap +4 -2
  55. package/src/ts/nx-migration/files/migration.ts.template +4 -0
  56. package/src/utils/agent-chat/files/common/agentcore.ts.template +2 -2
  57. package/src/utils/agent-connection/agent-connection.js +4 -0
  58. package/src/utils/agent-connection/agent-connection.js.map +1 -1
  59. package/src/utils/agent-connection/files/core-runtime-config/runtime-config.ts.template +11 -1
  60. package/src/utils/agent-connection/files/core-strands/base/model-errors-strands.ts.template +26 -15
  61. package/src/utils/agent-connection/files/core-strands/base/tool-errors-strands.ts.template +23 -16
  62. package/src/utils/agent-connection/files/py-core-runtime-config/runtime_config.py.template +4 -3
  63. package/src/utils/agent-core-constructs/agent-core-constructs.d.ts +4 -0
  64. package/src/utils/agent-core-constructs/agent-core-constructs.js +7 -1
  65. package/src/utils/agent-core-constructs/agent-core-constructs.js.map +1 -1
  66. package/src/utils/agent-core-constructs/files/cdk/app/agent-core/__nameKebabCase__/__nameKebabCase__.ts.template +131 -3
  67. package/src/utils/agent-core-constructs/files/terraform/app/agent-core/__nameKebabCase__/__nameKebabCase__.tf.template +180 -1
  68. package/src/utils/ast.d.ts +5 -0
  69. package/src/utils/ast.js +25 -0
  70. package/src/utils/ast.js.map +1 -1
  71. package/src/utils/connection/open-api/files/components/__apiNameClassName__Provider.tsx.template +2 -0
  72. package/src/utils/files/common/scripts/src/dynamodb/start-container.ts.template +0 -1
  73. package/src/utils/migration-versions.d.ts +15 -10
  74. package/src/utils/migration-versions.js +48 -36
  75. package/src/utils/migration-versions.js.map +1 -1
  76. package/src/utils/version-upgrade-migration/nx-package-updates.d.ts +18 -8
  77. package/src/utils/version-upgrade-migration/nx-package-updates.js +19 -11
  78. package/src/utils/version-upgrade-migration/nx-package-updates.js.map +1 -1
  79. package/src/utils/version-upgrade-migration/register.d.ts +6 -1
  80. package/src/utils/version-upgrade-migration/register.js +8 -3
  81. package/src/utils/version-upgrade-migration/register.js.map +1 -1
@@ -29,8 +29,8 @@ export const resolveRemoteAgent = async (): Promise<
29
29
  application,
30
30
  environment: 'default',
31
31
  transform: 'json',
32
- })) as { agentRuntimes?: Record<string, string> };
33
- const arn = config?.agentRuntimes?.['TestProjectAgent'];
32
+ })) as { agentRuntimes?: Record<string, { arn: string }> };
33
+ const arn = config?.agentRuntimes?.['TestProjectAgent']?.arn;
34
34
  if (!arn) {
35
35
  throw new Error(
36
36
  \`No deployed agent named 'TestProjectAgent' found in runtime configuration (application \${application}).\`,
@@ -131,8 +131,8 @@ export const resolveRemoteAgent = async (): Promise<
131
131
  application,
132
132
  environment: 'default',
133
133
  transform: 'json',
134
- })) as { agentRuntimes?: Record<string, string> };
135
- const arn = config?.agentRuntimes?.['TestProjectAgent'];
134
+ })) as { agentRuntimes?: Record<string, { arn: string }> };
135
+ const arn = config?.agentRuntimes?.['TestProjectAgent']?.arn;
136
136
  if (!arn) {
137
137
  throw new Error(
138
138
  \`No deployed agent named 'TestProjectAgent' found in runtime configuration (application \${application}).\`,
@@ -224,8 +224,8 @@ export const resolveRemoteAgent = async (): Promise<
224
224
  application,
225
225
  environment: 'default',
226
226
  transform: 'json',
227
- })) as { agentRuntimes?: Record<string, string> };
228
- const arn = config?.agentRuntimes?.['TestProjectAgent'];
227
+ })) as { agentRuntimes?: Record<string, { arn: string }> };
228
+ const arn = config?.agentRuntimes?.['TestProjectAgent']?.arn;
229
229
  if (!arn) {
230
230
  throw new Error(
231
231
  \`No deployed agent named 'TestProjectAgent' found in runtime configuration (application \${application}).\`,
@@ -308,8 +308,8 @@ export const resolveRemoteAgent = async (): Promise<
308
308
  application,
309
309
  environment: 'default',
310
310
  transform: 'json',
311
- })) as { agentRuntimes?: Record<string, string> };
312
- const arn = config?.agentRuntimes?.['TestProjectAgent'];
311
+ })) as { agentRuntimes?: Record<string, { arn: string }> };
312
+ const arn = config?.agentRuntimes?.['TestProjectAgent']?.arn;
313
313
  if (!arn) {
314
314
  throw new Error(
315
315
  \`No deployed agent named 'TestProjectAgent' found in runtime configuration (application \${application}).\`,
@@ -383,8 +383,8 @@ export const resolveRemoteAgent = async (): Promise<
383
383
  application,
384
384
  environment: 'default',
385
385
  transform: 'json',
386
- })) as { agentRuntimes?: Record<string, string> };
387
- const arn = config?.agentRuntimes?.['TestProjectAgent'];
386
+ })) as { agentRuntimes?: Record<string, { arn: string }> };
387
+ const arn = config?.agentRuntimes?.['TestProjectAgent']?.arn;
388
388
  if (!arn) {
389
389
  throw new Error(
390
390
  \`No deployed agent named 'TestProjectAgent' found in runtime configuration (application \${application}).\`,
@@ -493,8 +493,8 @@ export const resolveRemoteAgent = async (): Promise<
493
493
  application,
494
494
  environment: 'default',
495
495
  transform: 'json',
496
- })) as { agentRuntimes?: Record<string, string> };
497
- const arn = config?.agentRuntimes?.['TestProjectAgent'];
496
+ })) as { agentRuntimes?: Record<string, { arn: string }> };
497
+ const arn = config?.agentRuntimes?.['TestProjectAgent']?.arn;
498
498
  if (!arn) {
499
499
  throw new Error(
500
500
  \`No deployed agent named 'TestProjectAgent' found in runtime configuration (application \${application}).\`,
@@ -564,6 +564,81 @@ await chatLoop(new TrpcWebSocketAdapter(), process.env.URL ?? '');
564
564
  "
565
565
  `;
566
566
 
567
+ exports[`ts#agent generator > should generate session.ts using S3 storage by default > session.ts (s3) 1`] = `
568
+ "import { SessionManager } from '@strands-agents/sdk';
569
+ import { LocalFileStorage, S3Storage } from '@strands-agents/sdk/storage';
570
+ import {
571
+ getCurrentSessionId,
572
+ getAgentCoreRuntimeConfig,
573
+ } from '@proj/agent-connection';
574
+
575
+ /**
576
+ * Returns a SessionManager for persisting conversation state across
577
+ * invocations. Local development always uses local file storage for
578
+ * convenience, regardless of the configured session option. Without a
579
+ * configured session option, conversation state is kept in memory only and
580
+ * does not survive process restarts.
581
+ */
582
+ export const getSessionManager = async (): Promise<SessionManager> => {
583
+ const sessionId = getCurrentSessionId();
584
+ if (!sessionId) {
585
+ throw new Error(
586
+ 'No current session id — cannot resolve a SessionManager outside of a request scope.',
587
+ );
588
+ }
589
+ if (process.env.LOCAL_DEV === 'true') {
590
+ return new SessionManager({
591
+ sessionId,
592
+ storage: new LocalFileStorage(
593
+ '../../tmp/agents/strands/test-project-agent',
594
+ ),
595
+ });
596
+ }
597
+ const config = await getAgentCoreRuntimeConfig();
598
+ const bucketName =
599
+ config.agentRuntimes?.['TestProjectAgent']?.session?.bucketName;
600
+ if (!bucketName) {
601
+ throw new Error(
602
+ "No S3 bucket configured for this agent's session in runtime configuration.",
603
+ );
604
+ }
605
+ return new SessionManager({ sessionId, storage: new S3Storage(bucketName) });
606
+ };
607
+ "
608
+ `;
609
+
610
+ exports[`ts#agent generator > should generate session.ts using in-memory storage when session is in-memory > session.ts (in-memory) 1`] = `
611
+ "import { SessionManager } from '@strands-agents/sdk';
612
+ import { LocalFileStorage, InMemoryStorage } from '@strands-agents/sdk/storage';
613
+ import { getCurrentSessionId } from '@proj/agent-connection';
614
+
615
+ /**
616
+ * Returns a SessionManager for persisting conversation state across
617
+ * invocations. Local development always uses local file storage for
618
+ * convenience, regardless of the configured session option. Without a
619
+ * configured session option, conversation state is kept in memory only and
620
+ * does not survive process restarts.
621
+ */
622
+ export const getSessionManager = async (): Promise<SessionManager> => {
623
+ const sessionId = getCurrentSessionId();
624
+ if (!sessionId) {
625
+ throw new Error(
626
+ 'No current session id — cannot resolve a SessionManager outside of a request scope.',
627
+ );
628
+ }
629
+ if (process.env.LOCAL_DEV === 'true') {
630
+ return new SessionManager({
631
+ sessionId,
632
+ storage: new LocalFileStorage(
633
+ '../../tmp/agents/strands/test-project-agent',
634
+ ),
635
+ });
636
+ }
637
+ return new SessionManager({ sessionId, storage: new InMemoryStorage() });
638
+ };
639
+ "
640
+ `;
641
+
567
642
  exports[`ts#agent generator > should match snapshot for BedrockAgentCoreRuntime generated constructs files > agent-Dockerfile 1`] = `
568
643
  "FROM public.ecr.aws/docker/library/node:lts-slim
569
644
 
@@ -596,9 +671,22 @@ CMD [ "node", "--require", "@aws/aws-distro-opentelemetry-node-autoinstrumentati
596
671
  `;
597
672
 
598
673
  exports[`ts#agent generator > should match snapshot for BedrockAgentCoreRuntime generated constructs files > agent-construct.ts 1`] = `
599
- "import { Fn, Lazy, Names, Stack } from 'aws-cdk-lib';
674
+ "import { Fn, Lazy, Names, RemovalPolicy, Stack } from 'aws-cdk-lib';
600
675
  import { Platform } from 'aws-cdk-lib/aws-ecr-assets';
601
676
  import { Connections, IConnectable } from 'aws-cdk-lib/aws-ec2';
677
+ import {
678
+ BlockPublicAccess,
679
+ Bucket,
680
+ BucketEncryption,
681
+ } from 'aws-cdk-lib/aws-s3';
682
+ import { Key } from 'aws-cdk-lib/aws-kms';
683
+ import {
684
+ CfnDelivery,
685
+ CfnDeliveryDestination,
686
+ CfnDeliverySource,
687
+ LogGroup,
688
+ RetentionDays,
689
+ } from 'aws-cdk-lib/aws-logs';
602
690
  import { Construct } from 'constructs';
603
691
  import * as path from 'path';
604
692
  import * as url from 'url';
@@ -610,10 +698,13 @@ import {
610
698
  } from 'aws-cdk-lib/aws-bedrockagentcore';
611
699
  import {
612
700
  PolicyStatement,
701
+ Effect,
702
+ ServicePrincipal,
613
703
  IGrantable,
614
704
  IPrincipal,
615
705
  Grant,
616
706
  } from 'aws-cdk-lib/aws-iam';
707
+ import { suppressRules } from '../../../core/checkov.js';
617
708
  import { RuntimeConfig } from '../../../core/runtime-config.js';
618
709
  import { findWorkspaceRoot } from '../../../core/workspace.js';
619
710
 
@@ -647,6 +738,111 @@ export class SnapshotBedrockAgent
647
738
  platform: Platform.LINUX_ARM64,
648
739
  });
649
740
 
741
+ const sessionKey = new Key(this, 'SessionKey', {
742
+ enableKeyRotation: true,
743
+ });
744
+
745
+ // Allow CloudWatch Logs to use the session key for server access log delivery.
746
+ const stack = Stack.of(this);
747
+ sessionKey.addToResourcePolicy(
748
+ new PolicyStatement({
749
+ effect: Effect.ALLOW,
750
+ principals: [
751
+ new ServicePrincipal(\`logs.\${stack.region}.amazonaws.com\`),
752
+ ],
753
+ actions: [
754
+ 'kms:Encrypt',
755
+ 'kms:Decrypt',
756
+ 'kms:ReEncrypt*',
757
+ 'kms:GenerateDataKey*',
758
+ 'kms:DescribeKey',
759
+ ],
760
+ resources: ['*'],
761
+ conditions: {
762
+ ArnLike: {
763
+ 'kms:EncryptionContext:aws:logs:arn': \`arn:aws:logs:\${stack.region}:\${stack.account}:log-group:*\`,
764
+ },
765
+ },
766
+ }),
767
+ );
768
+
769
+ const sessionAccessLogs = new LogGroup(this, 'SessionAccessLogs', {
770
+ retention: RetentionDays.ONE_YEAR,
771
+ encryptionKey: sessionKey,
772
+ removalPolicy: RemovalPolicy.DESTROY,
773
+ });
774
+
775
+ const sessionBucket = new Bucket(this, 'SessionBucket', {
776
+ enforceSSL: true,
777
+ removalPolicy: RemovalPolicy.RETAIN,
778
+ encryption: BucketEncryption.KMS,
779
+ encryptionKey: sessionKey,
780
+ blockPublicAccess: BlockPublicAccess.BLOCK_ALL,
781
+ });
782
+ suppressRules(
783
+ sessionBucket,
784
+ ['CKV_AWS_21'],
785
+ 'Session data does not need versioning enabled',
786
+ );
787
+ suppressRules(
788
+ sessionBucket,
789
+ ['CKV2_AWS_61'],
790
+ 'Lifecycle configuration not required for session data',
791
+ );
792
+ suppressRules(
793
+ sessionBucket,
794
+ ['CKV_AWS_144'],
795
+ 'Cross-region replication not required for session data',
796
+ );
797
+ suppressRules(
798
+ sessionBucket,
799
+ ['CKV2_AWS_62'],
800
+ 'Event notifications not required for session data',
801
+ );
802
+ suppressRules(
803
+ sessionBucket,
804
+ ['CKV_AWS_18'],
805
+ 'Server access logs are delivered to CloudWatch Logs',
806
+ );
807
+
808
+ const sessionAccessLogsSource: CfnDeliverySource = new CfnDeliverySource(
809
+ this,
810
+ 'SessionAccessLogsSource',
811
+ {
812
+ name: Lazy.string({
813
+ produce: () =>
814
+ Names.uniqueResourceName(sessionAccessLogsSource, {
815
+ maxLength: 60,
816
+ }),
817
+ }),
818
+ logType: 'S3_SERVER_ACCESS_LOGS',
819
+ resourceArn: sessionBucket.bucketArn,
820
+ },
821
+ );
822
+ const sessionBucketPolicy = sessionBucket.policy;
823
+ if (sessionBucketPolicy) {
824
+ sessionAccessLogsSource.node.addDependency(sessionBucketPolicy);
825
+ }
826
+ const sessionAccessLogsDestination: CfnDeliveryDestination =
827
+ new CfnDeliveryDestination(this, 'SessionAccessLogsDestination', {
828
+ name: Lazy.string({
829
+ produce: () =>
830
+ Names.uniqueResourceName(sessionAccessLogsDestination, {
831
+ maxLength: 60,
832
+ }),
833
+ }),
834
+ destinationResourceArn: sessionAccessLogs.logGroupArn,
835
+ });
836
+ const sessionAccessLogsDelivery = new CfnDelivery(
837
+ this,
838
+ 'SessionAccessLogsDelivery',
839
+ {
840
+ deliverySourceName: sessionAccessLogsSource.name,
841
+ deliveryDestinationArn: sessionAccessLogsDestination.attrArn,
842
+ },
843
+ );
844
+ sessionAccessLogsDelivery.addDependency(sessionAccessLogsSource);
845
+
650
846
  this.agentCoreRuntime = new Runtime(this, 'SnapshotBedrockAgent', {
651
847
  runtimeName: Lazy.string({
652
848
  produce: () =>
@@ -675,11 +871,16 @@ export class SnapshotBedrockAgent
675
871
  }),
676
872
  );
677
873
 
874
+ sessionBucket.grantReadWrite(this.agentCoreRuntime);
875
+
678
876
  rc.grantReadAppConfig(this.agentCoreRuntime);
679
877
 
680
878
  rc.set('agentcore', 'agentRuntimes', {
681
879
  ...rc.get('agentcore').agentRuntimes,
682
- SnapshotBedrockAgent: this.agentCoreRuntime.agentRuntimeArn,
880
+ SnapshotBedrockAgent: {
881
+ arn: this.agentCoreRuntime.agentRuntimeArn,
882
+ session: { bucketName: sessionBucket.bucketName },
883
+ },
683
884
  });
684
885
  }
685
886
 
@@ -754,7 +955,16 @@ export * from './workspace.js';
754
955
  `;
755
956
 
756
957
  exports[`ts#agent generator > should match snapshot for Terraform generated files > terraform-agent.tf 1`] = `
757
- "variable "env" {
958
+ "terraform {
959
+ required_providers {
960
+ random = {
961
+ source = "hashicorp/random"
962
+ version = "3.9.0"
963
+ }
964
+ }
965
+ }
966
+
967
+ variable "env" {
758
968
  description = "Environment variables to pass to the agent core runtime"
759
969
  type = map(string)
760
970
  default = {}
@@ -805,6 +1015,145 @@ variable "appconfig_application_arn" {
805
1015
  type = string
806
1016
  }
807
1017
 
1018
+ data "aws_region" "current" {}
1019
+ data "aws_caller_identity" "current" {}
1020
+
1021
+ locals {
1022
+ # Delivery source/destination names have a 60 character maximum. Truncate
1023
+ # the agent name so these stay within the limit.
1024
+ session_access_logs_name_prefix = substr("terraform-snapshot-agent", 0, 30)
1025
+ }
1026
+
1027
+ # Uniquifies resource names below so this construct can be deployed more than
1028
+ # once (e.g. across stages) within the same account/region.
1029
+ resource "random_id" "session_unique_suffix" {
1030
+ byte_length = 4
1031
+ }
1032
+
1033
+ # KMS key encrypting the session bucket and its server access logs.
1034
+ resource "aws_kms_key" "session" {
1035
+ description = "KMS key for terraform-snapshot-agent agent session data"
1036
+ enable_key_rotation = true
1037
+ deletion_window_in_days = 7
1038
+
1039
+ policy = jsonencode({
1040
+ Version = "2012-10-17"
1041
+ Statement = [
1042
+ {
1043
+ Sid = "EnableIAMUserPermissions"
1044
+ Effect = "Allow"
1045
+ Principal = { AWS = "arn:aws:iam::\${data.aws_caller_identity.current.account_id}:root" }
1046
+ Action = "kms:*"
1047
+ Resource = "*"
1048
+ },
1049
+ {
1050
+ Sid = "AllowCloudWatchLogs"
1051
+ Effect = "Allow"
1052
+ Principal = { Service = "logs.\${data.aws_region.current.region}.amazonaws.com" }
1053
+ Action = [
1054
+ "kms:Encrypt",
1055
+ "kms:Decrypt",
1056
+ "kms:ReEncrypt*",
1057
+ "kms:GenerateDataKey*",
1058
+ "kms:DescribeKey"
1059
+ ]
1060
+ Resource = "*"
1061
+ Condition = {
1062
+ ArnLike = {
1063
+ "kms:EncryptionContext:aws:logs:arn" = "arn:aws:logs:\${data.aws_region.current.region}:\${data.aws_caller_identity.current.account_id}:log-group:*"
1064
+ }
1065
+ }
1066
+ }
1067
+ ]
1068
+ })
1069
+
1070
+ tags = var.tags
1071
+ }
1072
+
1073
+ # CloudWatch log group receiving the session bucket's server access logs.
1074
+ resource "aws_cloudwatch_log_group" "session_access_logs" {
1075
+ name = "/aws/s3/terraform-snapshot-agent-session-access-logs-\${random_id.session_unique_suffix.hex}"
1076
+ retention_in_days = 365
1077
+ kms_key_id = aws_kms_key.session.arn
1078
+
1079
+ tags = var.tags
1080
+ }
1081
+
1082
+ # Bucket storing the agent's session data
1083
+ resource "aws_s3_bucket" "session" {
1084
+ #checkov:skip=CKV2_AWS_61:Lifecycle configuration not required for session data
1085
+ #checkov:skip=CKV_AWS_144:Cross-region replication not required for session data
1086
+ #checkov:skip=CKV2_AWS_62:Event notifications not required for session data
1087
+ #checkov:skip=CKV_AWS_18:Server access logs are delivered to CloudWatch Logs (see aws_cloudwatch_log_delivery.session)
1088
+ #checkov:skip=CKV_AWS_21:Session data does not need versioning enabled
1089
+ force_destroy = false
1090
+ }
1091
+
1092
+ resource "aws_s3_bucket_server_side_encryption_configuration" "session" {
1093
+ bucket = aws_s3_bucket.session.id
1094
+
1095
+ rule {
1096
+ apply_server_side_encryption_by_default {
1097
+ kms_master_key_id = aws_kms_key.session.arn
1098
+ sse_algorithm = "aws:kms"
1099
+ }
1100
+ }
1101
+ }
1102
+
1103
+ resource "aws_s3_bucket_public_access_block" "session" {
1104
+ bucket = aws_s3_bucket.session.id
1105
+
1106
+ block_public_acls = true
1107
+ block_public_policy = true
1108
+ ignore_public_acls = true
1109
+ restrict_public_buckets = true
1110
+ }
1111
+
1112
+ resource "aws_s3_bucket_policy" "session" {
1113
+ bucket = aws_s3_bucket.session.id
1114
+
1115
+ policy = jsonencode({
1116
+ Version = "2012-10-17"
1117
+ Statement = [
1118
+ {
1119
+ Sid = "DenyInsecureConnections"
1120
+ Effect = "Deny"
1121
+ Principal = "*"
1122
+ Action = "s3:*"
1123
+ Resource = [
1124
+ aws_s3_bucket.session.arn,
1125
+ "\${aws_s3_bucket.session.arn}/*"
1126
+ ]
1127
+ Condition = {
1128
+ Bool = {
1129
+ "aws:SecureTransport" = "false"
1130
+ }
1131
+ }
1132
+ }
1133
+ ]
1134
+ })
1135
+ }
1136
+
1137
+ # Deliver the session bucket's server access logs to CloudWatch Logs.
1138
+ resource "aws_cloudwatch_log_delivery_source" "session" {
1139
+ name = "\${local.session_access_logs_name_prefix}-access-logs-source-\${random_id.session_unique_suffix.hex}"
1140
+ log_type = "S3_SERVER_ACCESS_LOGS"
1141
+ resource_arn = aws_s3_bucket.session.arn
1142
+ }
1143
+
1144
+ resource "aws_cloudwatch_log_delivery_destination" "session" {
1145
+ name = "\${local.session_access_logs_name_prefix}-access-logs-dest-\${random_id.session_unique_suffix.hex}"
1146
+
1147
+ delivery_destination_configuration {
1148
+ destination_resource_arn = aws_cloudwatch_log_group.session_access_logs.arn
1149
+ }
1150
+ }
1151
+
1152
+ resource "aws_cloudwatch_log_delivery" "session" {
1153
+ delivery_source_name = aws_cloudwatch_log_delivery_source.session.name
1154
+ delivery_destination_arn = aws_cloudwatch_log_delivery_destination.session.arn
1155
+ }
1156
+
808
1157
  module "agent_core_runtime" {
809
1158
  source = "../../../core/agent-core"
810
1159
  agent_runtime_name = "TerraformSnapshotAgent"
@@ -837,6 +1186,29 @@ module "agent_core_runtime" {
837
1186
  "arn:aws:bedrock:*:*:foundation-model/*",
838
1187
  "arn:aws:bedrock:*:*:inference-profile/*"
839
1188
  ]
1189
+ },
1190
+ # Grant access for the agent to read/write its session data
1191
+ {
1192
+ Effect = "Allow"
1193
+ Action = [
1194
+ "s3:GetObject",
1195
+ "s3:PutObject",
1196
+ "s3:DeleteObject",
1197
+ "s3:ListBucket"
1198
+ ]
1199
+ Resource = [
1200
+ aws_s3_bucket.session.arn,
1201
+ "\${aws_s3_bucket.session.arn}/*"
1202
+ ]
1203
+ },
1204
+ # Grant access for the agent to use the session bucket's KMS key
1205
+ {
1206
+ Effect = "Allow"
1207
+ Action = [
1208
+ "kms:Decrypt",
1209
+ "kms:GenerateDataKey*"
1210
+ ]
1211
+ Resource = [aws_kms_key.session.arn]
840
1212
  }
841
1213
  ], var.additional_iam_policy_statements)
842
1214
  tags = var.tags
@@ -848,7 +1220,7 @@ module "add_agent_runtime_to_runtime_config" {
848
1220
 
849
1221
  namespace = "agentcore"
850
1222
  key = "agentRuntimes"
851
- value = { "TerraformSnapshotAgent" = module.agent_core_runtime.agent_core_runtime_arn }
1223
+ value = { "TerraformSnapshotAgent" = { arn = module.agent_core_runtime.agent_core_runtime_arn, session = { bucketName = aws_s3_bucket.session.bucket } } }
852
1224
 
853
1225
  depends_on = [module.agent_core_runtime]
854
1226
  }
@@ -1391,7 +1763,11 @@ output "security_group_id" {
1391
1763
 
1392
1764
  exports[`ts#agent generator > should match snapshot for generated files > agent-agent.ts 1`] = `
1393
1765
  "import { Agent, tool } from '@strands-agents/sdk';
1394
- import { logModelErrors, logToolErrors } from '@proj/agent-connection';
1766
+ import {
1767
+ ModelErrorLoggingPlugin,
1768
+ ToolErrorLoggingPlugin,
1769
+ } from '@proj/agent-connection';
1770
+ import { getSessionManager } from './session.js';
1395
1771
  import { z } from 'zod';
1396
1772
 
1397
1773
  const multiply = tool({
@@ -1410,9 +1786,9 @@ export const getAgent = async () => {
1410
1786
  Use your tools for mathematical tasks.
1411
1787
  Refer to tools as your 'spellbook'.\`,
1412
1788
  tools: [multiply],
1789
+ sessionManager: await getSessionManager(),
1790
+ plugins: [new ModelErrorLoggingPlugin(), new ToolErrorLoggingPlugin()],
1413
1791
  });
1414
- logModelErrors(agent);
1415
- logToolErrors(agent);
1416
1792
  return agent;
1417
1793
  };
1418
1794
  "
@@ -1815,3 +2191,271 @@ export function zAsyncIterable<
1815
2191
  }
1816
2192
  "
1817
2193
  `;
2194
+
2195
+ exports[`ts#agent generator > should not create session bucket infrastructure when session is in-memory (Terraform) > terraform-agent.tf (in-memory) 1`] = `
2196
+ "variable "env" {
2197
+ description = "Environment variables to pass to the agent core runtime"
2198
+ type = map(string)
2199
+ default = {}
2200
+ }
2201
+
2202
+ variable "additional_iam_policy_statements" {
2203
+ description = "Additional IAM policy statements to attach to the agent core runtime role"
2204
+ type = list(object({
2205
+ Effect = string
2206
+ Action = list(string)
2207
+ Resource = list(string)
2208
+ }))
2209
+ default = []
2210
+ }
2211
+
2212
+ variable "tags" {
2213
+ description = "Tags to apply to resources"
2214
+ type = map(string)
2215
+ default = {}
2216
+ }
2217
+
2218
+ # VPC Configuration (optional)
2219
+ variable "enable_vpc" {
2220
+ description = "Run the agent core runtime inside a VPC. Set alongside vpc_id/subnet_ids."
2221
+ type = bool
2222
+ default = false
2223
+ }
2224
+
2225
+ variable "vpc_id" {
2226
+ description = "VPC ID to run the agent core runtime in. Required when enable_vpc is true."
2227
+ type = string
2228
+ default = null
2229
+ }
2230
+
2231
+ variable "subnet_ids" {
2232
+ description = "Subnet IDs to run the agent core runtime in. Required when enable_vpc is true."
2233
+ type = list(string)
2234
+ default = null
2235
+ }
2236
+
2237
+ variable "appconfig_application_id" {
2238
+ description = "ID of the shared runtime-config AppConfig application (from \`core/runtime-config/appconfig.application_id\`). The agent reads its runtime configuration from this application."
2239
+ type = string
2240
+ }
2241
+
2242
+ variable "appconfig_application_arn" {
2243
+ description = "ARN of the shared runtime-config AppConfig application (from \`core/runtime-config/appconfig.application_arn\`). Used to scope the IAM read permission granted to the agent runtime."
2244
+ type = string
2245
+ }
2246
+
2247
+ module "agent_core_runtime" {
2248
+ source = "../../../core/agent-core"
2249
+ agent_runtime_name = "InMemoryTerraformAgent"
2250
+ docker_image_tag = "proj-in-memory-terraform-agent:latest"
2251
+ server_protocol = "HTTP"
2252
+ enable_vpc = var.enable_vpc
2253
+ vpc_id = var.vpc_id
2254
+ subnet_ids = var.subnet_ids
2255
+
2256
+ env = merge({
2257
+ RUNTIME_CONFIG_APP_ID = var.appconfig_application_id
2258
+ }, var.env)
2259
+ additional_iam_policy_statements = concat([
2260
+ {
2261
+ Effect = "Allow"
2262
+ Action = [
2263
+ "appconfig:StartConfigurationSession",
2264
+ "appconfig:GetLatestConfiguration"
2265
+ ]
2266
+ Resource = ["\${var.appconfig_application_arn}/*"]
2267
+ },
2268
+ # Grant access for the agent to invoke bedrock models
2269
+ {
2270
+ Effect = "Allow"
2271
+ Action = [
2272
+ "bedrock:InvokeModel",
2273
+ "bedrock:InvokeModelWithResponseStream"
2274
+ ]
2275
+ Resource = [
2276
+ "arn:aws:bedrock:*:*:foundation-model/*",
2277
+ "arn:aws:bedrock:*:*:inference-profile/*"
2278
+ ]
2279
+ }
2280
+ ], var.additional_iam_policy_statements)
2281
+ tags = var.tags
2282
+ }
2283
+
2284
+ # Add agent runtime ARN to runtime config
2285
+ module "add_agent_runtime_to_runtime_config" {
2286
+ source = "../../../core/runtime-config/entry"
2287
+
2288
+ namespace = "agentcore"
2289
+ key = "agentRuntimes"
2290
+ value = { "InMemoryTerraformAgent" = { arn = module.agent_core_runtime.agent_core_runtime_arn } }
2291
+
2292
+ depends_on = [module.agent_core_runtime]
2293
+ }
2294
+
2295
+ output "agent_core_runtime_role_arn" {
2296
+ description = "ARN of the agent core runtime role"
2297
+ value = module.agent_core_runtime.agent_core_runtime_role_arn
2298
+ }
2299
+
2300
+ output "agent_core_runtime_arn" {
2301
+ description = "ARN of the Bedrock Agent Core runtime"
2302
+ value = module.agent_core_runtime.agent_core_runtime_arn
2303
+ }
2304
+
2305
+ output "invocation_url" {
2306
+ description = "HTTPS invocation URL of the runtime"
2307
+ value = module.agent_core_runtime.invocation_url
2308
+ }
2309
+
2310
+ output "appconfig_application_id" {
2311
+ description = "AppConfig Application ID for runtime config (passthrough of the shared \`appconfig_application_id\` input)."
2312
+ value = var.appconfig_application_id
2313
+ }
2314
+
2315
+ output "security_group_id" {
2316
+ description = "Security group ID of the agent core runtime, for use in ingress rules on resources it must reach (e.g. a database). Null unless enable_vpc is true."
2317
+ value = module.agent_core_runtime.security_group_id
2318
+ }
2319
+ "
2320
+ `;
2321
+
2322
+ exports[`ts#agent generator > should not create session bucket infrastructure when session is in-memory > agent-construct.ts (in-memory) 1`] = `
2323
+ "import { Fn, Lazy, Names, Stack } from 'aws-cdk-lib';
2324
+ import { Platform } from 'aws-cdk-lib/aws-ecr-assets';
2325
+ import { Connections, IConnectable } from 'aws-cdk-lib/aws-ec2';
2326
+ import { Construct } from 'constructs';
2327
+ import * as path from 'path';
2328
+ import * as url from 'url';
2329
+ import {
2330
+ AgentRuntimeArtifact,
2331
+ ProtocolType,
2332
+ Runtime,
2333
+ RuntimeProps,
2334
+ } from 'aws-cdk-lib/aws-bedrockagentcore';
2335
+ import {
2336
+ PolicyStatement,
2337
+ IGrantable,
2338
+ IPrincipal,
2339
+ Grant,
2340
+ } from 'aws-cdk-lib/aws-iam';
2341
+ import { RuntimeConfig } from '../../../core/runtime-config.js';
2342
+ import { findWorkspaceRoot } from '../../../core/workspace.js';
2343
+
2344
+ export type InMemoryAgentProps = Omit<
2345
+ RuntimeProps,
2346
+ | 'runtimeName'
2347
+ | 'protocolConfiguration'
2348
+ | 'agentRuntimeArtifact'
2349
+ | 'authorizerConfiguration'
2350
+ >;
2351
+
2352
+ export class InMemoryAgent
2353
+ extends Construct
2354
+ implements IGrantable, IConnectable
2355
+ {
2356
+ public readonly dockerImage: AgentRuntimeArtifact;
2357
+ public readonly agentCoreRuntime: Runtime;
2358
+
2359
+ constructor(scope: Construct, id: string, props?: InMemoryAgentProps) {
2360
+ super(scope, id);
2361
+
2362
+ const rc = RuntimeConfig.ensure(this);
2363
+
2364
+ // Resolve the bundle output directory containing the Dockerfile and built artifacts
2365
+ const bundleDir = path.join(
2366
+ findWorkspaceRoot(url.fileURLToPath(new URL(import.meta.url))),
2367
+ 'dist/apps/test-project/bundle/agent/in-memory-agent',
2368
+ );
2369
+
2370
+ this.dockerImage = AgentRuntimeArtifact.fromAsset(bundleDir, {
2371
+ platform: Platform.LINUX_ARM64,
2372
+ });
2373
+
2374
+ this.agentCoreRuntime = new Runtime(this, 'InMemoryAgent', {
2375
+ runtimeName: Lazy.string({
2376
+ produce: () =>
2377
+ Names.uniqueResourceName(this.agentCoreRuntime, { maxLength: 40 }),
2378
+ }),
2379
+ protocolConfiguration: ProtocolType.HTTP,
2380
+ agentRuntimeArtifact: this.dockerImage,
2381
+ ...props,
2382
+ environmentVariables: {
2383
+ RUNTIME_CONFIG_APP_ID: rc.appConfigApplicationId,
2384
+ ...props?.environmentVariables,
2385
+ },
2386
+ });
2387
+
2388
+ // Grant access for the agent to invoke bedrock models
2389
+ this.agentCoreRuntime.addToRolePolicy(
2390
+ new PolicyStatement({
2391
+ actions: [
2392
+ 'bedrock:InvokeModel',
2393
+ 'bedrock:InvokeModelWithResponseStream',
2394
+ ],
2395
+ resources: [
2396
+ 'arn:aws:bedrock:*:*:foundation-model/*',
2397
+ 'arn:aws:bedrock:*:*:inference-profile/*',
2398
+ ],
2399
+ }),
2400
+ );
2401
+
2402
+ rc.grantReadAppConfig(this.agentCoreRuntime);
2403
+
2404
+ rc.set('agentcore', 'agentRuntimes', {
2405
+ ...rc.get('agentcore').agentRuntimes,
2406
+ InMemoryAgent: {
2407
+ arn: this.agentCoreRuntime.agentRuntimeArn,
2408
+ },
2409
+ });
2410
+ }
2411
+
2412
+ /**
2413
+ * The principal to grant permissions to.
2414
+ */
2415
+ public get grantPrincipal(): IPrincipal {
2416
+ return this.agentCoreRuntime.grantPrincipal;
2417
+ }
2418
+
2419
+ /**
2420
+ * Network connections for this agent runtime.
2421
+ */
2422
+ public get connections(): Connections {
2423
+ return this.agentCoreRuntime.connections;
2424
+ }
2425
+
2426
+ /**
2427
+ * The HTTPS invocation URL of the runtime.
2428
+ */
2429
+ public get invocationUrl(): string {
2430
+ // The URL must URL-encode the runtime ARN (':' -> '%3A', '/' -> '%2F').
2431
+ // The ARN is a CDK token, so encode at deploy time via Fn.join/Fn.split.
2432
+ const encodedArn = Fn.join(
2433
+ '%2F',
2434
+ Fn.split(
2435
+ '/',
2436
+ Fn.join('%3A', Fn.split(':', this.agentCoreRuntime.agentRuntimeArn)),
2437
+ ),
2438
+ );
2439
+ return \`https://bedrock-agentcore.\${Stack.of(this).region}.amazonaws.com/runtimes/\${encodedArn}/invocations?qualifier=DEFAULT\`;
2440
+ }
2441
+
2442
+ /**
2443
+ * Grants IAM permissions to invoke this agent runtime.
2444
+ *
2445
+ * @param grantee - The IAM principal to grant permissions to
2446
+ */
2447
+ public grantInvokeAccess(grantee: IGrantable) {
2448
+ this.agentCoreRuntime.grantInvoke(grantee);
2449
+
2450
+ Grant.addToPrincipal({
2451
+ grantee,
2452
+ actions: ['bedrock-agentcore:InvokeAgentRuntimeWithWebSocketStream'],
2453
+ resourceArns: [
2454
+ this.agentCoreRuntime.agentRuntimeArn,
2455
+ \`\${this.agentCoreRuntime.agentRuntimeArn}/*\`,
2456
+ ],
2457
+ });
2458
+ }
2459
+ }
2460
+ "
2461
+ `;