diff --git a/README.md b/README.md index d3ac8714..5b7393ff 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,10 @@ Powertools for AWS Lambda (.NET) provides three core utilities: * **[Batch Processing](https://docs.powertools.aws.dev/lambda/dotnet/utilities/batch-processing/)** - The batch processing utility handles partial failures when processing batches from Amazon SQS, Amazon Kinesis Data Streams, and Amazon DynamoDB Streams. +* **[Event Handler AppSync Events](https://docs.powertools.aws.dev/lambda/dotnet/core/event_handler/appsync_events/)** - The event handler AppSync Events utility provides a simple way to handle AppSync events in your Lambda functions. It allows you to easily parse the event and access the data you need, without having to write complex code. + +* **[Event Handler Bedrock Agent Functions](https://docs.powertools.aws.dev/lambda/dotnet/core/event_handler/bedrock_agent_function/)** - The event handler Bedrock Agent Functions utility provides a simple way to handle Amazon Bedrock agent function events in your Lambda functions. It allows you to easily parse the event and access the data you need, without having to write complex code. + ### Installation The Powertools for AWS Lambda (.NET) utilities (.NET 6 and .NET 8) are available as NuGet packages. You can install the packages from [NuGet Gallery](https://www.nuget.org/packages?q=AWS+Lambda+Powertools*) or from Visual Studio editor by searching `AWS.Lambda.Powertools*` to see various utilities available. @@ -63,6 +67,14 @@ The Powertools for AWS Lambda (.NET) utilities (.NET 6 and .NET 8) are available `dotnet add package AWS.Lambda.Powertools.BatchProcessing` +* [AWS.Lambda.Powertools.EventHandler.AppSyncEvents](https://www.nuget.org/packages/AWS.Lambda.Powertools.EventHandler): + + `dotnet add package AWS.Lambda.Powertools.EventHandler` + +* [AWS.Lambda.Powertools.EventHandler.BedrockAgentFunction](https://www.nuget.org/packages/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction): + + `dotnet add package AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction` + ## Examples We have provided examples focused specifically on each of the utilities. Each solution comes with an AWS Serverless Application Model (AWS SAM) templates to run your functions as a Zip package using the AWS Lambda .NET 6 or .NET 8 managed runtime; or as a container package using the AWS base images for .NET. @@ -92,6 +104,7 @@ Knowing which companies are using this library is important to help prioritize t The following companies, among others, use Powertools: * [Caylent](https://caylent.com/) +* [Instil Software](https://instil.co/) * [Pushpay](https://pushpay.com/) ### Sharing your work diff --git a/docs/core/event_handler/bedrock_agent_function.md b/docs/core/event_handler/bedrock_agent_function.md new file mode 100644 index 00000000..e6884368 --- /dev/null +++ b/docs/core/event_handler/bedrock_agent_function.md @@ -0,0 +1,589 @@ +--- +title: Bedrock Agent Function Resolver +description: Event Handler - Bedrock Agent Function Resolver +--- + +# AWS Lambda Powertools for .NET - Bedrock Agent Function Resolver + +## Overview + +The Bedrock Agent Function Resolver is a utility for AWS Lambda that simplifies building serverless applications working with Amazon Bedrock Agents. This library eliminates boilerplate code typically required when implementing Lambda functions that serve as action groups for Bedrock Agents. + +Amazon Bedrock Agents can invoke functions to perform tasks based on user input. This library provides an elegant way to register, manage, and execute these functions with minimal code, handling all the parameter extraction and response formatting automatically. + +Create [Amazon Bedrock Agents](https://docs.aws.amazon.com/bedrock/latest/userguide/agents.html#agents-how) and focus on building your agent's logic without worrying about parsing and routing requests. + +```mermaid +flowchart LR + Bedrock[LLM] <-- uses --> Agent + You[User input] --> Agent + Agent[Bedrock Agent] <-- tool use --> Lambda + subgraph Agent[Bedrock Agent] + ToolDescriptions[Tool Definitions] + end + subgraph Lambda[Lambda Function] + direction TB + Parsing[Parameter Parsing] --> Routing + Routing --> Code[Your code] + Code --> ResponseBuilding[Response Building] + end + style You stroke:#0F0,stroke-width:2px +``` + +## Features + +* Easily expose tools for your Large Language Model (LLM) agents +* Automatic routing based on tool name and function details +* Graceful error handling and response formatting +* Fully compatible with .NET 8 AOT compilation through source generation + +## Terminology + +**Event handler** is a Powertools for AWS feature that processes an event, runs data parsing and validation, routes the request to a specific function, and returns a response to the caller in the proper format. + +**Function details** consist of a list of parameters, defined by their name, data type, and whether they are required. The agent uses these configurations to determine what information it needs to elicit from the user. + +**Action group** is a collection of two resources where you define the actions that the agent should carry out: an OpenAPI schema to define the APIs that the agent can invoke to carry out its tasks, and a Lambda function to execute those actions. + +**Large Language Models (LLM)** are very large deep learning models that are pre-trained on vast amounts of data, capable of extracting meanings from a sequence of text and understanding the relationship between words and phrases on it. + +**Amazon Bedrock Agent** is an Amazon Bedrock feature to build and deploy conversational agents that can interact with your customers using Large Language Models (LLM) and AWS Lambda functions. + + +## Installation + +Install the package via NuGet: + +```bash +dotnet add package AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction +``` + +### Required resources + +You must create an Amazon Bedrock Agent with at least one action group. Each action group can contain up to 5 tools, which in turn need to match the ones defined in your Lambda function. Bedrock must have permission to invoke your Lambda function. + +??? note "Click to see example SAM template" + ```yaml + AWSTemplateFormatVersion: '2010-09-09' + Transform: AWS::Serverless-2016-10-31 + + Globals: + Function: + Timeout: 30 + MemorySize: 256 + Runtime: dotnet8 + + Resources: + HelloWorldFunction: + Type: AWS::Serverless::Function + Properties: + Handler: FunctionHandler + CodeUri: hello_world + + AirlineAgentRole: + Type: AWS::IAM::Role + Properties: + RoleName: !Sub '${AWS::StackName}-AirlineAgentRole' + Description: 'Role for Bedrock Airline agent' + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: bedrock.amazonaws.com + Action: sts:AssumeRole + Policies: + - PolicyName: bedrock + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: 'bedrock:*' + Resource: + - !Sub 'arn:aws:bedrock:us-*::foundation-model/*' + - !Sub 'arn:aws:bedrock:us-*:*:inference-profile/*' + + BedrockAgentInvokePermission: + Type: AWS::Lambda::Permission + Properties: + FunctionName: !Ref HelloWorldFunction + Action: lambda:InvokeFunction + Principal: bedrock.amazonaws.com + SourceAccount: !Ref 'AWS::AccountId' + SourceArn: !Sub 'arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/${AirlineAgent}' + + # Bedrock Agent + AirlineAgent: + Type: AWS::Bedrock::Agent + Properties: + AgentName: AirlineAgent + Description: 'A simple Airline agent' + FoundationModel: !Sub 'arn:aws:bedrock:us-west-2:${AWS::AccountId}:inference-profile/us.amazon.nova-pro-v1:0' + Instruction: | + You are an airport traffic control agent. You will be given a city name and you will return the airport code for that city. + AgentResourceRoleArn: !GetAtt AirlineAgentRole.Arn + AutoPrepare: true + ActionGroups: + - ActionGroupName: AirlineActionGroup + ActionGroupExecutor: + Lambda: !GetAtt AirlineAgentFunction.Arn + FunctionSchema: + Functions: + - Name: getAirportCodeForCity + Description: 'Get the airport code for a given city' + Parameters: + city: + Type: string + Description: 'The name of the city to get the airport code for' + Required: true + ``` + +## Basic Usage + +To create an agent, use the `BedrockAgentFunctionResolver` to register your tools and handle the requests. The resolver will automatically parse the request, route it to the appropriate function, and return a well-formed response that includes the tool's output and any existing session attributes. + +=== "Executable asembly" + + ```csharp + using Amazon.Lambda.Core; + using Amazon.Lambda.RuntimeSupport; + using AWS.Lambda.Powertools.EventHandler.Resolvers; + using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; + + var resolver = new BedrockAgentFunctionResolver(); + + resolver + .Tool("GetWeather", (string city) => $"The weather in {city} is sunny") + .Tool("CalculateSum", (int a, int b) => $"The sum of {a} and {b} is {a + b}") + .Tool("GetCurrentTime", () => $"The current time is {DateTime.Now}"); + + // The function handler that will be called for each Lambda event + var handler = async (BedrockFunctionRequest input, ILambdaContext context) => + { + return await resolver.ResolveAsync(input, context); + }; + + // Build the Lambda runtime client passing in the handler to call for each + // event and the JSON serializer to use for translating Lambda JSON documents + // to .NET types. + await LambdaBootstrapBuilder.Create(handler, new DefaultLambdaJsonSerializer()) + .Build() + .RunAsync(); + ``` + +=== "Class Library" + + ```csharp + using AWS.Lambda.Powertools.EventHandler.Resolvers; + using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; + using Amazon.Lambda.Core; + + [assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] + + namespace MyLambdaFunction + { + public class Function + { + private readonly BedrockAgentFunctionResolver _resolver; + + public Function() + { + _resolver = new BedrockAgentFunctionResolver(); + + // Register simple tool functions + _resolver + .Tool("GetWeather", (string city) => $"The weather in {city} is sunny") + .Tool("CalculateSum", (int a, int b) => $"The sum of {a} and {b} is {a + b}") + .Tool("GetCurrentTime", () => $"The current time is {DateTime.Now}"); + } + + // Lambda handler function + public BedrockFunctionResponse FunctionHandler( + BedrockFunctionRequest input, ILambdaContext context) + { + return _resolver.Resolve(input, context); + } + } + } + ``` +When the Bedrock Agent invokes your Lambda function with a request to use the "GetWeather" tool and a parameter for "city", the resolver automatically extracts the parameter, passes it to your function, and formats the response. + +## Response Format + +You can return any type from your tool function, the library will automatically format the response in a way that Bedrock Agents expect. + +The response will include: + +- The action group name +- The function name +- The function response body, which can be a text response or other structured data in string format +- Any session attributes that were passed in the request or modified during the function execution + +The response body will **always be a string**. + +If you want to return an object the best practice is to override the `ToString()` method of your return type to provide a custom string representation, or if you don't override, create an anonymous object `return new {}` and pass your object, or simply return a string directly. + +```csharp +public class AirportInfo +{ + public string City { get; set; } = string.Empty; + public string Code { get; set; } = string.Empty; + public string Name { get; set; } = string.Empty; + + public override string ToString() + { + return $"{Name} ({Code}) in {City}"; + } +} + +resolver.Tool("getAirportCodeForCity", "Get airport code and full name for a specific city", (string city, ILambdaContext context) => +{ + var airportService = new AirportService(); + var airportInfo = airportService.GetAirportInfoForCity(city); + // Note: Best approach is to override the ToString method in the AirportInfo class + return airportInfo; +}); + +//Alternatively, you can return an anonymous object if you dont override ToString() +// return new { +// airportInfo +// }; +``` + +## How It Works with Amazon Bedrock Agents + +1. When a user interacts with a Bedrock Agent, the agent identifies when it needs to call an action to fulfill the user's request. +2. The agent determines which function to call and what parameters are needed. +3. Bedrock sends a request to your Lambda function with the function name and parameters. +4. The BedrockAgentFunctionResolver automatically: + - Finds the registered handler for the requested function + - Extracts and converts parameters to the correct types + - Invokes your handler with the parameters + - Formats the response in the way Bedrock Agents expect +5. The agent receives the response and uses it to continue the conversation with the user + +## Advanced Usage + +### Custom type serialization + +You can have your own custom types as arguments to the tool function. The library will automatically handle serialization and deserialization of these types. In this case, you need to ensure that your custom type is serializable to JSON, if serialization fails, the object will be null. + +```csharp hl_lines="4" +resolver.Tool( + name: "PriceCalculator", + description: "Calculate total price with tax", + handler: (MyCustomType myCustomType) => + { + var withTax = myCustomType.Price * 1.2m; + return $"Total price with tax: {withTax.ToString("F2", CultureInfo.InvariantCulture)}"; + } +); +``` + +### Custom type serialization native AOT + +For native AOT compilation, you can use JsonSerializerContext and pass it to `BedrockAgentFunctionResolver`. This allows the library to generate the necessary serialization code at compile time, ensuring compatibility with AOT. + +```csharp hl_lines="1 5 12-15" +var resolver = new BedrockAgentFunctionResolver(MycustomSerializationContext.Default); +resolver.Tool( + name: "PriceCalculator", + description: "Calculate total price with tax", + handler: (MyCustomType myCustomType) => + { + var withTax = myCustomType.Price * 1.2m; + return $"Total price with tax: {withTax.ToString("F2", CultureInfo.InvariantCulture)}"; + } +); + +[JsonSerializable(typeof(MyCustomType))] +public partial class MycustomSerializationContext : JsonSerializerContext +{ +} +``` + +### Accessing Lambda Context + +You can access to the original Lambda event or context for additional information. These are passed to the handler function as optional arguments. + +```csharp +resolver.Tool( + "LogRequest", + "Logs request information and returns confirmation", + (string requestId, ILambdaContext context) => + { + context.Logger.LogLine($"Processing request {requestId}"); + return $"Request {requestId} logged successfully"; + }); +``` + +### Handling errors + +By default, we will handle errors gracefully and return a well-formed response to the agent so that it can continue the conversation with the user. + +When an error occurs, we send back an error message in the response body that includes the error type and message. The agent will then use this information to let the user know that something went wrong. + +If you want to handle errors differently, you can return a `BedrockFunctionResponse` with a custom `Body` and `ResponseState` set to `FAILURE`. This is useful when you want to abort the conversation. + +```csharp +resolver.Tool("CustomFailure", () => +{ + // Return a custom FAILURE response + return new BedrockFunctionResponse + { + Response = new Response + { + ActionGroup = "TestGroup", + Function = "CustomFailure", + FunctionResponse = new FunctionResponse + { + ResponseBody = new ResponseBody + { + Text = new TextBody + { + Body = "Critical error occurred: Database unavailable" + } + }, + ResponseState = ResponseState.FAILURE // Mark as FAILURE to abort the conversation + } + } + }; +}); +``` + +### Setting session attributes + +When Bedrock Agents invoke your Lambda function, it can pass session attributes that you can use to store information across multiple interactions with the user. You can access these attributes in your handler function and modify them as needed. + +```csharp +// Create a counter tool that reads and updates session attributes +resolver.Tool("CounterTool", (BedrockFunctionRequest request) => +{ + // Read the current count from session attributes + int currentCount = 0; + if (request.SessionAttributes != null && + request.SessionAttributes.TryGetValue("counter", out var countStr) && + int.TryParse(countStr, out var count)) + { + currentCount = count; + } + + // Increment the counter + currentCount++; + + // Create a new dictionary with updated counter + var updatedSessionAttributes = new Dictionary(request.SessionAttributes ?? new Dictionary()) + { + ["counter"] = currentCount.ToString(), + ["lastAccessed"] = DateTime.UtcNow.ToString("o") + }; + + // Return response with updated session attributes + return new BedrockFunctionResponse + { + Response = new Response + { + ActionGroup = request.ActionGroup, + Function = request.Function, + FunctionResponse = new FunctionResponse + { + ResponseBody = new ResponseBody + { + Text = new TextBody { Body = $"Current count: {currentCount}" } + } + } + }, + SessionAttributes = updatedSessionAttributes, + PromptSessionAttributes = request.PromptSessionAttributes + }; +}); +``` + +### Asynchronous Functions + +Register and use asynchronous functions: + +```csharp +_resolver.Tool( + "FetchUserData", + "Fetches user data from external API", + async (string userId, ILambdaContext ctx) => + { + // Log the request + ctx.Logger.LogLine($"Fetching data for user {userId}"); + + // Simulate API call + await Task.Delay(100); + + // Return user information + return new { Id = userId, Name = "John Doe", Status = "Active" }.ToString(); + }); +``` + +### Direct Access to Request Payload + +Access the raw Bedrock Agent request: + +```csharp +_resolver.Tool( + "ProcessRawRequest", + "Processes the raw Bedrock Agent request", + (BedrockFunctionRequest input) => + { + var functionName = input.Function; + var parameterCount = input.Parameters.Count; + return $"Received request for {functionName} with {parameterCount} parameters"; + }); +``` + +## Dependency Injection + +The library supports dependency injection for integrating with services: + +```csharp +using Microsoft.Extensions.DependencyInjection; + +// Set up dependency injection +var services = new ServiceCollection(); +services.AddSingleton(); +services.AddBedrockResolver(); // Extension method to register the resolver + +var serviceProvider = services.BuildServiceProvider(); +var resolver = serviceProvider.GetRequiredService(); + +// Register a tool that uses an injected service +resolver.Tool( + "GetWeatherForecast", + "Gets the weather forecast for a location", + (string city, IWeatherService weatherService, ILambdaContext ctx) => + { + ctx.Logger.LogLine($"Getting weather for {city}"); + return weatherService.GetForecast(city); + }); +``` + +## Using Attributes to Define Tools + +You can define Bedrock Agent functions using attributes instead of explicit registration. This approach provides a clean, declarative way to organize your tools into classes: + +### Define Tool Classes with Attributes + +```csharp +// Define your tool class with BedrockFunctionType attribute +[BedrockFunctionType] +public class WeatherTools +{ + // Each method marked with BedrockFunctionTool attribute becomes a tool + [BedrockFunctionTool(Name = "GetWeather", Description = "Gets weather forecast for a location")] + public static string GetWeather(string city, int days) + { + return $"Weather forecast for {city} for the next {days} days: Sunny"; + } + + // Supports dependency injection and Lambda context access + [BedrockFunctionTool(Name = "GetDetailedForecast", Description = "Gets detailed weather forecast")] + public static string GetDetailedForecast( + string location, + IWeatherService weatherService, + ILambdaContext context) + { + context.Logger.LogLine($"Getting forecast for {location}"); + return weatherService.GetForecast(location); + } +} +``` + +### Register Tool Classes in Your Application + +Using the extension method provided in the library, you can easily register all tools from a class: + +```csharp + +var services = new ServiceCollection(); +services.AddSingleton(); +services.AddBedrockResolver(); // Extension method to register the resolver + +var serviceProvider = services.BuildServiceProvider(); +var resolver = serviceProvider.GetRequiredService() + .RegisterTool(); // Register tools from the class during service registration + +``` + +## Complete Example with Dependency Injection + +You can find examples in the [Powertools for AWS Lambda (.NET) GitHub repository](https://github.com/aws-powertools/powertools-lambda-dotnet/tree/develop/examples/Event%20Handler/BedrockAgentFunction). + + +```csharp +using Amazon.BedrockAgentRuntime.Model; +using Amazon.Lambda.Core; +using AWS.Lambda.Powertools.EventHandler; +using Microsoft.Extensions.DependencyInjection; + +[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] + +namespace MyBedrockAgent +{ + // Service interfaces and implementations + public interface IWeatherService + { + string GetForecast(string city); + } + + public class WeatherService : IWeatherService + { + public string GetForecast(string city) => $"Weather forecast for {city}: Sunny, 75°F"; + } + + public interface IProductService + { + string CheckInventory(string productId); + } + + public class ProductService : IProductService + { + public string CheckInventory(string productId) => $"Product {productId} has 25 units in stock"; + } + + // Main Lambda function + public class Function + { + private readonly BedrockAgentFunctionResolver _resolver; + + public Function() + { + // Set up dependency injection + var services = new ServiceCollection(); + services.AddSingleton(); + services.AddSingleton(); + services.AddBedrockResolver(); // Extension method to register the resolver + + var serviceProvider = services.BuildServiceProvider(); + _resolver = serviceProvider.GetRequiredService(); + + // Register tool functions that use injected services + _resolver + .Tool("GetWeatherForecast", + "Gets weather forecast for a city", + (string city, IWeatherService weatherService, ILambdaContext ctx) => + { + ctx.Logger.LogLine($"Weather request for {city}"); + return weatherService.GetForecast(city); + }) + .Tool("CheckInventory", + "Checks inventory for a product", + (string productId, IProductService productService) => + productService.CheckInventory(productId)) + .Tool("GetServerTime", + "Returns the current server time", + () => DateTime.Now.ToString("F")); + } + + public ActionGroupInvocationOutput FunctionHandler( + ActionGroupInvocationInput input, ILambdaContext context) + { + return _resolver.Resolve(input, context); + } + } +} +``` \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index 29875d66..68251d62 100644 --- a/docs/index.md +++ b/docs/index.md @@ -163,6 +163,9 @@ Knowing which companies are using this library is important to help prioritize t [**Caylent**](https://caylent.com/){target="_blank" rel="nofollow"} { .card } +[**Instil Software**](https://instil.co/){target="_blank" rel="nofollow"} +{ .card } + [**Pushpay**](https://pushpay.com/){target="_blank" rel="nofollow"} { .card } diff --git a/examples/Event Handler/BedrockAgentFunction/infra/.gitignore b/examples/Event Handler/BedrockAgentFunction/infra/.gitignore new file mode 100644 index 00000000..f60797b6 --- /dev/null +++ b/examples/Event Handler/BedrockAgentFunction/infra/.gitignore @@ -0,0 +1,8 @@ +*.js +!jest.config.js +*.d.ts +node_modules + +# CDK asset staging directory +.cdk.staging +cdk.out diff --git a/examples/Event Handler/BedrockAgentFunction/infra/.npmignore b/examples/Event Handler/BedrockAgentFunction/infra/.npmignore new file mode 100644 index 00000000..c1d6d45d --- /dev/null +++ b/examples/Event Handler/BedrockAgentFunction/infra/.npmignore @@ -0,0 +1,6 @@ +*.ts +!*.d.ts + +# CDK asset staging directory +.cdk.staging +cdk.out diff --git a/examples/Event Handler/BedrockAgentFunction/infra/README.md b/examples/Event Handler/BedrockAgentFunction/infra/README.md new file mode 100644 index 00000000..9315fe5b --- /dev/null +++ b/examples/Event Handler/BedrockAgentFunction/infra/README.md @@ -0,0 +1,14 @@ +# Welcome to your CDK TypeScript project + +This is a blank project for CDK development with TypeScript. + +The `cdk.json` file tells the CDK Toolkit how to execute your app. + +## Useful commands + +* `npm run build` compile typescript to js +* `npm run watch` watch for changes and compile +* `npm run test` perform the jest unit tests +* `npx cdk deploy` deploy this stack to your default AWS account/region +* `npx cdk diff` compare deployed stack with current state +* `npx cdk synth` emits the synthesized CloudFormation template diff --git a/examples/Event Handler/BedrockAgentFunction/infra/cdk.json b/examples/Event Handler/BedrockAgentFunction/infra/cdk.json new file mode 100644 index 00000000..eea31fee --- /dev/null +++ b/examples/Event Handler/BedrockAgentFunction/infra/cdk.json @@ -0,0 +1,96 @@ +{ + "app": "npx ts-node --prefer-ts-exts bin/infra.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/core:validateSnapshotRemovalPolicy": true, + "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, + "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, + "@aws-cdk/aws-route53-patters:useCertificate": true, + "@aws-cdk/customresources:installLatestAwsSdkDefault": false, + "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true, + "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true, + "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true, + "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true, + "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true, + "@aws-cdk/aws-redshift:columnId": true, + "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true, + "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true, + "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true, + "@aws-cdk/aws-kms:aliasNameRef": true, + "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true, + "@aws-cdk/core:includePrefixInUniqueNameGeneration": true, + "@aws-cdk/aws-efs:denyAnonymousAccess": true, + "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true, + "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true, + "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true, + "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true, + "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true, + "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true, + "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true, + "@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true, + "@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true, + "@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true, + "@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true, + "@aws-cdk/aws-eks:nodegroupNameAttribute": true, + "@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true, + "@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true, + "@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false, + "@aws-cdk/aws-s3:keepNotificationInImportedBucket": false, + "@aws-cdk/aws-ecs:enableImdsBlockingDeprecatedFeature": false, + "@aws-cdk/aws-ecs:disableEcsImdsBlocking": true, + "@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": true, + "@aws-cdk/aws-dynamodb:resourcePolicyPerReplica": true, + "@aws-cdk/aws-ec2:ec2SumTImeoutEnabled": true, + "@aws-cdk/aws-appsync:appSyncGraphQLAPIScopeLambdaPermission": true, + "@aws-cdk/aws-rds:setCorrectValueForDatabaseInstanceReadReplicaInstanceResourceId": true, + "@aws-cdk/core:cfnIncludeRejectComplexResourceUpdateCreatePolicyIntrinsics": true, + "@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackages": true, + "@aws-cdk/aws-stepfunctions-tasks:fixRunEcsTaskPolicy": true, + "@aws-cdk/aws-ec2:bastionHostUseAmazonLinux2023ByDefault": true, + "@aws-cdk/aws-route53-targets:userPoolDomainNameMethodWithoutCustomResource": true, + "@aws-cdk/aws-elasticloadbalancingV2:albDualstackWithoutPublicIpv4SecurityGroupRulesDefault": true, + "@aws-cdk/aws-iam:oidcRejectUnauthorizedConnections": true, + "@aws-cdk/core:enableAdditionalMetadataCollection": true, + "@aws-cdk/aws-lambda:createNewPoliciesWithAddToRolePolicy": false, + "@aws-cdk/aws-s3:setUniqueReplicationRoleName": true, + "@aws-cdk/aws-events:requireEventBusPolicySid": true, + "@aws-cdk/core:aspectPrioritiesMutating": true, + "@aws-cdk/aws-dynamodb:retainTableReplica": true, + "@aws-cdk/aws-stepfunctions:useDistributedMapResultWriterV2": true, + "@aws-cdk/s3-notifications:addS3TrustKeyPolicyForSnsSubscriptions": true, + "@aws-cdk/aws-ec2:requirePrivateSubnetsForEgressOnlyInternetGateway": true, + "@aws-cdk/aws-s3:publicAccessBlockedByDefault": true + } +} diff --git a/examples/Event Handler/BedrockAgentFunction/infra/jest.config.js b/examples/Event Handler/BedrockAgentFunction/infra/jest.config.js new file mode 100644 index 00000000..08263b89 --- /dev/null +++ b/examples/Event Handler/BedrockAgentFunction/infra/jest.config.js @@ -0,0 +1,8 @@ +module.exports = { + testEnvironment: 'node', + roots: ['/test'], + testMatch: ['**/*.test.ts'], + transform: { + '^.+\\.tsx?$': 'ts-jest' + } +}; diff --git a/examples/Event Handler/BedrockAgentFunction/infra/lib/bedrockagents-stack.ts b/examples/Event Handler/BedrockAgentFunction/infra/lib/bedrockagents-stack.ts new file mode 100644 index 00000000..001d9912 --- /dev/null +++ b/examples/Event Handler/BedrockAgentFunction/infra/lib/bedrockagents-stack.ts @@ -0,0 +1,121 @@ +import { + Stack, + type StackProps, + CfnOutput, + RemovalPolicy, + Arn, + Duration, +} from 'aws-cdk-lib'; +import type { Construct } from 'constructs'; +import { Runtime, Function as LambdaFunction, Code, Architecture } from 'aws-cdk-lib/aws-lambda'; +import { LogGroup, RetentionDays } from 'aws-cdk-lib/aws-logs'; +import { CfnAgent } from 'aws-cdk-lib/aws-bedrock'; +import { + PolicyDocument, + PolicyStatement, + Role, + ServicePrincipal, +} from 'aws-cdk-lib/aws-iam'; + +export class BedrockAgentsStack extends Stack { + constructor(scope: Construct, id: string, props?: StackProps) { + super(scope, id, props); + + const fnName = 'BedrockAgentsFn'; + const logGroup = new LogGroup(this, 'MyLogGroup', { + logGroupName: `/aws/lambda/${fnName}`, + removalPolicy: RemovalPolicy.DESTROY, + retention: RetentionDays.ONE_DAY, + }); + + const fn = new LambdaFunction(this, 'MyFunction', { + functionName: fnName, + logGroup, + timeout: Duration.minutes(3), + runtime: Runtime.DOTNET_8, + handler: 'BedrockAgentFunction', + code: Code.fromAsset('../release/BedrockAgentFunction.zip'), + architecture: Architecture.X86_64, + }); + + const agentRole = new Role(this, 'MyAgentRole', { + assumedBy: new ServicePrincipal('bedrock.amazonaws.com'), + description: 'Role for Bedrock airport agent', + inlinePolicies: { + bedrock: new PolicyDocument({ + statements: [ + new PolicyStatement({ + actions: [ + 'bedrock:*', + ], + resources: [ + Arn.format( + { + service: 'bedrock', + resource: 'foundation-model/*', + region: 'us-*', + account: '', + }, + Stack.of(this) + ), + Arn.format( + { + service: 'bedrock', + resource: 'inference-profile/*', + region: 'us-*', + account: '*', + }, + Stack.of(this) + ), + ], + }), + ], + }), + }, + }); + + const agent = new CfnAgent(this, 'MyCfnAgent', { + agentName: 'airportAgent', + actionGroups: [ + { + actionGroupName: 'airportActionGroup', + actionGroupExecutor: { + lambda: fn.functionArn, + }, + functionSchema: { + functions: [ + { + name: 'getAirportCodeForCity', + description: 'Get airport code and full airport name for a specific city', + parameters: { + city: { + type: 'string', + description: 'The name of the city to get the airport code for', + required: true, + }, + }, + }, + ], + }, + }, + ], + agentResourceRoleArn: agentRole.roleArn, + autoPrepare: true, + description: 'A simple airport agent', + foundationModel: `arn:aws:bedrock:us-west-2:${Stack.of(this).account}:inference-profile/us.amazon.nova-pro-v1:0`, + instruction: + 'You are an airport traffic control agent. You will be given a city name and you will return the airport code and airport full name for that city.', + }); + + fn.addPermission('BedrockAgentInvokePermission', { + principal: new ServicePrincipal('bedrock.amazonaws.com'), + action: 'lambda:InvokeFunction', + sourceAccount: this.account, + sourceArn: `arn:aws:bedrock:${this.region}:${this.account}:agent/${agent.attrAgentId}`, + }); + + new CfnOutput(this, 'FunctionArn', { + value: fn.functionArn, + }); + } +} diff --git a/examples/Event Handler/BedrockAgentFunction/infra/package-lock.json b/examples/Event Handler/BedrockAgentFunction/infra/package-lock.json new file mode 100644 index 00000000..cb3ffa66 --- /dev/null +++ b/examples/Event Handler/BedrockAgentFunction/infra/package-lock.json @@ -0,0 +1,4448 @@ +{ + "name": "infra", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "infra", + "version": "0.1.0", + "dependencies": { + "aws-cdk-lib": "2.198.0", + "constructs": "^10.0.0" + }, + "bin": { + "infra": "bin/infra.js" + }, + "devDependencies": { + "@types/jest": "^29.5.14", + "@types/node": "22.7.9", + "aws-cdk": "2.1017.1", + "jest": "^29.7.0", + "ts-jest": "^29.2.5", + "ts-node": "^10.9.2", + "typescript": "~5.6.3" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@aws-cdk/asset-awscli-v1": { + "version": "2.2.237", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.237.tgz", + "integrity": "sha512-OlXylbXI52lboFVJBFLae+WB99qWmI121x/wXQHEMj2RaVNVbWE+OAHcDk2Um1BitUQCaTf9ki57B0Fuqx0Rvw==", + "license": "Apache-2.0" + }, + "node_modules/@aws-cdk/asset-node-proxy-agent-v6": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.1.0.tgz", + "integrity": "sha512-7bY3J8GCVxLupn/kNmpPc5VJz8grx+4RKfnnJiO1LG+uxkZfANZG3RMHhE+qQxxwkyQ9/MfPtTpf748UhR425A==", + "license": "Apache-2.0" + }, + "node_modules/@aws-cdk/cloud-assembly-schema": { + "version": "41.2.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-41.2.0.tgz", + "integrity": "sha512-JaulVS6z9y5+u4jNmoWbHZRs9uGOnmn/ktXygNWKNu1k6lF3ad4so3s18eRu15XCbUIomxN9WPYT6Ehh7hzONw==", + "bundleDependencies": [ + "jsonschema", + "semver" + ], + "license": "Apache-2.0", + "dependencies": { + "jsonschema": "~1.4.1", + "semver": "^7.7.1" + }, + "engines": { + "node": ">= 14.15.0" + } + }, + "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/jsonschema": { + "version": "1.4.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/semver": { + "version": "7.7.1", + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.3.tgz", + "integrity": "sha512-V42wFfx1ymFte+ecf6iXghnnP8kWTO+ZLXIyZq+1LAXHHvTZdVxicn4yiVYdYMGaCO3tmqub11AorKkv+iodqw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.4.tgz", + "integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.27.3", + "@babel/helpers": "^7.27.4", + "@babel/parser": "^7.27.4", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.27.4", + "@babel/types": "^7.27.3", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.3.tgz", + "integrity": "sha512-xnlJYj5zepml8NXtjkG0WquFUv8RskFqyFcVgTBp5k+NaA/8uw/K+OSVf8AMGw5e9HKP2ETd5xpK5MLZQD6b4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.3", + "@babel/types": "^7.27.3", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", + "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.4.tgz", + "integrity": "sha512-Y+bO6U+I7ZKaM5G5rDUZiYfUvQPUibYmAFe7EnKdnKBbVXDZxvp+MWOH5gYciY0EPk4EScsuFMQBbEfpdRKSCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.4.tgz", + "integrity": "sha512-BRmLHGwpUqLFR2jzx9orBuX/ABDkj2jLKOXrHDTN2aOKL+jFDDKaRNo9nyYsIl9h/UE/7lMKdDjKQQyxKKDZ7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz", + "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.3", + "@babel/parser": "^7.27.4", + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.3", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.3.tgz", + "integrity": "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", + "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.14", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", + "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/node": { + "version": "22.7.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.9.tgz", + "integrity": "sha512-jrTfRC7FM6nChvU7X2KqcrgquofrWLFDeYC1hKfwNWomVvrn7JIksqf344WN2X/y8xrgqBd2dJATZV4GbatBfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/aws-cdk": { + "version": "2.1017.1", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1017.1.tgz", + "integrity": "sha512-KtDdkMhfVjDeexjpMrVoSlz2mTYI5BE/KotvJ7iFbZy1G0nkpW1ImZ54TdBefeeFmZ+8DAjU3I6nUFtymyOI1A==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "cdk": "bin/cdk" + }, + "engines": { + "node": ">= 14.15.0" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/aws-cdk-lib": { + "version": "2.198.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.198.0.tgz", + "integrity": "sha512-CyZ+lnRsCsLskzQLPO0EiGl5EVcLluhfa67df3b8/gJfsm+91SHJa75OH+ymdGtUp5Vn/MWUPsujw0EhWMfsIQ==", + "bundleDependencies": [ + "@balena/dockerignore", + "case", + "fs-extra", + "ignore", + "jsonschema", + "minimatch", + "punycode", + "semver", + "table", + "yaml", + "mime-types" + ], + "license": "Apache-2.0", + "dependencies": { + "@aws-cdk/asset-awscli-v1": "2.2.237", + "@aws-cdk/asset-node-proxy-agent-v6": "^2.1.0", + "@aws-cdk/cloud-assembly-schema": "^41.2.0", + "@balena/dockerignore": "^1.0.2", + "case": "1.6.3", + "fs-extra": "^11.3.0", + "ignore": "^5.3.2", + "jsonschema": "^1.5.0", + "mime-types": "^2.1.35", + "minimatch": "^3.1.2", + "punycode": "^2.3.1", + "semver": "^7.7.2", + "table": "^6.9.0", + "yaml": "1.10.2" + }, + "engines": { + "node": ">= 14.15.0" + }, + "peerDependencies": { + "constructs": "^10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/@balena/dockerignore": { + "version": "1.0.2", + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/aws-cdk-lib/node_modules/ajv": { + "version": "8.17.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/aws-cdk-lib/node_modules/ansi-regex": { + "version": "5.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/ansi-styles": { + "version": "4.3.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aws-cdk-lib/node_modules/astral-regex": { + "version": "2.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/balanced-match": { + "version": "1.0.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/brace-expansion": { + "version": "1.1.11", + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/aws-cdk-lib/node_modules/case": { + "version": "1.6.3", + "inBundle": true, + "license": "(MIT OR GPL-3.0-or-later)", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/color-convert": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/color-name": { + "version": "1.1.4", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/concat-map": { + "version": "0.0.1", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/emoji-regex": { + "version": "8.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/fast-deep-equal": { + "version": "3.1.3", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/fast-uri": { + "version": "3.0.6", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "inBundle": true, + "license": "BSD-3-Clause" + }, + "node_modules/aws-cdk-lib/node_modules/fs-extra": { + "version": "11.3.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/aws-cdk-lib/node_modules/graceful-fs": { + "version": "4.2.11", + "inBundle": true, + "license": "ISC" + }, + "node_modules/aws-cdk-lib/node_modules/ignore": { + "version": "5.3.2", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/aws-cdk-lib/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/json-schema-traverse": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/jsonfile": { + "version": "6.1.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/aws-cdk-lib/node_modules/jsonschema": { + "version": "1.5.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/aws-cdk-lib/node_modules/lodash.truncate": { + "version": "4.4.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/mime-db": { + "version": "1.52.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/aws-cdk-lib/node_modules/mime-types": { + "version": "2.1.35", + "inBundle": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/aws-cdk-lib/node_modules/minimatch": { + "version": "3.1.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/aws-cdk-lib/node_modules/punycode": { + "version": "2.3.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/aws-cdk-lib/node_modules/require-from-string": { + "version": "2.0.2", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/semver": { + "version": "7.7.2", + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/aws-cdk-lib/node_modules/slice-ansi": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/aws-cdk-lib/node_modules/string-width": { + "version": "4.2.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/strip-ansi": { + "version": "6.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/table": { + "version": "6.9.0", + "inBundle": true, + "license": "BSD-3-Clause", + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/universalify": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/yaml": { + "version": "1.10.2", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", + "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", + "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001718", + "electron-to-chromium": "^1.5.160", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001720", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001720.tgz", + "integrity": "sha512-Ec/2yV2nNPwb4DnTANEV99ZWwm3ZWfdlfkQbWSDDt+PsXEVYwlhPH8tdMaPunYTKKmz7AnHi2oNEi1GcmKCD8g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/constructs": { + "version": "10.4.2", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.4.2.tgz", + "integrity": "sha512-wsNxBlAott2qg8Zv87q3eYZYgheb9lchtBfjHzzLHtXbttwSrHPs1NNQbBrmbb1YZvYg2+Vh0Dor76w4mFxJkA==", + "license": "Apache-2.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", + "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.161", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.161.tgz", + "integrity": "sha512-hwtetwfKNZo/UlwHIVBlKZVdy7o8bIZxxKs0Mv/ROPiQQQmDgdm5a+KvKtBsxM8ZjFzTaCeLoodZ8jiBE3o9rA==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jake": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", + "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-jest": { + "version": "29.3.4", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.3.4.tgz", + "integrity": "sha512-Iqbrm8IXOmV+ggWHOTEbjwyCf2xZlUMv5npExksXohL+tk8va4Fjhb+X2+Rt9NBmgO7bJ8WpnMLOwih/DnMlFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bs-logger": "^0.2.6", + "ejs": "^3.1.10", + "fast-json-stable-stringify": "^2.1.0", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.7.2", + "type-fest": "^4.41.0", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0", + "@jest/types": "^29.0.0", + "babel-jest": "^29.0.0", + "jest": "^29.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/examples/Event Handler/BedrockAgentFunction/infra/package.json b/examples/Event Handler/BedrockAgentFunction/infra/package.json new file mode 100644 index 00000000..eb6545ca --- /dev/null +++ b/examples/Event Handler/BedrockAgentFunction/infra/package.json @@ -0,0 +1,26 @@ +{ + "name": "infra", + "version": "0.1.0", + "bin": { + "infra": "bin/infra.js" + }, + "scripts": { + "build": "tsc", + "watch": "tsc -w", + "test": "jest", + "cdk": "cdk" + }, + "devDependencies": { + "@types/jest": "^29.5.14", + "@types/node": "22.7.9", + "jest": "^29.7.0", + "ts-jest": "^29.2.5", + "aws-cdk": "2.1017.1", + "ts-node": "^10.9.2", + "typescript": "~5.6.3" + }, + "dependencies": { + "aws-cdk-lib": "2.198.0", + "constructs": "^10.0.0" + } +} diff --git a/examples/Event Handler/BedrockAgentFunction/infra/tsconfig.json b/examples/Event Handler/BedrockAgentFunction/infra/tsconfig.json new file mode 100644 index 00000000..28bb557f --- /dev/null +++ b/examples/Event Handler/BedrockAgentFunction/infra/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": [ + "es2022" + ], + "declaration": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": false, + "inlineSourceMap": true, + "inlineSources": true, + "experimentalDecorators": true, + "strictPropertyInitialization": false, + "typeRoots": [ + "./node_modules/@types" + ] + }, + "exclude": [ + "node_modules", + "cdk.out" + ] +} diff --git a/examples/Event Handler/BedrockAgentFunction/src/AirportService.cs b/examples/Event Handler/BedrockAgentFunction/src/AirportService.cs new file mode 100644 index 00000000..aa26e7f9 --- /dev/null +++ b/examples/Event Handler/BedrockAgentFunction/src/AirportService.cs @@ -0,0 +1,222 @@ +namespace BedrockAgentFunction; + +public class AirportService +{ + private readonly Dictionary _airportsByCity = new(StringComparer.OrdinalIgnoreCase) + { + { + "New York", + new AirportInfo { City = "New York", Code = "JFK", Name = "John F. Kennedy International Airport" } + }, + { "London", new AirportInfo { City = "London", Code = "LHR", Name = "London Heathrow Airport" } }, + { "Paris", new AirportInfo { City = "Paris", Code = "CDG", Name = "Charles de Gaulle Airport" } }, + { "Tokyo", new AirportInfo { City = "Tokyo", Code = "HND", Name = "Tokyo Haneda Airport" } }, + { "Sydney", new AirportInfo { City = "Sydney", Code = "SYD", Name = "Sydney Airport" } }, + { + "Los Angeles", + new AirportInfo { City = "Los Angeles", Code = "LAX", Name = "Los Angeles International Airport" } + }, + { "Berlin", new AirportInfo { City = "Berlin", Code = "TXL", Name = "Berlin Tegel Airport" } }, + { "Dubai", new AirportInfo { City = "Dubai", Code = "DXB", Name = "Dubai International Airport" } }, + { + "Toronto", + new AirportInfo { City = "Toronto", Code = "YYZ", Name = "Toronto Pearson International Airport" } + }, + { "Singapore", new AirportInfo { City = "Singapore", Code = "SIN", Name = "Singapore Changi Airport" } }, + { "Hong Kong", new AirportInfo { City = "Hong Kong", Code = "HKG", Name = "Hong Kong International Airport" } }, + { "Madrid", new AirportInfo { City = "Madrid", Code = "MAD", Name = "Adolfo Suárez Madrid–Barajas Airport" } }, + { "Rome", new AirportInfo { City = "Rome", Code = "FCO", Name = "Leonardo da Vinci International Airport" } }, + { "Moscow", new AirportInfo { City = "Moscow", Code = "SVO", Name = "Sheremetyevo International Airport" } }, + { + "São Paulo", + new AirportInfo + { + City = "São Paulo", Code = "GRU", + Name = "São Paulo/Guarulhos–Governador André Franco Montoro International Airport" + } + }, + { "Istanbul", new AirportInfo { City = "Istanbul", Code = "IST", Name = "Istanbul Airport" } }, + { "Bangkok", new AirportInfo { City = "Bangkok", Code = "BKK", Name = "Suvarnabhumi Airport" } }, + { + "Mexico City", + new AirportInfo { City = "Mexico City", Code = "MEX", Name = "Mexico City International Airport" } + }, + { "Cairo", new AirportInfo { City = "Cairo", Code = "CAI", Name = "Cairo International Airport" } }, + { + "Buenos Aires", + new AirportInfo { City = "Buenos Aires", Code = "EZE", Name = "Ministro Pistarini International Airport" } + }, + { + "Kuala Lumpur", + new AirportInfo { City = "Kuala Lumpur", Code = "KUL", Name = "Kuala Lumpur International Airport" } + }, + { "Amsterdam", new AirportInfo { City = "Amsterdam", Code = "AMS", Name = "Amsterdam Airport Schiphol" } }, + { "Barcelona", new AirportInfo { City = "Barcelona", Code = "BCN", Name = "Barcelona–El Prat Airport" } }, + { "Lima", new AirportInfo { City = "Lima", Code = "LIM", Name = "Jorge Chávez International Airport" } }, + { "Seoul", new AirportInfo { City = "Seoul", Code = "ICN", Name = "Incheon International Airport" } }, + { + "Rio de Janeiro", + new AirportInfo + { + City = "Rio de Janeiro", Code = "GIG", + Name = "Rio de Janeiro/Galeão–Antonio Carlos Jobim International Airport" + } + }, + { "Dublin", new AirportInfo { City = "Dublin", Code = "DUB", Name = "Dublin Airport" } }, + { "Brussels", new AirportInfo { City = "Brussels", Code = "BRU", Name = "Brussels Airport" } }, + { "Lisbon", new AirportInfo { City = "Lisbon", Code = "LIS", Name = "Lisbon Portela Airport" } }, + { "Athens", new AirportInfo { City = "Athens", Code = "ATH", Name = "Athens International Airport" } }, + { "Oslo", new AirportInfo { City = "Oslo", Code = "OSL", Name = "Oslo Airport, Gardermoen" } }, + { "Stockholm", new AirportInfo { City = "Stockholm", Code = "ARN", Name = "Stockholm Arlanda Airport" } }, + { "Helsinki", new AirportInfo { City = "Helsinki", Code = "HEL", Name = "Helsinki-Vantaa Airport" } }, + { "Prague", new AirportInfo { City = "Prague", Code = "PRG", Name = "Václav Havel Airport Prague" } }, + { "Warsaw", new AirportInfo { City = "Warsaw", Code = "WAW", Name = "Warsaw Chopin Airport" } }, + { "Copenhagen", new AirportInfo { City = "Copenhagen", Code = "CPH", Name = "Copenhagen Airport" } }, + { + "Budapest", + new AirportInfo { City = "Budapest", Code = "BUD", Name = "Budapest Ferenc Liszt International Airport" } + }, + { "Osaka", new AirportInfo { City = "Osaka", Code = "KIX", Name = "Kansai International Airport" } }, + { + "San Francisco", + new AirportInfo { City = "San Francisco", Code = "SFO", Name = "San Francisco International Airport" } + }, + { "Miami", new AirportInfo { City = "Miami", Code = "MIA", Name = "Miami International Airport" } }, + { + "Seattle", new AirportInfo { City = "Seattle", Code = "SEA", Name = "Seattle–Tacoma International Airport" } + }, + { "Vancouver", new AirportInfo { City = "Vancouver", Code = "YVR", Name = "Vancouver International Airport" } }, + { "Melbourne", new AirportInfo { City = "Melbourne", Code = "MEL", Name = "Melbourne Airport" } }, + { "Auckland", new AirportInfo { City = "Auckland", Code = "AKL", Name = "Auckland Airport" } }, + { "Doha", new AirportInfo { City = "Doha", Code = "DOH", Name = "Hamad International Airport" } }, + { + "Kuwait City", new AirportInfo { City = "Kuwait City", Code = "KWI", Name = "Kuwait International Airport" } + }, + { + "Bangalore", new AirportInfo { City = "Bangalore", Code = "BLR", Name = "Kempegowda International Airport" } + }, + { + "Beijing", + new AirportInfo { City = "Beijing", Code = "PEK", Name = "Beijing Capital International Airport" } + }, + { + "Shanghai", + new AirportInfo { City = "Shanghai", Code = "PVG", Name = "Shanghai Pudong International Airport" } + }, + { "Manila", new AirportInfo { City = "Manila", Code = "MNL", Name = "Ninoy Aquino International Airport" } }, + { + "Jakarta", new AirportInfo { City = "Jakarta", Code = "CGK", Name = "Soekarno–Hatta International Airport" } + }, + { + "Santiago", + new AirportInfo + { City = "Santiago", Code = "SCL", Name = "Comodoro Arturo Merino Benítez International Airport" } + }, + { "Lagos", new AirportInfo { City = "Lagos", Code = "LOS", Name = "Murtala Muhammed International Airport" } }, + { "Nairobi", new AirportInfo { City = "Nairobi", Code = "NBO", Name = "Jomo Kenyatta International Airport" } }, + { "Chicago", new AirportInfo { City = "Chicago", Code = "ORD", Name = "O'Hare International Airport" } }, + { + "Atlanta", + new AirportInfo + { City = "Atlanta", Code = "ATL", Name = "Hartsfield–Jackson Atlanta International Airport" } + }, + { + "Dallas", + new AirportInfo { City = "Dallas", Code = "DFW", Name = "Dallas/Fort Worth International Airport" } + }, + { + "Washington, D.C.", + new AirportInfo + { City = "Washington, D.C.", Code = "IAD", Name = "Washington Dulles International Airport" } + }, + { "Boston", new AirportInfo { City = "Boston", Code = "BOS", Name = "Logan International Airport" } }, + { + "Philadelphia", + new AirportInfo { City = "Philadelphia", Code = "PHL", Name = "Philadelphia International Airport" } + }, + { "Orlando", new AirportInfo { City = "Orlando", Code = "MCO", Name = "Orlando International Airport" } }, + { "Denver", new AirportInfo { City = "Denver", Code = "DEN", Name = "Denver International Airport" } }, + { + "Phoenix", + new AirportInfo { City = "Phoenix", Code = "PHX", Name = "Phoenix Sky Harbor International Airport" } + }, + { "Las Vegas", new AirportInfo { City = "Las Vegas", Code = "LAS", Name = "McCarran International Airport" } }, + { + "Houston", new AirportInfo { City = "Houston", Code = "IAH", Name = "George Bush Intercontinental Airport" } + }, + { + "Detroit", + new AirportInfo { City = "Detroit", Code = "DTW", Name = "Detroit Metropolitan Wayne County Airport" } + }, + { + "Charlotte", + new AirportInfo { City = "Charlotte", Code = "CLT", Name = "Charlotte Douglas International Airport" } + }, + { + "Baltimore", + new AirportInfo + { + City = "Baltimore", Code = "BWI", Name = "Baltimore/Washington International Thurgood Marshall Airport" + } + }, + { + "Minneapolis", + new AirportInfo + { City = "Minneapolis", Code = "MSP", Name = "Minneapolis–Saint Paul International Airport" } + }, + { "San Diego", new AirportInfo { City = "San Diego", Code = "SAN", Name = "San Diego International Airport" } }, + { "Portland", new AirportInfo { City = "Portland", Code = "PDX", Name = "Portland International Airport" } }, + { + "Salt Lake City", + new AirportInfo { City = "Salt Lake City", Code = "SLC", Name = "Salt Lake City International Airport" } + }, + { + "Cincinnati", + new AirportInfo + { City = "Cincinnati", Code = "CVG", Name = "Cincinnati/Northern Kentucky International Airport" } + }, + { + "St. Louis", + new AirportInfo { City = "St. Louis", Code = "STL", Name = "St. Louis Lambert International Airport" } + }, + { + "Indianapolis", + new AirportInfo { City = "Indianapolis", Code = "IND", Name = "Indianapolis International Airport" } + }, + { "Tampa", new AirportInfo { City = "Tampa", Code = "TPA", Name = "Tampa International Airport" } }, + { "Milan", new AirportInfo { City = "Milan", Code = "MXP", Name = "Milan Malpensa Airport" } }, + { "Frankfurt", new AirportInfo { City = "Frankfurt", Code = "FRA", Name = "Frankfurt am Main Airport" } }, + { "Munich", new AirportInfo { City = "Munich", Code = "MUC", Name = "Munich Airport" } }, + { + "Mumbai", + new AirportInfo + { City = "Mumbai", Code = "BOM", Name = "Chhatrapati Shivaji Maharaj International Airport" } + }, + { "Cape Town", new AirportInfo { City = "Cape Town", Code = "CPT", Name = "Cape Town International Airport" } }, + { "Zurich", new AirportInfo { City = "Zurich", Code = "ZRH", Name = "Zurich Airport" } }, + { "Vienna", new AirportInfo { City = "Vienna", Code = "VIE", Name = "Vienna International Airport" } } + // Add more airports as needed + }; + + public AirportInfo GetAirportInfoForCity(string city) + { + if (_airportsByCity.TryGetValue(city, out var airportInfo)) + { + return airportInfo; + } + + throw new KeyNotFoundException($"No airport information found for city: {city}"); + } +} + +public class AirportInfo +{ + public string City { get; set; } = string.Empty; + public string Code { get; set; } = string.Empty; + public string Name { get; set; } = string.Empty; + + public override string ToString() + { + return $"{Name} ({Code}) in {City}"; + } +} \ No newline at end of file diff --git a/examples/Event Handler/BedrockAgentFunction/src/BedrockAgentFunction.csproj b/examples/Event Handler/BedrockAgentFunction/src/BedrockAgentFunction.csproj new file mode 100644 index 00000000..bcd2c51c --- /dev/null +++ b/examples/Event Handler/BedrockAgentFunction/src/BedrockAgentFunction.csproj @@ -0,0 +1,22 @@ + + + Exe + net8.0 + enable + enable + true + Lambda + + true + + true + + + + + + + + + + \ No newline at end of file diff --git a/examples/Event Handler/BedrockAgentFunction/src/Function.cs b/examples/Event Handler/BedrockAgentFunction/src/Function.cs new file mode 100644 index 00000000..c4e847ef --- /dev/null +++ b/examples/Event Handler/BedrockAgentFunction/src/Function.cs @@ -0,0 +1,45 @@ +using Amazon.Lambda.Core; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; +using AWS.Lambda.Powertools.EventHandler.Resolvers; +using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; +using AWS.Lambda.Powertools.Logging; +using BedrockAgentFunction; +using Microsoft.Extensions.Logging; + + +var logger = LoggerFactory.Create(builder => +{ + builder.AddPowertoolsLogger(config => { config.Service = "AirportService"; }); +}).CreatePowertoolsLogger(); + +var resolver = new BedrockAgentFunctionResolver(); + + +resolver.Tool("getAirportCodeForCity", "Get airport code and full name for a specific city", (string city, ILambdaContext context) => +{ + logger.LogInformation("Getting airport code for city: {City}", city); + var airportService = new AirportService(); + var airportInfo = airportService.GetAirportInfoForCity(city); + + logger.LogInformation("Airport for {City}: {AirportInfoCode} - {AirportInfoName}", city, airportInfo.Code, airportInfo.Name); + + // Note: Best approach is to override the ToString method in the AirportInfo class + return airportInfo; +}); + + +// The function handler that will be called for each Lambda event +var handler = async (BedrockFunctionRequest input, ILambdaContext context) => +{ + return await resolver.ResolveAsync(input, context); +}; + +// Build the Lambda runtime client passing in the handler to call for each +// event and the JSON serializer to use for translating Lambda JSON documents +// to .NET types. +await LambdaBootstrapBuilder.Create(handler, new DefaultLambdaJsonSerializer()) + .Build() + .RunAsync(); + + diff --git a/examples/Event Handler/BedrockAgentFunction/src/Readme.md b/examples/Event Handler/BedrockAgentFunction/src/Readme.md new file mode 100644 index 00000000..d0cfb668 --- /dev/null +++ b/examples/Event Handler/BedrockAgentFunction/src/Readme.md @@ -0,0 +1,47 @@ +# Powertools for AWS Lambda .NET - Bedrock Agent Function example + +This starter project consists of: +* Function.cs - file contain C# top level statements that define the function to be called for each event and starts the Lambda runtime client. +* AirportService.cs - Static list of airport codes and names used by the function. +* aws-lambda-tools-defaults.json - default argument settings for use with Visual Studio and command line deployment tools for AWS + +## Executable Assembly + +.NET Lambda projects that use C# top level statements like this project must be deployed as an executable assembly instead of a class library. To indicate to Lambda that the .NET function is an executable assembly the +Lambda function handler value is set to the .NET Assembly name. This is different then deploying as a class library where the function handler string includes the assembly, type and method name. + +To deploy as an executable assembly the Lambda runtime client must be started to listen for incoming events to process. To start +the Lambda runtime client add the `Amazon.Lambda.RuntimeSupport` NuGet package and add the following code at the end of the +of the file containing top-level statements to start the runtime. + +```csharp +await LambdaBootstrapBuilder.Create(handler, new DefaultLambdaJsonSerializer()) + .Build() + .RunAsync(); +``` + +Pass into the Lambda runtime client a function handler as either an `Action<>` or `Func<>` for the code that +should be called for each event. If the handler takes in an input event besides `System.IO.Stream` then +the JSON serializer must also be passed into the `Create` method. + +## Here are some steps to follow to get started from the command line: + +Once you have edited your template and code you can deploy your application using the [Amazon.Lambda.Tools Global Tool](https://github.com/aws/aws-extensions-for-dotnet-cli#aws-lambda-amazonlambdatools) from the command line. + +Install Amazon.Lambda.Tools Global Tools if not already installed. +``` + dotnet tool install -g Amazon.Lambda.Tools +``` + +If already installed check if new version is available. +``` + dotnet tool update -g Amazon.Lambda.Tools +``` + +Deploy function to AWS Lambda +``` + cd "BedrockAgentFunction/src" + dotnet lambda package --output-package ../release/BedrockAgentFunction.zip + cd ../infra + npm run cdk deploy -- --require-approval never +``` \ No newline at end of file diff --git a/examples/Event Handler/BedrockAgentFunction/src/aws-lambda-tools-defaults.json b/examples/Event Handler/BedrockAgentFunction/src/aws-lambda-tools-defaults.json new file mode 100644 index 00000000..1dc447ae --- /dev/null +++ b/examples/Event Handler/BedrockAgentFunction/src/aws-lambda-tools-defaults.json @@ -0,0 +1,15 @@ +{ + "Information": [ + "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", + "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", + "dotnet lambda help", + "All the command line options for the Lambda command can be specified in this file." + ], + "profile": "", + "region": "", + "configuration": "Release", + "function-runtime": "dotnet8", + "function-memory-size": 512, + "function-timeout": 30, + "function-handler": "BedrockAgentFunction" +} \ No newline at end of file diff --git a/libraries/AWS.Lambda.Powertools.sln b/libraries/AWS.Lambda.Powertools.sln index 5d7cd4f9..c3056d14 100644 --- a/libraries/AWS.Lambda.Powertools.sln +++ b/libraries/AWS.Lambda.Powertools.sln @@ -109,6 +109,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWS.Lambda.Powertools.Event EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWS.Lambda.Powertools.EventHandler", "src\AWS.Lambda.Powertools.EventHandler\AWS.Lambda.Powertools.EventHandler.csproj", "{F4B8D5AF-D3CA-4910-A14D-E5BAEF0FD1DE}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction", "src\AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction\AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.csproj", "{281F7EB5-ACE5-458F-BC88-46A8899DF3BA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore", "src\AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore\AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore.csproj", "{8A22F22E-D10A-4897-A89A-DC76C267F6BB}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -590,6 +594,30 @@ Global {F4B8D5AF-D3CA-4910-A14D-E5BAEF0FD1DE}.Release|x64.Build.0 = Release|Any CPU {F4B8D5AF-D3CA-4910-A14D-E5BAEF0FD1DE}.Release|x86.ActiveCfg = Release|Any CPU {F4B8D5AF-D3CA-4910-A14D-E5BAEF0FD1DE}.Release|x86.Build.0 = Release|Any CPU + {281F7EB5-ACE5-458F-BC88-46A8899DF3BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {281F7EB5-ACE5-458F-BC88-46A8899DF3BA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {281F7EB5-ACE5-458F-BC88-46A8899DF3BA}.Debug|x64.ActiveCfg = Debug|Any CPU + {281F7EB5-ACE5-458F-BC88-46A8899DF3BA}.Debug|x64.Build.0 = Debug|Any CPU + {281F7EB5-ACE5-458F-BC88-46A8899DF3BA}.Debug|x86.ActiveCfg = Debug|Any CPU + {281F7EB5-ACE5-458F-BC88-46A8899DF3BA}.Debug|x86.Build.0 = Debug|Any CPU + {281F7EB5-ACE5-458F-BC88-46A8899DF3BA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {281F7EB5-ACE5-458F-BC88-46A8899DF3BA}.Release|Any CPU.Build.0 = Release|Any CPU + {281F7EB5-ACE5-458F-BC88-46A8899DF3BA}.Release|x64.ActiveCfg = Release|Any CPU + {281F7EB5-ACE5-458F-BC88-46A8899DF3BA}.Release|x64.Build.0 = Release|Any CPU + {281F7EB5-ACE5-458F-BC88-46A8899DF3BA}.Release|x86.ActiveCfg = Release|Any CPU + {281F7EB5-ACE5-458F-BC88-46A8899DF3BA}.Release|x86.Build.0 = Release|Any CPU + {8A22F22E-D10A-4897-A89A-DC76C267F6BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8A22F22E-D10A-4897-A89A-DC76C267F6BB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8A22F22E-D10A-4897-A89A-DC76C267F6BB}.Debug|x64.ActiveCfg = Debug|Any CPU + {8A22F22E-D10A-4897-A89A-DC76C267F6BB}.Debug|x64.Build.0 = Debug|Any CPU + {8A22F22E-D10A-4897-A89A-DC76C267F6BB}.Debug|x86.ActiveCfg = Debug|Any CPU + {8A22F22E-D10A-4897-A89A-DC76C267F6BB}.Debug|x86.Build.0 = Debug|Any CPU + {8A22F22E-D10A-4897-A89A-DC76C267F6BB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8A22F22E-D10A-4897-A89A-DC76C267F6BB}.Release|Any CPU.Build.0 = Release|Any CPU + {8A22F22E-D10A-4897-A89A-DC76C267F6BB}.Release|x64.ActiveCfg = Release|Any CPU + {8A22F22E-D10A-4897-A89A-DC76C267F6BB}.Release|x64.Build.0 = Release|Any CPU + {8A22F22E-D10A-4897-A89A-DC76C267F6BB}.Release|x86.ActiveCfg = Release|Any CPU + {8A22F22E-D10A-4897-A89A-DC76C267F6BB}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution @@ -641,5 +669,7 @@ Global {7FC6DD65-0352-4139-8D08-B25C0A0403E3} = {4EAB66F9-C9CB-4E8A-BEE6-A14CD7FDE02F} {61374D8E-F77C-4A31-AE07-35DAF1847369} = {1CFF5568-8486-475F-81F6-06105C437528} {F4B8D5AF-D3CA-4910-A14D-E5BAEF0FD1DE} = {73C9B1E5-3893-47E8-B373-17E5F5D7E6F5} + {281F7EB5-ACE5-458F-BC88-46A8899DF3BA} = {73C9B1E5-3893-47E8-B373-17E5F5D7E6F5} + {8A22F22E-D10A-4897-A89A-DC76C267F6BB} = {73C9B1E5-3893-47E8-B373-17E5F5D7E6F5} EndGlobalSection EndGlobal diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore.csproj b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore.csproj new file mode 100644 index 00000000..5e5c6666 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore.csproj @@ -0,0 +1,26 @@ + + + + + AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore + Powertools for AWS Lambda (.NET) - Event Handler Bedrock Agent Function Resolver AspNetCore package. + AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore + AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore + net8.0 + false + enable + enable + + + + + + + + + + + + + + diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore/BedrockFunctionRegistration.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore/BedrockFunctionRegistration.cs new file mode 100644 index 00000000..9004a49d --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore/BedrockFunctionRegistration.cs @@ -0,0 +1,56 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +namespace AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore; + +/// +/// Helper class for function registration with fluent API pattern. +/// +internal class BedrockFunctionRegistration +{ + private readonly BedrockAgentFunctionResolver _resolver; + + /// + /// Initializes a new instance of the class. + /// + /// The Bedrock agent function resolver. + public BedrockFunctionRegistration(BedrockAgentFunctionResolver resolver) + { + _resolver = resolver; + } + + /// + /// Adds a function to the Bedrock resolver. + /// + /// The name of the function. + /// The delegate handler. + /// Optional description of the function. + /// The function registration instance for method chaining. + /// + /// + /// app.MapBedrockFunction("GetWeather", (string city, int month) => + /// $"Weather forecast for {city} in month {month}: Warm and sunny"); + /// + /// app.MapBedrockFunction("Calculate", (int x, int y) => + /// $"Result: {x + y}"); + /// ); + /// + /// + public BedrockFunctionRegistration Add(string name, Delegate handler, string description = "") + { + _resolver.Tool(name, description, handler); + return this; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore/BedrockMinimalApiExtensions.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore/BedrockMinimalApiExtensions.cs new file mode 100644 index 00000000..9cbe0ae1 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore/BedrockMinimalApiExtensions.cs @@ -0,0 +1,173 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System.Diagnostics.CodeAnalysis; +using System.Text.Json; +using System.Text.Json.Serialization; +using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; + +namespace AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore; + +// Source generation for JSON serialization +[JsonSerializable(typeof(BedrockFunctionRequest))] +internal partial class BedrockJsonContext : JsonSerializerContext +{ +} + +/// +/// Extension methods for registering Bedrock Agent Functions in ASP.NET Core Minimal API. +/// +public static class BedrockMinimalApiExtensions +{ + // Static flag to track if handler is mapped (thread-safe with volatile) + private static volatile bool _bedrockRequestHandlerMapped; + + // JSON options with case insensitivity + private static readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }; + + /// + /// Maps an individual Bedrock Agent function that will be called directly from the root endpoint. + /// The function name is extracted from the incoming request payload. + /// + /// The web application to configure. + /// The name of the function to register. + /// The delegate handler that implements the function. + /// Optional description of the function. + /// The web application instance. + /// + /// + /// // Register individual functions + /// app.MapBedrockFunction("GetWeather", (string city, int month) => + /// $"Weather forecast for {city} in month {month}: Warm and sunny"); + /// + /// app.MapBedrockFunction("Calculate", (int x, int y) => + /// $"Result: {x + y}"); + /// + /// + public static WebApplication MapBedrockFunction( + this WebApplication app, + string functionName, + Delegate handler, + string description = "") + { + // Get or create the resolver from services + var resolver = app.Services.GetService() + ?? new BedrockAgentFunctionResolver(); + + // Register the function with the resolver + resolver.Tool(functionName, description, handler); + + // Ensure we have a global handler for Bedrock requests + EnsureBedrockRequestHandler(app, resolver); + + return app; + } + + [UnconditionalSuppressMessage("AOT", "IL3050:RequiresDynamicCode", + Justification = "The handler implementation is controlled and AOT-compatible")] + [UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode", + Justification = "The handler implementation is controlled and trim-compatible")] + private static void EnsureBedrockRequestHandler(WebApplication app, BedrockAgentFunctionResolver resolver) + { + // Check if we've already mapped the handler (we only need to do this once) + if (_bedrockRequestHandlerMapped) + return; + + // Map the root endpoint to handle all Bedrock Agent Function requests + app.MapPost("/", [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Handler is AOT-friendly")] + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Handler is trim-friendly")] + async (HttpContext context) => + { + try + { + // Read the request body + string requestBody; + using (var reader = new StreamReader(context.Request.Body)) + { + requestBody = await reader.ReadToEndAsync(); + } + + // Use source-generated serialization for the request + var bedrockRequest = JsonSerializer.Deserialize(requestBody, + BedrockJsonContext.Default.BedrockFunctionRequest); + + if (bedrockRequest == null) + return Results.BadRequest("Invalid request format"); + + // Process the request through the resolver + var result = await resolver.ResolveAsync(bedrockRequest); + + // For the response, use the standard serializer with suppressed warnings + // This is more compatible with different response types + context.Response.ContentType = "application/json"; + await context.Response.WriteAsJsonAsync(result, JsonOptions); + return Results.Empty; + } + catch (Exception ex) + { + return Results.Problem($"Error processing Bedrock Agent request: {ex.Message}"); + } + }); + + // Mark that we've set up the handler + _bedrockRequestHandlerMapped = true; + } + + /// + /// Registers all methods from a class marked with BedrockFunctionTypeAttribute. + /// + /// The type containing tool methods marked with BedrockFunctionToolAttribute + /// The web application to configure. + /// The web application instance. + /// + /// + /// // Define your tool class + /// [BedrockFunctionType] + /// public class WeatherTools + /// { + /// [BedrockFunctionTool(Name = "GetWeather", Description = "Gets weather forecast")] + /// public static string GetWeather(string location, int days) + /// { + /// return $"Weather forecast for {location} for the next {days} days"; + /// } + /// } + /// + /// // Register all tools from the class + /// app.MapBedrockToolClass<WeatherTools>(); + /// + /// + public static WebApplication MapBedrockToolType<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T>( + this WebApplication app) + where T : class + { + // Get or create the resolver from services + var resolver = app.Services.GetService() + ?? new BedrockAgentFunctionResolver(); + + // Register the tool class + resolver.RegisterTool(); + + // Ensure we have a global handler for Bedrock requests + EnsureBedrockRequestHandler(app, resolver); + + return app; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore/Readme.md b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore/Readme.md new file mode 100644 index 00000000..8cc31365 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore/Readme.md @@ -0,0 +1,115 @@ +# Experimental work in progress, not yet released + +# AWS Lambda Powertools for .NET - Bedrock Agent Function Resolver for ASP.NET Core + +## Overview +This library provides ASP.NET Core integration for the AWS Lambda Powertools Bedrock Agent Function Resolver. It enables you to easily expose Bedrock Agent functions as endpoints in your ASP.NET Core applications using a simple, fluent API. + +## Features + +- **Minimal API Integration**: Register Bedrock Agent functions using familiar ASP.NET Core Minimal API patterns +- **AOT Compatibility**: Full support for .NET 8 AOT compilation through source generation +- **Simple Function Registration**: Register functions with a fluent API +- **Automatic Request Processing**: Automatic parsing of Bedrock Agent requests and formatting of responses +- **Error Handling**: Built-in error handling for Bedrock Agent function requests + +## Installation + +Install the package via NuGet: + +```bash +dotnet add package AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore +``` + +## Basic Usage + +Here's how to register Bedrock Agent functions in your ASP.NET Core application: + +```csharp +using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore; + +var builder = WebApplication.CreateBuilder(args); +var app = builder.Build(); + +// Register individual functions +app.MapBedrockFunction("GetWeather", (string city, int month) => + $"Weather forecast for {city} in month {month}: Warm and sunny"); + +app.MapBedrockFunction("Calculate", (int x, int y) => + $"Result: {x + y}"); + +app.Run(); +``` + +When Amazon Bedrock Agent sends a request to your application, the appropriate function will be invoked with the extracted parameters, and the response will be formatted correctly for the agent. + +## Using with Dependency Injection + +Register the Bedrock resolver with dependency injection for more advanced scenarios: + +```csharp +using AWS.Lambda.Powertools.EventHandler.Resolvers; +using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore; + +var builder = WebApplication.CreateBuilder(args); + +// Register the resolver and any other services +builder.Services.AddBedrockResolver(); +builder.Services.AddSingleton(); + +var app = builder.Build(); + +// Register functions that use injected services +app.MapBedrockFunction("GetWeatherForecast", + (string city, IWeatherService weatherService) => + weatherService.GetForecast(city), + "Gets weather forecast for a city"); + +app.Run(); +``` + +## Advanced Usage + +### Function Documentation + +Add descriptions to your functions for better documentation: + +```csharp +app.MapBedrockFunction("GetWeather", + (string city, int month) => $"Weather forecast for {city} in month {month}: Warm and sunny", + "Gets weather forecast for a specific city and month"); +``` + +### Working with Tool Classes + +Use the `MapBedrockToolClass()` method to register all functions from a class directly: + +```csharp +[BedrockFunctionType] +public class WeatherTools +{ + [BedrockFunctionTool(Name = "GetWeather", Description = "Gets weather forecast")] + public static string GetWeather(string location, int days) + { + return $"Weather forecast for {location} for the next {days} days"; + } +} + +// In Program.cs - directly register the tool class +app.MapBedrockToolClass(); +``` + +## How It Works + +1. When you call `MapBedrockFunction`, the function is registered with the resolver +2. An HTTP endpoint is set up at the root path (/) to handle incoming Bedrock Agent requests +3. When a request arrives, the library: + - Deserializes the JSON payload + - Extracts the function name and parameters + - Invokes the matching function with the appropriate parameters + - Serializes the result and returns it as a response + +## Requirements + +- .NET 8.0 or later +- ASP.NET Core 8.0 or later \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.csproj b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.csproj new file mode 100644 index 00000000..e6cbf62a --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.csproj @@ -0,0 +1,19 @@ + + + + + AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction + Powertools for AWS Lambda (.NET) - Event Handler Bedrock Agent Function Resolver package. + net8.0 + false + enable + enable + true + + + + + + + + \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/BedrockAgentFunctionResolver.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/BedrockAgentFunctionResolver.cs new file mode 100644 index 00000000..d0e15c09 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/BedrockAgentFunctionResolver.cs @@ -0,0 +1,377 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System.Text.Json.Serialization.Metadata; +using Amazon.Lambda.Core; +using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; +using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Helpers; + +// ReSharper disable once CheckNamespace +namespace AWS.Lambda.Powertools.EventHandler.Resolvers +{ + /// + /// A resolver for Bedrock Agent functions that allows registering handlers for tool functions. + /// + /// + /// Basic usage: + /// + /// var resolver = new BedrockAgentFunctionResolver(); + /// resolver.Tool("GetWeather", (string city) => $"Weather in {city} is sunny"); + /// + /// // Lambda handler + /// public BedrockFunctionResponse FunctionHandler(BedrockFunctionRequest input, ILambdaContext context) + /// { + /// return resolver.Resolve(input, context); + /// } + /// + /// + public class BedrockAgentFunctionResolver + { + private readonly + Dictionary> + _handlers = new(); + + private readonly ParameterTypeValidator _parameterValidator = new(); + private readonly ResultConverter _resultConverter = new(); + private readonly ParameterMapper _parameterMapper; + + /// + /// Initializes a new instance of the class. + /// Optionally accepts a type resolver for JSON serialization. + /// + public BedrockAgentFunctionResolver(IJsonTypeInfoResolver? typeResolver = null) + { + _parameterMapper = new ParameterMapper(typeResolver); + } + + /// + /// Checks if another tool can be registered, and logs a warning if the maximum limit is reached + /// or if a tool with the same name is already registered + /// + /// The name of the tool being registered + /// True if the tool can be registered, false if the maximum limit is reached + private bool CanRegisterTool(string name) + { + if (_handlers.ContainsKey(name)) + { + Console.WriteLine($"WARNING: Tool {name} already registered. Overwriting with new definition."); + } + + return true; + } + + /// + /// Registers a handler that directly accepts BedrockFunctionRequest and returns BedrockFunctionResponse + /// + /// The name of the tool function + /// The handler function that accepts input and context and returns output + /// Optional description of the tool function + /// The resolver instance for method chaining + public BedrockAgentFunctionResolver Tool( + string name, + Func handler, + string description = "") + { + ArgumentNullException.ThrowIfNull(handler); + + if (!CanRegisterTool(name)) + return this; + + _handlers[name] = handler; + return this; + } + + /// + /// Registers a handler that directly accepts BedrockFunctionRequest and returns BedrockFunctionResponse + /// + /// The name of the tool function + /// The handler function that accepts input and returns output + /// Optional description of the tool function + /// The resolver instance for method chaining + public BedrockAgentFunctionResolver Tool( + string name, + Func handler, + string description = "") + { + ArgumentNullException.ThrowIfNull(handler); + + if (!CanRegisterTool(name)) + return this; + + _handlers[name] = (input, _) => handler(input); + return this; + } + + /// + /// Registers a parameter-less handler that returns BedrockFunctionResponse + /// + /// The name of the tool function + /// The handler function that returns output + /// Optional description of the tool function + /// The resolver instance for method chaining + public BedrockAgentFunctionResolver Tool( + string name, + Func handler, + string description = "") + { + ArgumentNullException.ThrowIfNull(handler); + + if (!CanRegisterTool(name)) + return this; + + _handlers[name] = (_, _) => handler(); + return this; + } + + /// + /// Registers a parameter-less handler with automatic string conversion + /// + /// The name of the tool function + /// The handler function that returns a string + /// Optional description of the tool function + /// The resolver instance for method chaining + public BedrockAgentFunctionResolver Tool( + string name, + Func handler, + string description = "") + { + ArgumentNullException.ThrowIfNull(handler); + + if (!CanRegisterTool(name)) + return this; + + _handlers[name] = (input, _) => BedrockFunctionResponse.WithText( + handler(), + input.ActionGroup, + name, + input.SessionAttributes, + input.PromptSessionAttributes, + new Dictionary()); + return this; + } + + /// + /// Registers a parameter-less handler with automatic object conversion + /// + /// The name of the tool function + /// The handler function that returns an object + /// Optional description of the tool function + /// The resolver instance for method chaining + public BedrockAgentFunctionResolver Tool( + string name, + Func handler, + string description = "") + { + ArgumentNullException.ThrowIfNull(handler); + + if (!CanRegisterTool(name)) + return this; + + _handlers[name] = (input, _) => + { + var result = handler(); + return _resultConverter.ConvertToOutput(result, input); + }; + return this; + } + + /// + /// Registers a handler for a tool function with automatically converted return type (no description). + /// + /// The name of the tool function + /// The delegate handler function + /// The resolver instance for method chaining + public BedrockAgentFunctionResolver Tool( + string name, + Delegate handler) + { + return Tool(name, "", handler); + } + + /// + /// Registers a handler for a tool function with description and automatically converted return type. + /// + /// The name of the tool function + /// Description of the tool function + /// The delegate handler function + /// The resolver instance for method chaining + public BedrockAgentFunctionResolver Tool( + string name, + string description, + Delegate handler) + { + return Tool(name, description, handler); + } + + /// + /// Registers a handler for a tool function with typed return value (no description). + /// + /// The return type of the handler + /// The name of the tool function + /// The delegate handler function + /// The resolver instance for method chaining + public BedrockAgentFunctionResolver Tool( + string name, + Delegate handler) + { + return Tool(name, "", handler); + } + + /// + /// Registers a handler for a tool function with description and typed return value. + /// + /// The return type of the handler + /// The name of the tool function + /// Description of the tool function + /// The delegate handler function + /// The resolver instance for method chaining + public BedrockAgentFunctionResolver Tool( + string name, + string description, + Delegate handler) + { + ArgumentNullException.ThrowIfNull(handler); + + if (!CanRegisterTool(name)) + return this; + + _handlers[name] = RegisterToolHandler(handler, name); + return this; + } + + private Func RegisterToolHandler( + Delegate handler, string functionName) + { + return (input, context) => + { + try + { + // Map parameters from Bedrock input and DI + var serviceProvider = (this as DiBedrockAgentFunctionResolver)?.ServiceProvider; + var args = _parameterMapper.MapParameters(handler.Method, input, context, serviceProvider); + + // Execute the handler and process result + return ExecuteHandlerAndProcessResult(handler, args, input, context, functionName); + } + catch (Exception ex) + { + context?.Logger.LogError(ex.ToString()); + var innerException = ex.InnerException ?? ex; + return BedrockFunctionResponse.WithText( + $"Error when invoking tool: {innerException.Message}", + input.ActionGroup, + functionName, + input.SessionAttributes, + input.PromptSessionAttributes, + new Dictionary()); + } + }; + } + + private BedrockFunctionResponse ExecuteHandlerAndProcessResult( + Delegate handler, + object?[] args, + BedrockFunctionRequest input, + ILambdaContext? context, + string functionName) + { + try + { + // Execute the handler + var result = handler.DynamicInvoke(args); + + // Process various result types + return _resultConverter.ProcessResult(result, input, functionName, context); + } + catch (Exception ex) + { + context?.Logger.LogError(ex.ToString()); + var innerException = ex.InnerException ?? ex; + return BedrockFunctionResponse.WithText( + $"Error when invoking tool: {innerException.Message}", + input.ActionGroup, + functionName, + input.SessionAttributes, + input.PromptSessionAttributes, + new Dictionary()); + } + } + + /// + /// Resolves and processes a Bedrock Agent function invocation. + /// + /// The Bedrock Agent input containing the function name and parameters + /// Optional Lambda context + /// The output from the function execution + public BedrockFunctionResponse Resolve(BedrockFunctionRequest input, ILambdaContext? context = null) + { + return ResolveAsync(input, context).GetAwaiter().GetResult(); + } + + /// + /// Asynchronously resolves and processes a Bedrock Agent function invocation. + /// + /// The Bedrock Agent input containing the function name and parameters + /// Optional Lambda context + /// A task that completes with the output from the function execution + public async Task ResolveAsync(BedrockFunctionRequest input, + ILambdaContext? context = null) + { + return await Task.FromResult(HandleEvent(input, context)); + } + + private BedrockFunctionResponse HandleEvent(BedrockFunctionRequest input, ILambdaContext? context) + { + if (string.IsNullOrEmpty(input.Function)) + { + return BedrockFunctionResponse.WithText( + "No tool specified in the request", + input.ActionGroup, + "", + input.SessionAttributes, + input.PromptSessionAttributes, + new Dictionary()); + } + + if (_handlers.TryGetValue(input.Function, out var handler)) + { + try + { + return handler(input, context); + } + catch (Exception ex) + { + context?.Logger.LogError(ex.ToString()); + return BedrockFunctionResponse.WithText( + $"Error when invoking tool: {ex.Message}", + input.ActionGroup, + input.Function, + input.SessionAttributes, + input.PromptSessionAttributes, + new Dictionary()); + } + } + + context?.Logger.LogWarning($"Tool {input.Function} has not been registered."); + return BedrockFunctionResponse.WithText( + $"Error: Tool {input.Function} has not been registered in handler", + input.ActionGroup, + input.Function, + input.SessionAttributes, + input.PromptSessionAttributes, + new Dictionary()); + } + } +} diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/BedrockAgentFunctionResolverExtensions.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/BedrockAgentFunctionResolverExtensions.cs new file mode 100644 index 00000000..307152a3 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/BedrockAgentFunctionResolverExtensions.cs @@ -0,0 +1,121 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System.Diagnostics.CodeAnalysis; +using System.Linq.Expressions; +using System.Reflection; +using System.Text.Json.Serialization.Metadata; +using Microsoft.Extensions.DependencyInjection; + +// ReSharper disable once CheckNamespace +namespace AWS.Lambda.Powertools.EventHandler.Resolvers +{ + /// + /// Extension methods for Bedrock Agent Function Resolver. + /// + public static class BedrockResolverExtensions + { + /// + /// Registers a Bedrock Agent Function Resolver with dependency injection support. + /// + /// The service collection to add the resolver to. + /// + /// The updated service collection. + /// + /// + /// public void ConfigureServices(IServiceCollection services) + /// { + /// services.AddBedrockResolver(); + /// + /// // Now you can inject BedrockAgentFunctionResolver into your services + /// } + /// + /// + public static IServiceCollection AddBedrockResolver( + this IServiceCollection services, + IJsonTypeInfoResolver? typeResolver = null) + { + services.AddSingleton(sp => + new DiBedrockAgentFunctionResolver(sp, typeResolver)); + return services; + } + + /// + /// Registers tools from a type marked with BedrockFunctionTypeAttribute. + /// + /// The type containing tool methods marked with BedrockFunctionToolAttribute + /// The resolver to register tools with + /// The resolver for method chaining + /// + /// + /// // Define your tool class + /// [BedrockFunctionType] + /// public class WeatherTools + /// { + /// [BedrockFunctionTool(Name = "GetWeather", Description = "Gets weather forecast")] + /// public static string GetWeather(string location, int days) + /// { + /// return $"Weather forecast for {location} for the next {days} days"; + /// } + /// } + /// + /// // Register the tools + /// var resolver = new BedrockAgentFunctionResolver(); + /// resolver.RegisterTool<WeatherTools>(); + /// + /// + public static BedrockAgentFunctionResolver RegisterTool< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T>( + this BedrockAgentFunctionResolver resolver) + where T : class + { + var type = typeof(T); + + // Check if class has the BedrockFunctionType attribute + if (!type.IsDefined(typeof(BedrockFunctionTypeAttribute), false)) + return resolver; + + // Look at all static methods with the tool attribute + foreach (var method in type.GetMethods(BindingFlags.Static | BindingFlags.Public)) + { + var attr = method.GetCustomAttribute(); + if (attr == null) continue; + + string toolName = attr.Name ?? method.Name; + string description = attr.Description ?? + string.Empty; + + // Create delegate from the static method + var del = Delegate.CreateDelegate( + GetDelegateType(method), + method); + + // Call the Tool method directly instead of using reflection + resolver.Tool(toolName, description, del); + } + + return resolver; + } + + private static Type GetDelegateType(MethodInfo method) + { + var parameters = method.GetParameters(); + var parameterTypes = parameters.Select(p => p.ParameterType).ToList(); + parameterTypes.Add(method.ReturnType); + + return Expression.GetDelegateType(parameterTypes.ToArray()); + } + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/BedrockFunctionResolverContext.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/BedrockFunctionResolverContext.cs new file mode 100644 index 00000000..f0f6e3fb --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/BedrockFunctionResolverContext.cs @@ -0,0 +1,29 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System.Text.Json.Serialization; + +// ReSharper disable once CheckNamespace +namespace AWS.Lambda.Powertools.EventHandler.Resolvers; + +[JsonSerializable(typeof(string[]))] +[JsonSerializable(typeof(int[]))] +[JsonSerializable(typeof(long[]))] +[JsonSerializable(typeof(double[]))] +[JsonSerializable(typeof(bool[]))] +[JsonSerializable(typeof(decimal[]))] +internal partial class BedrockFunctionResolverContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/BedrockFunctionToolAttribute.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/BedrockFunctionToolAttribute.cs new file mode 100644 index 00000000..562e7804 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/BedrockFunctionToolAttribute.cs @@ -0,0 +1,60 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +// ReSharper disable once CheckNamespace +namespace AWS.Lambda.Powertools.EventHandler.Resolvers; + +/// +/// Marks a method as a Bedrock Agent function tool. +/// +/// +/// +/// [BedrockFunctionTool(Name = "GetWeather", Description = "Gets the weather for a location")] +/// public static string GetWeather(string location, int days) +/// { +/// return $"Weather forecast for {location} for the next {days} days"; +/// } +/// +/// +[AttributeUsage(AttributeTargets.Method)] +public class BedrockFunctionToolAttribute : Attribute +{ + /// + /// The name of the tool. If not specified, the method name will be used. + /// + public string? Name { get; set; } + + /// + /// The description of the tool. Used to provide context about the tool's functionality. + /// + public string? Description { get; set; } +} + +/// +/// Marks a class as containing Bedrock Agent function tools. +/// +/// +/// +/// [BedrockFunctionType] +/// public class WeatherTools +/// { +/// // Methods that can be registered as tools +/// } +/// +/// +[AttributeUsage(AttributeTargets.Class)] +public class BedrockFunctionTypeAttribute : Attribute +{ +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/DiBedrockAgentFunctionResolver.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/DiBedrockAgentFunctionResolver.cs new file mode 100644 index 00000000..82064d43 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/DiBedrockAgentFunctionResolver.cs @@ -0,0 +1,25 @@ +using System.Text.Json.Serialization.Metadata; + +namespace AWS.Lambda.Powertools.EventHandler.Resolvers; + +/// +/// Extended Bedrock Agent Function Resolver with dependency injection support. +/// +internal class DiBedrockAgentFunctionResolver : BedrockAgentFunctionResolver +{ + /// + /// Gets the service provider used for dependency injection. + /// + public IServiceProvider ServiceProvider { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The service provider for dependency injection. + /// + public DiBedrockAgentFunctionResolver(IServiceProvider serviceProvider, IJsonTypeInfoResolver? typeResolver = null) + : base(typeResolver) + { + ServiceProvider = serviceProvider; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Helpers/ParameterAccessor.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Helpers/ParameterAccessor.cs new file mode 100644 index 00000000..277905ea --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Helpers/ParameterAccessor.cs @@ -0,0 +1,154 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System.Globalization; + +namespace AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Helpers; + +/// +/// Provides strongly-typed access to the parameters of an agent function call. +/// +internal class ParameterAccessor +{ + private readonly List _parameters; + + internal ParameterAccessor(List? parameters) + { + _parameters = parameters ?? new List(); + } + + /// + /// Gets a parameter value by name with type conversion. + /// + public T Get(string name) + { + var parameter = _parameters.FirstOrDefault(p => string.Equals(p.Name, name, StringComparison.OrdinalIgnoreCase)); + if (parameter == null || parameter.Value == null) + { + return default!; + } + + return ConvertParameter(parameter); + } + + /// + /// Gets a parameter value by index with type conversion. + /// + public T GetAt(int index) + { + if (index < 0 || index >= _parameters.Count) + { + return default!; + } + + var parameter = _parameters[index]; + if (parameter.Value == null) + { + return default!; + } + + return ConvertParameter(parameter); + } + + /// + /// Gets a parameter value by name with fallback to a default value. + /// + public T GetOrDefault(string name, T defaultValue) + { + var parameter = _parameters.FirstOrDefault(p => string.Equals(p.Name, name, StringComparison.OrdinalIgnoreCase)); + if (parameter == null || parameter.Value == null) + { + return defaultValue; + } + + try + { + var result = ConvertParameter(parameter); + // If conversion returns default value but we have a non-null parameter, + // that means conversion failed, so return the provided default value + if (EqualityComparer.Default.Equals(result, default) && parameter.Value != null) + { + return defaultValue; + } + return result; + } + catch + { + return defaultValue; + } + } + + private static T ConvertParameter(Parameter? parameter) + { + if (parameter == null || parameter.Value == null) + { + return default!; + } + + // Handle different types explicitly for AOT compatibility + if (typeof(T) == typeof(string)) + { + return (T)(object)parameter.Value; + } + + if (typeof(T) == typeof(int) || typeof(T) == typeof(int?)) + { + if (int.TryParse(parameter.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out int result)) + { + return (T)(object)result; + } + return default!; + } + + if (typeof(T) == typeof(double) || typeof(T) == typeof(double?)) + { + if (double.TryParse(parameter.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out double result)) + { + return (T)(object)result; + } + return default!; + } + + if (typeof(T) == typeof(bool) || typeof(T) == typeof(bool?)) + { + if (bool.TryParse(parameter.Value, out bool result)) + { + return (T)(object)result; + } + return default!; + } + + if (typeof(T) == typeof(long) || typeof(T) == typeof(long?)) + { + if (long.TryParse(parameter.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out long result)) + { + return (T)(object)result; + } + return default!; + } + + if (typeof(T) == typeof(decimal) || typeof(T) == typeof(decimal?)) + { + if (decimal.TryParse(parameter.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out decimal result)) + { + return (T)(object)result; + } + return default!; + } + + // Return default for array and complex types + return default!; + } +} diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Helpers/ParameterMapper.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Helpers/ParameterMapper.cs new file mode 100644 index 00000000..1f723d98 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Helpers/ParameterMapper.cs @@ -0,0 +1,209 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System.Reflection; +using System.Text.Json; +using System.Text.Json.Serialization.Metadata; +using Amazon.Lambda.Core; +using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; + +namespace AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Helpers +{ + /// + /// Maps parameters for Bedrock Agent function handlers + /// + internal class ParameterMapper + { + private readonly ParameterTypeValidator _validator = new(); + private readonly IJsonTypeInfoResolver? _typeResolver; + + public ParameterMapper(IJsonTypeInfoResolver? typeResolver = null) + { + _typeResolver = typeResolver; + } + + /// + /// Maps parameters for a handler method from a Bedrock function request + /// + /// The handler method + /// The Bedrock function request + /// The Lambda context + /// Optional service provider for dependency injection + /// Array of arguments to pass to the handler + public object?[] MapParameters( + MethodInfo methodInfo, + BedrockFunctionRequest input, + ILambdaContext? context, + IServiceProvider? serviceProvider) + { + var parameters = methodInfo.GetParameters(); + var args = new object?[parameters.Length]; + var accessor = new ParameterAccessor(input.Parameters); + + for (var i = 0; i < parameters.Length; i++) + { + var parameter = parameters[i]; + var paramType = parameter.ParameterType; + + if (paramType == typeof(ILambdaContext)) + { + args[i] = context; + continue; // Skip further processing for this parameter + } + else if (paramType == typeof(BedrockFunctionRequest)) + { + args[i] = input; + continue; // Skip further processing for this parameter + } + + // Try to deserialize custom complex type from InputText + if (!string.IsNullOrEmpty(input.InputText) && + !paramType.IsPrimitive && + paramType != typeof(string) && + !paramType.IsEnum) + { + try + { + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }; + + if (_typeResolver != null) + { + options.TypeInfoResolver = _typeResolver; + + // Get the JsonTypeInfo for the parameter type + var jsonTypeInfo = _typeResolver.GetTypeInfo(paramType, options); + if (jsonTypeInfo != null) + { + // Use the AOT-friendly overload with JsonTypeInfo + args[i] = JsonSerializer.Deserialize(input.InputText, jsonTypeInfo); + + if (args[i] != null) + { + continue; + } + } + } + else + { + // Fallback to non-AOT deserialization with warning +#pragma warning disable IL2026, IL3050 + args[i] = JsonSerializer.Deserialize(input.InputText, paramType, options); +#pragma warning restore IL2026, IL3050 + + if (args[i] != null) + { + continue; + } + } + } + catch + { + // Deserialization failed, continue to regular parameter mapping + } + } + + if (_validator.IsBedrockParameter(paramType)) + { + args[i] = MapBedrockParameter(paramType, parameter.Name ?? $"arg{i}", accessor); + } + else if (serviceProvider != null) + { + // Resolve from DI + args[i] = serviceProvider.GetService(paramType); + } + } + + return args; + } + + private object? MapBedrockParameter(Type paramType, string paramName, ParameterAccessor accessor) + { + // Array parameter handling + if (paramType.IsArray) + { + return MapArrayParameter(paramType, paramName, accessor); + } + + // Scalar parameter handling + return MapScalarParameter(paramType, paramName, accessor); + } + + private object? MapArrayParameter(Type paramType, string paramName, ParameterAccessor accessor) + { + var jsonArrayStr = accessor.Get(paramName); + + if (string.IsNullOrEmpty(jsonArrayStr)) + { + return null; + } + + try + { + // AOT-compatible deserialization using source generation + if (paramType == typeof(string[])) + return JsonSerializer.Deserialize(jsonArrayStr, BedrockFunctionResolverContext.Default.StringArray); + if (paramType == typeof(int[])) + return JsonSerializer.Deserialize(jsonArrayStr, BedrockFunctionResolverContext.Default.Int32Array); + if (paramType == typeof(long[])) + return JsonSerializer.Deserialize(jsonArrayStr, BedrockFunctionResolverContext.Default.Int64Array); + if (paramType == typeof(double[])) + return JsonSerializer.Deserialize(jsonArrayStr, BedrockFunctionResolverContext.Default.DoubleArray); + if (paramType == typeof(bool[])) + return JsonSerializer.Deserialize(jsonArrayStr, + BedrockFunctionResolverContext.Default.BooleanArray); + if (paramType == typeof(decimal[])) + return JsonSerializer.Deserialize(jsonArrayStr, + BedrockFunctionResolverContext.Default.DecimalArray); + } + catch (JsonException) + { + // Return null on error + } + + return null; + } + + private object? MapScalarParameter(Type paramType, string paramName, ParameterAccessor accessor) + { + if (paramType == typeof(string)) + return accessor.Get(paramName); + if (paramType == typeof(int)) + return accessor.Get(paramName); + if (paramType == typeof(long)) + return accessor.Get(paramName); + if (paramType == typeof(double)) + return accessor.Get(paramName); + if (paramType == typeof(bool)) + return accessor.Get(paramName); + if (paramType == typeof(decimal)) + return accessor.Get(paramName); + if (paramType == typeof(DateTime)) + return accessor.Get(paramName); + if (paramType == typeof(Guid)) + return accessor.Get(paramName); + if (paramType.IsEnum) + { + // For enums, get as string and parse + var strValue = accessor.Get(paramName); + return !string.IsNullOrEmpty(strValue) ? Enum.Parse(paramType, strValue) : null; + } + + return null; + } + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Helpers/ParameterTypeValidator.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Helpers/ParameterTypeValidator.cs new file mode 100644 index 00000000..164c4241 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Helpers/ParameterTypeValidator.cs @@ -0,0 +1,50 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +namespace AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Helpers +{ + /// + /// Validates parameter types for Bedrock Agent functions + /// + internal class ParameterTypeValidator + { + private static readonly HashSet BedrockParameterTypes = new() + { + typeof(string), + typeof(int), + typeof(long), + typeof(double), + typeof(bool), + typeof(decimal), + typeof(DateTime), + typeof(Guid), + typeof(string[]), + typeof(int[]), + typeof(long[]), + typeof(double[]), + typeof(bool[]), + typeof(decimal[]) + }; + + /// + /// Checks if a type is a valid Bedrock parameter type + /// + /// The type to check + /// True if the type is valid for Bedrock parameters + public bool IsBedrockParameter(Type type) => + BedrockParameterTypes.Contains(type) || type.IsEnum || + (type.IsArray && BedrockParameterTypes.Contains(type.GetElementType()!)); + } +} diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Helpers/ResultConverter.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Helpers/ResultConverter.cs new file mode 100644 index 00000000..72bbe383 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Helpers/ResultConverter.cs @@ -0,0 +1,237 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System.Globalization; +using Amazon.Lambda.Core; +using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; + +namespace AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Helpers +{ + /// + /// Converts handler results to BedrockFunctionResponse + /// + internal class ResultConverter + { + /// + /// Processes results from handler functions and converts to BedrockFunctionResponse + /// + public BedrockFunctionResponse ProcessResult( + object? result, + BedrockFunctionRequest input, + string functionName, + ILambdaContext? context) + { + // Direct return for BedrockFunctionResponse + if (result is BedrockFunctionResponse output) + return EnsureResponseMetadata(output, input, functionName); + + // Handle async results with specific type checks (AOT-compatible) + if (result is Task outputTask) + return EnsureResponseMetadata(outputTask.Result, input, functionName); + + // Handle various Task types + if (result is Task task) + { + return HandleTaskResult(task, input); + } + + // Handle regular (non-task) results + return ConvertToOutput(result, input); + } + + private BedrockFunctionResponse HandleTaskResult(Task task, BedrockFunctionRequest input) + { + // For Task + if (task is Task stringTask) + return ConvertToOutput((TResult)(object)stringTask.Result, input); + + // For Task + if (task is Task intTask) + return ConvertToOutput((TResult)(object)intTask.Result, input); + + // For Task + if (task is Task boolTask) + return ConvertToOutput((TResult)(object)boolTask.Result, input); + + // For Task + if (task is Task doubleTask) + return ConvertToOutput((TResult)(object)doubleTask.Result, input); + + // For Task + if (task is Task longTask) + return ConvertToOutput((TResult)(object)longTask.Result, input); + + // For Task + if (task is Task decimalTask) + return ConvertToOutput((TResult)(object)decimalTask.Result, input); + + // For Task + if (task is Task dateTimeTask) + return ConvertToOutput((TResult)(object)dateTimeTask.Result, input); + + // For Task + if (task is Task guidTask) + return ConvertToOutput((TResult)(object)guidTask.Result, input); + + // For Task + if (task is Task objectTask) + return ConvertToOutput((TResult)objectTask.Result, input); + + // For regular Task with no result + task.GetAwaiter().GetResult(); + return BedrockFunctionResponse.WithText( + string.Empty, + input.ActionGroup, + input.Function, + input.SessionAttributes, + input.PromptSessionAttributes, + new Dictionary()); + } + + /// + /// Converts a result to a BedrockFunctionResponse + /// + public BedrockFunctionResponse ConvertToOutput(T result, BedrockFunctionRequest input) + { + var function = input.Function; + + if (EqualityComparer.Default.Equals(result, default(T))) + { + return CreateEmptyResponse(input); + } + + // If result is already a BedrockFunctionResponse, ensure metadata is set + if (result is BedrockFunctionResponse output) + { + return EnsureResponseMetadata(output, input, function); + } + + // Handle primitive types + return ConvertPrimitiveToOutput(result, input); + } + + private BedrockFunctionResponse ConvertPrimitiveToOutput(T result, BedrockFunctionRequest input) + { + var actionGroup = input.ActionGroup; + var function = input.Function; + + // For primitive types and strings, convert to string + if (result is string str) + { + return BedrockFunctionResponse.WithText( + str, + actionGroup, + function, + input.SessionAttributes, + input.PromptSessionAttributes, + new Dictionary()); + } + + if (result is int intVal) + { + return BedrockFunctionResponse.WithText( + intVal.ToString(CultureInfo.InvariantCulture), + actionGroup, + function, + input.SessionAttributes, + input.PromptSessionAttributes, + new Dictionary()); + } + + if (result is double doubleVal) + { + return BedrockFunctionResponse.WithText( + doubleVal.ToString(CultureInfo.InvariantCulture), + actionGroup, + function, + input.SessionAttributes, + input.PromptSessionAttributes, + new Dictionary()); + } + + if (result is bool boolVal) + { + return BedrockFunctionResponse.WithText( + boolVal.ToString(), + actionGroup, + function, + input.SessionAttributes, + input.PromptSessionAttributes, + new Dictionary()); + } + + if (result is long longVal) + { + return BedrockFunctionResponse.WithText( + longVal.ToString(CultureInfo.InvariantCulture), + actionGroup, + function, + input.SessionAttributes, + input.PromptSessionAttributes, + new Dictionary()); + } + + if (result is decimal decimalVal) + { + return BedrockFunctionResponse.WithText( + decimalVal.ToString(CultureInfo.InvariantCulture), + actionGroup, + function, + input.SessionAttributes, + input.PromptSessionAttributes, + new Dictionary()); + } + + // For any other type, use ToString() + return BedrockFunctionResponse.WithText( + result?.ToString() ?? string.Empty, + actionGroup, + function, + input.SessionAttributes, + input.PromptSessionAttributes, + new Dictionary()); + } + + private BedrockFunctionResponse CreateEmptyResponse(BedrockFunctionRequest input) + { + return BedrockFunctionResponse.WithText( + string.Empty, + input.ActionGroup, + input.Function, + input.SessionAttributes, + input.PromptSessionAttributes, + new Dictionary()); + } + + private BedrockFunctionResponse EnsureResponseMetadata( + BedrockFunctionResponse response, + BedrockFunctionRequest input, + string functionName) + { + // If the action group or function are not set in the output, use the provided values + if (string.IsNullOrEmpty(response.Response.ActionGroup)) + { + response.Response.ActionGroup = input.ActionGroup; + } + + if (string.IsNullOrEmpty(response.Response.Function)) + { + response.Response.Function = functionName; + } + + return response; + } + } +} diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/InternalsVisibleTo.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/InternalsVisibleTo.cs new file mode 100644 index 00000000..9e952373 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/InternalsVisibleTo.cs @@ -0,0 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("AWS.Lambda.Powertools.EventHandler.Tests")] \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/Agent.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/Agent.cs new file mode 100644 index 00000000..05b5d20e --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/Agent.cs @@ -0,0 +1,48 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System.Text.Json.Serialization; + +namespace AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; + +/// +/// Contains information about the name, ID, alias, and version of the agent that the action group belongs to. +/// +public class Agent +{ + /// + /// Gets or sets the name of the agent. + /// + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// + /// Gets or sets the version of the agent. + /// + [JsonPropertyName("version")] + public string Version { get; set; } = string.Empty; + + /// + /// Gets or sets the ID of the agent. + /// + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; + + /// + /// Gets or sets the alias of the agent. + /// + [JsonPropertyName("alias")] + public string Alias { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/BedrockFunctionRequest.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/BedrockFunctionRequest.cs new file mode 100644 index 00000000..0a9fa9ef --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/BedrockFunctionRequest.cs @@ -0,0 +1,79 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System.Text.Json.Serialization; + +namespace AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; + +/// +/// Represents the input for a Bedrock Agent function. +/// +public class BedrockFunctionRequest +{ + /// + /// The version of the message that identifies the format of the event data going into the Lambda function and the expected format of the response from a Lambda function. Amazon Bedrock only supports version 1.0. + /// + [JsonPropertyName("messageVersion")] + public string MessageVersion { get; set; } = "1.0"; + + /// + /// The name of the function as defined in the function details for the action group. + /// + [JsonPropertyName("function")] + public string Function { get; set; } = string.Empty; + + /// + /// Contains a list of objects. Each object contains the name, type, and value of a parameter in the API operation, as defined in the OpenAPI schema, or in the function. + /// + [JsonPropertyName("parameters")] + public List Parameters { get; set; } = new List(); + + /// + /// The unique identifier of the agent session. + /// + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; + + /// + /// Contains information about the name, ID, alias, and version of the agent that the action group belongs to. + /// + [JsonPropertyName("agent")] + public Agent? Agent { get; set; } + + /// + /// The name of the action group. + /// + [JsonPropertyName("actionGroup")] + public string ActionGroup { get; set; } = string.Empty; + + /// + /// Contains session attributes and their values. These attributes are stored over a session and provide context for the agent. + /// For more information, see Session and prompt session attributes. + /// + [JsonPropertyName("sessionAttributes")] + public Dictionary SessionAttributes { get; set; } = new Dictionary(); + + /// + /// Contains prompt session attributes and their values. These attributes are stored over a turn and provide context for the agent. + /// + [JsonPropertyName("promptSessionAttributes")] + public Dictionary PromptSessionAttributes { get; set; } = new Dictionary(); + + /// + /// The user input for the conversation turn. + /// + [JsonPropertyName("inputText")] + public string InputText { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/BedrockFunctionResponse.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/BedrockFunctionResponse.cs new file mode 100644 index 00000000..4d90dcba --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/BedrockFunctionResponse.cs @@ -0,0 +1,86 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System.Text.Json.Serialization; + +namespace AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; + +/// +/// The version of the message that identifies the format of the event data going into the Lambda function and the expected format of the response from a Lambda function. Amazon Bedrock only supports version 1.0. +/// +public class BedrockFunctionResponse +{ + /// + /// Gets or sets the message version. + /// + [JsonPropertyName("messageVersion")] + public string MessageVersion { get; } = "1.0"; + + /// + /// Gets or sets the response. + /// + [JsonPropertyName("response")] + public Response Response { get; set; } = new Response(); + + /// + /// Contains session attributes and their values. For more information, Session and prompt session attributes. + /// + [JsonPropertyName("sessionAttributes")] + public Dictionary SessionAttributes { get; set; } = new Dictionary(); + + /// + /// Contains prompt attributes and their values. For more information, Session and prompt session attributes. + /// + [JsonPropertyName("promptSessionAttributes")] + public Dictionary PromptSessionAttributes { get; set; } = new Dictionary(); + + /// + /// Contains a list of query configurations for knowledge bases attached to the agent. For more information, Knowledge base retrieval configurations. + /// + [JsonPropertyName("knowledgeBasesConfiguration")] + public Dictionary KnowledgeBasesConfiguration { get; set; } = new Dictionary(); + + + /// + /// Creates a new instance of BedrockFunctionResponse with the specified text. + /// + public static BedrockFunctionResponse WithText( + string? text, + string actionGroup = "", + string function = "", + Dictionary? sessionAttributes = null, + Dictionary? promptSessionAttributes = null, + Dictionary? knowledgeBasesConfiguration = null) + { + return new BedrockFunctionResponse + { + Response = new Response + { + ActionGroup = actionGroup, + Function = function, + FunctionResponse = new FunctionResponse + { + ResponseBody = new ResponseBody + { + Text = new TextBody { Body = text ?? string.Empty } + } + } + }, + SessionAttributes = sessionAttributes ?? new Dictionary(), + PromptSessionAttributes = promptSessionAttributes ?? new Dictionary(), + KnowledgeBasesConfiguration = knowledgeBasesConfiguration ?? new Dictionary() + }; + } +} diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/FunctionResponse.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/FunctionResponse.cs new file mode 100644 index 00000000..5366c902 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/FunctionResponse.cs @@ -0,0 +1,52 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System.Text.Json.Serialization; +// ReSharper disable InconsistentNaming +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; + +/// +/// Represents the function response part of a Response. +/// +public class FunctionResponse +{ + /// + /// Contains an object that defines the response from execution of the function. The key is the content type (currently only TEXT is supported) and the value is an object containing the body of the response. + /// + [JsonPropertyName("responseBody")] + public ResponseBody ResponseBody { get; set; } = new ResponseBody(); + + /// + /// (Optional) – Set to one of the following states to define the agent's behavior after processing the action: + /// + /// FAILURE – The agent throws a DependencyFailedException for the current session. Applies when the function execution fails because of a dependency failure. + /// REPROMPT – The agent passes a response string to the model to reprompt it. Applies when the function execution fails because of invalid input. + /// + [JsonPropertyName("responseState")] + [JsonConverter(typeof(JsonStringEnumConverter))] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public ResponseState? ResponseState { get; set; } +} + +/// +/// Represents the response state of a function response. +/// +public enum ResponseState +{ + FAILURE, + REPROMPT +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/Parameter.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/Parameter.cs new file mode 100644 index 00000000..481eca67 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/Parameter.cs @@ -0,0 +1,44 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System.Text.Json.Serialization; + +// ReSharper disable once CheckNamespace +namespace AWS.Lambda.Powertools.EventHandler.Resolvers +{ + /// + /// Represents a parameter for a Bedrock Agent function. + /// + public class Parameter + { + /// + /// Gets or sets the name of the parameter. + /// + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// + /// Gets or sets the type of the parameter. + /// + [JsonPropertyName("type")] + public string Type { get; set; } = string.Empty; + + /// + /// Gets or sets the value of the parameter. + /// + [JsonPropertyName("value")] + public string Value { get; set; } = string.Empty; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/Response.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/Response.cs new file mode 100644 index 00000000..5d0720be --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/Response.cs @@ -0,0 +1,42 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System.Text.Json.Serialization; + +namespace AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; + +/// +/// Represents the response part of an BedrockFunctionResponse. +/// +public class Response +{ + /// + /// Gets or sets the action group. + /// + [JsonPropertyName("actionGroup")] + public string ActionGroup { get; internal set; } = string.Empty; + + /// + /// Gets or sets the function. + /// + [JsonPropertyName("function")] + public string Function { get; internal set; } = string.Empty; + + /// + /// Gets or sets the function response. + /// + [JsonPropertyName("functionResponse")] + public FunctionResponse FunctionResponse { get; set; } = new FunctionResponse(); +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/ResponseBody.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/ResponseBody.cs new file mode 100644 index 00000000..3081af44 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/ResponseBody.cs @@ -0,0 +1,30 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System.Text.Json.Serialization; + +namespace AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; + +/// +/// Represents the response body part of a FunctionResponse. +/// +public class ResponseBody +{ + /// + /// Gets or sets the text body. + /// + [JsonPropertyName("TEXT")] + public TextBody Text { get; set; } = new TextBody(); +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/TextBody.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/TextBody.cs new file mode 100644 index 00000000..f3240a87 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/TextBody.cs @@ -0,0 +1,30 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System.Text.Json.Serialization; + +namespace AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; + +/// +/// Represents the text body part of a ResponseBody. +/// +public class TextBody +{ + /// + /// Gets or sets the body text. + /// + [JsonPropertyName("body")] + public string Body { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Readme.md b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Readme.md new file mode 100644 index 00000000..9904f64e --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Readme.md @@ -0,0 +1,399 @@ +# AWS Lambda Powertools for .NET - Bedrock Agent Function Resolver + +## Overview +The Bedrock Agent Function Resolver is a utility for AWS Lambda that simplifies building serverless applications working with Amazon Bedrock Agents. This library eliminates boilerplate code typically required when implementing Lambda functions that serve as action groups for Bedrock Agents. + +Amazon Bedrock Agents can invoke functions to perform tasks based on user input. This library provides an elegant way to register, manage, and execute these functions with minimal code, handling all the parameter extraction and response formatting automatically. + +## Features + +- **Simple Tool Registration**: Register functions with descriptive names that Bedrock Agents can invoke +- **Automatic Parameter Handling**: Parameters are automatically extracted from Bedrock Agent requests and converted to the appropriate types +- **Lambda Context Access**: Easy access to Lambda context for logging and AWS Lambda features +- **Dependency Injection Support**: Seamless integration with .NET's dependency injection system +- **AOT Compatibility**: Fully compatible with .NET 8 AOT compilation through source generation + +## Terminology + +**Event handler** is a Powertools for AWS feature that processes an event, runs data parsing and validation, routes the request to a specific function, and returns a response to the caller in the proper format. + +**Function details** consist of a list of parameters, defined by their name, data type, and whether they are required. The agent uses these configurations to determine what information it needs to elicit from the user. + +**Action group** is a collection of two resources where you define the actions that the agent should carry out: an OpenAPI schema to define the APIs that the agent can invoke to carry out its tasks, and a Lambda function to execute those actions. + +**Large Language Models (LLM)** are very large deep learning models that are pre-trained on vast amounts of data, capable of extracting meanings from a sequence of text and understanding the relationship between words and phrases on it. + +**Amazon Bedrock Agent** is an Amazon Bedrock feature to build and deploy conversational agents that can interact with your customers using Large Language Models (LLM) and AWS Lambda functions. + + +## Installation + +Install the package via NuGet: + +```bash +dotnet add package AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction +``` + +## Basic Usage + +To create an agent, use the `BedrockAgentFunctionResolver` to register your tools and handle the requests. The resolver will automatically parse the request, route it to the appropriate function, and return a well-formed response that includes the tool's output and any existing session attributes. + +```csharp +using Amazon.BedrockAgentRuntime.Model; +using Amazon.Lambda.Core; +using AWS.Lambda.Powertools.EventHandler; + +[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] + +namespace MyLambdaFunction +{ + public class Function + { + private readonly BedrockAgentFunctionResolver _resolver; + + public Function() + { + _resolver = new BedrockAgentFunctionResolver(); + + // Register simple tool functions + _resolver + .Tool("GetWeather", (string city) => $"The weather in {city} is sunny") + .Tool("CalculateSum", (int a, int b) => $"The sum of {a} and {b} is {a + b}") + .Tool("GetCurrentTime", () => $"The current time is {DateTime.Now}"); + } + + // Lambda handler function + public ActionGroupInvocationOutput FunctionHandler( + ActionGroupInvocationInput input, ILambdaContext context) + { + return _resolver.Resolve(input, context); + } + } +} +``` + +When the Bedrock Agent invokes your Lambda function with a request to use the "GetWeather" tool and a parameter for "city", the resolver automatically extracts the parameter, passes it to your function, and formats the response. + +## Advanced Usage + +### Functions with Descriptions + +Add descriptive information to your tool functions: + +```csharp +_resolver.Tool( + "CheckInventory", + "Checks if a product is available in inventory", + (string productId, bool checkWarehouse) => + { + return checkWarehouse + ? $"Product {productId} has 15 units in warehouse" + : $"Product {productId} has 5 units in store"; + }); +``` + +### Accessing Lambda Context + +You can access to the original Lambda event or context for additional information. These are passed to the handler function as optional arguments. + +```csharp +_resolver.Tool( + "LogRequest", + "Logs request information and returns confirmation", + (string requestId, ILambdaContext context) => + { + context.Logger.LogLine($"Processing request {requestId}"); + return $"Request {requestId} logged successfully"; + }); +``` + +### Handling errors + +By default, we will handle errors gracefully and return a well-formed response to the agent so that it can continue the conversation with the user. + +When an error occurs, we send back an error message in the response body that includes the error type and message. The agent will then use this information to let the user know that something went wrong. + +If you want to handle errors differently, you can return a `BedrockFunctionResponse` with a custom `Body` and `ResponseState` set to `FAILURE`. This is useful when you want to abort the conversation. + +```csharp +resolver.Tool("CustomFailure", () => +{ + // Return a custom FAILURE response + return new BedrockFunctionResponse + { + Response = new Response + { + ActionGroup = "TestGroup", + Function = "CustomFailure", + FunctionResponse = new FunctionResponse + { + ResponseBody = new ResponseBody + { + Text = new TextBody + { + Body = "Critical error occurred: Database unavailable" + } + }, + ResponseState = ResponseState.FAILURE // Mark as FAILURE to abort the conversation + } + } + }; +}); +``` +### Setting session attributes + +When Bedrock Agents invoke your Lambda function, it can pass session attributes that you can use to store information across multiple interactions with the user. You can access these attributes in your handler function and modify them as needed. + +```csharp +// Create a counter tool that reads and updates session attributes +resolver.Tool("CounterTool", (BedrockFunctionRequest request) => +{ + // Read the current count from session attributes + int currentCount = 0; + if (request.SessionAttributes != null && + request.SessionAttributes.TryGetValue("counter", out var countStr) && + int.TryParse(countStr, out var count)) + { + currentCount = count; + } + + // Increment the counter + currentCount++; + + // Create a new dictionary with updated counter + var updatedSessionAttributes = new Dictionary(request.SessionAttributes ?? new Dictionary()) + { + ["counter"] = currentCount.ToString(), + ["lastAccessed"] = DateTime.UtcNow.ToString("o") + }; + + // Return response with updated session attributes + return new BedrockFunctionResponse + { + Response = new Response + { + ActionGroup = request.ActionGroup, + Function = request.Function, + FunctionResponse = new FunctionResponse + { + ResponseBody = new ResponseBody + { + Text = new TextBody { Body = $"Current count: {currentCount}" } + } + } + }, + SessionAttributes = updatedSessionAttributes, + PromptSessionAttributes = request.PromptSessionAttributes + }; +}); +``` + +### Asynchronous Functions + +Register and use asynchronous functions: + +```csharp +_resolver.Tool( + "FetchUserData", + "Fetches user data from external API", + async (string userId, ILambdaContext ctx) => + { + // Log the request + ctx.Logger.LogLine($"Fetching data for user {userId}"); + + // Simulate API call + await Task.Delay(100); + + // Return user information + return new { Id = userId, Name = "John Doe", Status = "Active" }.ToString(); + }); +``` + +### Direct Access to Request Payload + +Access the raw Bedrock Agent request: + +```csharp +_resolver.Tool( + "ProcessRawRequest", + "Processes the raw Bedrock Agent request", + (ActionGroupInvocationInput input) => + { + var functionName = input.Function; + var parameterCount = input.Parameters.Count; + return $"Received request for {functionName} with {parameterCount} parameters"; + }); +``` + +## Dependency Injection + +The library supports dependency injection for integrating with services: + +```csharp +using Microsoft.Extensions.DependencyInjection; + +// Set up dependency injection +var services = new ServiceCollection(); +services.AddSingleton(); +services.AddBedrockResolver(); // Extension method to register the resolver + +var serviceProvider = services.BuildServiceProvider(); +var resolver = serviceProvider.GetRequiredService(); + +// Register a tool that uses an injected service +resolver.Tool( + "GetWeatherForecast", + "Gets the weather forecast for a location", + (string city, IWeatherService weatherService, ILambdaContext ctx) => + { + ctx.Logger.LogLine($"Getting weather for {city}"); + return weatherService.GetForecast(city); + }); +``` + +## How It Works with Amazon Bedrock Agents + +1. When a user interacts with a Bedrock Agent, the agent identifies when it needs to call an action to fulfill the user's request. +2. The agent determines which function to call and what parameters are needed. +3. Bedrock sends a request to your Lambda function with the function name and parameters. +4. The BedrockAgentFunctionResolver automatically: + - Finds the registered handler for the requested function + - Extracts and converts parameters to the correct types + - Invokes your handler with the parameters + - Formats the response in the way Bedrock Agents expect +5. The agent receives the response and uses it to continue the conversation with the user + +## Supported Parameter Types + +- `string` +- `int` +- `number` +- `bool` +- `enum` types +- `ILambdaContext` (for accessing Lambda context) +- `ActionGroupInvocationInput` (for accessing raw request) +- Any service registered in dependency injection + + +## Using Attributes to Define Tools + +You can define Bedrock Agent functions using attributes instead of explicit registration. This approach provides a clean, declarative way to organize your tools into classes: + +### Define Tool Classes with Attributes + +```csharp +// Define your tool class with BedrockFunctionType attribute +[BedrockFunctionType] +public class WeatherTools +{ + // Each method marked with BedrockFunctionTool attribute becomes a tool + [BedrockFunctionTool(Name = "GetWeather", Description = "Gets weather forecast for a location")] + public static string GetWeather(string city, int days) + { + return $"Weather forecast for {city} for the next {days} days: Sunny"; + } + + // Supports dependency injection and Lambda context access + [BedrockFunctionTool(Name = "GetDetailedForecast", Description = "Gets detailed weather forecast")] + public static string GetDetailedForecast( + string location, + IWeatherService weatherService, + ILambdaContext context) + { + context.Logger.LogLine($"Getting forecast for {location}"); + return weatherService.GetForecast(location); + } +} +``` + +### Register Tool Classes in Your Application + +Using the extension method provided in the library, you can easily register all tools from a class: + +```csharp + +var services = new ServiceCollection(); +services.AddSingleton(); +services.AddBedrockResolver(); // Extension method to register the resolver + +var serviceProvider = services.BuildServiceProvider(); +var resolver = serviceProvider.GetRequiredService() + .RegisterTool(); // Register tools from the class during service registration + +``` + +## Complete Example with Dependency Injection + +```csharp +using Amazon.BedrockAgentRuntime.Model; +using Amazon.Lambda.Core; +using AWS.Lambda.Powertools.EventHandler; +using Microsoft.Extensions.DependencyInjection; + +[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] + +namespace MyBedrockAgent +{ + // Service interfaces and implementations + public interface IWeatherService + { + string GetForecast(string city); + } + + public class WeatherService : IWeatherService + { + public string GetForecast(string city) => $"Weather forecast for {city}: Sunny, 75°F"; + } + + public interface IProductService + { + string CheckInventory(string productId); + } + + public class ProductService : IProductService + { + public string CheckInventory(string productId) => $"Product {productId} has 25 units in stock"; + } + + // Main Lambda function + public class Function + { + private readonly BedrockAgentFunctionResolver _resolver; + + public Function() + { + // Set up dependency injection + var services = new ServiceCollection(); + services.AddSingleton(); + services.AddSingleton(); + services.AddBedrockResolver(); // Extension method to register the resolver + + var serviceProvider = services.BuildServiceProvider(); + _resolver = serviceProvider.GetRequiredService(); + + // Register tool functions that use injected services + _resolver + .Tool("GetWeatherForecast", + "Gets weather forecast for a city", + (string city, IWeatherService weatherService, ILambdaContext ctx) => + { + ctx.Logger.LogLine($"Weather request for {city}"); + return weatherService.GetForecast(city); + }) + .Tool("CheckInventory", + "Checks inventory for a product", + (string productId, IProductService productService) => + productService.CheckInventory(productId)) + .Tool("GetServerTime", + "Returns the current server time", + () => DateTime.Now.ToString("F")); + } + + public ActionGroupInvocationOutput FunctionHandler( + ActionGroupInvocationInput input, ILambdaContext context) + { + return _resolver.Resolve(input, context); + } + } +} +``` \ No newline at end of file diff --git a/libraries/src/Directory.Packages.props b/libraries/src/Directory.Packages.props index db4a6a7f..a4421f6f 100644 --- a/libraries/src/Directory.Packages.props +++ b/libraries/src/Directory.Packages.props @@ -9,10 +9,12 @@ + + diff --git a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/AWS.Lambda.Powertools.EventHandler.Tests.csproj b/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/AWS.Lambda.Powertools.EventHandler.Tests.csproj index eef47181..2d37bab6 100644 --- a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/AWS.Lambda.Powertools.EventHandler.Tests.csproj +++ b/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/AWS.Lambda.Powertools.EventHandler.Tests.csproj @@ -17,6 +17,7 @@ + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -32,6 +33,7 @@ + @@ -40,6 +42,11 @@ PreserveNewest + + + + PreserveNewest + diff --git a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/BedrockAgentFunctionResolverAdditionalTests.cs b/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/BedrockAgentFunctionResolverAdditionalTests.cs new file mode 100644 index 00000000..3f73c686 --- /dev/null +++ b/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/BedrockAgentFunctionResolverAdditionalTests.cs @@ -0,0 +1,340 @@ +using Amazon.Lambda.Core; +using Amazon.Lambda.TestUtilities; +using AWS.Lambda.Powertools.EventHandler.Resolvers; +using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; + +namespace AWS.Lambda.Powertools.EventHandler.BedrockAgentFunction +{ + public class BedrockAgentFunctionResolverAdditionalTests + { + [Fact] + public async Task ResolveAsync_WithValidInput_ReturnsResult() + { + // Arrange + var resolver = new BedrockAgentFunctionResolver(); + resolver.Tool("AsyncTest", () => "Async result"); + + var input = new BedrockFunctionRequest { Function = "AsyncTest" }; + var context = new TestLambdaContext(); + + // Act + var result = await resolver.ResolveAsync(input, context); + + // Assert + Assert.Equal("Async result", result.Response.FunctionResponse.ResponseBody.Text.Body); + } + + [Fact] + public void Tool_WithNullHandler_ThrowsException() + { + // Arrange + var resolver = new BedrockAgentFunctionResolver(); + Func nullHandler = null!; + + // Act/Assert + Assert.Throws(() => resolver.Tool("NullTest", nullHandler)); + } + + [Fact] + public void Resolve_WithNullFunction_ReturnsErrorResponse() + { + // Arrange + var resolver = new BedrockAgentFunctionResolver(); + var input = new BedrockFunctionRequest { Function = null }; + + // Act + var result = resolver.Resolve(input); + + // Assert + Assert.Equal("No tool specified in the request", result.Response.FunctionResponse.ResponseBody.Text.Body); + } + + [Fact] + public void Resolve_WithEmptyFunction_ReturnsErrorResponse() + { + // Arrange + var resolver = new BedrockAgentFunctionResolver(); + var input = new BedrockFunctionRequest { Function = "" }; + + // Act + var result = resolver.Resolve(input); + + // Assert + Assert.Equal("No tool specified in the request", result.Response.FunctionResponse.ResponseBody.Text.Body); + } + + [Fact] + public void Tool_WithHandlerThrowingException_ReturnsErrorResponse() + { + // Arrange + var resolver = new BedrockAgentFunctionResolver(); + resolver.Tool("ExceptionTest", (BedrockFunctionRequest input, ILambdaContext ctx) => { + throw new InvalidOperationException("Handler exception"); + return new BedrockFunctionResponse(); + }); + + var input = new BedrockFunctionRequest { Function = "ExceptionTest" }; + + // Act + var result = resolver.Resolve(input); + + // Assert + Assert.Equal("Error when invoking tool: Handler exception", result.Response.FunctionResponse.ResponseBody.Text.Body); + } + + [Fact] + public void Tool_WithDynamicInvokeException_ReturnsErrorResponse() + { + // Arrange + var resolver = new BedrockAgentFunctionResolver(); + resolver.Tool("ExceptionTest", (Func)(() => { + throw new InvalidOperationException("Dynamic invoke exception"); + })); + + var input = new BedrockFunctionRequest { Function = "ExceptionTest" }; + + // Act + var result = resolver.Resolve(input); + + // Assert + Assert.Contains("Error when invoking tool", result.Response.FunctionResponse.ResponseBody.Text.Body); + } + + [Fact] + public void Tool_ObjectFunctionRegistration_ReturnsObjectAsString() + { + // Arrange + var testObject = new TestObject { Id = 123, Name = "Test" }; + var resolver = new BedrockAgentFunctionResolver(); + resolver.Tool("ObjectTest", () => testObject); + + var input = new BedrockFunctionRequest { Function = "ObjectTest" }; + + // Act + var result = resolver.Resolve(input); + + // Assert + Assert.Equal(testObject.ToString(), result.Response.FunctionResponse.ResponseBody.Text.Body); + } + + [Fact] + public async Task Resolve_WithAsyncTask_HandlesCorrectly() + { + // Arrange + var resolver = new BedrockAgentFunctionResolver(); + resolver.Tool("AsyncTaskTest", async (string message) => { + await Task.Delay(10); // Simulate async work + return $"Processed: {message}"; + }); + + var input = new BedrockFunctionRequest { + Function = "AsyncTaskTest", + Parameters = new List { + new Parameter { Name = "message", Value = "hello", Type = "String" } + } + }; + + // Act + var result = resolver.Resolve(input); + + // Assert + Assert.Equal("Processed: hello", result.Response.FunctionResponse.ResponseBody.Text.Body); + } + + [Fact] + public void Tool_WithBedrockFunctionResponseHandlerNoContext_MapsCorrectly() + { + // Arrange + var resolver = new BedrockAgentFunctionResolver(); + resolver.Tool("NoContextTest", (BedrockFunctionRequest request) => new BedrockFunctionResponse + { + Response = new Response + { + ActionGroup = "TestGroup", + Function = "NoContextTest", + FunctionResponse = new FunctionResponse + { + ResponseBody = new ResponseBody + { + Text = new TextBody { Body = "No context needed" } + } + } + } + }); + + var input = new BedrockFunctionRequest { Function = "NoContextTest" }; + + // Act + var result = resolver.Resolve(input); + + // Assert + Assert.Equal("No context needed", result.Response.FunctionResponse.ResponseBody.Text.Body); + } + + [Fact] + public void Tool_WithBedrockFunctionResponseHandler_MapsCorrectly() + { + // Arrange + var resolver = new BedrockAgentFunctionResolver(); + resolver.Tool("ResponseTest", () => new BedrockFunctionResponse + { + Response = new Response + { + ActionGroup = "TestGroup", + Function = "ResponseTest", + FunctionResponse = new FunctionResponse + { + ResponseBody = new ResponseBody + { + Text = new TextBody { Body = "Direct response" } + } + } + } + }); + + var input = new BedrockFunctionRequest { Function = "ResponseTest" }; + + // Act + var result = resolver.Resolve(input); + + // Assert + Assert.Equal("Direct response", result.Response.FunctionResponse.ResponseBody.Text.Body); + } + + [Fact] + public void Tool_WithCustomFailureResponse_ReturnsFailureState() + { + // Arrange + var resolver = new BedrockAgentFunctionResolver(); + resolver.Tool("CustomFailure", () => + { + // Return a custom FAILURE response + return new BedrockFunctionResponse + { + Response = new Response + { + ActionGroup = "TestGroup", + Function = "CustomFailure", + FunctionResponse = new FunctionResponse + { + ResponseBody = new ResponseBody + { + Text = new TextBody + { + Body = "Critical error occurred: Database unavailable" + } + }, + ResponseState = ResponseState.FAILURE // Mark as FAILURE to abort the conversation + } + } + }; + }); + + var input = new BedrockFunctionRequest { Function = "CustomFailure" }; + var context = new TestLambdaContext(); + + // Act + var result = resolver.Resolve(input, context); + + // Assert + Assert.Equal("Critical error occurred: Database unavailable", result.Response.FunctionResponse.ResponseBody.Text.Body); + Assert.Equal("FAILURE", result.Response.FunctionResponse.ResponseState.ToString()); + } + + [Fact] + public void Tool_WithSessionAttributesPersistence_MaintainsStateAcrossInvocations() + { + // Arrange + var resolver = new BedrockAgentFunctionResolver(); + + // Create a counter tool that reads and updates session attributes + resolver.Tool("CounterTool", (BedrockFunctionRequest request) => + { + // Read the current count from session attributes + int currentCount = 0; + if (request.SessionAttributes != null && + request.SessionAttributes.TryGetValue("counter", out var countStr) && + int.TryParse(countStr, out var count)) + { + currentCount = count; + } + + // Increment the counter + currentCount++; + + // Create a new dictionary with updated counter + var updatedSessionAttributes = new Dictionary(request.SessionAttributes ?? new Dictionary()) + { + ["counter"] = currentCount.ToString(), + ["lastAccessed"] = DateTime.UtcNow.ToString("o") + }; + + // Return response with updated session attributes + return new BedrockFunctionResponse + { + Response = new Response + { + ActionGroup = request.ActionGroup, + Function = request.Function, + FunctionResponse = new FunctionResponse + { + ResponseBody = new ResponseBody + { + Text = new TextBody { Body = $"Current count: {currentCount}" } + } + } + }, + SessionAttributes = updatedSessionAttributes, + PromptSessionAttributes = request.PromptSessionAttributes + }; + }); + + // First invocation - should start with 0 and increment to 1 + var firstInput = new BedrockFunctionRequest + { + Function = "CounterTool", + SessionAttributes = new Dictionary(), + PromptSessionAttributes = new Dictionary { ["prompt"] = "initial" } + }; + + // Second invocation - should use the session attributes from first response + var secondInput = new BedrockFunctionRequest { Function = "CounterTool" }; + + // Act + var firstResult = resolver.Resolve(firstInput); + // In a real scenario, the agent would pass the updated session attributes back to us + secondInput.SessionAttributes = firstResult.SessionAttributes; + secondInput.PromptSessionAttributes = firstResult.PromptSessionAttributes; + var secondResult = resolver.Resolve(secondInput); + + // Now a third invocation to verify the counter keeps incrementing + var thirdInput = new BedrockFunctionRequest { Function = "CounterTool" }; + thirdInput.SessionAttributes = secondResult.SessionAttributes; + thirdInput.PromptSessionAttributes = secondResult.PromptSessionAttributes; + var thirdResult = resolver.Resolve(thirdInput); + + // Assert + Assert.Equal("Current count: 1", firstResult.Response.FunctionResponse.ResponseBody.Text.Body); + Assert.Equal("Current count: 2", secondResult.Response.FunctionResponse.ResponseBody.Text.Body); + Assert.Equal("Current count: 3", thirdResult.Response.FunctionResponse.ResponseBody.Text.Body); + + // Verify session attributes are maintained + Assert.Equal("1", firstResult.SessionAttributes["counter"]); + Assert.Equal("2", secondResult.SessionAttributes["counter"]); + Assert.Equal("3", thirdResult.SessionAttributes["counter"]); + + // Verify prompt attributes are preserved + Assert.Equal("initial", firstResult.PromptSessionAttributes["prompt"]); + Assert.Equal("initial", secondResult.PromptSessionAttributes["prompt"]); + Assert.Equal("initial", thirdResult.PromptSessionAttributes["prompt"]); + } + + private class TestObject + { + public int Id { get; set; } + public string Name { get; set; } = ""; + + public override string ToString() => $"{Name} (ID: {Id})"; + } + } +} diff --git a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/BedrockAgentFunctionResolverExceptionTests.cs b/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/BedrockAgentFunctionResolverExceptionTests.cs new file mode 100644 index 00000000..b05c3f42 --- /dev/null +++ b/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/BedrockAgentFunctionResolverExceptionTests.cs @@ -0,0 +1,68 @@ +using Amazon.Lambda.TestUtilities; +using AWS.Lambda.Powertools.EventHandler.Resolvers; +using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; + +namespace AWS.Lambda.Powertools.EventHandler.BedrockAgentFunction +{ + public class BedrockAgentFunctionResolverExceptionTests + { + [Fact] + public void RegisterToolHandler_WithParameterMappingException_ReturnsErrorResponse() + { + // Arrange + var resolver = new BedrockAgentFunctionResolver(); + + // Register a tool that requires a complex parameter that can't be mapped automatically + resolver.Tool("ComplexTest", (TestComplexType complex) => $"Name: {complex.Name}"); + + var input = new BedrockFunctionRequest + { + Function = "ComplexTest", + Parameters = new List + { + // This parameter can't be automatically mapped to the complex type + new Parameter { Name = "complex", Value = "{\"name\":\"Test\"}", Type = "String" } + } + }; + var context = new TestLambdaContext(); + + // Act + var result = resolver.Resolve(input, context); + + // Assert + // This should trigger the parameter mapping exception path + Assert.Contains("Error when invoking tool:", result.Response.FunctionResponse.ResponseBody.Text.Body); + } + + [Fact] + public void RegisterToolHandler_WithNestedExceptionInDelegateInvoke_HandlesCorrectly() + { + // Arrange + var resolver = new BedrockAgentFunctionResolver(); + + // Register a tool with a delegate that will throw an exception with inner exception + resolver.Tool("NestedExceptionTest", () => { + throw new AggregateException("Outer exception", + new ApplicationException("Inner exception message")); + return "Should not reach here"; + }); + + var input = new BedrockFunctionRequest { Function = "NestedExceptionTest" }; + var context = new TestLambdaContext(); + + // Act + var result = resolver.Resolve(input, context); + + // Assert + // The error should contain the inner exception message + Assert.Contains("Inner exception message", result.Response.FunctionResponse.ResponseBody.Text.Body); + } + + // A test complex type that can't be automatically mapped from parameters + private class TestComplexType + { + public string Name { get; set; } = ""; + public int Value { get; set; } + } + } +} diff --git a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/BedrockAgentFunctionResolverTests.cs b/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/BedrockAgentFunctionResolverTests.cs new file mode 100644 index 00000000..1090a248 --- /dev/null +++ b/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/BedrockAgentFunctionResolverTests.cs @@ -0,0 +1,943 @@ +using System.Globalization; +using System.Text; +using System.Text.Json.Serialization; +using Amazon.Lambda.Core; +using Amazon.Lambda.TestUtilities; +using AWS.Lambda.Powertools.EventHandler.Resolvers; +using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; +using Microsoft.Extensions.DependencyInjection; + +#pragma warning disable CS0162 // Unreachable code detected + + +namespace AWS.Lambda.Powertools.EventHandler.BedrockAgentFunction; + +public class BedrockAgentFunctionResolverTests +{ + [Fact] + public void TestFunctionHandlerWithNoParameters() + { + // Arrange + var resolver = new BedrockAgentFunctionResolver(); + resolver.Tool("TestFunction", () => new BedrockFunctionResponse + { + Response = new Response + { + ActionGroup = "TestGroup", + Function = "TestFunction", + FunctionResponse = new FunctionResponse + { + ResponseBody = new ResponseBody + { + Text = new TextBody { Body = "Hello, World!" } + } + } + } + }); + + var input = new BedrockFunctionRequest { Function = "TestFunction" }; + var context = new TestLambdaContext(); + + // Act + var result = resolver.Resolve(input, context); + + // Assert + Assert.Equal("Hello, World!", result.Response.FunctionResponse.ResponseBody.Text.Body); + } + + [Fact] + public void TestFunctionHandlerWithDescription() + { + // Arrange + var resolver = new BedrockAgentFunctionResolver(); + resolver.Tool("TestFunction", () => new BedrockFunctionResponse + { + Response = new Response + { + ActionGroup = "TestGroup", + Function = "TestFunction", + FunctionResponse = new FunctionResponse + { + ResponseBody = new ResponseBody + { + Text = new TextBody { Body = "Hello, World!" } + } + } + } + }, + "This is a test function"); + + var input = new BedrockFunctionRequest { Function = "TestFunction" }; + var context = new TestLambdaContext(); + + // Act + var result = resolver.Resolve(input, context); + + // Assert + Assert.Equal("Hello, World!", result.Response.FunctionResponse.ResponseBody.Text.Body); + } + + [Fact] + public void TestFunctionHandlerWithMultiplTools() + { + // Arrange + var resolver = new BedrockAgentFunctionResolver(); + + resolver.Tool("TestFunction1", () => new BedrockFunctionResponse + { + Response = new Response + { + ActionGroup = "TestGroup", + Function = "TestFunction", + FunctionResponse = new FunctionResponse + { + ResponseBody = new ResponseBody + { + Text = new TextBody { Body = "Hello from Function 1!" } + } + } + } + }); + resolver.Tool("TestFunction2", () => new BedrockFunctionResponse + { + Response = new Response + { + ActionGroup = "TestGroup", + Function = "TestFunction", + FunctionResponse = new FunctionResponse + { + ResponseBody = new ResponseBody + { + Text = new TextBody { Body = "Hello from Function 2!" } + } + } + } + }); + + var input1 = new BedrockFunctionRequest { Function = "TestFunction1" }; + var input2 = new BedrockFunctionRequest { Function = "TestFunction2" }; + var context = new TestLambdaContext(); + + // Act + var result1 = resolver.Resolve(input1, context); + var result2 = resolver.Resolve(input2, context); + + // Assert + Assert.Equal("Hello from Function 1!", result1.Response.FunctionResponse.ResponseBody.Text.Body); + Assert.Equal("Hello from Function 2!", result2.Response.FunctionResponse.ResponseBody.Text.Body); + } + + [Fact] + public void TestFunctionHandlerWithMultiplToolsDuplicate() + { + // Arrange + var resolver = new BedrockAgentFunctionResolver(); + resolver.Tool("TestFunction1", () => new BedrockFunctionResponse + { + Response = new Response + { + ActionGroup = "TestGroup", + Function = "TestFunction", + FunctionResponse = new FunctionResponse + { + ResponseBody = new ResponseBody + { + Text = new TextBody { Body = "Hello from Function 1!" } + } + } + } + }); + resolver.Tool("TestFunction1", () => new BedrockFunctionResponse + { + Response = new Response + { + ActionGroup = "TestGroup", + Function = "TestFunction", + FunctionResponse = new FunctionResponse + { + ResponseBody = new ResponseBody + { + Text = new TextBody { Body = "Hello from Function 2!" } + } + } + } + }); + + var input1 = new BedrockFunctionRequest { Function = "TestFunction1" }; + var input2 = new BedrockFunctionRequest { Function = "TestFunction1" }; + var context = new TestLambdaContext(); + + // Act + var result1 = resolver.Resolve(input1, context); + var result2 = resolver.Resolve(input2, context); + + // Assert + Assert.Equal("Hello from Function 2!", result1.Response.FunctionResponse.ResponseBody.Text.Body); + Assert.Equal("Hello from Function 2!", result2.Response.FunctionResponse.ResponseBody.Text.Body); + } + + + [Fact] + public void TestFunctionHandlerWithInput() + { + // Arrange + var resolver = new BedrockAgentFunctionResolver(); + resolver.Tool("TestFunction", + (input, context) => new BedrockFunctionResponse + { + Response = new Response + { + ActionGroup = "TestGroup", + Function = "TestFunction", + FunctionResponse = new FunctionResponse + { + ResponseBody = new ResponseBody + { + Text = new TextBody { Body = $"Hello, {input.Function}!" } + } + } + } + }); + + var input = new BedrockFunctionRequest { Function = "TestFunction" }; + var context = new TestLambdaContext(); + + // Act + var result = resolver.Resolve(input, context); + + // Assert + Assert.Equal("Hello, TestFunction!", result.Response.FunctionResponse.ResponseBody.Text.Body); + } + + [Fact] + public void TestFunctionHandlerNoToolMatch() + { + // Arrange + var resolver = new BedrockAgentFunctionResolver(); + resolver.Tool("TestFunction", () => new BedrockFunctionResponse + { + Response = new Response + { + ActionGroup = "TestGroup", + Function = "TestFunction", + FunctionResponse = new FunctionResponse + { + ResponseBody = new ResponseBody + { + Text = new TextBody { Body = "Hello, World!" } + } + } + } + }); + + var input = new BedrockFunctionRequest { Function = "NonExistentFunction" }; + var context = new TestLambdaContext(); + + // Act + var result = resolver.Resolve(input, context); + + // Assert + Assert.Equal($"Error: Tool {input.Function} has not been registered in handler", + result.Response.FunctionResponse.ResponseBody.Text.Body); + } + + [Fact] + public void TestFunctionHandlerWithEvent() + { + // Arrange + var resolver = new BedrockAgentFunctionResolver(); + resolver.Tool( + name: "GetCustomForecast", + description: "Get detailed forecast for a location", + handler: (string location, int days, ILambdaContext ctx) => + { + ctx.Logger.LogLine($"Getting forecast for {location}"); + return $"{days}-day forecast for {location}"; + } + ); + + resolver.Tool( + name: "Greet", + description: "Greet a user", + handler: (string name) => { return $"Hello {name}"; } + ); + + resolver.Tool( + name: "Simple", + description: "Greet a user", + handler: () => { return "Hello"; } + ); + + var input = new BedrockFunctionRequest + { + Function = "GetCustomForecast", + Parameters = new List + { + new Parameter + { + Name = "location", + Value = "Lisbon", + Type = "String" + }, + new Parameter + { + Name = "days", + Value = "1", + Type = "Number" + } + } + }; + + var context = new TestLambdaContext(); + + // Act + var result = resolver.Resolve(input, context); + + // Assert + Assert.Equal("1-day forecast for Lisbon", result.Response.FunctionResponse.ResponseBody.Text.Body); + } + + [Fact] + public void TestFunctionHandlerWithEventAndServices() + { + // Setup DI + var services = new ServiceCollection(); + services.AddSingleton(new MyImplementation()); + services.AddBedrockResolver(); + + var serviceProvider = services.BuildServiceProvider(); + var resolver = serviceProvider.GetRequiredService(); + + resolver.Tool( + name: "GetCustomForecast", + description: "Get detailed forecast for a location", + handler: async (string location, int days, IMyInterface client, ILambdaContext ctx) => + { + var resp = await client.DoSomething(location, days); + return resp; + } + ); + + var input = new BedrockFunctionRequest + { + Function = "GetCustomForecast", + Parameters = new List + { + new Parameter + { + Name = "location", + Value = "Lisbon", + Type = "String" + }, + new Parameter + { + Name = "days", + Value = "1", + Type = "Number" + } + } + }; + + var context = new TestLambdaContext(); + + // Act + var result = resolver.Resolve(input, context); + + // Assert + Assert.Equal("Forecast for Lisbon for 1 days", result.Response.FunctionResponse.ResponseBody.Text.Body); + } + + [Fact] + public void TestFunctionHandlerWithBooleanParameter() + { + // Arrange + var resolver = new BedrockAgentFunctionResolver(); + resolver.Tool( + name: "TestBool", + description: "Test boolean parameter", + handler: (bool isEnabled) => { return $"Feature is {(isEnabled ? "enabled" : "disabled")}"; } + ); + + var input = new BedrockFunctionRequest + { + Function = "TestBool", + Parameters = new List + { + new Parameter + { + Name = "isEnabled", + Value = "true", + Type = "Boolean" + } + } + }; + + // Act + var result = resolver.Resolve(input); + + // Assert + Assert.Equal("Feature is enabled", result.Response.FunctionResponse.ResponseBody.Text.Body); + } + + [Fact] + public void TestFunctionHandlerWithMissingRequiredParameter() + { + // Arrange + var resolver = new BedrockAgentFunctionResolver(); + resolver.Tool( + name: "RequiredParam", + description: "Function with required parameter", + handler: (string name) => $"Hello, {name}!" + ); + + var input = new BedrockFunctionRequest + { + Function = "RequiredParam", + Parameters = new List() // Empty parameters + }; + + // Act + var result = resolver.Resolve(input); + + // Assert + Assert.Contains("Hello, !", result.Response.FunctionResponse.ResponseBody.Text.Body); + } + + [Fact] + public void TestFunctionHandlerWithMultipleParameterTypes() + { + // Arrange + var resolver = new BedrockAgentFunctionResolver(); + resolver.Tool( + name: "ComplexFunction", + description: "Test multiple parameter types", + handler: (string name, int count, bool isActive) => + { + return $"Name: {name}, Count: {count}, Active: {isActive}"; + } + ); + + var input = new BedrockFunctionRequest + { + Function = "ComplexFunction", + Parameters = new List + { + new Parameter { Name = "name", Value = "Test", Type = "String" }, + new Parameter { Name = "count", Value = "5", Type = "Integer" }, + new Parameter { Name = "isActive", Value = "true", Type = "Boolean" } + } + }; + + // Act + var result = resolver.Resolve(input); + + // Assert + Assert.Equal("Name: Test, Count: 5, Active: True", result.Response.FunctionResponse.ResponseBody.Text.Body); + } + + public enum TestEnum + { + Option1, + Option2, + Option3 + } + + [Fact] + public void TestFunctionHandlerWithEnumParameter() + { + // Arrange + var resolver = new BedrockAgentFunctionResolver(); + resolver.Tool( + name: "EnumTest", + description: "Test enum parameter", + handler: (TestEnum option) => { return $"Selected option: {option}"; } + ); + + var input = new BedrockFunctionRequest + { + Function = "EnumTest", + Parameters = new List + { + new Parameter + { + Name = "option", + Value = "Option2", + Type = "String" // Enums come as strings + } + } + }; + + // Act + var result = resolver.Resolve(input); + + // Assert + Assert.Equal("Selected option: Option2", result.Response.FunctionResponse.ResponseBody.Text.Body); + } + + [Fact] + public void TestParameterNameCaseSensitivity() + { + // Arrange + var resolver = new BedrockAgentFunctionResolver(); + resolver.Tool( + name: "CaseTest", + description: "Test case sensitivity", + handler: (string userName) => $"Hello, {userName}!" + ); + + var input = new BedrockFunctionRequest + { + Function = "CaseTest", + Parameters = new List + { + new Parameter + { + Name = "UserName", // Different case than parameter + Value = "John", + Type = "String" + } + } + }; + + // Act + var result = resolver.Resolve(input); + + // Assert + Assert.Equal("Hello, John!", result.Response.FunctionResponse.ResponseBody.Text.Body); + } + + [Fact] + public void TestParameterOrderIndependence() + { + // Arrange + var resolver = new BedrockAgentFunctionResolver(); + resolver.Tool( + name: "OrderTest", + description: "Test parameter order independence", + handler: (string firstName, string lastName) => { return $"Name: {firstName} {lastName}"; } + ); + + var input = new BedrockFunctionRequest + { + Function = "OrderTest", + Parameters = new List + { + // Parameters in reverse order of handler parameters + new Parameter { Name = "lastName", Value = "Smith", Type = "String" }, + new Parameter { Name = "firstName", Value = "John", Type = "String" } + } + }; + + // Act + var result = resolver.Resolve(input); + + // Assert + Assert.Equal("Name: John Smith", result.Response.FunctionResponse.ResponseBody.Text.Body); + } + + [Fact] + public void TestFunctionHandlerWithDecimalParameter() + { + // Arrange + var resolver = new BedrockAgentFunctionResolver(); + resolver.Tool( + name: "PriceCalculator", + description: "Calculate total price with tax", + handler: (decimal price) => + { + var withTax = price * 1.2m; + return $"Total price with tax: {withTax.ToString("F2", CultureInfo.InvariantCulture)}"; + } + ); + + var input = new BedrockFunctionRequest + { + Function = "PriceCalculator", + Parameters = new List + { + new Parameter + { + Name = "price", + Value = "29.99", + Type = "Number" + } + } + }; + + // Act + var result = resolver.Resolve(input); + + // Assert + Assert.Contains("35.99", result.Response.FunctionResponse.ResponseBody.Text.Body); + } + + [Fact] + public void TestFunctionHandlerWithStringArrayParameter() + { + // Arrange + var resolver = new BedrockAgentFunctionResolver(); + resolver.Tool( + name: "ProcessWorkout", + description: "Process workout exercises", + handler: (string[] exercises) => + { + var result = new StringBuilder(); + result.AppendLine("Your workout plan:"); + + for (int i = 0; i < exercises.Length; i++) + { + result.AppendLine($" {i + 1}. {exercises[i]}"); + } + + return result.ToString(); + } + ); + + var input = new BedrockFunctionRequest + { + Function = "ProcessWorkout", + Parameters = new List + { + new Parameter + { + Name = "exercises", + Value = + "[\"Squats, 3 sets of 10 reps\",\"Push-ups, 3 sets of 10 reps\",\"Plank, 3 sets of 30 seconds\"]", + Type = "String" // The type is String since it contains JSON + } + } + }; + + // Act + var result = resolver.Resolve(input); + + // Assert + Assert.Contains("Your workout plan:", result.Response.FunctionResponse.ResponseBody.Text.Body); + Assert.Contains("1. Squats, 3 sets of 10 reps", result.Response.FunctionResponse.ResponseBody.Text.Body); + Assert.Contains("2. Push-ups, 3 sets of 10 reps", result.Response.FunctionResponse.ResponseBody.Text.Body); + Assert.Contains("3. Plank, 3 sets of 30 seconds", result.Response.FunctionResponse.ResponseBody.Text.Body); + } + + [Fact] + public void TestFunctionHandlerWithExceptionInHandler() + { + // Arrange + var resolver = new BedrockAgentFunctionResolver(); + resolver.Tool( + name: "ThrowingFunction", + description: "Function that throws exception", + handler: () => + { + throw new InvalidOperationException("Test error"); + return "This will not run:"; + } + ); + + var input = new BedrockFunctionRequest { Function = "ThrowingFunction" }; + + // Act + var result = resolver.Resolve(input); + + // Assert + Assert.Contains("Error when invoking tool: Test error", result.Response.FunctionResponse.ResponseBody.Text.Body); + } + + [Fact] + public void TestSessionAttributesPreservation() + { + // Arrange + var resolver = new BedrockAgentFunctionResolver(); + resolver.Tool( + name: "SessionTest", + description: "Test session attributes preservation", + handler: (string message) => message + ); + + var input = new BedrockFunctionRequest + { + Function = "SessionTest", + ActionGroup = "TestGroup", + Parameters = new List + { + new Parameter { Name = "message", Value = "Hello", Type = "String" } + }, + SessionAttributes = new Dictionary + { + { "userId", "12345" }, + { "preferredLanguage", "en-US" } + }, + PromptSessionAttributes = new Dictionary + { + { "context", "customer_support" }, + { "previousQuestion", "How do I reset my password?" } + } + }; + + // Act + var result = resolver.Resolve(input); + + // Assert + Assert.Equal("Hello", result.Response.FunctionResponse.ResponseBody.Text.Body); + Assert.Equal(2, result.SessionAttributes.Count); + Assert.Equal("12345", result.SessionAttributes["userId"]); + Assert.Equal("en-US", result.SessionAttributes["preferredLanguage"]); + Assert.Equal(2, result.PromptSessionAttributes.Count); + Assert.Equal("customer_support", result.PromptSessionAttributes["context"]); + Assert.Equal("How do I reset my password?", result.PromptSessionAttributes["previousQuestion"]); + } + + [Fact] + public void TestSessionAttributesPreservationWithErrorHandling() + { + // Arrange + var resolver = new BedrockAgentFunctionResolver(); + resolver.Tool( + name: "ErrorTest", + description: "Test session attributes preservation with error", + handler: () => { throw new Exception("Test error"); return "This will not run"; } + ); + + var input = new BedrockFunctionRequest + { + Function = "ErrorTest", + ActionGroup = "TestGroup", + SessionAttributes = new Dictionary + { + { "userId", "12345" }, + { "session", "active" } + }, + PromptSessionAttributes = new Dictionary + { + { "lastAction", "login" } + } + }; + + // Act + var result = resolver.Resolve(input); + + // Assert + Assert.Contains("Error when invoking tool: Test error", result.Response.FunctionResponse.ResponseBody.Text.Body); + Assert.Equal(2, result.SessionAttributes.Count); + Assert.Equal("12345", result.SessionAttributes["userId"]); + Assert.Equal("active", result.SessionAttributes["session"]); + Assert.Equal(1, result.PromptSessionAttributes?.Count); + Assert.Equal("login", result.PromptSessionAttributes?["lastAction"]); + } + + [Fact] + public void TestSessionAttributesPreservationWithNoToolMatch() + { + // Arrange + var resolver = new BedrockAgentFunctionResolver(); + + var input = new BedrockFunctionRequest + { + Function = "NonExistentTool", + SessionAttributes = new Dictionary + { + { "preferredTheme", "dark" } + }, + PromptSessionAttributes = new Dictionary + { + { "lastVisited", "homepage" } + } + }; + + // Act + var result = resolver.Resolve(input); + + // Assert + Assert.Contains($"Error: Tool {input.Function} has not been registered in handler", result.Response.FunctionResponse.ResponseBody.Text.Body); + Assert.Equal(1, result.SessionAttributes?.Count); + Assert.Equal("dark", result.SessionAttributes?["preferredTheme"]); + Assert.Equal(1, result.PromptSessionAttributes?.Count); + Assert.Equal("homepage", result.PromptSessionAttributes?["lastVisited"]); + } + + [Fact] + public void TestSReturningNull() + { + // Arrange + var resolver = new BedrockAgentFunctionResolver(); + resolver.Tool( + name: "NullTest", + description: "Test session attributes preservation with error", + handler: () => + { + string test = null!; + return test; + } + ); + + var input = new BedrockFunctionRequest + { + Function = "NullTest", + }; + + // Act + var result = resolver.Resolve(input); + + // Assert + Assert.Equal("", result.Response.FunctionResponse.ResponseBody.Text.Body); + } + + [Fact] + public void TestToolOverrideWithWarning() + { + // Arrange + var resolver = new BedrockAgentFunctionResolver(); + + // Register a tool + resolver.Tool("Calculator", () => "Original Calculator"); + + // Register same tool again with different implementation + resolver.Tool("Calculator", () => "New Calculator"); + + // Verify the tool was overridden + var input = new BedrockFunctionRequest { Function = "Calculator" }; + var result = resolver.Resolve(input); + + // The second registration should have overwritten the first + Assert.Equal("New Calculator", result.Response.FunctionResponse.ResponseBody.Text.Body); + } + + [Fact] + public void TestFunctionHandlerWithCustomType() + { + // Arrange + var resolver = new BedrockAgentFunctionResolver(); + resolver.Tool( + name: "PriceCalculator", + description: "Calculate total price with tax", + handler: (MyCustomType myCustomType) => + { + var withTax = myCustomType.Price * 1.2m; + return $"Total price with tax: {withTax.ToString("F2", CultureInfo.InvariantCulture)}"; + } + ); + + var input = new BedrockFunctionRequest + { + Function = "PriceCalculator", + InputText = "{\"Price\": 29.99}", // JSON representation of MyCustomType + }; + + // Act + var result = resolver.Resolve(input); + + // Assert + Assert.Contains("35.99", result.Response.FunctionResponse.ResponseBody.Text.Body); + } + + [Fact] + public void TestFunctionHandlerWithCustomTypeWithTypeInfoResolver() + { + // Arrange + var resolver = new BedrockAgentFunctionResolver(MycustomSerializationContext.Default); + resolver.Tool( + name: "PriceCalculator", + description: "Calculate total price with tax", + handler: (MyCustomType myCustomType) => + { + var withTax = myCustomType.Price * 1.2m; + return $"Total price with tax: {withTax.ToString("F2", CultureInfo.InvariantCulture)}"; + } + ); + + var input = new BedrockFunctionRequest + { + Function = "PriceCalculator", + InputText = "{\"Price\": 29.99}", // JSON representation of MyCustomType + }; + + // Act + var result = resolver.Resolve(input); + + // Assert + Assert.Contains("35.99", result.Response.FunctionResponse.ResponseBody.Text.Body); + } + + [Fact] + public void TestAttributeBasedToolRegistration() + { + // Arrange + + var services = new ServiceCollection(); + services.AddSingleton(new MyImplementation()); + services.AddBedrockResolver(); + + var serviceProvider = services.BuildServiceProvider(); + var resolver = serviceProvider.GetRequiredService() + .RegisterTool(); + + // Create test input for echo function + var echoInput = new BedrockFunctionRequest + { + Function = "Echo", + Parameters = new List + { + new Parameter { Name = "message", Value = "Hello world", Type = "String" } + } + }; + + // Create test input for calculate function + var calcInput = new BedrockFunctionRequest + { + Function = "Calculate", + Parameters = new List + { + new Parameter { Name = "x", Value = "5", Type = "Number" }, + new Parameter { Name = "y", Value = "3", Type = "Number" } + } + }; + + // Act + var echoResult = resolver.Resolve(echoInput); + var calcResult = resolver.Resolve(calcInput); + + // Assert + Assert.Equal("You asked: Forecast for Lisbon for 1 days", echoResult.Response.FunctionResponse.ResponseBody.Text.Body); + Assert.Equal("Result: 8", calcResult.Response.FunctionResponse.ResponseBody.Text.Body); + } + + // Example tool class using attributes + [BedrockFunctionType] + public class AttributeBasedTool + { + [BedrockFunctionTool(Name = "Echo", Description = "Echoes back the input message")] + public static string EchoMessage(string message, IMyInterface myInterface, ILambdaContext context) + { + return $"You asked: {myInterface.DoSomething("Lisbon", 1).Result}"; + } + + [BedrockFunctionTool(Name = "Calculate", Description = "Adds two numbers together")] + public static string Calculate(int x, int y) + { + return $"Result: {x + y}"; + } + } +} + +public interface IMyInterface +{ + Task DoSomething(string location, int days); +} + +public class MyImplementation : IMyInterface +{ + public async Task DoSomething(string location, int days) + { + return await Task.FromResult($"Forecast for {location} for {days} days"); + } +} + +public class MyCustomType +{ + public decimal Price { get; set; } +} + + +[JsonSerializable(typeof(MyCustomType))] +public partial class MycustomSerializationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/Helpers/ParameterAccessorTests.cs b/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/Helpers/ParameterAccessorTests.cs new file mode 100644 index 00000000..b5d54046 --- /dev/null +++ b/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/Helpers/ParameterAccessorTests.cs @@ -0,0 +1,338 @@ +using AWS.Lambda.Powertools.EventHandler.Resolvers; +using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Helpers; + +namespace AWS.Lambda.Powertools.EventHandler.BedrockAgentFunction.Helpers +{ + public class ParameterAccessorTests + { + [Fact] + public void Get_WithStringParameter_ReturnsValue() + { + // Arrange + var parameters = new List + { + new Parameter { Name = "name", Value = "TestValue", Type = "String" } + }; + var accessor = new ParameterAccessor(parameters); + + // Act + var result = accessor.Get("name"); + + // Assert + Assert.Equal("TestValue", result); + } + + [Fact] + public void Get_WithIntParameter_ReturnsValue() + { + // Arrange + var parameters = new List + { + new Parameter { Name = "age", Value = "30", Type = "Number" } + }; + var accessor = new ParameterAccessor(parameters); + + // Act + var result = accessor.Get("age"); + + // Assert + Assert.Equal(30, result); + } + + [Fact] + public void Get_WithBoolParameter_ReturnsValue() + { + // Arrange + var parameters = new List + { + new Parameter { Name = "active", Value = "true", Type = "Boolean" } + }; + var accessor = new ParameterAccessor(parameters); + + // Act + var result = accessor.Get("active"); + + // Assert + Assert.True(result); + } + + [Fact] + public void Get_WithLongParameter_ReturnsValue() + { + // Arrange + var parameters = new List + { + new Parameter { Name = "bigNumber", Value = "9223372036854775807", Type = "Number" } + }; + var accessor = new ParameterAccessor(parameters); + + // Act + var result = accessor.Get("bigNumber"); + + // Assert + Assert.Equal(9223372036854775807, result); + } + + [Fact] + public void Get_WithDoubleParameter_ReturnsValue() + { + // Arrange + var parameters = new List + { + new Parameter { Name = "price", Value = "99.99", Type = "Number" } + }; + var accessor = new ParameterAccessor(parameters); + + // Act + var result = accessor.Get("price"); + + // Assert + Assert.Equal(99.99, result); + } + + [Fact] + public void Get_WithDecimalParameter_ReturnsValue() + { + // Arrange + var parameters = new List + { + new Parameter { Name = "amount", Value = "123.456", Type = "Number" } + }; + var accessor = new ParameterAccessor(parameters); + + // Act + var result = accessor.Get("amount"); + + // Assert + Assert.Equal(123.456m, result); + } + + [Fact] + public void Get_WithNonExistentParameter_ReturnsDefault() + { + // Arrange + var parameters = new List + { + new Parameter { Name = "existing", Value = "value", Type = "String" } + }; + var accessor = new ParameterAccessor(parameters); + + // Act + var stringResult = accessor.Get("nonExistent"); + var intResult = accessor.Get("nonExistent"); + var boolResult = accessor.Get("nonExistent"); + + // Assert + Assert.Null(stringResult); + Assert.Equal(0, intResult); + Assert.False(boolResult); + } + + [Fact] + public void Get_WithCaseSensitivity_WorksCaseInsensitively() + { + // Arrange + var parameters = new List + { + new Parameter { Name = "userName", Value = "John", Type = "String" } + }; + var accessor = new ParameterAccessor(parameters); + + // Act + var result1 = accessor.Get("userName"); + var result2 = accessor.Get("UserName"); + var result3 = accessor.Get("USERNAME"); + + // Assert + Assert.Equal("John", result1); + Assert.Equal("John", result2); + Assert.Equal("John", result3); + } + + [Fact] + public void Get_WithNullParameters_ReturnsDefault() + { + // Arrange + var accessor = new ParameterAccessor(null); + + // Act + var stringResult = accessor.Get("any"); + var intResult = accessor.Get("any"); + + // Assert + Assert.Null(stringResult); + Assert.Equal(0, intResult); + } + + [Fact] + public void Get_WithInvalidType_ReturnsDefault() + { + // Arrange + var parameters = new List + { + new Parameter { Name = "number", Value = "not-a-number", Type = "Number" } + }; + var accessor = new ParameterAccessor(parameters); + + // Act + var result = accessor.Get("number"); + + // Assert + Assert.Equal(0, result); + } + + [Fact] + public void Get_WithEmptyParameters_ReturnsDefault() + { + // Arrange + var parameters = new List(); + var accessor = new ParameterAccessor(parameters); + + // Act + var result = accessor.Get("anything"); + + // Assert + Assert.Null(result); + } + + [Fact] + public void GetAt_WithValidIndex_ReturnsValue() + { + // Arrange + var parameters = new List + { + new Parameter { Name = "first", Value = "Value1", Type = "String" }, + new Parameter { Name = "second", Value = "42", Type = "Number" }, + new Parameter { Name = "third", Value = "true", Type = "Boolean" } + }; + var accessor = new ParameterAccessor(parameters); + + // Act + var stringResult = accessor.GetAt(0); + var intResult = accessor.GetAt(1); + var boolResult = accessor.GetAt(2); + + // Assert + Assert.Equal("Value1", stringResult); + Assert.Equal(42, intResult); + Assert.True(boolResult); + } + + [Fact] + public void GetAt_WithInvalidIndex_ReturnsDefaultValue() + { + // Arrange + var parameters = new List + { + new Parameter { Name = "param", Value = "Value", Type = "String" } + }; + var accessor = new ParameterAccessor(parameters); + + // Act + var negativeIndexResult = accessor.GetAt(-1); + var tooLargeIndexResult = accessor.GetAt(1); + + // Assert + Assert.Null(negativeIndexResult); + Assert.Null(tooLargeIndexResult); + } + + [Fact] + public void GetAt_WithNullParameters_ReturnsDefaultValue() + { + // Arrange + var accessor = new ParameterAccessor(null); + + // Act + var result = accessor.GetAt(0); + + // Assert + Assert.Null(result); + } + + [Fact] + public void GetAt_WithNullValue_ReturnsDefaultValue() + { + // Arrange + var parameters = new List + { + new Parameter { Name = "param", Value = null, Type = "String" } + }; + var accessor = new ParameterAccessor(parameters); + + // Act + var result = accessor.GetAt(0); + + // Assert + Assert.Null(result); + } + + [Fact] + public void GetOrDefault_WithExistingParameter_ReturnsValue() + { + // Arrange + var parameters = new List + { + new Parameter { Name = "name", Value = "TestValue", Type = "String" } + }; + var accessor = new ParameterAccessor(parameters); + + // Act + var result = accessor.GetOrDefault("name", "DefaultValue"); + + // Assert + Assert.Equal("TestValue", result); + } + + [Fact] + public void GetOrDefault_WithNonExistentParameter_ReturnsDefaultValue() + { + // Arrange + var parameters = new List + { + new Parameter { Name = "existing", Value = "value", Type = "String" } + }; + var accessor = new ParameterAccessor(parameters); + + // Act + var result = accessor.GetOrDefault("nonExistent", "DefaultValue"); + + // Assert + Assert.Equal("DefaultValue", result); + } + + [Fact] + public void GetOrDefault_WithNullValue_ReturnsDefaultValue() + { + // Arrange + var parameters = new List + { + new Parameter { Name = "param", Value = null, Type = "String" } + }; + var accessor = new ParameterAccessor(parameters); + + // Act + var result = accessor.GetOrDefault("param", "DefaultValue"); + + // Assert + Assert.Equal("DefaultValue", result); + } + + [Fact] + public void GetOrDefault_WithInvalidConversion_ReturnsDefaultValue() + { + // Arrange + var parameters = new List + { + new Parameter { Name = "invalidNumber", Value = "not-a-number", Type = "Number" } + }; + var accessor = new ParameterAccessor(parameters); + + // Act + var result = accessor.GetOrDefault("invalidNumber", 999); + + // Assert + Assert.Equal(999, result); + } + } +} diff --git a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/Helpers/ParameterMapperTests.cs b/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/Helpers/ParameterMapperTests.cs new file mode 100644 index 00000000..b4cd5705 --- /dev/null +++ b/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/Helpers/ParameterMapperTests.cs @@ -0,0 +1,311 @@ +using Amazon.Lambda.Core; +using Amazon.Lambda.TestUtilities; +using AWS.Lambda.Powertools.EventHandler.Resolvers; +using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Helpers; +using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; +using NSubstitute; + +namespace AWS.Lambda.Powertools.EventHandler.BedrockAgentFunction.Helpers +{ + public class ParameterMapperTests + { + private readonly ParameterMapper _mapper = new(); + + [Fact] + public void MapParameters_WithNoParameters_ReturnsEmptyArray() + { + // Arrange + var methodInfo = typeof(TestMethodsClass).GetMethod(nameof(TestMethodsClass.NoParameters))!; + var input = new BedrockFunctionRequest(); + var context = new TestLambdaContext(); + + // Act + var result = _mapper.MapParameters(methodInfo, input, context, null); + + // Assert + Assert.Empty(result); + } + + [Fact] + public void MapParameters_WithLambdaContext_MapsCorrectly() + { + // Arrange + var methodInfo = typeof(TestMethodsClass).GetMethod(nameof(TestMethodsClass.WithLambdaContext))!; + var input = new BedrockFunctionRequest(); + var context = new TestLambdaContext(); + + // Act + var result = _mapper.MapParameters(methodInfo, input, context, null); + + // Assert + Assert.Single(result); + Assert.Same(context, result[0]); + } + + [Fact] + public void MapParameters_WithBedrockFunctionRequest_MapsCorrectly() + { + // Arrange + var methodInfo = typeof(TestMethodsClass).GetMethod(nameof(TestMethodsClass.WithBedrockFunctionRequest))!; + var input = new BedrockFunctionRequest(); + var context = new TestLambdaContext(); + + // Act + var result = _mapper.MapParameters(methodInfo, input, context, null); + + // Assert + Assert.Single(result); + Assert.Same(input, result[0]); + } + + [Fact] + public void MapParameters_WithStringParameter_MapsCorrectly() + { + // Arrange + var methodInfo = typeof(TestMethodsClass).GetMethod(nameof(TestMethodsClass.WithStringParameter))!; + var input = new BedrockFunctionRequest + { + Parameters = new List + { + new() { Name = "name", Value = "TestValue", Type = "String" } + } + }; + var context = new TestLambdaContext(); + + // Act + var result = _mapper.MapParameters(methodInfo, input, context, null); + + // Assert + Assert.Single(result); + Assert.Equal("TestValue", result[0]); + } + + [Fact] + public void MapParameters_WithIntParameter_MapsCorrectly() + { + // Arrange + var methodInfo = typeof(TestMethodsClass).GetMethod(nameof(TestMethodsClass.WithIntParameter))!; + var input = new BedrockFunctionRequest + { + Parameters = new List + { + new() { Name = "value", Value = "42", Type = "Number" } + } + }; + var context = new TestLambdaContext(); + + // Act + var result = _mapper.MapParameters(methodInfo, input, context, null); + + // Assert + Assert.Single(result); + Assert.Equal(42, result[0]); + } + + [Fact] + public void MapParameters_WithBoolParameter_MapsCorrectly() + { + // Arrange + var methodInfo = typeof(TestMethodsClass).GetMethod(nameof(TestMethodsClass.WithBoolParameter))!; + var input = new BedrockFunctionRequest + { + Parameters = new List + { + new() { Name = "flag", Value = "true", Type = "Boolean" } + } + }; + var context = new TestLambdaContext(); + + // Act + var result = _mapper.MapParameters(methodInfo, input, context, null); + + // Assert + Assert.Single(result); + Assert.True((bool)result[0]!); + } + + [Fact] + public void MapParameters_WithEnumParameter_MapsCorrectly() + { + // Arrange + var methodInfo = typeof(TestMethodsClass).GetMethod(nameof(TestMethodsClass.WithEnumParameter))!; + var input = new BedrockFunctionRequest + { + Parameters = new List + { + new() { Name = "testEnum", Value = "Option2", Type = "String" } + } + }; + var context = new TestLambdaContext(); + + // Act + var result = _mapper.MapParameters(methodInfo, input, context, null); + + // Assert + Assert.Single(result); + Assert.Equal(TestEnum.Option2, result[0]); + } + + [Fact] + public void MapParameters_WithStringArrayParameter_MapsCorrectly() + { + // Arrange + var methodInfo = typeof(TestMethodsClass).GetMethod(nameof(TestMethodsClass.WithStringArrayParameter))!; + var input = new BedrockFunctionRequest + { + Parameters = new List + { + new() { Name = "values", Value = "[\"one\",\"two\",\"three\"]", Type = "String" } + } + }; + var context = new TestLambdaContext(); + + // Act + var result = _mapper.MapParameters(methodInfo, input, context, null); + + // Assert + Assert.Single(result); + var array = (string[])result[0]!; + Assert.Equal(3, array.Length); + Assert.Equal("one", array[0]); + Assert.Equal("two", array[1]); + Assert.Equal("three", array[2]); + } + + [Fact] + public void MapParameters_WithIntArrayParameter_MapsCorrectly() + { + // Arrange + var methodInfo = typeof(TestMethodsClass).GetMethod(nameof(TestMethodsClass.WithIntArrayParameter))!; + var input = new BedrockFunctionRequest + { + Parameters = new List + { + new() { Name = "values", Value = "[1,2,3]", Type = "String" } + } + }; + var context = new TestLambdaContext(); + + // Act + var result = _mapper.MapParameters(methodInfo, input, context, null); + + // Assert + Assert.Single(result); + var array = (int[])result[0]!; + Assert.Equal(3, array.Length); + Assert.Equal(1, array[0]); + Assert.Equal(2, array[1]); + Assert.Equal(3, array[2]); + } + + [Fact] + public void MapParameters_WithInvalidJsonArray_ReturnsNull() + { + // Arrange + var methodInfo = typeof(TestMethodsClass).GetMethod(nameof(TestMethodsClass.WithStringArrayParameter))!; + var input = new BedrockFunctionRequest + { + Parameters = new List + { + new() { Name = "values", Value = "[invalid json]", Type = "String" } + } + }; + var context = new TestLambdaContext(); + + // Act + var result = _mapper.MapParameters(methodInfo, input, context, null); + + // Assert + Assert.Single(result); + Assert.Null(result[0]); + } + + [Fact] + public void MapParameters_WithServiceProvider_ResolvesService() + { + // Arrange + var methodInfo = typeof(TestMethodsClass).GetMethod(nameof(TestMethodsClass.WithDependencyInjection))!; + var input = new BedrockFunctionRequest(); + var context = new TestLambdaContext(); + + // Create a test service + var testService = new TestService(); + + // Setup service provider + var serviceProvider = Substitute.For(); + serviceProvider.GetService(typeof(ITestService)).Returns(testService); + + // Act + var result = _mapper.MapParameters(methodInfo, input, context, serviceProvider); + + // Assert + Assert.Equal(3, result.Length); + Assert.Same(context, result[0]); + Assert.Same(input, result[1]); + Assert.Same(testService, result[2]); + } + + [Fact] + public void MapParameters_WithMultipleParameterTypes_MapsAllCorrectly() + { + // Arrange + var methodInfo = typeof(TestMethodsClass).GetMethod(nameof(TestMethodsClass.WithMultipleParameterTypes))!; + var input = new BedrockFunctionRequest + { + Parameters = new List + { + new() { Name = "name", Value = "TestUser", Type = "String" }, + new() { Name = "age", Value = "30", Type = "Number" }, + new() { Name = "isActive", Value = "true", Type = "Boolean" } + } + }; + var context = new TestLambdaContext(); + + // Act + var result = _mapper.MapParameters(methodInfo, input, context, null); + + // Assert + Assert.Equal(4, result.Length); + Assert.Equal("TestUser", result[0]); + Assert.Equal(30, result[1]); + Assert.True((bool)result[2]!); + Assert.Same(context, result[3]); + } + + public class TestMethodsClass + { + public void NoParameters() { } + + public void WithLambdaContext(ILambdaContext context) { } + + public void WithBedrockFunctionRequest(BedrockFunctionRequest request) { } + + public void WithStringParameter(string name) { } + + public void WithIntParameter(int value) { } + + public void WithBoolParameter(bool flag) { } + + public void WithEnumParameter(TestEnum testEnum) { } + + public void WithStringArrayParameter(string[] values) { } + + public void WithIntArrayParameter(int[] values) { } + + public void WithDependencyInjection(ILambdaContext context, BedrockFunctionRequest request, ITestService service) { } + + public void WithMultipleParameterTypes(string name, int age, bool isActive, ILambdaContext context) { } + } + + public interface ITestService { } + + public class TestService : ITestService { } + + public enum TestEnum + { + Option1, + Option2, + Option3 + } + } +} diff --git a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/Helpers/ParameterTypeValidatorTests.cs b/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/Helpers/ParameterTypeValidatorTests.cs new file mode 100644 index 00000000..b8ec3353 --- /dev/null +++ b/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/Helpers/ParameterTypeValidatorTests.cs @@ -0,0 +1,49 @@ +using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Helpers; + +namespace AWS.Lambda.Powertools.EventHandler.BedrockAgentFunction.Helpers +{ + public class ParameterTypeValidatorTests + { + private readonly ParameterTypeValidator _validator = new(); + + [Theory] + [InlineData(typeof(string), true)] + [InlineData(typeof(int), true)] + [InlineData(typeof(long), true)] + [InlineData(typeof(double), true)] + [InlineData(typeof(bool), true)] + [InlineData(typeof(decimal), true)] + [InlineData(typeof(DateTime), true)] + [InlineData(typeof(Guid), true)] + [InlineData(typeof(string[]), true)] + [InlineData(typeof(int[]), true)] + [InlineData(typeof(long[]), true)] + [InlineData(typeof(double[]), true)] + [InlineData(typeof(bool[]), true)] + [InlineData(typeof(decimal[]), true)] + [InlineData(typeof(TestEnum), true)] // Enum should be valid + [InlineData(typeof(object), false)] + [InlineData(typeof(Dictionary), false)] + [InlineData(typeof(List), false)] + [InlineData(typeof(float), false)] + [InlineData(typeof(char), false)] + [InlineData(typeof(byte), false)] + [InlineData(typeof(float[]), false)] + [InlineData(typeof(object[]), false)] + public void IsBedrockParameter_WithVariousTypes_ReturnsExpectedResult(Type type, bool expected) + { + // Act + var result = _validator.IsBedrockParameter(type); + + // Assert + Assert.Equal(expected, result); + } + + private enum TestEnum + { + One, + Two, + Three + } + } +} diff --git a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/Helpers/ResultConverterTests.cs b/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/Helpers/ResultConverterTests.cs new file mode 100644 index 00000000..437118e5 --- /dev/null +++ b/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/Helpers/ResultConverterTests.cs @@ -0,0 +1,276 @@ +using Amazon.Lambda.Core; +using Amazon.Lambda.TestUtilities; +using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Helpers; +using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; + +namespace AWS.Lambda.Powertools.EventHandler.BedrockAgentFunction.Helpers +{ + public class ResultConverterTests + { + private readonly ResultConverter _converter = new(); + private readonly BedrockFunctionRequest _defaultInput = new() + { + Function = "TestFunction", + ActionGroup = "TestGroup", + SessionAttributes = new Dictionary { { "testKey", "testValue" } }, + PromptSessionAttributes = new Dictionary { { "promptKey", "promptValue" } } + }; + private readonly string _functionName = "TestFunction"; + private readonly ILambdaContext _context = new TestLambdaContext(); + + [Fact] + public void ProcessResult_WithBedrockFunctionResponse_ReturnsUnchanged() + { + // Arrange + var response = BedrockFunctionResponse.WithText( + "Test response", + "TestGroup", + "TestFunction", + new Dictionary(), + new Dictionary(), + new Dictionary()); + + // Act + var result = _converter.ProcessResult(response, _defaultInput, _functionName, _context); + + // Assert + Assert.Same(response, result); + } + + [Fact] + public void ProcessResult_WithNullValue_ReturnsEmptyResponse() + { + // Arrange + object? nullValue = null; + + // Act + var result = _converter.ProcessResult(nullValue, _defaultInput, _functionName, _context); + + // Assert + Assert.Equal(string.Empty, result.Response.FunctionResponse.ResponseBody.Text.Body); + Assert.Equal(_defaultInput.ActionGroup, result.Response.ActionGroup); + Assert.Equal(_defaultInput.Function, result.Response.Function); + } + + [Fact] + public void ProcessResult_WithStringValue_ReturnsTextResponse() + { + // Arrange + var stringValue = "Hello, world!"; + + // Act + var result = _converter.ProcessResult(stringValue, _defaultInput, _functionName, _context); + + // Assert + Assert.Equal(stringValue, result.Response.FunctionResponse.ResponseBody.Text.Body); + } + + [Fact] + public void ProcessResult_WithIntValue_ReturnsTextResponse() + { + // Arrange + var intValue = 42; + + // Act + var result = _converter.ProcessResult(intValue, _defaultInput, _functionName, _context); + + // Assert + Assert.Equal("42", result.Response.FunctionResponse.ResponseBody.Text.Body); + } + + [Fact] + public void ProcessResult_WithDecimalValue_ReturnsTextResponse() + { + // Arrange + var decimalValue = 42.5m; + + // Act + var result = _converter.ProcessResult(decimalValue, _defaultInput, _functionName, _context); + + // Assert + Assert.Equal("42.5", result.Response.FunctionResponse.ResponseBody.Text.Body); + } + + [Fact] + public void ProcessResult_WithBoolValue_ReturnsTextResponse() + { + // Arrange + var boolValue = true; + + // Act + var result = _converter.ProcessResult(boolValue, _defaultInput, _functionName, _context); + + // Assert + Assert.Equal("True", result.Response.FunctionResponse.ResponseBody.Text.Body); + } + + [Fact] + public void ProcessResult_WithObjectValue_ReturnsToString() + { + // Arrange + var testObject = new TestObject { Name = "Test", Value = 42 }; + + // Act + var result = _converter.ProcessResult(testObject, _defaultInput, _functionName, _context); + + // Assert + Assert.Equal(testObject.ToString(), result.Response.FunctionResponse.ResponseBody.Text.Body); + } + + [Fact] + public async Task ProcessResult_WithTaskStringResult_ReturnsTextResponse() + { + // Arrange + Task task = Task.FromResult("Async result"); + + // Act + var result = _converter.ProcessResult(task, _defaultInput, _functionName, _context); + + // Assert + Assert.Equal("Async result", result.Response.FunctionResponse.ResponseBody.Text.Body); + } + + [Fact] + public async Task ProcessResult_WithTaskIntResult_ReturnsTextResponse() + { + // Arrange + Task task = Task.FromResult(42); + + // Act + var result = _converter.ProcessResult(task, _defaultInput, _functionName, _context); + + // Assert + Assert.Equal("42", result.Response.FunctionResponse.ResponseBody.Text.Body); + } + + [Fact] + public async Task ProcessResult_WithTaskBoolResult_ReturnsTextResponse() + { + // Arrange + Task task = Task.FromResult(true); + + // Act + var result = _converter.ProcessResult(task, _defaultInput, _functionName, _context); + + // Assert + Assert.Equal("True", result.Response.FunctionResponse.ResponseBody.Text.Body); + } + + [Fact] + public async Task ProcessResult_WithVoidTask_ReturnsEmptyResponse() + { + // Arrange + Task task = Task.CompletedTask; + + // Act + var result = _converter.ProcessResult(task, _defaultInput, _functionName, _context); + + // Assert + Assert.Equal(string.Empty, result.Response.FunctionResponse.ResponseBody.Text.Body); + } + + [Fact] + public async Task ProcessResult_WithTaskBedrockResponse_ReturnsResponse() + { + // Arrange + var response = BedrockFunctionResponse.WithText( + "Async response", + "AsyncGroup", + "AsyncFunction", + new Dictionary(), + new Dictionary(), + new Dictionary()); + + Task task = Task.FromResult(response); + + // Act + var result = _converter.ProcessResult(task, _defaultInput, _functionName, _context); + + // Assert + Assert.Equal("Async response", result.Response.FunctionResponse.ResponseBody.Text.Body); + Assert.Equal("AsyncGroup", result.Response.ActionGroup); + Assert.Equal("AsyncFunction", result.Response.Function); + } + + [Fact] + public void EnsureResponseMetadata_WithEmptyMetadata_FillsFromInput() + { + // Arrange + var response = BedrockFunctionResponse.WithText( + "Test response", + "", // Empty action group + "", // Empty function name + _defaultInput.SessionAttributes, + _defaultInput.PromptSessionAttributes, + new Dictionary()); + + // Act + var result = _converter.ConvertToOutput(response, _defaultInput); + + // Assert + Assert.Equal("Test response", result.Response.FunctionResponse.ResponseBody.Text.Body); + Assert.Equal(_defaultInput.ActionGroup, result.Response.ActionGroup); // Filled from input + Assert.Equal(_defaultInput.Function, result.Response.Function); // Filled from input + } + + [Fact] + public void ConvertToOutput_PreservesSessionAttributes() + { + // Arrange + var sessionAttributes = new Dictionary { { "userID", "test123" } }; + var promptAttributes = new Dictionary { { "context", "testing" } }; + + var input = new BedrockFunctionRequest + { + Function = "TestFunction", + ActionGroup = "TestGroup", + SessionAttributes = sessionAttributes, + PromptSessionAttributes = promptAttributes + }; + + // Act + var result = _converter.ConvertToOutput("Test response", input); + + // Assert + Assert.Equal(sessionAttributes, result.SessionAttributes); + Assert.Equal(promptAttributes, result.PromptSessionAttributes); + } + + [Fact] + public void ProcessResult_WithLongValue_ReturnsTextResponse() + { + // Arrange + long longValue = 9223372036854775807; + + // Act + var result = _converter.ProcessResult(longValue, _defaultInput, _functionName, _context); + + // Assert + Assert.Equal("9223372036854775807", result.Response.FunctionResponse.ResponseBody.Text.Body); + } + + [Fact] + public void ProcessResult_WithDoubleValue_ReturnsTextResponse() + { + // Arrange + double doubleValue = 123.456; + + // Act + var result = _converter.ProcessResult(doubleValue, _defaultInput, _functionName, _context); + + // Assert + Assert.Equal("123.456", result.Response.FunctionResponse.ResponseBody.Text.Body); + } + + private class TestObject + { + public string Name { get; set; } = ""; + public int Value { get; set; } + + public override string ToString() + { + return $"{Name}:{Value}"; + } + } + } +} diff --git a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/bedrockFunctionEvent.json b/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/bedrockFunctionEvent.json new file mode 100644 index 00000000..f2cedeb1 --- /dev/null +++ b/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/bedrockFunctionEvent.json @@ -0,0 +1,27 @@ +{ + "messageVersion": "1.0", + "function": "sum_numbers", + "sessionId": "455081292773641", + "agent": { + "name": "powertools-test", + "version": "DRAFT", + "id": "WPMRGAPAPJ", + "alias": "TSTALIASID" + }, + "parameters": [ + { + "name": "a", + "type": "number", + "value": "1" + }, + { + "name": "b", + "type": "number", + "value": "1" + } + ], + "actionGroup": "utility-tasks", + "sessionAttributes": {}, + "promptSessionAttributes": {}, + "inputText": "Sum 1 and 1" +} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/AppSyncEventsTests.cs b/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/EventHandler/AppSyncEventsTests.cs similarity index 90% rename from libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/AppSyncEventsTests.cs rename to libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/EventHandler/AppSyncEventsTests.cs index b4301f93..07c0e9fa 100644 --- a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/AppSyncEventsTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/EventHandler/AppSyncEventsTests.cs @@ -3,8 +3,10 @@ using Amazon.Lambda.Core; using Amazon.Lambda.TestUtilities; using AWS.Lambda.Powertools.EventHandler.AppSyncEvents; +#pragma warning disable CS8604 // Possible null reference argument. +#pragma warning disable CS8602 // Dereference of a possibly null reference. -namespace AWS.Lambda.Powertools.EventHandler.Tests; +namespace AWS.Lambda.Powertools.EventHandler; public class AppSyncEventsTests { @@ -74,10 +76,10 @@ public async Task Should_Return_Unchanged_Payload_Async() var lambdaContext = new TestLambdaContext(); var app = new AppSyncEventsResolver(); - app.OnPublishAsync("/default/channel", async payload => + app.OnPublishAsync("/default/channel", payload => { // Handle channel1 events - return payload; + return Task.FromResult(payload); }); // Act @@ -101,7 +103,7 @@ public async Task Should_Handle_Error_In_Event_Processing() var lambdaContext = new TestLambdaContext(); var app = new AppSyncEventsResolver(); - app.OnPublishAsync("/default/channel", async (payload) => + app.OnPublishAsync("/default/channel", (payload) => { // Throw exception for second event if (payload.ContainsKey("event_2")) @@ -109,7 +111,7 @@ public async Task Should_Handle_Error_In_Event_Processing() throw new InvalidOperationException("Test error"); } - return payload; + return Task.FromResult(payload); }); // Act @@ -137,10 +139,10 @@ public async Task Should_Match_Path_With_Wildcard() var app = new AppSyncEventsResolver(); int callCount = 0; - app.OnPublishAsync("/default/*", async (payload) => + app.OnPublishAsync("/default/*", (payload) => { callCount++; - return new Dictionary { ["wildcard_matched"] = true }; + return Task.FromResult(new Dictionary { ["wildcard_matched"] = true }); }); // Act @@ -162,9 +164,9 @@ public async Task Should_Authorize_Subscription() var lambdaContext = new TestLambdaContext(); var app = new AppSyncEventsResolver(); - app.OnPublishAsync("/default/channel", async (payload) => payload); + app.OnPublishAsync("/default/channel", (payload) => Task.FromResult(payload)); - app.OnSubscribeAsync("/default/*", async (info) => true); + app.OnSubscribeAsync("/default/*", (info) => Task.FromResult(true)); var subscribeEvent = new AppSyncEventsRequest { Info = new Information @@ -263,8 +265,7 @@ public async Task Should_Handle_Error_In_Aggregate_Mode_Async() var lambdaContext = new TestLambdaContext(); var app = new AppSyncEventsResolver(); - app.OnPublishAggregateAsync("/default/channel", - async (evt, ctx) => { throw new InvalidOperationException("Aggregate error"); }); + app.OnPublishAggregateAsync("/default/channel", (evt, ctx) => { throw new InvalidOperationException("Aggregate error"); }); // Act var result = await app.ResolveAsync(_appSyncEvent, lambdaContext); @@ -311,7 +312,7 @@ public async Task Should_Handle_TransformingPayload_Async() var lambdaContext = new TestLambdaContext(); var app = new AppSyncEventsResolver(); - app.OnPublishAsync("/default/channel", async (payload) => + app.OnPublishAsync("/default/channel", (payload) => { // Transform each event payload var transformedPayload = new Dictionary(); @@ -320,7 +321,7 @@ public async Task Should_Handle_TransformingPayload_Async() transformedPayload[$"transformed_{key}"] = $"transformed_{payload[key]}"; } - return transformedPayload; + return Task.FromResult(transformedPayload); }); // Act @@ -462,11 +463,9 @@ public async Task Should_Replace_Handler_When_RegisteringTwice_Async() var lambdaContext = new TestLambdaContext(); var app = new AppSyncEventsResolver(); - app.OnPublishAsync("/default/channel", - async (payload) => { return new Dictionary { ["handler"] = "first" }; }); + app.OnPublishAsync("/default/channel", (payload) => { return Task.FromResult(new Dictionary { ["handler"] = "first" }); }); - app.OnPublishAsync("/default/channel", - async (payload) => { return new Dictionary { ["handler"] = "second" }; }); + app.OnPublishAsync("/default/channel", (payload) => { return Task.FromResult(new Dictionary { ["handler"] = "second" }); }); // Act var result = await app.ResolveAsync(_appSyncEvent, lambdaContext); @@ -513,7 +512,7 @@ public async Task Aggregate_Handler_Can_Return_Individual_Results_With_Ids() app.OnPublishAsync("/default/channel12", (payload) => { throw new Exception("My custom exception"); }); - app.OnPublishAggregateAsync("/default/channel", async (evt) => + app.OnPublishAggregateAsync("/default/channel", (evt) => { // Iterate through events and return individual results with IDs var results = new List(); @@ -555,7 +554,7 @@ public async Task Aggregate_Handler_Can_Return_Individual_Results_With_Ids() } } - return new AppSyncEventsResponse { Events = results }; + return Task.FromResult(new AppSyncEventsResponse { Events = results }); }); // Act @@ -583,13 +582,13 @@ public async Task Should_Verify_Ids_Are_Preserved_In_Error_Case() var app = new AppSyncEventsResolver(); // Create handlers that throw exceptions for specific events - app.OnPublishAsync("/default/channel", async (payload) => + app.OnPublishAsync("/default/channel", (payload) => { if (payload.ContainsKey("event_1")) throw new InvalidOperationException("Error for event 1"); if (payload.ContainsKey("event_3")) throw new ArgumentException("Error for event 3"); - return payload; + return Task.FromResult(payload); }); // Act @@ -615,16 +614,16 @@ public async Task Should_Match_Most_Specific_Handler_Only() int firstHandlerCalls = 0; int secondHandlerCalls = 0; - app.OnPublishAsync("/default/channel", async (payload) => + app.OnPublishAsync("/default/channel", (payload) => { firstHandlerCalls++; - return new Dictionary { ["handler"] = "first" }; + return Task.FromResult(new Dictionary { ["handler"] = "first" }); }); - app.OnPublishAsync("/default/*", async (payload) => + app.OnPublishAsync("/default/*", (payload) => { secondHandlerCalls++; - return new Dictionary { ["handler"] = "second" }; + return Task.FromResult(new Dictionary { ["handler"] = "second" }); }); // Act @@ -667,18 +666,18 @@ public async Task Should_Handle_Multiple_Keys_In_Payload() ] }; - app.OnPublishAsync("/default/channel", async (payload) => + app.OnPublishAsync("/default/channel", (payload) => { // Check that both keys are present Assert.Equal("data_1", payload["event_1"]); Assert.Equal("data_1a", payload["event_1a"]); // Return a processed result with both keys - return new Dictionary + return Task.FromResult(new Dictionary { ["processed_1"] = payload["event_1"], ["processed_1a"] = payload["event_1a"] - }; + }); }); // Act @@ -699,14 +698,11 @@ public async Task Should_Only_Use_First_Matching_Handler_By_Specificity() var app = new AppSyncEventsResolver(); // Register handlers with different specificity - app.OnPublishAsync("/*", async (payload) => - new Dictionary { ["handler"] = "least-specific" }); + app.OnPublishAsync("/*", (payload) => Task.FromResult(new Dictionary { ["handler"] = "least-specific" })); - app.OnPublishAsync("/default/*", async (payload) => - new Dictionary { ["handler"] = "more-specific" }); + app.OnPublishAsync("/default/*", (payload) => Task.FromResult(new Dictionary { ["handler"] = "more-specific" })); - app.OnPublishAsync("/default/channel", async (payload) => - new Dictionary { ["handler"] = "most-specific" }); + app.OnPublishAsync("/default/channel", (payload) => Task.FromResult(new Dictionary { ["handler"] = "most-specific" })); // Act var result = await app.ResolveAsync(_appSyncEvent, lambdaContext); @@ -744,8 +740,7 @@ public async Task Should_Fallback_To_Less_Specific_Handler_If_No_Exact_Match() ] }; - app.OnPublishAsync("/default/*", async (payload) => - new Dictionary { ["handler"] = "wildcard-handler" }); + app.OnPublishAsync("/default/*", (payload) => Task.FromResult(new Dictionary { ["handler"] = "wildcard-handler" })); // Act var result = await app.ResolveAsync(fallbackEvent, lambdaContext); @@ -763,7 +758,7 @@ public async Task Should_Return_Null_When_Subscribing_To_Path_Without_Publish_Ha var app = new AppSyncEventsResolver(); // Only set up a subscribe handler without corresponding publish handler - app.OnSubscribeAsync("/subscribe-only", async (info) => true); + app.OnSubscribeAsync("/subscribe-only", (info) => Task.FromResult(true)); var subscribeEvent = new AppSyncEventsRequest { @@ -824,7 +819,7 @@ public async Task Should_Return_UnauthorizedException_When_Throwing_Unauthorized var lambdaContext = new TestLambdaContext(); var app = new AppSyncEventsResolver(); - app.OnPublishAsync(publishPath, async (payload) => payload); + app.OnPublishAsync(publishPath, (payload) => Task.FromResult(payload)); app.OnSubscribeAsync(subscribePath, (info, lambdaContext) => { throw new UnauthorizedException("OOPS"); }); diff --git a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/RouteHandlerRegistryTests.cs b/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/EventHandler/RouteHandlerRegistryTests.cs similarity index 93% rename from libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/RouteHandlerRegistryTests.cs rename to libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/EventHandler/RouteHandlerRegistryTests.cs index 92c9da3a..ac712da6 100644 --- a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/RouteHandlerRegistryTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/EventHandler/RouteHandlerRegistryTests.cs @@ -1,7 +1,13 @@ +using System.Diagnostics.CodeAnalysis; using AWS.Lambda.Powertools.EventHandler.Internal; +#pragma warning disable CS8605 // Unboxing a possibly null value. +#pragma warning disable CS8601 // Possible null reference assignment. +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +#pragma warning disable CS8602 // Dereference of a possibly null reference. -namespace AWS.Lambda.Powertools.EventHandler.Tests; +namespace AWS.Lambda.Powertools.EventHandler; +[SuppressMessage("Usage", "xUnit1031:Do not use blocking task operations in test method")] public class RouteHandlerRegistryTests { [Theory] @@ -14,7 +20,7 @@ public class RouteHandlerRegistryTests [InlineData("default/*", false)] // Not starting with slash [InlineData("", false)] // Empty path [InlineData(null, false)] // Null path - public void IsValidPath_ShouldValidateCorrectly(string path, bool expected) + public void IsValidPath_ShouldValidateCorrectly(string? path, bool expected) { // Create a private method accessor to test private IsValidPath method var registry = new RouteHandlerRegistry(); diff --git a/mkdocs.yml b/mkdocs.yml index 9afa0fd3..8feab461 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -26,6 +26,7 @@ nav: - utilities/batch-processing.md - Event Handler: - core/event_handler/appsync_events.md + - core/event_handler/bedrock_agent_function.md - utilities/parameters.md - utilities/jmespath-functions.md - Resources: diff --git a/version.json b/version.json index 0b56b18d..fd3b9502 100644 --- a/version.json +++ b/version.json @@ -9,6 +9,7 @@ "Parameters": "1.3.1", "Idempotency": "1.3.0", "BatchProcessing": "1.2.1", - "EventHandler": "1.0.0" + "EventHandler": "1.0.0", + "EventHandler.Resolvers.BedrockAgentFunction": "1.0.0" } }