From 33b653b39d213527fb8dfbaa04affe6eea4587ce Mon Sep 17 00:00:00 2001 From: Dustin Sweigart Date: Mon, 11 Aug 2025 12:12:40 -0400 Subject: [PATCH 01/13] Add missing named parameter to boto3 client creation for region name (#358) --- lambda/repository/ingestion_service.py | 2 +- lambda/repository/rag_document_repo.py | 3 ++- lambda/repository/state_machine/list_modified_objects.py | 3 ++- lambda/utilities/common_functions.py | 2 +- lambda/utilities/db_setup_iam_auth.py | 2 +- lambda/utilities/file_processing.py | 2 +- lib/serve/rest-api/src/utils/rds_auth.py | 2 +- test/lambda/test_db_setup_iam_auth.py | 2 +- 8 files changed, 10 insertions(+), 8 deletions(-) diff --git a/lambda/repository/ingestion_service.py b/lambda/repository/ingestion_service.py index b2a1bb5e1..a405c16c0 100644 --- a/lambda/repository/ingestion_service.py +++ b/lambda/repository/ingestion_service.py @@ -28,7 +28,7 @@ class IngestionAction(str, Enum): class DocumentIngestionService: def _submit_job(self, job: IngestionJob, action: IngestionAction) -> None: # Submit AWS Batch job - batch_client = boto3.client("batch") + batch_client = boto3.client("batch", region_name=os.environ["AWS_REGION"]) response = batch_client.submit_job( jobName=f"document-{action.value}-{job.id}", jobQueue=os.environ["LISA_INGESTION_JOB_QUEUE_NAME"], diff --git a/lambda/repository/rag_document_repo.py b/lambda/repository/rag_document_repo.py index 19d36d60b..bf3c34bd8 100644 --- a/lambda/repository/rag_document_repo.py +++ b/lambda/repository/rag_document_repo.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. import logging +import os from typing import Generator, Optional import boto3 @@ -32,7 +33,7 @@ def __init__(self, document_table_name: str, sub_document_table_name: str): dynamodb = boto3.resource("dynamodb") self.doc_table = dynamodb.Table(document_table_name) self.subdoc_table = dynamodb.Table(sub_document_table_name) - self.s3_client = boto3.client("s3") + self.s3_client = boto3.client("s3", region_name=os.environ["AWS_REGION"]) self.vs_repo = VectorStoreRepository() def delete_by_id(self, document_id: str) -> None: diff --git a/lambda/repository/state_machine/list_modified_objects.py b/lambda/repository/state_machine/list_modified_objects.py index 17ad6003a..9321f401e 100644 --- a/lambda/repository/state_machine/list_modified_objects.py +++ b/lambda/repository/state_machine/list_modified_objects.py @@ -15,6 +15,7 @@ """Lambda handlers for ListModifiedObjects state machine.""" import logging +import os from datetime import datetime, timedelta, timezone from typing import Any, Dict @@ -112,7 +113,7 @@ def handle_list_modified_objects(event: Dict[str, Any], context: Any) -> Dict[st validate_bucket_prefix(bucket, prefix) # Initialize S3 client - s3_client = boto3.client("s3") + s3_client = boto3.client("s3", region_name=os.environ["AWS_REGION"]) # Calculate timestamp for 24 hours ago twenty_four_hours_ago = datetime.now(timezone.utc) - timedelta(hours=24) diff --git a/lambda/utilities/common_functions.py b/lambda/utilities/common_functions.py index 5ddfadddc..8666e6bfe 100644 --- a/lambda/utilities/common_functions.py +++ b/lambda/utilities/common_functions.py @@ -459,7 +459,7 @@ def _get_lambda_role_arn() -> str: str The full ARN of the Lambda execution role """ - sts = boto3.client("sts") + sts = boto3.client("sts", region_name=os.environ["AWS_REGION"]) identity = sts.get_caller_identity() return cast(str, identity["Arn"]) # This will include the role name diff --git a/lambda/utilities/db_setup_iam_auth.py b/lambda/utilities/db_setup_iam_auth.py index da6b8acad..07a65cc7e 100644 --- a/lambda/utilities/db_setup_iam_auth.py +++ b/lambda/utilities/db_setup_iam_auth.py @@ -22,7 +22,7 @@ def get_db_credentials(secret_arn: str) -> Any: """Retrieve database credentials from Secrets Manager""" - client = boto3.client("secretsmanager") + client = boto3.client("secretsmanager", region_name=os.environ["AWS_REGION"]) try: response = client.get_secret_value(SecretId=secret_arn) diff --git a/lambda/utilities/file_processing.py b/lambda/utilities/file_processing.py index 252cb9730..f2f9c6857 100644 --- a/lambda/utilities/file_processing.py +++ b/lambda/utilities/file_processing.py @@ -32,7 +32,7 @@ logger = logging.getLogger(__name__) session = boto3.Session() -s3 = session.client("s3") +s3 = session.client("s3", region_name=os.environ["AWS_REGION"]) def _get_metadata(s3_uri: str, name: str) -> dict: diff --git a/lib/serve/rest-api/src/utils/rds_auth.py b/lib/serve/rest-api/src/utils/rds_auth.py index 05dd785f9..dd777b93a 100644 --- a/lib/serve/rest-api/src/utils/rds_auth.py +++ b/lib/serve/rest-api/src/utils/rds_auth.py @@ -32,7 +32,7 @@ def _get_lambda_role_arn() -> str: str The full ARN of the Lambda execution role """ - sts = boto3.client("sts") + sts = boto3.client("sts", region_name=os.environ["AWS_REGION"]) identity = sts.get_caller_identity() return cast(str, identity["Arn"]) # This will include the role name diff --git a/test/lambda/test_db_setup_iam_auth.py b/test/lambda/test_db_setup_iam_auth.py index 49df00ef5..796429108 100644 --- a/test/lambda/test_db_setup_iam_auth.py +++ b/test/lambda/test_db_setup_iam_auth.py @@ -90,7 +90,7 @@ def test_get_db_credentials_success(): assert result == secret_value # Verify the client was called correctly - mock_client.assert_called_once_with("secretsmanager") + mock_client.assert_called_once_with("secretsmanager", region_name="us-east-1") mock_secretsmanager.get_secret_value.assert_called_once_with(SecretId=secret_arn) From d9c877f8cd521b57881d7f83d174c4bf6035527a Mon Sep 17 00:00:00 2001 From: bedanley Date: Tue, 12 Aug 2025 21:56:11 -0600 Subject: [PATCH 02/13] Enhance model deployer logs (#357) * Enhance image builder logs --- Makefile | 61 +++++++++++---------- lambda/authorizer/lambda_functions.py | 2 +- lambda/dockerimagebuilder/__init__.py | 60 ++++++++++++++++++-- lambda/models/state_machine/create_model.py | 25 ++++++--- lambda/models/state_machine/delete_model.py | 10 +++- lambda/models/state_machine/update_model.py | 7 ++- lib/api-base/ecsCluster.ts | 35 +++++++++++- lib/models/docker-image-builder.ts | 7 +++ test/cdk/stacks/nag.test.ts | 2 +- test/lambda/test_dockerimagebuilder.py | 6 +- 10 files changed, 162 insertions(+), 53 deletions(-) diff --git a/Makefile b/Makefile index db3e118d4..a12fb4dfa 100644 --- a/Makefile +++ b/Makefile @@ -177,36 +177,40 @@ dockerCheck: ## Check if models are uploaded modelCheck: - @$(foreach MODEL_ID,$(MODEL_IDS), \ - $(PROJECT_DIR)/scripts/check-for-models.sh -m $(MODEL_ID) -s $(MODEL_BUCKET); \ - if \ - [ $$? != 0 ]; \ - then \ - localModelDir="./models"; \ - if \ - [ ! -d "$localModelDir" ]; \ - then \ - mkdir "$localModelDir"; \ - fi; \ - echo; \ - echo "Preparing to download, convert, and upload safetensors for model: $(MODEL_ID)"; \ - echo "Local directory: '$$localModelDir' will be used to store downloaded and converted model weights"; \ - echo "Note: sudo privileges required to remove model dir due to docker mount using root"; \ - echo "Would you like to continue? [y/N] "; \ - read confirm_download; \ - if \ - [ $${confirm_download:-'N'} = 'y' ]; \ - then \ - mkdir -p $$localModelDir; \ + @access_token=""; \ + for MODEL_ID in $(MODEL_IDS); do \ + $(PROJECT_DIR)/scripts/check-for-models.sh -m $$MODEL_ID -s $(MODEL_BUCKET); \ + if [ $$? != 0 ]; then \ + localModelDir="./models"; \ + if [ ! -d "$$localModelDir" ]; then \ + mkdir "$$localModelDir"; \ + fi; \ + echo; \ + echo "Preparing to download, convert, and upload safetensors for model: $$MODEL_ID"; \ + echo "Local directory: '$$localModelDir' will be used to store downloaded and converted model weights"; \ + echo "Note: sudo privileges required to remove model dir due to docker mount using root"; \ + echo "Would you like to continue? [y/N] "; \ + read confirm_download; \ + if [ $${confirm_download:-'N'} = 'y' ]; then \ + mkdir -p $$localModelDir; \ + if [ -z "$$access_token" ]; then \ + if [ -n "$$HUGGINGFACE_TOKEN" ]; then \ + access_token="$$HUGGINGFACE_TOKEN"; \ + elif [ -f ".hf_token_cache" ]; then \ + access_token=$$(cat .hf_token_cache); \ + else \ echo "What is your huggingface access token? "; \ read access_token; \ - echo "Converting and uploading safetensors for model: $(MODEL_ID)"; \ - tgiImage=$$(yq -r '[.ecsModels[] | select(.inferenceContainer == "tgi") | .baseImage] | first' $(PROJECT_DIR)/config-custom.yaml); \ - echo $$tgiImage; \ - $(PROJECT_DIR)/scripts/convert-and-upload-model.sh -m $(MODEL_ID) -s $(MODEL_BUCKET) -a $$access_token -t $$tgiImage -d $$localModelDir; \ + echo "$$access_token" > .hf_token_cache; \ + fi; \ fi; \ + echo "Converting and uploading safetensors for model: $$MODEL_ID"; \ + tgiImage=$$(yq -r '[.ecsModels[] | select(.inferenceContainer == "tgi") | .baseImage] | first' $(PROJECT_DIR)/config-custom.yaml); \ + echo $$tgiImage; \ + $(PROJECT_DIR)/scripts/convert-and-upload-model.sh -m $$MODEL_ID -s $(MODEL_BUCKET) -a $$access_token -t $$tgiImage -d $$localModelDir; \ + fi; \ fi; \ - ) + done ## Run all clean commands clean: cleanTypeScript cleanPython cleanCfn cleanMisc @@ -243,6 +247,7 @@ cleanCfn: ## Delete all misc files cleanMisc: @find . -type f -name "*.DS_Store" -delete + @rm -f .hf_token_cache ## Login Docker CLI to Amazon Elastic Container Registry @@ -284,7 +289,7 @@ endef ## Deploy all infrastructure deploy: installPythonRequirements dockerCheck dockerLogin cleanMisc modelCheck buildNpmModules $(call print_config) -ifneq (,$(findstring true, $(HEADLESS))) +ifeq ($(HEADLESS),true) npx cdk deploy ${STACK} $(if $(PROFILE),--profile ${PROFILE}) --require-approval never -c ${ENV}='$(shell echo '${${ENV}}')'; else @printf "Is the configuration correct? [y/N] "\ @@ -298,7 +303,7 @@ endif ## Tear down all infrastructure destroy: cleanMisc $(call print_config) -ifneq (,$(findstring true, $(HEADLESS))) +ifeq ($(HEADLESS),true) npx cdk destroy ${STACK} --force $(if $(PROFILE),--profile ${PROFILE}); else @printf "Is the configuration correct? [y/N] "\ diff --git a/lambda/authorizer/lambda_functions.py b/lambda/authorizer/lambda_functions.py index c5b577c0b..c5ed40650 100644 --- a/lambda/authorizer/lambda_functions.py +++ b/lambda/authorizer/lambda_functions.py @@ -47,7 +47,7 @@ def lambda_handler(event: Dict[str, Any], context) -> Dict[str, Any]: # type: i id_token = get_id_token(event) if not id_token: - logger.warn("Missing id_token in request. Denying access.") + logger.warning("Missing id_token in request. Denying access.") logger.info(f"REST API authorization handler completed with 'Deny' for resource {event['methodArn']}") return generate_policy(effect="Deny", resource=event["methodArn"]) diff --git a/lambda/dockerimagebuilder/__init__.py b/lambda/dockerimagebuilder/__init__.py index 9b957965f..f50fae2a5 100644 --- a/lambda/dockerimagebuilder/__init__.py +++ b/lambda/dockerimagebuilder/__init__.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import logging import os import shlex import uuid @@ -20,11 +21,44 @@ import boto3 from botocore.exceptions import ClientError +# Configure logging +logger = logging.getLogger() +logger.setLevel(logging.INFO) + user_data_template = """#! /bin/bash -ex export AWS_REGION={{AWS_REGION}} -(r=5;while ! yum install -y docker ; do ((--r))||exit;sleep 60;done) +export LOG_GROUP={{LOG_GROUP}} + +# Install CloudWatch agent and docker +(r=5;while ! yum install -y docker amazon-cloudwatch-agent ; do ((--r))||exit;sleep 60;done) + +# Configure CloudWatch agent +cat > /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json << EOF +{ + "logs": { + "logs_collected": { + "files": { + "collect_list": [ + { + "file_path": "/var/log/docker-build.log", + "log_group_name": "${LOG_GROUP}", + "log_stream_name": "docker-build-{{IMAGE_ID}}" + } + ] + } + } + } +} +EOF + +# Start services systemctl start docker + +# Start CloudWatch agent with configuration +/opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -c file:/opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json -s + +# Setup build environment mkdir /home/ec2-user/docker_resources aws --region ${AWS_REGION} s3 sync s3://{{BUCKET_NAME}} /home/ec2-user/docker_resources cd /home/ec2-user/docker_resources/{{LAYER_TO_ADD}} @@ -35,11 +69,13 @@ done & function buildTagPush() { + echo "Starting Docker build for {{IMAGE_ID}}" | tee -a /var/log/docker-build.log sed -iE 's/^FROM.*/FROM {{BASE_IMAGE}}/' Dockerfile - docker build -t {{IMAGE_ID}} --build-arg BASE_IMAGE={{BASE_IMAGE}} --build-arg MOUNTS3_DEB_URL={{MOUNTS3_DEB_URL}} . && \ - docker tag {{IMAGE_ID}} {{ECR_URI}}:{{IMAGE_ID}} && \ - aws --region ${AWS_REGION} ecr get-login-password | docker login --username AWS --password-stdin {{ECR_URI}} && \ - docker push {{ECR_URI}}:{{IMAGE_ID}} + docker build -t {{IMAGE_ID}} --build-arg BASE_IMAGE={{BASE_IMAGE}} --build-arg MOUNTS3_DEB_URL={{MOUNTS3_DEB_URL}} . 2>&1 | tee -a /var/log/docker-build.log && \ + docker tag {{IMAGE_ID}} {{ECR_URI}}:{{IMAGE_ID}} 2>&1 | tee -a /var/log/docker-build.log && \ + aws --region ${AWS_REGION} ecr get-login-password | docker login --username AWS --password-stdin {{ECR_URI}} 2>&1 | tee -a /var/log/docker-build.log && \ + docker push {{ECR_URI}}:{{IMAGE_ID}} 2>&1 | tee -a /var/log/docker-build.log + echo "Build completed with exit code $?" | tee -a /var/log/docker-build.log return $? } @@ -48,10 +84,14 @@ def handler(event: Dict[str, Any], context) -> Dict[str, Any]: # type: ignore [no-untyped-def] + logger.info(f"Starting Docker image builder with event: {event}") + base_image = event["base_image"] layer_to_add = event["layer_to_add"] mounts3_deb_url = os.environ["LISA_MOUNTS3_DEB_URL"] + logger.info(f"Building image with base: {base_image}, layer: {layer_to_add}") + ec2_resource = boto3.resource("ec2", region_name=os.environ["AWS_REGION"]) ssm_client = boto3.client("ssm", region_name=os.environ["AWS_REGION"]) @@ -59,8 +99,11 @@ def handler(event: Dict[str, Any], context) -> Dict[str, Any]: # type: ignore [ ami_id = response["Parameter"]["Value"] image_tag = str(uuid.uuid4()) + logger.info(f"Using AMI: {ami_id}, Image tag: {image_tag}") + rendered_userdata = user_data_template rendered_userdata = rendered_userdata.replace("{{AWS_REGION}}", shlex.quote(os.environ["AWS_REGION"])) + rendered_userdata = rendered_userdata.replace("{{LOG_GROUP}}", shlex.quote(context.log_group_name)) rendered_userdata = rendered_userdata.replace("{{BUCKET_NAME}}", shlex.quote(os.environ["LISA_DOCKER_BUCKET"])) rendered_userdata = rendered_userdata.replace("{{LAYER_TO_ADD}}", shlex.quote(layer_to_add)) rendered_userdata = rendered_userdata.replace("{{BASE_IMAGE}}", shlex.quote(base_image)) @@ -96,9 +139,14 @@ def handler(event: Dict[str, Any], context) -> Dict[str, Any]: # type: ignore [ instance_params["SubnetId"] = os.environ["LISA_SUBNET_ID"] # Create instance with parameters + logger.info(f"Creating Builder EC2 instance with params: {instance_params}") instances = ec2_resource.create_instances(**instance_params) - return {"instance_id": instances[0].instance_id, "image_tag": image_tag} + instance_id = instances[0].instance_id + logger.info(f"Successfully created Builder EC2 instance: {instance_id}") + + return {"instance_id": instance_id, "image_tag": image_tag} except ClientError as e: + logger.error(f"Failed to create EC2 instance: {str(e)}") raise e diff --git a/lambda/models/state_machine/create_model.py b/lambda/models/state_machine/create_model.py index 7d9071347..e6c1b1255 100644 --- a/lambda/models/state_machine/create_model.py +++ b/lambda/models/state_machine/create_model.py @@ -15,9 +15,10 @@ """Lambda handlers for CreateModel state machine.""" import json +import logging import os from copy import deepcopy -from datetime import datetime +from datetime import datetime, UTC from typing import Any, Dict import boto3 @@ -31,6 +32,9 @@ ) from utilities.common_functions import get_cert_path, get_rest_api_container_endpoint, retry_config +logger = logging.getLogger() +logger.setLevel(logging.INFO) + lambdaConfig = Config(connect_timeout=60, read_timeout=600, retries={"max_attempts": 1}) lambdaClient = boto3.client("lambda", region_name=os.environ["AWS_REGION"], config=lambdaConfig) ecrClient = boto3.client("ecr", region_name=os.environ["AWS_REGION"], config=retry_config) @@ -69,8 +73,9 @@ def get_container_path(inference_container_type: InferenceContainer) -> str: def handle_set_model_to_creating(event: Dict[str, Any], context: Any) -> Dict[str, Any]: """Set DDB entry to CREATING status.""" + logger.info(f"Setting model to CREATING status: {event.get('modelId')}") output_dict = deepcopy(event) - request = CreateModelRequest.validate(event) + request = CreateModelRequest.model_validate(event) is_lisa_managed = all( ( @@ -91,7 +96,7 @@ def handle_set_model_to_creating(event: Dict[str, Any], context: Any) -> Dict[st ExpressionAttributeValues={ ":model_status": ModelStatus.CREATING, ":model_config": event, - ":lm": int(datetime.utcnow().timestamp()), + ":lm": int(datetime.now(UTC).timestamp()), }, ) output_dict["create_infra"] = is_lisa_managed @@ -100,8 +105,9 @@ def handle_set_model_to_creating(event: Dict[str, Any], context: Any) -> Dict[st def handle_start_copy_docker_image(event: Dict[str, Any], context: Any) -> Dict[str, Any]: """Start process for copying Docker image into local AWS account.""" + logger.info(f"Starting Docker image copy for model: {event.get('modelId')}") output_dict = deepcopy(event) - request = CreateModelRequest.validate(event) + request = CreateModelRequest.model_validate(event) image_path = get_container_path(request.inferenceContainer) output_dict["containerConfig"]["image"]["path"] = image_path @@ -153,7 +159,7 @@ def handle_poll_docker_image_available(event: Dict[str, Any], context: Any) -> D def handle_start_create_stack(event: Dict[str, Any], context: Any) -> Dict[str, Any]: """Start model infrastructure creation.""" output_dict = deepcopy(event) - request = CreateModelRequest.validate(event) + request = CreateModelRequest.model_validate(event) def camelize_object(o): # type: ignore[no-untyped-def] o2 = {} @@ -204,7 +210,7 @@ def camelize_object(o): # type: ignore[no-untyped-def] ExpressionAttributeValues={ ":stack_name": stack_name, ":stack_arn": stack_arn, - ":lm": int(datetime.utcnow().timestamp()), + ":lm": int(datetime.now(UTC).timestamp()), }, ) @@ -292,7 +298,7 @@ def handle_add_model_to_litellm(event: Dict[str, Any], context: Any) -> Dict[str ExpressionAttributeValues={ ":ms": ModelStatus.IN_SERVICE, ":lid": litellm_id, - ":lm": int(datetime.utcnow().timestamp()), + ":lm": int(datetime.now(UTC).timestamp()), ":mu": litellm_params.get("api_base", ""), ":asg": event.get("autoScalingGroup", ""), }, @@ -313,14 +319,17 @@ def handle_failure(event: Dict[str, Any], context: Any) -> Dict[str, Any]: Expectation of this function is to terminate the EC2 instance if it is still running, and to set the model status to Failed. Cleaning up the CloudFormation stack, if it still exists, will happen in the DeleteModel API. """ + logger.error(f"Handling state machine failure: {event}") error_dict = json.loads( # error from SFN is json payload on top of json payload we add to the exception json.loads(event["Cause"])["errorMessage"] ) error_reason = error_dict["error"] original_event = error_dict["event"] + logger.error(f"Failure reason: {error_reason}, Model ID: {original_event.get('modelId', 'unknown')}") # terminate EC2 instance if we have one recorded if "image_info" in original_event and "instance_id" in original_event["image_info"]: + logger.info(f"Terminating EC2 instance: {original_event['image_info']['instance_id']}") ec2Client.terminate_instances(InstanceIds=[original_event["image_info"]["instance_id"]]) # set model as Failed in DDB, so it shows as such in the UI. adds error reason as well. @@ -329,7 +338,7 @@ def handle_failure(event: Dict[str, Any], context: Any) -> Dict[str, Any]: UpdateExpression="SET model_status = :ms, last_modified_date = :lm, failure_reason = :fr", ExpressionAttributeValues={ ":ms": ModelStatus.FAILED, - ":lm": int(datetime.utcnow().timestamp()), + ":lm": int(datetime.now(UTC).timestamp()), ":fr": error_reason, }, ) diff --git a/lambda/models/state_machine/delete_model.py b/lambda/models/state_machine/delete_model.py index 8ba002e86..e5f4ac9cc 100644 --- a/lambda/models/state_machine/delete_model.py +++ b/lambda/models/state_machine/delete_model.py @@ -14,9 +14,10 @@ """Lambda handlers for DeleteModel state machine.""" +import logging import os from copy import deepcopy -from datetime import datetime +from datetime import datetime, UTC from typing import Any, Dict from uuid import uuid4 @@ -26,6 +27,9 @@ from ..domain_objects import ModelStatus +logger = logging.getLogger() +logger.setLevel(logging.INFO) + # Clients cloudformation = boto3.client("cloudformation", region_name=os.environ["AWS_REGION"], config=retry_config) dynamodb = boto3.resource("dynamodb", region_name=os.environ["AWS_REGION"], config=retry_config) @@ -53,6 +57,7 @@ def handle_set_model_to_deleting(event: Dict[str, Any], context: Any) -> Dict[st """Start deletion workflow based on user-specified model input.""" output_dict = deepcopy(event) model_id = event["modelId"] + logger.info(f"Starting deletion workflow for model: {model_id}") model_key = {"model_id": model_id} item = ddb_table.get_item( Key=model_key, @@ -68,7 +73,7 @@ def handle_set_model_to_deleting(event: Dict[str, Any], context: Any) -> Dict[st Key=model_key, UpdateExpression="SET last_modified_date = :lmd, model_status = :ms", ExpressionAttributeValues={ - ":lmd": int(datetime.utcnow().timestamp()), + ":lmd": int(datetime.now(UTC).timestamp()), ":ms": ModelStatus.DELETING, }, ) @@ -85,6 +90,7 @@ def handle_delete_from_litellm(event: Dict[str, Any], context: Any) -> Dict[str, def handle_delete_stack(event: Dict[str, Any], context: Any) -> Dict[str, Any]: """Initialize stack deletion.""" stack_arn = event[CFN_STACK_ARN] + logger.info(f"Deleting CloudFormation stack: {stack_arn}") client_request_token = str(uuid4()) cloudformation.delete_stack( StackName=stack_arn, diff --git a/lambda/models/state_machine/update_model.py b/lambda/models/state_machine/update_model.py index d458605e7..89b87b872 100644 --- a/lambda/models/state_machine/update_model.py +++ b/lambda/models/state_machine/update_model.py @@ -18,7 +18,7 @@ import logging import os from copy import deepcopy -from datetime import datetime +from datetime import datetime, UTC from typing import Any, Dict import boto3 @@ -107,7 +107,7 @@ def handle_job_intake(event: Dict[str, Any], context: Any) -> Dict[str, Any]: ddb_update_expression = "SET model_status = :ms, last_modified_date = :lm" ddb_update_values = { ":ms": ModelStatus.UPDATING, - ":lm": int(datetime.utcnow().timestamp()), + ":lm": int(datetime.now(UTC).timestamp()), } if is_activation_request: @@ -212,6 +212,7 @@ def handle_poll_capacity(event: Dict[str, Any], context: Any) -> Dict[str, Any]: output_dict = deepcopy(event) model_id = event["model_id"] asg_name = event["asg_name"] + logger.info(f"Polling capacity for model {model_id}, ASG: {asg_name}") asg_info = autoscaling_client.describe_auto_scaling_groups(AutoScalingGroupNames=[asg_name])["AutoScalingGroups"][0] desired_capacity = asg_info["DesiredCapacity"] @@ -263,7 +264,7 @@ def handle_finish_update(event: Dict[str, Any], context: Any) -> Dict[str, Any]: ddb_update_expression = "SET model_status = :ms, last_modified_date = :lm" ddb_update_values: Dict[str, Any] = { - ":lm": int(datetime.utcnow().timestamp()), + ":lm": int(datetime.now(UTC).timestamp()), } if polling_error := event.get("polling_error", None): diff --git a/lib/api-base/ecsCluster.ts b/lib/api-base/ecsCluster.ts index 48fe9a347..1dfc4b1e9 100644 --- a/lib/api-base/ecsCluster.ts +++ b/lib/api-base/ecsCluster.ts @@ -17,6 +17,7 @@ // ECS Cluster Construct. import { Duration, RemovalPolicy } from 'aws-cdk-lib'; import { BlockDeviceVolume, GroupMetrics, Monitoring } from 'aws-cdk-lib/aws-autoscaling'; +import { LogGroup, RetentionDays } from 'aws-cdk-lib/aws-logs'; import { Metric, Stats } from 'aws-cdk-lib/aws-cloudwatch'; import { InstanceType, ISecurityGroup } from 'aws-cdk-lib/aws-ec2'; import { @@ -41,7 +42,7 @@ import { BaseApplicationListenerProps, SslPolicy, } from 'aws-cdk-lib/aws-elasticloadbalancingv2'; -import { IRole, ManagedPolicy, Role } from 'aws-cdk-lib/aws-iam'; +import { Effect, IRole, ManagedPolicy, PolicyStatement, Role } from 'aws-cdk-lib/aws-iam'; import { StringParameter } from 'aws-cdk-lib/aws-ssm'; import { Construct } from 'constructs'; @@ -160,6 +161,8 @@ export class ECSCluster extends Construct { mountPoints.push(nvmeMountPoint); } + // Add CloudWatch Logs permissions to EC2 instance role for ECS logging + autoScalingGroup.role.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName('CloudWatchLogsFullAccess')); // Add permissions to use SSM in dev environment for EC2 debugging purposes only if (config.deploymentStage === 'dev') { autoScalingGroup.role.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMFullAccess')); @@ -184,6 +187,13 @@ export class ECSCluster extends Construct { environment.CURL_CA_BUNDLE = config.certificateAuthorityBundle; } + // Create CloudWatch log group with explicit retention + const logGroup = new LogGroup(this, createCdkId([ecsConfig.identifier, 'LogGroup']), { + logGroupName: `/aws/ecs/${config.deploymentName}-${ecsConfig.identifier}`, + retention: RetentionDays.ONE_WEEK, + removalPolicy: config.removalPolicy + }); + // Retrieve execution role if it has been overridden const executionRole = config.roles ? Role.fromRoleArn( this, @@ -204,6 +214,24 @@ export class ECSCluster extends Construct { ...(executionRole && { executionRole }), }); + // Grant CloudWatch logs permissions to both task role and execution role + logGroup.grantWrite(taskRole); + if (executionRole) { + logGroup.grantWrite(executionRole); + } else { + // If no custom execution role, ensure the default execution role has CloudWatch permissions + // This is critical for log stream creation during container startup + taskDefinition.addToExecutionRolePolicy(new PolicyStatement({ + effect: Effect.ALLOW, + actions: [ + 'logs:CreateLogGroup', + 'logs:CreateLogStream', + 'logs:PutLogEvents', + 'logs:DescribeLogStreams' + ], + resources: [logGroup.logGroupArn, `${logGroup.logGroupArn}:*`] + })); + } // Add container to task definition const containerHealthCheckConfig = ecsConfig.containerConfig.healthCheckConfig; const containerHealthCheck: HealthCheck = { @@ -227,7 +255,10 @@ export class ECSCluster extends Construct { containerName: createCdkId([config.deploymentName, ecsConfig.identifier], 32, 2), image, environment, - logging: LogDriver.awsLogs({ streamPrefix: ecsConfig.identifier }), + logging: LogDriver.awsLogs({ + logGroup: logGroup, + streamPrefix: ecsConfig.identifier + }), gpuCount: Ec2Metadata.get(ecsConfig.instanceType).gpuCount, memoryReservationMiB: Ec2Metadata.get(ecsConfig.instanceType).memory - ecsConfig.containerMemoryBuffer, portMappings: [{ hostPort: 80, containerPort: 8080, protocol: Protocol.TCP }], diff --git a/lib/models/docker-image-builder.ts b/lib/models/docker-image-builder.ts index 6e285500c..06e16ec93 100644 --- a/lib/models/docker-image-builder.ts +++ b/lib/models/docker-image-builder.ts @@ -151,6 +151,13 @@ export class DockerImageBuilder extends Construct { ], resources: ['*'], }), + new PolicyStatement({ + actions: [ + 'logs:CreateLogStream', + 'logs:PutLogEvents', + ], + resources: ['*'], + }), ], }); diff --git a/test/cdk/stacks/nag.test.ts b/test/cdk/stacks/nag.test.ts index 5934eca8e..e2feb7bac 100644 --- a/test/cdk/stacks/nag.test.ts +++ b/test/cdk/stacks/nag.test.ts @@ -40,7 +40,7 @@ const nagResults: NagResult = { LisaModels: [1,73,0,60], LisaNetworking: [1,2,3,5], LisaRAG: [3,53,0,50], - LisaServe: [1,22,0,33], + LisaServe: [1,24,0,32], LisaUI: [0,15,0,7], LisaMetrics: [1,11,0,12] }; diff --git a/test/lambda/test_dockerimagebuilder.py b/test/lambda/test_dockerimagebuilder.py index f27ac9398..5c679be78 100644 --- a/test/lambda/test_dockerimagebuilder.py +++ b/test/lambda/test_dockerimagebuilder.py @@ -44,7 +44,9 @@ @pytest.fixture def lambda_context(): """Create a mock Lambda context.""" - return MagicMock() + context = MagicMock() + context.log_group_name = "/aws/lambda/test-function" + return context def test_handler_success(lambda_context): @@ -178,7 +180,7 @@ def test_handler_ssm_error(lambda_context): handler(event, lambda_context) -def test_user_data_template_rendering(): +def test_user_data_template_rendering(lambda_context): """Test that user data template is properly rendered.""" event = {"base_image": "python:3.9-slim", "layer_to_add": "test-layer"} From d6c43b10ddf15456533b340dc693c0b21b8b85bf Mon Sep 17 00:00:00 2001 From: Evan Stohlmann Date: Mon, 18 Aug 2025 14:53:24 -0400 Subject: [PATCH 03/13] BR Knowledge Base support --- .github/workflows/code.smoke-test.yml | 3 - README.md | 74 +- cypress/package-lock.json | 2607 ----------------- lambda/repository/lambda_functions.py | 40 +- .../repository/pipeline_delete_documents.py | 15 +- .../repository/pipeline_ingest_documents.py | 23 +- .../state_machine/cleanup_repo_docs.py | 10 +- lambda/utilities/bedrock_kb.py | 116 + lib/core/iam/rag.json | 18 +- lib/docs/package.json | 2 +- .../state_machine/create-store.ts | 72 +- .../state_machine/delete-store.ts | 15 +- lib/schema/configSchema.ts | 7 + lib/schema/ragSchema.ts | 12 + lib/user-interface/react/package.json | 2 +- .../components/chatbot/components/Message.tsx | 2 +- .../configuration/RepositoryActions.tsx | 12 +- .../BedrockKnowledgeBaseConfigForm.tsx | 78 + .../createRepository/RepositoryConfigForm.tsx | 15 + package-lock.json | 248 +- test/lambda/test_repository_lambda.py | 60 +- .../src/lib/bedrock_knowledge_base.ts | 37 + vector_store_deployer/src/lib/index.ts | 7 + 23 files changed, 635 insertions(+), 2840 deletions(-) delete mode 100644 cypress/package-lock.json create mode 100644 lambda/utilities/bedrock_kb.py create mode 100644 lib/user-interface/react/src/components/configuration/createRepository/BedrockKnowledgeBaseConfigForm.tsx create mode 100644 vector_store_deployer/src/lib/bedrock_knowledge_base.ts diff --git a/.github/workflows/code.smoke-test.yml b/.github/workflows/code.smoke-test.yml index d3393791d..346895d68 100644 --- a/.github/workflows/code.smoke-test.yml +++ b/.github/workflows/code.smoke-test.yml @@ -22,9 +22,6 @@ jobs: - name: Install base dependencies run: npm ci - - name: Install Cypress deps - run: npm ci --prefix cypress - # ──────────────────────────────────────────────────── # Start LISA UI in background & wait until it’s ready # ──────────────────────────────────────────────────── diff --git a/README.md b/README.md index 56f1b5ab7..e5e456147 100644 --- a/README.md +++ b/README.md @@ -1,82 +1,34 @@ # LLM Inference Solution for Amazon Dedicated Cloud (LISA) - [![Full Documentation](https://img.shields.io/badge/Full%20Documentation-blue?style=for-the-badge&logo=Vite&logoColor=white)](https://awslabs.github.io/LISA/) - ## What is LISA? - -LISA is an infrastructure-as-code solution providing scalable, low latency access to customers’ generative LLMs and -embedding language models. LISA accelerates and supports customers’ GenAI experimentation and adoption, particularly in -regions where Amazon Bedrock is not available. LISA allows customers to move quickly rather than independently solve the -undifferentiated heavy lifting of hosting and inference architecture. Customers deploy LISA into a single AWS account -and integrate it with an identity provider. Customers bring their own models to LISA for self-hosting and inference -supported by Amazon Elastic Container Service (ECS). Model configuration is managed through LISA’s model management -APIs. - -As use cases and model requirements grow, customers can configure LISA with external model providers. Through OpenAI's -API spec via the LiteLLM proxy, LISA is compatible with 100+ models from various providers, including Amazon Bedrock and -Amazon Jumpstart. LISA customers can centralize communication across many model providers via LiteLLM, leveraging LISA -for model orchestration. Using LISA as a model orchestration layer allows customers to standardize integrations with -externally hosted models in a single place. Without an orchestration layer, customers must individually manage unique -API integrations with each provider. - +Our large language model (LLM) inference solution for the Amazon Dedicated Cloud (ADC), LISA, is an open source infrastructure-as-code solution. Customers deploy LISA directly into an Amazon Web Services (AWS) account. While specially designed for ADC regions that support government customers' most sensitive workloads, LISA is also compatible with commercial regions. LISA supports model self-hosting via Amazon Elastic Container Service (ECS). LISA's LiteLLM support also makes it compatible with 100+ models hosted by external model providers, including Amazon Bedrock. LISA further complements Amazon Bedrock by accelerating GenAI adoption. LISA's optional chat assistant user interface (UI) supports model management, model prompting, document summarization, chat session management, prompt libraries, retrieval augmented generation (RAG), automated document ingestion pipelines, and other advanced features. Customers can choose to integrate custom UIs directly with LISA, relying on LISA for centralized model orchestration, chat session management, and RAG. LISA is scalable and ready to support production use cases. The roadmap is customer-driven, with new capabilities launching monthly. ## Key Features - -* **Self Host Models:** Bring your own text generation and embedding models to LISA for hosting and inference. -* **Model Orchestration:** Centralize and standardize configuration with 100+ models from model providers via LiteLLM, - including Amazon Bedrock models. -* **Chatbot User Interface:** Through the chatbot user interface, users can prompt LLMs, receive responses, modify prompt - templates, change model arguments, and manage their session history. Administrators can control available features via - the configuration page. -* **Retrieval-augmented generation (RAG):** RAG reduces the need for fine-tuning, an expensive and time-consuming - undertaking, and delivers more contextually relevant outputs. LISA offers RAG through Amazon OpenSearch or - PostgreSQL’s PGVector extension on Amazon RDS. -* **Non-RAG Model Context:** Users can upload documents to their chat sessions to enhance responses or support use cases - like document summarization. -* **Model Management:** Administrators can add, remove, and update models configured with LISA through the model management - configuration page or APIs. -* **OpenAI API spec:** LISA can be configured with compatible tooling. For example, customers can configure LISA as the - model provider for the [Continue](https://www.continue.dev/) plugin, an open-source AI code assistance for JetBrains and Visual Studio Code - integrated development environments (IDEs). This allows users to select from any LISA-configured model to support LLM - prompting directly in their IDE. -* **Libraries:** If your workflow includes libraries such as [LangChain](https://python.langchain.com/) - or [OpenAI](https://github.com/openai/openai-python), then you can place LISA in your - application by changing only the endpoint and headers for the client objects. -* **FedRAMP:** The AWS services that LISA leverages are FedRAMP High compliant. -* **Ongoing Releases:** We offer on-going release with new functionality. LISA’s roadmap is customer driven. - +* **Open source**: No subscription or licensing fees. LISA costs are based on service usage. The roadmap is customer-driven with monthly releases. LISA is backed by a software development team. +* **Model Flexibility**: Bring your own models for self-hosting, or quickly configure LISA with 100+ models supported by third-party model providers, including Amazon Bedrock. +* **Model Orchestration**: Centralize and standardize unique API calls to third-party model providers automatically with LISA via LiteLLM. LISA standardizes the unique API calls into the OpenAI format automatically. All that is required is an API key, model name, and API endpoint. +* **Modular Components**: Accelerate GenAI adoption with secure, scalable software. LISA supports various use cases through configurable components: model serving and orchestration, chat user interface with advanced capabilities, authentication, retrieval augmented generation (RAG), Anthropic’s Model Context Protocol (MCP), and APIs. +* **CodeGen**: Supports OpenAI’s API specification, making LISA easily configurable with compatible solutions like the Continue plugin for VSCode and JetBrains integrated development environments (IDEs). This allows users to select from any LISA configured model to support LLM prompting directly in their IDE. +* **FedRAMP**: Leverages FedRAMP High compliant services. ## Deployment Prerequisites - ### Pre-Deployment Steps - -* Set up and have access to an AWS account with appropriate permissions - * All the resource creation that happens as part of CDK deployments expects Administrator or Administrator-like - permissions with resource creation and mutation permissions. Installation will not succeed if this profile does - not have permissions to create and edit arbitrary resources for the system. Note: This level of permissions is not - required for the runtime of LISA. This is only necessary for deployment and subsequent updates. -* Familiarity with AWS Cloud Development Kit (CDK) and infrastructure-as-code principles -* Optional: If using the chat UI, Have your Identity Provider (IdP) information and access -* Optional: Have your VPC information available, if you are using an existing one for your deployment -* Note: CDK and Model Management both leverage AWS Systems Manager Agent (SSM) parameter store. Confirm that SSM is approved for use by your organization before beginning. - +* Set up or have access to an AWS account. +* Ensure that your AWS account has the appropriate permissions. Resource creation during the AWS CDK deployment expects Administrator or Administrator-like permissions, to include resource creation and mutation permissions. Installation will not succeed if this profile does not have permissions to create and edit arbitrary resources for the system. This level of permissions is not required for the runtime of LISA. This is only necessary for deployment and subsequent updates. +* If using the chat UI, have your Identity Provider (IdP) information available, and access. +* If using an existing VPC, have its information available. +* Familiarity with AWS Cloud Development Kit (CDK) and infrastructure-as-code principles is a plus. +* AWS CDK and Model Management both leverage AWS Systems Manager Agent (SSM) parameter store. Confirm that SSM is approved for use by your organization before beginning. If you're new to CDK, review the [AWS CDK Documentation](https://docs.aws.amazon.com/cdk/v2/guide/home.html) and consult with your AWS support team. ### Software - * AWS CLI installed and configured * Python 3.9 or later * Node.js 14 or later * Docker installed and running * Sufficient disk space for model downloads and conversions - - ## Getting Started - For detailed instructions on setting up, configuring, and deploying LISA, please refer to our separate documentation on installation and usage. - - [Deployment Guide](lib/docs/admin/getting-started.md) - [Configuration](lib/docs/config/configuration.md) - ## License - Although this repository is released under the Apache 2.0 license, when configured to use PGVector as a RAG store it uses the third party `psycopg2-binary` library. The `psycopg2-binary` project's licensing includes diff --git a/cypress/package-lock.json b/cypress/package-lock.json deleted file mode 100644 index 6f0ce46e1..000000000 --- a/cypress/package-lock.json +++ /dev/null @@ -1,2607 +0,0 @@ -{ - "name": "@awslabs/lisae2e", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "@awslabs/lisae2e", - "version": "1.0.0", - "devDependencies": { - "@types/node": "^22.14.1", - "cypress": "^14.3.0", - "lint-staged": "^15.5.1", - "lodash": "^4.17.21" - } - }, - "node_modules/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/@cypress/request": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.8.tgz", - "integrity": "sha512-h0NFgh1mJmm1nr4jCwkGHwKneVYKghUyWe6TMNrk0B9zsjAJxpg8C4/+BAcmLgCPa1vj1V8rNUaILl+zYRUWBQ==", - "dev": true, - "dependencies": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~4.0.0", - "http-signature": "~1.4.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "performance-now": "^2.1.0", - "qs": "6.14.0", - "safe-buffer": "^5.1.2", - "tough-cookie": "^5.0.0", - "tunnel-agent": "^0.6.0", - "uuid": "^8.3.2" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@cypress/xvfb": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz", - "integrity": "sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==", - "dev": true, - "dependencies": { - "debug": "^3.1.0", - "lodash.once": "^4.1.1" - } - }, - "node_modules/@cypress/xvfb/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/@types/node": { - "version": "22.14.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.1.tgz", - "integrity": "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==", - "dev": true, - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@types/sinonjs__fake-timers": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz", - "integrity": "sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==", - "dev": true - }, - "node_modules/@types/sizzle": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.9.tgz", - "integrity": "sha512-xzLEyKB50yqCUPUJkIsrVvoWNfFUbIZI+RspLWt8u+tIW/BetMBZtgV2LY/2o+tYH8dRvQ+eoPf3NdhQCcLE2w==", - "dev": true - }, - "node_modules/@types/yauzl": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", - "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", - "dev": true, - "optional": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "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, - "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, - "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, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/arch": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", - "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "dev": true, - "dependencies": { - "safer-buffer": "~2.1.0" - } - }, - "node_modules/assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "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 - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true - }, - "node_modules/at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/aws4": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", - "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", - "dev": true - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", - "dev": true, - "dependencies": { - "tweetnacl": "^0.14.3" - } - }, - "node_modules/blob-util": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz", - "integrity": "sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==", - "dev": true - }, - "node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true - }, - "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, - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/cachedir": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz", - "integrity": "sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", - "dev": true - }, - "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, - "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/chalk/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, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/check-more-types": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz", - "integrity": "sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/ci-info": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.1.0.tgz", - "integrity": "sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "engines": { - "node": ">=8" - } - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-table3": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", - "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0" - }, - "engines": { - "node": "10.* || >= 12.*" - }, - "optionalDependencies": { - "@colors/colors": "1.5.0" - } - }, - "node_modules/cli-truncate": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", - "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", - "dev": true, - "dependencies": { - "slice-ansi": "^3.0.0", - "string-width": "^4.2.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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, - "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 - }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/commander": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", - "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/common-tags": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", - "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", - "dev": true, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "dev": true - }, - "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, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/cypress": { - "version": "14.3.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-14.3.0.tgz", - "integrity": "sha512-rRfPl9Z0/CczuYybBEoLbDVuT1OGkhYaJ0+urRCshgiDRz6QnoA0KQIQnPx7MJ3zy+VCsbUU1pV74n+6cbJEdg==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "@cypress/request": "^3.0.8", - "@cypress/xvfb": "^1.2.4", - "@types/sinonjs__fake-timers": "8.1.1", - "@types/sizzle": "^2.3.2", - "arch": "^2.2.0", - "blob-util": "^2.0.2", - "bluebird": "^3.7.2", - "buffer": "^5.7.1", - "cachedir": "^2.3.0", - "chalk": "^4.1.0", - "check-more-types": "^2.24.0", - "ci-info": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-table3": "~0.6.5", - "commander": "^6.2.1", - "common-tags": "^1.8.0", - "dayjs": "^1.10.4", - "debug": "^4.3.4", - "enquirer": "^2.3.6", - "eventemitter2": "6.4.7", - "execa": "4.1.0", - "executable": "^4.1.1", - "extract-zip": "2.0.1", - "figures": "^3.2.0", - "fs-extra": "^9.1.0", - "getos": "^3.2.1", - "is-installed-globally": "~0.4.0", - "lazy-ass": "^1.6.0", - "listr2": "^3.8.3", - "lodash": "^4.17.21", - "log-symbols": "^4.0.0", - "minimist": "^1.2.8", - "ospath": "^1.2.2", - "pretty-bytes": "^5.6.0", - "process": "^0.11.10", - "proxy-from-env": "1.0.0", - "request-progress": "^3.0.0", - "semver": "^7.7.1", - "supports-color": "^8.1.1", - "tmp": "~0.2.3", - "tree-kill": "1.2.2", - "untildify": "^4.0.0", - "yauzl": "^2.10.0" - }, - "bin": { - "cypress": "bin/cypress" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - } - }, - "node_modules/dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", - "dev": true, - "dependencies": { - "assert-plus": "^1.0.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/dayjs": { - "version": "1.11.13", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", - "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", - "dev": true - }, - "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dev": true, - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", - "dev": true, - "dependencies": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "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 - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/enquirer": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", - "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", - "dev": true, - "dependencies": { - "ansi-colors": "^4.1.1", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/environment": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", - "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/eventemitter2": { - "version": "6.4.7", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.7.tgz", - "integrity": "sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==", - "dev": true - }, - "node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", - "dev": true - }, - "node_modules/execa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/executable": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", - "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", - "dev": true, - "dependencies": { - "pify": "^2.2.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "node_modules/extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" - }, - "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" - } - }, - "node_modules/extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", - "dev": true, - "engines": [ - "node >=0.6.0" - ] - }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "dev": true, - "dependencies": { - "pend": "~1.2.0" - } - }, - "node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/form-data": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", - "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dev": true, - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "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, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-east-asian-width": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", - "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/getos": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/getos/-/getos-3.2.1.tgz", - "integrity": "sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==", - "dev": true, - "dependencies": { - "async": "^3.2.0" - } - }, - "node_modules/getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", - "dev": true, - "dependencies": { - "assert-plus": "^1.0.0" - } - }, - "node_modules/global-dirs": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", - "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", - "dev": true, - "dependencies": { - "ini": "2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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 - }, - "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, - "engines": { - "node": ">=8" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/http-signature": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz", - "integrity": "sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==", - "dev": true, - "dependencies": { - "assert-plus": "^1.0.0", - "jsprim": "^2.0.2", - "sshpk": "^1.18.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/human-signals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "dev": true, - "engines": { - "node": ">=8.12.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ini": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", - "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "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, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-installed-globally": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", - "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", - "dev": true, - "dependencies": { - "global-dirs": "^3.0.0", - "is-path-inside": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "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, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "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 - }, - "node_modules/isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", - "dev": true - }, - "node_modules/jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "dev": true - }, - "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "dev": true - }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true - }, - "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/jsprim": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", - "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "dependencies": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - } - }, - "node_modules/lazy-ass": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz", - "integrity": "sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==", - "dev": true, - "engines": { - "node": "> 0.8" - } - }, - "node_modules/lilconfig": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", - "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", - "dev": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" - } - }, - "node_modules/lint-staged": { - "version": "15.5.1", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.5.1.tgz", - "integrity": "sha512-6m7u8mue4Xn6wK6gZvSCQwBvMBR36xfY24nF5bMTf2MHDYG6S3yhJuOgdYVw99hsjyDt2d4z168b3naI8+NWtQ==", - "dev": true, - "dependencies": { - "chalk": "^5.4.1", - "commander": "^13.1.0", - "debug": "^4.4.0", - "execa": "^8.0.1", - "lilconfig": "^3.1.3", - "listr2": "^8.2.5", - "micromatch": "^4.0.8", - "pidtree": "^0.6.0", - "string-argv": "^0.3.2", - "yaml": "^2.7.0" - }, - "bin": { - "lint-staged": "bin/lint-staged.js" - }, - "engines": { - "node": ">=18.12.0" - }, - "funding": { - "url": "https://opencollective.com/lint-staged" - } - }, - "node_modules/lint-staged/node_modules/ansi-escapes": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", - "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", - "dev": true, - "dependencies": { - "environment": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/lint-staged/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/lint-staged/node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", - "dev": true, - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/lint-staged/node_modules/cli-cursor": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", - "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", - "dev": true, - "dependencies": { - "restore-cursor": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/cli-truncate": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", - "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", - "dev": true, - "dependencies": { - "slice-ansi": "^5.0.0", - "string-width": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/commander": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", - "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", - "dev": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/lint-staged/node_modules/emoji-regex": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", - "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", - "dev": true - }, - "node_modules/lint-staged/node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/lint-staged/node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", - "dev": true, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", - "dev": true, - "engines": { - "node": ">=16.17.0" - } - }, - "node_modules/lint-staged/node_modules/is-fullwidth-code-point": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", - "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/listr2": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.3.2.tgz", - "integrity": "sha512-vsBzcU4oE+v0lj4FhVLzr9dBTv4/fHIa57l+GCwovP8MoFNZJTOhGU8PXd4v2VJCbECAaijBiHntiekFMLvo0g==", - "dev": true, - "dependencies": { - "cli-truncate": "^4.0.0", - "colorette": "^2.0.20", - "eventemitter3": "^5.0.1", - "log-update": "^6.1.0", - "rfdc": "^1.4.1", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/lint-staged/node_modules/log-update": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", - "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", - "dev": true, - "dependencies": { - "ansi-escapes": "^7.0.0", - "cli-cursor": "^5.0.0", - "slice-ansi": "^7.1.0", - "strip-ansi": "^7.1.0", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/log-update/node_modules/is-fullwidth-code-point": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", - "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", - "dev": true, - "dependencies": { - "get-east-asian-width": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/log-update/node_modules/slice-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", - "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", - "dev": true, - "dependencies": { - "ansi-styles": "^6.2.1", - "is-fullwidth-code-point": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/lint-staged/node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/npm-run-path": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", - "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", - "dev": true, - "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "dev": true, - "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/restore-cursor": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", - "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", - "dev": true, - "dependencies": { - "onetime": "^7.0.0", - "signal-exit": "^4.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/restore-cursor/node_modules/onetime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", - "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", - "dev": true, - "dependencies": { - "mimic-function": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/lint-staged/node_modules/slice-ansi": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", - "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^6.0.0", - "is-fullwidth-code-point": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/lint-staged/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/lint-staged/node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/wrap-ansi": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", - "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/listr2": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.14.0.tgz", - "integrity": "sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g==", - "dev": true, - "dependencies": { - "cli-truncate": "^2.1.0", - "colorette": "^2.0.16", - "log-update": "^4.0.0", - "p-map": "^4.0.0", - "rfdc": "^1.3.0", - "rxjs": "^7.5.1", - "through": "^2.3.8", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "enquirer": ">= 2.3.0 < 3" - }, - "peerDependenciesMeta": { - "enquirer": { - "optional": true - } - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "node_modules/lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", - "dev": true - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", - "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", - "dev": true, - "dependencies": { - "ansi-escapes": "^4.3.0", - "cli-cursor": "^3.1.0", - "slice-ansi": "^4.0.0", - "wrap-ansi": "^6.2.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "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/log-update/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "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 - }, - "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, - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.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, - "engines": { - "node": ">=6" - } - }, - "node_modules/mimic-function": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", - "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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 - }, - "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, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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, - "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, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ospath": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/ospath/-/ospath-1.2.2.tgz", - "integrity": "sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==", - "dev": true - }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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, - "engines": { - "node": ">=8" - } - }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true - }, - "node_modules/performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", - "dev": true - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pidtree": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", - "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", - "dev": true, - "bin": { - "pidtree": "bin/pidtree.js" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pretty-bytes": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", - "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "dev": true, - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/proxy-from-env": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", - "integrity": "sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==", - "dev": true - }, - "node_modules/pump": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", - "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", - "dev": true, - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/request-progress": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", - "integrity": "sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg==", - "dev": true, - "dependencies": { - "throttleit": "^1.0.0" - } - }, - "node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/rfdc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "dev": true - }, - "node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "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, - "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, - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dev": true, - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "dev": true, - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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 - }, - "node_modules/slice-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", - "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/sshpk": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", - "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", - "dev": true, - "dependencies": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - }, - "bin": { - "sshpk-conv": "bin/sshpk-conv", - "sshpk-sign": "bin/sshpk-sign", - "sshpk-verify": "bin/sshpk-verify" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/string-argv": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", - "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", - "dev": true, - "engines": { - "node": ">=0.6.19" - } - }, - "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, - "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, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "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, - "engines": { - "node": ">=6" - } - }, - "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, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/throttleit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.1.tgz", - "integrity": "sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true - }, - "node_modules/tldts": { - "version": "6.1.86", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", - "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", - "dev": true, - "dependencies": { - "tldts-core": "^6.1.86" - }, - "bin": { - "tldts": "bin/cli.js" - } - }, - "node_modules/tldts-core": { - "version": "6.1.86", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", - "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", - "dev": true - }, - "node_modules/tmp": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", - "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", - "dev": true, - "engines": { - "node": ">=14.14" - } - }, - "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, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/tough-cookie": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", - "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", - "dev": true, - "dependencies": { - "tldts": "^6.1.32" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "dev": true, - "bin": { - "tree-kill": "cli.js" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true - }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", - "dev": true - }, - "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, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true - }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/untildify": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", - "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "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, - "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, - "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 - }, - "node_modules/yaml": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz", - "integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==", - "dev": true, - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "dev": true, - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - } - } -} diff --git a/lambda/repository/lambda_functions.py b/lambda/repository/lambda_functions.py index c5ea1888f..912a6128b 100644 --- a/lambda/repository/lambda_functions.py +++ b/lambda/repository/lambda_functions.py @@ -28,6 +28,7 @@ from repository.ingestion_service import DocumentIngestionService from repository.rag_document_repo import RagDocumentRepository from repository.vector_store_repo import VectorStoreRepository +from utilities.bedrock_kb import is_bedrock_kb_repository, retrieve_documents from utilities.common_functions import ( admin_only, api_wrapper, @@ -50,6 +51,7 @@ iam_client = boto3.client("iam", region_name, config=retry_config) step_functions_client = boto3.client("stepfunctions", region_name, config=retry_config) ddb_client = boto3.client("dynamodb", region_name, config=retry_config) +bedrock_client = boto3.client("bedrock-agent-runtime", region_name, config=retry_config) s3 = session.client( "s3", region_name, @@ -311,13 +313,33 @@ def similarity_search(event: dict, context: dict) -> Dict[str, Any]: id_token = get_id_token(event) - embeddings = _get_embeddings(model_name=model_name, id_token=id_token) - vs = get_vector_store_client(repository_id, index=model_name, embeddings=embeddings) - docs = vs.similarity_search( - query, - k=top_k, - ) - doc_content = [{"Document": {"page_content": doc.page_content, "metadata": doc.metadata}} for doc in docs] + docs: List[Dict[str, Any]] = [] + if is_bedrock_kb_repository(repository): + docs = retrieve_documents( + bedrock_runtime_client=bedrock_client, + repository=repository, + query=query, + top_k=int(top_k), + repository_id=repository_id, + ) + else: + embeddings = _get_embeddings(model_name=model_name, id_token=id_token) + vs = get_vector_store_client(repository_id, index=model_name, embeddings=embeddings) + results = vs.similarity_search( + query, + k=top_k, + ) + docs = [{"page_content": r.page_content, "metadata": r.metadata} for r in results] + + doc_content = [ + { + "Document": { + "page_content": doc.get("page_content", ""), + "metadata": doc.get("metadata", {}), + } + } + for doc in docs + ] doc_return = {"docs": doc_content} logger.info(f"Returning: {doc_return}") @@ -486,9 +508,9 @@ def download_document(event: dict, context: dict) -> str: document_id = path_params.get("documentId") _ensure_repository_access(event, vs_repo.find_repository_by_id(repository_id)) - doc = doc_repo.find_by_id(repository_id=repository_id, document_id=document_id) + doc = doc_repo.find_by_id(document_id=document_id) - source = doc.get("source") + source = doc.source bucket, key = source.replace("s3://", "").split("/", 1) url: str = s3.generate_presigned_url( diff --git a/lambda/repository/pipeline_delete_documents.py b/lambda/repository/pipeline_delete_documents.py index 9a031fe13..452ea7835 100644 --- a/lambda/repository/pipeline_delete_documents.py +++ b/lambda/repository/pipeline_delete_documents.py @@ -20,16 +20,20 @@ from models.domain_objects import IngestionJob, IngestionStatus, IngestionType from repository.ingestion_job_repo import IngestionJobRepository from repository.pipeline_ingest_documents import remove_document_from_vectorstore +from repository.vector_store_repo import VectorStoreRepository +from utilities.bedrock_kb import delete_document_from_kb, is_bedrock_kb_repository from utilities.common_functions import retry_config from .lambda_functions import DocumentIngestionService, RagDocumentRepository ingestion_service = DocumentIngestionService() ingestion_job_repository = IngestionJobRepository() +vs_repo = VectorStoreRepository() logger = logging.getLogger(__name__) s3 = boto3.client("s3", region_name=os.environ["AWS_REGION"], config=retry_config) +bedrock_agent = boto3.client("bedrock-agent", region_name=os.environ["AWS_REGION"], config=retry_config) rag_document_repository = RagDocumentRepository(os.environ["RAG_DOCUMENT_TABLE"], os.environ["RAG_SUB_DOCUMENT_TABLE"]) @@ -42,7 +46,16 @@ def pipeline_delete(job: IngestionJob) -> None: if rag_document: # Actually remove from vector store - remove_document_from_vectorstore(rag_document) + repository = vs_repo.find_repository_by_id(job.repository_id) + if is_bedrock_kb_repository(repository): + delete_document_from_kb( + s3_client=s3, + bedrock_agent_client=bedrock_agent, + job=job, + repository=repository, + ) + else: + remove_document_from_vectorstore(rag_document) # Remove from DDB rag_document_repository.delete_by_id(rag_document.document_id) diff --git a/lambda/repository/pipeline_ingest_documents.py b/lambda/repository/pipeline_ingest_documents.py index a2ff9828c..d53a7b0b6 100644 --- a/lambda/repository/pipeline_ingest_documents.py +++ b/lambda/repository/pipeline_ingest_documents.py @@ -23,6 +23,8 @@ from models.domain_objects import FixedChunkingStrategy, IngestionJob, IngestionStatus, IngestionType, RagDocument from repository.ingestion_job_repo import IngestionJobRepository from repository.lambda_functions import RagDocumentRepository +from repository.vector_store_repo import VectorStoreRepository +from utilities.bedrock_kb import ingest_document_to_kb, is_bedrock_kb_repository from utilities.common_functions import get_username, retry_config from utilities.file_processing import generate_chunks from utilities.vector_store import get_vector_store_client @@ -33,10 +35,12 @@ ingestion_job_table = dynamodb.Table(os.environ["LISA_INGESTION_JOB_TABLE_NAME"]) ingestion_service = DocumentIngestionService() ingestion_job_repository = IngestionJobRepository() +vs_repo = VectorStoreRepository() logger = logging.getLogger(__name__) session = boto3.Session() s3 = boto3.client("s3", region_name=os.environ["AWS_REGION"], config=retry_config) +bedrock_agent = boto3.client("bedrock-agent", region_name=os.environ["AWS_REGION"], config=retry_config) ssm_client = boto3.client("ssm", region_name=os.environ["AWS_REGION"], config=retry_config) rag_document_repository = RagDocumentRepository(os.environ["RAG_DOCUMENT_TABLE"], os.environ["RAG_SUB_DOCUMENT_TABLE"]) @@ -44,9 +48,19 @@ def pipeline_ingest(job: IngestionJob) -> None: try: # chunk and save chunks in vector store - documents = generate_chunks(job) - texts, metadatas = prepare_chunks(documents, job.repository_id) - all_ids = store_chunks_in_vectorstore(texts, metadatas, job.repository_id, job.collection_id) + repository = vs_repo.find_repository_by_id(job.repository_id) + all_ids = [] + if is_bedrock_kb_repository(repository): + ingest_document_to_kb( + s3_client=s3, + bedrock_agent_client=bedrock_agent, + job=job, + repository=repository, + ) + else: + documents = generate_chunks(job) + texts, metadatas = prepare_chunks(documents, job.repository_id) + all_ids = store_chunks_in_vectorstore(texts, metadatas, job.repository_id, job.collection_id) # remove old for rag_document in rag_document_repository.find_by_source( @@ -56,7 +70,8 @@ def pipeline_ingest(job: IngestionJob) -> None: if prev_job: ingestion_job_repository.update_status(prev_job, IngestionStatus.DELETE_IN_PROGRESS) - remove_document_from_vectorstore(rag_document) + if not is_bedrock_kb_repository(repository): + remove_document_from_vectorstore(rag_document) rag_document_repository.delete_by_id(rag_document.document_id) if prev_job: diff --git a/lambda/repository/state_machine/cleanup_repo_docs.py b/lambda/repository/state_machine/cleanup_repo_docs.py index 2c302bddb..23f55ac8b 100644 --- a/lambda/repository/state_machine/cleanup_repo_docs.py +++ b/lambda/repository/state_machine/cleanup_repo_docs.py @@ -16,6 +16,7 @@ import os from typing import Any, Dict +from pydantic import BaseModel from repository.rag_document_repo import RagDocumentRepository logger = logging.getLogger(__name__) @@ -42,4 +43,11 @@ def lambda_handler(event: Dict[str, Any], context: Any) -> Dict[str, Any] | Any: doc_repo.delete_s3_docs(repository_id=repository_id, docs=docs) - return {"repositoryId": repository_id, "stackName": stack_name, "documents": docs, "lastEvaluated": last_evaluated} + # Ensure JSON-serializable payload for Step Functions when Pydantic models are provided + serializable_docs = [doc.model_dump() if isinstance(doc, BaseModel) else doc for doc in docs] + return { + "repositoryId": repository_id, + "stackName": stack_name, + "documents": serializable_docs, + "lastEvaluated": last_evaluated, + } diff --git a/lambda/utilities/bedrock_kb.py b/lambda/utilities/bedrock_kb.py new file mode 100644 index 000000000..5943c55d0 --- /dev/null +++ b/lambda/utilities/bedrock_kb.py @@ -0,0 +1,116 @@ +# 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. + +"""Utilities for handling Bedrock Knowledge Base specific operations. + +This module centralizes logic related to repositories of type +"bedrock_knowledge_base" so that call sites can remain concise. +""" + +from __future__ import annotations + +import os +from typing import Any, Dict, List + +BEDROCK_KB_TYPE = "bedrock_knowledge_base" + + +def is_bedrock_kb_repository(repository: Dict[str, Any]) -> bool: + """Return True if the repository is a Bedrock Knowledge Base.""" + return repository.get("type", "") == BEDROCK_KB_TYPE + + +def retrieve_documents( + bedrock_runtime_client: Any, + repository: Dict[str, Any], + query: str, + top_k: int, + repository_id: str, +) -> List[Dict[str, Any]]: + """Retrieve documents from Bedrock Knowledge Base. + + Args: + bedrock_runtime_client: boto3 bedrock-agent-runtime client + repository: Repository configuration dictionary + query: Text query to search + top_k: Number of results to return + repository_id: Repository identifier to include in metadata + + Returns: + List of documents in the format expected by callers + """ + bedrock_config = repository.get("bedrockKnowledgeBaseConfig", {}) + + response = bedrock_runtime_client.retrieve( + knowledgeBaseId=bedrock_config.get("bedrockKnowledgeBaseId", None), + retrievalQuery={"text": query}, + retrievalConfiguration={"vectorSearchConfiguration": {"numberOfResults": int(top_k)}}, + ) + + docs: List[Dict[str, Any]] = [] + for doc in response.get("retrievalResults", []): + uri = (doc.get("location", {}) or {}).get("s3Location", {}).get("uri") + name = uri.split("/")[-1] if uri else None + docs.append( + { + "page_content": (doc.get("content", {}) or {}).get("text", ""), + "metadata": { + "source": uri, + "name": name, + "repository_id": repository_id, + }, + } + ) + + return docs + + +def ingest_document_to_kb( + s3_client: Any, + bedrock_agent_client: Any, + job: Any, + repository: Dict[str, Any], +) -> None: + """Copy the source object into the KB datasource bucket and trigger ingestion.""" + bedrock_config = repository.get("bedrockKnowledgeBaseConfig", {}) + + source_bucket = job.s3_path.split("/")[2] + s3_client.copy_object( + CopySource={"Bucket": source_bucket, "Key": job.s3_path.split(source_bucket + "/")[1]}, + Bucket=bedrock_config.get("bedrockKnowledgeDatasourceS3Bucket", None), + Key=os.path.basename(job.s3_path), + ) + bedrock_agent_client.start_ingestion_job( + knowledgeBaseId=bedrock_config.get("bedrockKnowledgeBaseId", None), + dataSourceId=bedrock_config.get("bedrockKnowledgeDatasourceId", None), + ) + + +def delete_document_from_kb( + s3_client: Any, + bedrock_agent_client: Any, + job: Any, + repository: Dict[str, Any], +) -> None: + """Remove the source object from the KB datasource bucket and re-sync the KB.""" + bedrock_config = repository.get("bedrockKnowledgeBaseConfig", {}) + + s3_client.delete_object( + Bucket=bedrock_config.get("bedrockKnowledgeDatasourceS3Bucket", None), + Key=os.path.basename(job.s3_path), + ) + bedrock_agent_client.start_ingestion_job( + knowledgeBaseId=bedrock_config.get("bedrockKnowledgeBaseId", None), + dataSourceId=bedrock_config.get("bedrockKnowledgeDatasourceId", None), + ) diff --git a/lib/core/iam/rag.json b/lib/core/iam/rag.json index 18a1d21ff..3b99a8bec 100644 --- a/lib/core/iam/rag.json +++ b/lib/core/iam/rag.json @@ -53,6 +53,22 @@ "iam:GetServerCertificate" ], "Resource": "arn:${AWS::Partition}:iam::${AWS::AccountId}:server-certificate/*" - } + }, + { + "Effect": "Allow", + "Action": [ + "s3:PutObject", + "s3:DeleteObject" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "bedrock:StartIngestionJob", + "bedrock:Retrieve" + ], + "Resource": "*" + } ] } diff --git a/lib/docs/package.json b/lib/docs/package.json index fc975b442..bbb871314 100644 --- a/lib/docs/package.json +++ b/lib/docs/package.json @@ -15,6 +15,6 @@ "author": "", "license": "Apache-2.0", "devDependencies": { - "vitepress": "^1.6.3" + "vitepress": "^1.6.4" } } diff --git a/lib/rag/vector-store/state_machine/create-store.ts b/lib/rag/vector-store/state_machine/create-store.ts index 02765c19c..8b2d30131 100644 --- a/lib/rag/vector-store/state_machine/create-store.ts +++ b/lib/rag/vector-store/state_machine/create-store.ts @@ -53,6 +53,8 @@ export class CreateStoreStateMachine extends Construct { resultPath: '$.dynamoResult', }); + const createVectorStoreInfraChoice = new sfn.Choice(this, 'CreateVectorStoreInfraChoice'); + // Task to invoke a Lambda function to deploy the vector store const deployVectorStore = new tasks.LambdaInvoke(this, 'DeployVectorStore', { lambdaFunction: lambda.Function.fromFunctionArn(this, 'VectorStoreDeployer', vectorStoreDeployerFnArn), @@ -85,6 +87,17 @@ export class CreateStoreStateMachine extends Construct { time: sfn.WaitTime.duration(Duration.seconds(30)), }); + // Task to update the status of the vector store entry to 'COMPLETED' on successful deployment + const updateBedrockKBSuccess = new tasks.DynamoUpdateItem(this, 'UpdateBedrockKBSuccess', { + table: vectorStoreConfigTable, + key: { repositoryId: tasks.DynamoAttributeValue.fromString(sfn.JsonPath.stringAt('$.body.ragConfig.repositoryId')) }, + updateExpression: 'SET #status = :status', + expressionAttributeNames: { '#status': 'status' }, + expressionAttributeValues: { + ':status': tasks.DynamoAttributeValue.fromString('CREATE_COMPLETE') + }, + }); + // Task to update the status of the vector store entry to 'COMPLETED' on successful deployment const updateSuccessStatus = new tasks.DynamoUpdateItem(this, 'UpdateSuccessStatus', { table: vectorStoreConfigTable, @@ -93,7 +106,7 @@ export class CreateStoreStateMachine extends Construct { expressionAttributeNames: { '#status': 'status', '#stackName': 'stackName' }, expressionAttributeValues: { ':status': tasks.DynamoAttributeValue.fromString('CREATE_COMPLETE'), - ':stackName': tasks.DynamoAttributeValue.fromString(sfn.JsonPath.stringAt('$.deployResult.stackName')) + ':stackName': tasks.DynamoAttributeValue.fromString(sfn.JsonPath.stringAt('$.deployResult.stackName') ?? '') }, }); @@ -111,34 +124,37 @@ export class CreateStoreStateMachine extends Construct { // Define the sequence of tasks and conditions in the state machine const definition = createVectorStoreEntry - .next(deployVectorStore.addCatch(updateFailureStatus)) - .next( - checkDeploymentStatus.next( - new sfn.Choice(this, 'DeploymentComplete?') - .when( - sfn.Condition.and( - sfn.Condition.isPresent('$.deployResult.status'), - sfn.Condition.or( - sfn.Condition.stringEquals('$.deployResult.status', 'CREATE_IN_PROGRESS'), - sfn.Condition.stringEquals('$.deployResult.status', 'UPDATE_IN_PROGRESS'), - sfn.Condition.stringEquals('$.deployResult.status', 'UPDATE_COMPLETE_CLEANUP_IN_PROGRESS'), - ), - ), - wait.next(checkDeploymentStatus) - ) - .when( - sfn.Condition.and( - sfn.Condition.isPresent('$.deployResult.status'), - sfn.Condition.or( - sfn.Condition.stringEquals('$.deployResult.status', 'CREATE_COMPLETE'), - sfn.Condition.stringEquals('$.deployResult.status', 'UPDATE_COMPLETE'), - ), - ), - updateSuccessStatus + .next(createVectorStoreInfraChoice + .when(sfn.Condition.and(sfn.Condition.stringEquals('$.body.ragConfig.type', 'bedrock_knowledge_base'), + sfn.Condition.isNotPresent('$.body.ragConfig.pipelines[0]')), updateBedrockKBSuccess) + .otherwise(deployVectorStore.addCatch(updateFailureStatus) + .next( + checkDeploymentStatus.next( + new sfn.Choice(this, 'DeploymentComplete?') + .when( + sfn.Condition.and( + sfn.Condition.isPresent('$.deployResult.status'), + sfn.Condition.or( + sfn.Condition.stringEquals('$.deployResult.status', 'CREATE_IN_PROGRESS'), + sfn.Condition.stringEquals('$.deployResult.status', 'UPDATE_IN_PROGRESS'), + sfn.Condition.stringEquals('$.deployResult.status', 'UPDATE_COMPLETE_CLEANUP_IN_PROGRESS'), + ), + ), + wait.next(checkDeploymentStatus) + ) + .when( + sfn.Condition.and( + sfn.Condition.isPresent('$.deployResult.status'), + sfn.Condition.or( + sfn.Condition.stringEquals('$.deployResult.status', 'CREATE_COMPLETE'), + sfn.Condition.stringEquals('$.deployResult.status', 'UPDATE_COMPLETE'), + ), + ), + updateSuccessStatus + ) + .otherwise(updateFailureStatus) ) - .otherwise(updateFailureStatus) - ) - ); + ))); // Create a new state machine using the definition and roles specified this.stateMachine = new sfn.StateMachine(this, 'CreateStoreStateMachine', { diff --git a/lib/rag/vector-store/state_machine/delete-store.ts b/lib/rag/vector-store/state_machine/delete-store.ts index 9ca543530..38135d15c 100644 --- a/lib/rag/vector-store/state_machine/delete-store.ts +++ b/lib/rag/vector-store/state_machine/delete-store.ts @@ -125,6 +125,17 @@ export class DeleteStoreStateMachine extends Construct { resultPath: '$.updateDynamoDbResult', }); + const handleCleanupBedrockKnowledgeBase = new Choice(this, 'BedrockKnowledgeBase') + .when(sfn.Condition.and(sfn.Condition.stringEquals('$.ddbResult.Item.config.M.type.S', 'bedrock_knowledge_base'), + sfn.Condition.isNull('$.stackName')), deleteDynamoDbEntry) + .otherwise(deleteStack); + + const getRepoFromDdb = new tasks.DynamoGetItem(this, 'GetRepoFromDdb', { + table: ragVectorStoreTable, + key: { repositoryId: tasks.DynamoAttributeValue.fromString(sfn.JsonPath.stringAt('$.repositoryId')) }, + resultPath: '$.ddbResult', + }).next(handleCleanupBedrockKnowledgeBase); + const lambdaPath = config.lambdaPath || LAMBDA_PATH; const cleanupDocsFunc = new Function(this, 'CleanupRepositoryDocsFunc', { runtime: getDefaultRuntime(), @@ -148,7 +159,7 @@ export class DeleteStoreStateMachine extends Construct { }), outputPath: OUTPUT_PATH, })) - .otherwise(deleteStack); + .otherwise(getRepoFromDdb); const cleanupDocs = new LambdaInvoke(this, 'CleanupRepositoryDocs', { lambdaFunction: cleanupDocsFunc, @@ -161,7 +172,7 @@ export class DeleteStoreStateMachine extends Construct { const shouldSkipCleanup = new Choice(this, 'ShouldSkipCleanup') .when(Condition.and(Condition.isPresent('$.skipDocumentRemoval'), Condition.booleanEquals('$.skipDocumentRemoval', true)), - deleteStack) + handleCleanupBedrockKnowledgeBase) .otherwise(cleanupDocs.next(hasMoreDocs)); deleteStack.next(checkStackStatus.addCatch(deleteDynamoDbEntry, { diff --git a/lib/schema/configSchema.ts b/lib/schema/configSchema.ts index b175ebc26..a7b91d1b8 100644 --- a/lib/schema/configSchema.ts +++ b/lib/schema/configSchema.ts @@ -342,6 +342,13 @@ export class Ec2Metadata { maxThroughput: 400, vCpus: 96, }, + 'p4de.24xlarge': { + memory: 1152 * 1000, + gpuCount: 8, + nvmePath: '/dev/nvme1n1', + maxThroughput: 400, + vCpus: 96, + }, 'p5.48xlarge': { memory: 2000 * 1000, gpuCount: 8, diff --git a/lib/schema/ragSchema.ts b/lib/schema/ragSchema.ts index fe16d27da..c4986ba59 100644 --- a/lib/schema/ragSchema.ts +++ b/lib/schema/ragSchema.ts @@ -22,8 +22,17 @@ import { EbsDeviceVolumeType } from './cdk'; export enum RagRepositoryType { OPENSEARCH = 'opensearch', PGVECTOR = 'pgvector', + BEDROCK_KNOWLEDGE_BASE = 'bedrock_knowledge_base', } +export const BedrockKnowledgeBaseInstanceConfig = z.object({ + bedrockKnowledgeBaseName: z.string().describe('The name of the Bedrock Knowledge Base.'), + bedrockKnowledgeBaseId: z.string().describe('The id of the Bedrock Knowledge Base.'), + bedrockKnowledgeDatasourceName: z.string().describe('The name of the Bedrock Knowledge Datasource.'), + bedrockKnowledgeDatasourceId: z.string().describe('The id of the Bedrock Knowledge Datasource.'), + bedrockKnowledgeDatasourceS3Bucket: z.string().describe('The S3 bucket of the Bedrock Knowledge Base.'), +}); + export const OpenSearchNewClusterConfig = z.object({ dataNodes: z.number().min(1).default(2).describe('The number of data nodes (instances) to use in the Amazon OpenSearch Service domain.'), dataNodeInstanceType: z.string().default('r7g.large.search').describe('The instance type for your data nodes'), @@ -78,6 +87,8 @@ export const RdsInstanceConfig = z.object({ export type RdsConfig = z.infer; +export type BedrockKnowledgeBaseConfig = z.infer; + export const RagRepositoryConfigSchema = z .object({ repositoryId: z.string() @@ -89,6 +100,7 @@ export const RagRepositoryConfigSchema = z type: z.nativeEnum(RagRepositoryType).describe('The vector store designated for this repository.'), opensearchConfig: z.union([OpenSearchExistingClusterConfig, OpenSearchNewClusterConfig]).optional(), rdsConfig: RdsInstanceConfig.optional(), + bedrockKnowledgeBaseConfig: BedrockKnowledgeBaseInstanceConfig.optional(), pipelines: z.array(RagRepositoryPipeline).optional().default([]).describe('Rag ingestion pipeline for automated inclusion into a vector store from S3'), allowedGroups: z.array(z.string().nonempty()).optional().default([]).describe('The groups provided by the Identity Provider that have access to this repository. If no groups are specified, access is granted to everyone.'), }) diff --git a/lib/user-interface/react/package.json b/lib/user-interface/react/package.json index 89cce429f..5cfdb8dac 100644 --- a/lib/user-interface/react/package.json +++ b/lib/user-interface/react/package.json @@ -49,7 +49,7 @@ "typescript": "~5.1.6", "unraw": "^3.0.0", "use-mcp": "^0.0.18", - "vitepress": "^1.6.3" + "vitepress": "^1.6.4" }, "devDependencies": { "@types/markdown-it": "^14.1.2", diff --git a/lib/user-interface/react/src/components/chatbot/components/Message.tsx b/lib/user-interface/react/src/components/chatbot/components/Message.tsx index 2ad53a4a1..3fdffff26 100644 --- a/lib/user-interface/react/src/components/chatbot/components/Message.tsx +++ b/lib/user-interface/react/src/components/chatbot/components/Message.tsx @@ -252,7 +252,7 @@ export default function Message ({ message, isRunning, showMetadata, isStreaming )} - {message?.type === 'ai' && !isRunning && !callingToolName && ( + {message?.type === 'ai' && !isRunning && !callingToolName && message?.content && ( , noti useEffect(() => { if (!isDeleteLoading && isDeleteSuccess && selectedRepo) { - notificationService.generateNotification(`Successfully deleted repository: ${selectedRepo.repositoryId}`, 'success'); + notificationService.generateNotification(`Successfully deleted repository: ${selectedRepo?.repositoryId}`, 'success'); setSelectedItems([]); + setDisabledModel(false); + setShowModal(false); } else if (!isDeleteLoading && isDeleteError && selectedRepo) { notificationService.generateNotification(`Error deleting repository: ${deleteError.data?.message ?? deleteError.data}`, 'error'); setSelectedItems([]); + setDisabledModel(false); + setShowModal(false); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [isDeleteSuccess, isDeleteError, deleteError, isDeleteLoading]); useEffect(() => { if (!isUpdating && isUpdateSuccess && selectedRepo) { - notificationService.generateNotification(`Successfully updated repository: ${selectedRepo.repositoryId}`, 'success'); + notificationService.generateNotification(`Successfully updated repository: ${selectedRepo?.repositoryId}`, 'success'); setSelectedItems([]); } else if (!isUpdating && isUpdateError && selectedRepo) { notificationService.generateNotification(`Error updating repository: ${updateError.data?.message ?? updateError.data}`, 'error'); @@ -113,14 +117,14 @@ function RepositoryActionButton (dispatch: ThunkDispatch, noti dispatch(setConfirmationModal({ action: 'Delete', resourceName: 'Repository', - onConfirm: () => deleteMutation(selectedRepo.repositoryId), + onConfirm: () => deleteMutation(selectedRepo?.repositoryId), onDismiss: () => { setDisabledModel(false); setShowModal(false); }, description: ( -

This will delete the following repository: {selectedRepo.repositoryId}.

+

This will delete the following repository: {selectedRepo?.repositoryId}.

{selectedRepo?.legacy && & BedrockKnowledgeBaseConfigProps): ReactElement { + const { item, touchFields, setFields, formErrors, isEdit } = props; + + return ( + Bedrock Knowledge Base Config}> + + + touchFields(['bedrockKnowledgeBaseConfig.bedrockKnowledgeBaseName'])} + onChange={({ detail }) => setFields({ 'bedrockKnowledgeBaseConfig.bedrockKnowledgeBaseName': detail.value })} + placeholder='Knowledge Base Name' disabled={isEdit} /> + + + touchFields(['bedrockKnowledgeBaseConfig.bedrockKnowledgeBaseId'])} + onChange={({ detail }) => setFields({ 'bedrockKnowledgeBaseConfig.bedrockKnowledgeBaseId': detail.value })} + placeholder='Knowledge Base ID' disabled={isEdit} /> + + + touchFields(['bedrockKnowledgeBaseConfig.bedrockKnowledgeDatasourceName'])} + onChange={({ detail }) => setFields({ 'bedrockKnowledgeBaseConfig.bedrockKnowledgeDatasourceName': detail.value })} + placeholder='Knowledge Base Datasource Name' disabled={isEdit} /> + + + touchFields(['bedrockKnowledgeBaseConfig.bedrockKnowledgeDatasourceId'])} + onChange={({ detail }) => setFields({ 'bedrockKnowledgeBaseConfig.bedrockKnowledgeDatasourceId': detail.value })} + placeholder='Knowledge Base Datasource ID' disabled={isEdit} /> + + + touchFields(['bedrockKnowledgeBaseConfig.bedrockKnowledgeDatasourceS3Bucket'])} + onChange={({ detail }) => setFields({ 'bedrockKnowledgeBaseConfig.bedrockKnowledgeDatasourceS3Bucket': detail.value })} + placeholder='Knowledge Base Datasource S3 Bucket' disabled={isEdit} /> + + + + ); +} diff --git a/lib/user-interface/react/src/components/configuration/createRepository/RepositoryConfigForm.tsx b/lib/user-interface/react/src/components/configuration/createRepository/RepositoryConfigForm.tsx index a3adeb0a2..c38151b1b 100644 --- a/lib/user-interface/react/src/components/configuration/createRepository/RepositoryConfigForm.tsx +++ b/lib/user-interface/react/src/components/configuration/createRepository/RepositoryConfigForm.tsx @@ -26,11 +26,13 @@ import { RagRepositoryConfigSchema, RagRepositoryType, RdsInstanceConfig, + BedrockKnowledgeBaseInstanceConfig } from '#root/lib/schema'; import { getDefaults } from '#root/lib/schema/zodUtil'; import { ArrayInputField } from '../../../shared/form/array-input'; import { RdsConfigForm } from './RdsConfigForm'; import { OpenSearchConfigForm } from './OpenSearchConfigForm'; +import { BedrockKnowledgeBaseConfigForm } from './BedrockKnowledgeBaseConfigForm'; export type RepositoryConfigProps = { isEdit: boolean @@ -73,12 +75,21 @@ export function RepositoryConfigForm (props: FormProps & Re setFields({ 'rdsConfig': getDefaults(RdsInstanceConfig) }); } setFields({ 'opensearchConfig': undefined }); + setFields({ 'bedrockKnowledgeBaseConfig': undefined }); } if (detail.selectedOption.value === RagRepositoryType.OPENSEARCH) { if (item.opensearchConfig === undefined) { setFields({ 'opensearchConfig': getDefaults(OpenSearchNewClusterConfig) }); } setFields({ 'rdsConfig': undefined }); + setFields({ 'bedrockKnowledgeBaseConfig': undefined }); + } + if (detail.selectedOption.value === RagRepositoryType.BEDROCK_KNOWLEDGE_BASE) { + if (item.bedrockKnowledgeBaseConfig === undefined) { + setFields({ 'bedrockKnowledgeBaseConfig': getDefaults(BedrockKnowledgeBaseInstanceConfig) }); + } + setFields({ 'rdsConfig': undefined }); + setFields({ 'opensearchConfig': undefined }); } setFields({ 'type': detail.selectedOption.value }); }} @@ -99,6 +110,10 @@ export function RepositoryConfigForm (props: FormProps & Re } + {item.type === RagRepositoryType.BEDROCK_KNOWLEDGE_BASE && + + } = 14.0.0" + } + }, "node_modules/@algolia/autocomplete-core": { "version": "1.17.7", "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.17.7.tgz", @@ -211,180 +226,180 @@ } }, "node_modules/@algolia/client-abtesting": { - "version": "5.34.0", - "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.34.0.tgz", - "integrity": "sha512-d6ardhDtQsnMpyr/rPrS3YuIE9NYpY4rftkC7Ap9tyuhZ/+V3E/LH+9uEewPguKzVqduApdwJzYq2k+vAXVEbQ==", + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.35.0.tgz", + "integrity": "sha512-uUdHxbfHdoppDVflCHMxRlj49/IllPwwQ2cQ8DLC4LXr3kY96AHBpW0dMyi6ygkn2MtFCc6BxXCzr668ZRhLBQ==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.34.0", - "@algolia/requester-browser-xhr": "5.34.0", - "@algolia/requester-fetch": "5.34.0", - "@algolia/requester-node-http": "5.34.0" + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-analytics": { - "version": "5.34.0", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.34.0.tgz", - "integrity": "sha512-WXIByjHNA106JO1Dj6b4viSX/yMN3oIB4qXr2MmyEmNq0MgfuPfPw8ayLRIZPa9Dp27hvM3G8MWJ4RG978HYFw==", + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.35.0.tgz", + "integrity": "sha512-SunAgwa9CamLcRCPnPHx1V2uxdQwJGqb1crYrRWktWUdld0+B2KyakNEeVn5lln4VyeNtW17Ia7V7qBWyM/Skw==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.34.0", - "@algolia/requester-browser-xhr": "5.34.0", - "@algolia/requester-fetch": "5.34.0", - "@algolia/requester-node-http": "5.34.0" + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-common": { - "version": "5.34.0", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.34.0.tgz", - "integrity": "sha512-JeN1XJLZIkkv6yK0KT93CIXXk+cDPUGNg5xeH4fN9ZykYFDWYRyqgaDo+qvg4RXC3WWkdQ+hogQuuCk4Y3Eotw==", + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.35.0.tgz", + "integrity": "sha512-ipE0IuvHu/bg7TjT2s+187kz/E3h5ssfTtjpg1LbWMgxlgiaZIgTTbyynM7NfpSJSKsgQvCQxWjGUO51WSCu7w==", "license": "MIT", "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-insights": { - "version": "5.34.0", - "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.34.0.tgz", - "integrity": "sha512-gdFlcQa+TWXJUsihHDlreFWniKPFIQ15i5oynCY4m9K3DCex5g5cVj9VG4Hsquxf2t6Y0yv8w6MvVTGDO8oRLw==", + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.35.0.tgz", + "integrity": "sha512-UNbCXcBpqtzUucxExwTSfAe8gknAJ485NfPN6o1ziHm6nnxx97piIbcBQ3edw823Tej2Wxu1C0xBY06KgeZ7gA==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.34.0", - "@algolia/requester-browser-xhr": "5.34.0", - "@algolia/requester-fetch": "5.34.0", - "@algolia/requester-node-http": "5.34.0" + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-personalization": { - "version": "5.34.0", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.34.0.tgz", - "integrity": "sha512-g91NHhIZDkh1IUeNtsUd8V/ZxuBc2ByOfDqhCkoQY3Z/mZszhpn3Czn6AR5pE81fx793vMaiOZvQVB5QttArkQ==", + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.35.0.tgz", + "integrity": "sha512-/KWjttZ6UCStt4QnWoDAJ12cKlQ+fkpMtyPmBgSS2WThJQdSV/4UWcqCUqGH7YLbwlj3JjNirCu3Y7uRTClxvA==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.34.0", - "@algolia/requester-browser-xhr": "5.34.0", - "@algolia/requester-fetch": "5.34.0", - "@algolia/requester-node-http": "5.34.0" + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-query-suggestions": { - "version": "5.34.0", - "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.34.0.tgz", - "integrity": "sha512-cvRApDfFrlJ3Vcn37U4Nd/7S6T8cx7FW3mVLJPqkkzixv8DQ/yV+x4VLirxOtGDdq3KohcIbIGWbg1QuyOZRvQ==", + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.35.0.tgz", + "integrity": "sha512-8oCuJCFf/71IYyvQQC+iu4kgViTODbXDk3m7yMctEncRSRV+u2RtDVlpGGfPlJQOrAY7OONwJlSHkmbbm2Kp/w==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.34.0", - "@algolia/requester-browser-xhr": "5.34.0", - "@algolia/requester-fetch": "5.34.0", - "@algolia/requester-node-http": "5.34.0" + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-search": { - "version": "5.34.0", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.34.0.tgz", - "integrity": "sha512-m9tK4IqJmn+flEPRtuxuHgiHmrKV0su5fuVwVpq8/es4DMjWMgX1a7Lg1PktvO8AbKaTp9kTtBAPnwXpuCwmEg==", + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.35.0.tgz", + "integrity": "sha512-FfmdHTrXhIduWyyuko1YTcGLuicVbhUyRjO3HbXE4aP655yKZgdTIfMhZ/V5VY9bHuxv/fGEh3Od1Lvv2ODNTg==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.34.0", - "@algolia/requester-browser-xhr": "5.34.0", - "@algolia/requester-fetch": "5.34.0", - "@algolia/requester-node-http": "5.34.0" + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/ingestion": { - "version": "1.34.0", - "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.34.0.tgz", - "integrity": "sha512-2rxy4XoeRtIpzxEh5u5UgDC5HY4XbNdjzNgFx1eDrfFkSHpEVjirtLhISMy2N5uSFqYu1uUby5/NC1Soq8J7iw==", + "version": "1.35.0", + "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.35.0.tgz", + "integrity": "sha512-gPzACem9IL1Co8mM1LKMhzn1aSJmp+Vp434An4C0OBY4uEJRcqsLN3uLBlY+bYvFg8C8ImwM9YRiKczJXRk0XA==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.34.0", - "@algolia/requester-browser-xhr": "5.34.0", - "@algolia/requester-fetch": "5.34.0", - "@algolia/requester-node-http": "5.34.0" + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/monitoring": { - "version": "1.34.0", - "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.34.0.tgz", - "integrity": "sha512-OJiDhlJX8ZdWAndc50Z6aUEW/YmnhFK2ul3rahMw5/c9Damh7+oY9SufoK2LimJejy+65Qka06YPG29v2G/vww==", + "version": "1.35.0", + "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.35.0.tgz", + "integrity": "sha512-w9MGFLB6ashI8BGcQoVt7iLgDIJNCn4OIu0Q0giE3M2ItNrssvb8C0xuwJQyTy1OFZnemG0EB1OvXhIHOvQwWw==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.34.0", - "@algolia/requester-browser-xhr": "5.34.0", - "@algolia/requester-fetch": "5.34.0", - "@algolia/requester-node-http": "5.34.0" + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/recommend": { - "version": "5.34.0", - "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.34.0.tgz", - "integrity": "sha512-fzNQZAdVxu/Gnbavy8KW5gurApwdYcPW6+pjO7Pw8V5drCR3eSqnOxSvp79rhscDX8ezwqMqqK4F3Hsq+KpRzg==", + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.35.0.tgz", + "integrity": "sha512-AhrVgaaXAb8Ue0u2nuRWwugt0dL5UmRgS9LXe0Hhz493a8KFeZVUE56RGIV3hAa6tHzmAV7eIoqcWTQvxzlJeQ==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.34.0", - "@algolia/requester-browser-xhr": "5.34.0", - "@algolia/requester-fetch": "5.34.0", - "@algolia/requester-node-http": "5.34.0" + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-browser-xhr": { - "version": "5.34.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.34.0.tgz", - "integrity": "sha512-gEI0xjzA/xvMpEdYmgQnf6AQKllhgKRtnEWmwDrnct+YPIruEHlx1dd7nRJTy/33MiYcCxkB4khXpNrHuqgp3Q==", + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.35.0.tgz", + "integrity": "sha512-diY415KLJZ6x1Kbwl9u96Jsz0OstE3asjXtJ9pmk1d+5gPuQ5jQyEsgC+WmEXzlec3iuVszm8AzNYYaqw6B+Zw==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.34.0" + "@algolia/client-common": "5.35.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-fetch": { - "version": "5.34.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.34.0.tgz", - "integrity": "sha512-5SwGOttpbACT4jXzfSJ3mnTcF46SVNSnZ1JjxC3qBa3qKi4U0CJGzuVVy3L798u8dG5H0SZ2MAB5v7180Gnqew==", + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.35.0.tgz", + "integrity": "sha512-uydqnSmpAjrgo8bqhE9N1wgcB98psTRRQXcjc4izwMB7yRl9C8uuAQ/5YqRj04U0mMQ+fdu2fcNF6m9+Z1BzDQ==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.34.0" + "@algolia/client-common": "5.35.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-node-http": { - "version": "5.34.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.34.0.tgz", - "integrity": "sha512-409XlyIyEXrxyGjWxd0q5RASizHSRVUU0AXPCEdqnbcGEzbCgL1n7oYI8YxzE/RqZLha+PNwWCcTVn7EE5tyyQ==", + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.35.0.tgz", + "integrity": "sha512-RgLX78ojYOrThJHrIiPzT4HW3yfQa0D7K+MQ81rhxqaNyNBu4F1r+72LNHYH/Z+y9I1Mrjrd/c/Ue5zfDgAEjQ==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.34.0" + "@algolia/client-common": "5.35.0" }, "engines": { "node": ">= 14.0.0" @@ -2987,9 +3002,9 @@ "license": "BSD-3-Clause" }, "node_modules/@iconify-json/simple-icons": { - "version": "1.2.43", - "resolved": "https://registry.npmjs.org/@iconify-json/simple-icons/-/simple-icons-1.2.43.tgz", - "integrity": "sha512-JERgKGFRfZdyjGyTvVBVW5rftahy9tNUX+P+0QUnbaAEWvEMexXHE9863YVMVrIRhoj/HybGsibg8ZWieo/NDg==", + "version": "1.2.47", + "resolved": "https://registry.npmjs.org/@iconify-json/simple-icons/-/simple-icons-1.2.47.tgz", + "integrity": "sha512-wa/2O7G4sBmwSEWWLh5C+HeY00lVOoWYRKJOYQtk7lAbQrHUReD1ijiGOyTynV1YavxtNueL1CBA1UZmYJfOrQ==", "license": "CC0-1.0", "dependencies": { "@iconify/types": "*" @@ -5744,24 +5759,25 @@ } }, "node_modules/algoliasearch": { - "version": "5.34.0", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.34.0.tgz", - "integrity": "sha512-wioVnf/8uuG8Bmywhk5qKIQ3wzCCtmdvicPRb0fa3kKYGGoewfgDqLEaET1MV2NbTc3WGpPv+AgauLVBp1nB9A==", - "license": "MIT", - "dependencies": { - "@algolia/client-abtesting": "5.34.0", - "@algolia/client-analytics": "5.34.0", - "@algolia/client-common": "5.34.0", - "@algolia/client-insights": "5.34.0", - "@algolia/client-personalization": "5.34.0", - "@algolia/client-query-suggestions": "5.34.0", - "@algolia/client-search": "5.34.0", - "@algolia/ingestion": "1.34.0", - "@algolia/monitoring": "1.34.0", - "@algolia/recommend": "5.34.0", - "@algolia/requester-browser-xhr": "5.34.0", - "@algolia/requester-fetch": "5.34.0", - "@algolia/requester-node-http": "5.34.0" + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.35.0.tgz", + "integrity": "sha512-Y+moNhsqgLmvJdgTsO4GZNgsaDWv8AOGAaPeIeHKlDn/XunoAqYbA+XNpBd1dW8GOXAUDyxC9Rxc7AV4kpFcIg==", + "license": "MIT", + "dependencies": { + "@algolia/abtesting": "1.1.0", + "@algolia/client-abtesting": "5.35.0", + "@algolia/client-analytics": "5.35.0", + "@algolia/client-common": "5.35.0", + "@algolia/client-insights": "5.35.0", + "@algolia/client-personalization": "5.35.0", + "@algolia/client-query-suggestions": "5.35.0", + "@algolia/client-search": "5.35.0", + "@algolia/ingestion": "1.35.0", + "@algolia/monitoring": "1.35.0", + "@algolia/recommend": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" }, "engines": { "node": ">= 14.0.0" @@ -6938,7 +6954,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001699", + "version": "1.0.30001731", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001731.tgz", + "integrity": "sha512-lDdp2/wrOmTRWuoB5DpfNkC0rJDU8DqRa6nYL6HK6sytw70QMopt/NIc/9SM7ylItlBWfACXk0tEn37UWM/+mg==", "dev": true, "funding": [ { @@ -8438,7 +8456,6 @@ }, "node_modules/es-set-tostringtag": { "version": "2.1.0", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -9522,11 +9539,15 @@ } }, "node_modules/form-data": { - "version": "4.0.1", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { @@ -14151,9 +14172,9 @@ "license": "MIT" }, "node_modules/preact": { - "version": "10.26.9", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.26.9.tgz", - "integrity": "sha512-SSjF9vcnF27mJK1XyFMNJzFd5u3pQiATFqoaDy03XuN00u4ziveVVEGt5RKJrDR8MHE/wJo9Nnad56RLzS2RMA==", + "version": "10.27.0", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.27.0.tgz", + "integrity": "sha512-/DTYoB6mwwgPytiqQTh/7SFRL98ZdiD8Sk8zIUVOxtwq4oWcwrcd1uno9fE/zZmUaUrFNYzbH14CPebOz9tZQw==", "license": "MIT", "funding": { "type": "opencollective", @@ -16142,10 +16163,11 @@ "dev": true }, "node_modules/tmp": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", - "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.14" } @@ -16941,9 +16963,9 @@ } }, "node_modules/vitepress": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.6.3.tgz", - "integrity": "sha512-fCkfdOk8yRZT8GD9BFqusW3+GggWYZ/rYncOfmgcDtP3ualNHCAg+Robxp2/6xfH1WwPHtGpPwv7mbA3qomtBw==", + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.6.4.tgz", + "integrity": "sha512-+2ym1/+0VVrbhNyRoFFesVvBvHAVMZMK0rw60E3X/5349M1GuVdKeazuksqopEdvkKwKGs21Q729jX81/bkBJg==", "license": "MIT", "dependencies": { "@docsearch/css": "3.8.2", diff --git a/test/lambda/test_repository_lambda.py b/test/lambda/test_repository_lambda.py index 72db09439..c79721de6 100644 --- a/test/lambda/test_repository_lambda.py +++ b/test/lambda/test_repository_lambda.py @@ -1563,7 +1563,11 @@ def test_real_download_document_function(): mock_vs_repo.find_repository_by_id.return_value = {"allowedGroups": ["test-group"], "status": "active"} - mock_doc_repo.find_by_id.return_value = {"username": "test-user", "source": "s3://test-bucket/test-key"} + # Create a mock RagDocument object + mock_doc = MagicMock() + mock_doc.source = "s3://test-bucket/test-key" + mock_doc.username = "test-user" + mock_doc_repo.find_by_id.return_value = mock_doc mock_s3.generate_presigned_url.return_value = "https://test-url" @@ -1578,8 +1582,9 @@ def test_real_download_document_function(): # The function is wrapped by api_wrapper, so we get an HTTP response assert result["statusCode"] == 200 - body = json.loads(result["body"]) - assert body == "https://test-url" + # The function returns a string URL, which gets JSON serialized by api_wrapper + # So the body is a JSON-encoded string + assert result["body"] == '"https://test-url"' def test_real_list_docs_function(): @@ -1754,3 +1759,52 @@ def test_ensure_document_ownership_edge_cases(): with pytest.raises(ValueError): _ensure_document_ownership(event, docs) + + +def test_real_similarity_search_bedrock_kb_function(): + """Test the actual similarity_search function for Bedrock Knowledge Base repositories""" + from repository.lambda_functions import similarity_search + + with patch("repository.lambda_functions.vs_repo") as mock_vs_repo, patch( + "repository.lambda_functions.bedrock_client" + ) as mock_bedrock, patch("repository.lambda_functions.get_groups") as mock_get_groups: + + mock_get_groups.return_value = ["test-group"] + mock_vs_repo.find_repository_by_id.return_value = { + "type": "bedrock_knowledge_base", + "allowedGroups": ["test-group"], + "bedrockKnowledgeBaseConfig": {"bedrockKnowledgeBaseId": "kb-123"}, + "status": "active", + } + + mock_bedrock.retrieve.return_value = { + "retrievalResults": [ + { + "content": {"text": "KB doc content"}, + "location": {"s3Location": {"uri": "s3://bucket/path/doc1.pdf"}}, + }, + { + "content": {"text": "Second"}, + "location": {"s3Location": {"uri": "s3://bucket/path/doc2.txt"}}, + }, + ] + } + + event = { + "requestContext": { + "authorizer": {"claims": {"username": "test-user"}, "groups": json.dumps(["test-group"])} + }, + "pathParameters": {"repositoryId": "test-repo"}, + "queryStringParameters": {"modelName": "test-model", "query": "test query", "topK": "2"}, + } + + result = similarity_search(event, SimpleNamespace()) + + assert result["statusCode"] == 200 + body = json.loads(result["body"]) + assert "docs" in body + assert len(body["docs"]) == 2 + first_doc = body["docs"][0]["Document"] + assert first_doc["page_content"] == "KB doc content" + assert first_doc["metadata"]["source"] == "s3://bucket/path/doc1.pdf" + assert first_doc["metadata"]["name"] == "doc1.pdf" diff --git a/vector_store_deployer/src/lib/bedrock_knowledge_base.ts b/vector_store_deployer/src/lib/bedrock_knowledge_base.ts new file mode 100644 index 000000000..38f019b7d --- /dev/null +++ b/vector_store_deployer/src/lib/bedrock_knowledge_base.ts @@ -0,0 +1,37 @@ +/** + 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. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License 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. +*/ +import { StackProps } from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import { RagRepositoryConfig, PartialConfig } from '../../../lib/schema'; +import { PipelineStack } from './pipeline-stack'; + +// Type definition for BedrockKnowledgeBaseStack properties +type BedrockKnowledgeBaseStackProps = StackProps & { + config: PartialConfig, + ragConfig: RagRepositoryConfig, +}; + +// BedrockKnowledgeBaseStack class, extending PipelineStack +export class BedrockKnowledgeBaseStack extends PipelineStack { + constructor (scope: Construct, id: string, props: BedrockKnowledgeBaseStackProps) { + super(scope, id, props); + + // Destructure the configuration properties + const { config, ragConfig } = props; + + this.createPipelineRules(config, ragConfig); + } +} diff --git a/vector_store_deployer/src/lib/index.ts b/vector_store_deployer/src/lib/index.ts index 4ed73a213..660c4cb72 100644 --- a/vector_store_deployer/src/lib/index.ts +++ b/vector_store_deployer/src/lib/index.ts @@ -18,6 +18,7 @@ import { AddPermissionBoundary } from '@cdklabs/cdk-enterprise-iac'; import { OpenSearchVectorStoreStack } from './opensearch'; import { PGVectorStoreStack } from './pgvector'; import { RagRepositoryConfigSchema, RagRepositoryType,PartialConfigSchema } from '../../../lib/schema'; +import { BedrockKnowledgeBaseStack } from './bedrock_knowledge_base'; const app = new App(); @@ -46,6 +47,12 @@ if (ragConfig.type === RagRepositoryType.OPENSEARCH) { stack = new PGVectorStoreStack(app, stackName, { ...vectorStoreProps, }); +} else if (ragConfig.type === RagRepositoryType.BEDROCK_KNOWLEDGE_BASE) { + if (ragConfig.pipelines){ + stack = new BedrockKnowledgeBaseStack(app, stackName, { + ...vectorStoreProps, + }); + } } else { console.error(`Unsupported repository type: ${ragConfig.type}`); throw new Error(`Unsupported repository type: ${ragConfig.type}`); From 4584092220c86d3fd5208b8524371c9880f5d784 Mon Sep 17 00:00:00 2001 From: bedanley Date: Mon, 18 Aug 2025 14:39:08 -0600 Subject: [PATCH 04/13] Feature/filter sessions (#363) * Add session filter button --- .../chatbot/components/Sessions.tsx | 113 ++++++++++++------ 1 file changed, 76 insertions(+), 37 deletions(-) diff --git a/lib/user-interface/react/src/components/chatbot/components/Sessions.tsx b/lib/user-interface/react/src/components/chatbot/components/Sessions.tsx index 19e9fa69d..e5c03252c 100644 --- a/lib/user-interface/react/src/components/chatbot/components/Sessions.tsx +++ b/lib/user-interface/react/src/components/chatbot/components/Sessions.tsx @@ -17,7 +17,7 @@ import SpaceBetween from '@cloudscape-design/components/space-between'; import Link from '@cloudscape-design/components/link'; import Header from '@cloudscape-design/components/header'; -import { ButtonDropdown, Grid} from '@cloudscape-design/components'; +import { ButtonDropdown, Grid, Input, Popover } from '@cloudscape-design/components'; import Button from '@cloudscape-design/components/button'; import { useCollection } from '@cloudscape-design/collection-hooks'; import { useLazyGetConfigurationQuery } from '@/shared/reducers/configuration.reducer'; @@ -29,7 +29,7 @@ import { } from '@/shared/reducers/session.reducer'; import { useAppDispatch } from '@/config/store'; import { useNotificationService } from '@/shared/util/hooks'; -import { useEffect, useState } from 'react'; +import { useEffect, useState, useMemo } from 'react'; import { useAuth } from 'react-oidc-context'; import { IConfiguration } from '@/shared/model/configuration.model'; import { useNavigate } from 'react-router-dom'; @@ -42,7 +42,7 @@ import JSZip from 'jszip'; import { downloadFile } from '@/shared/util/downloader'; import { setConfirmationModal } from '@/shared/reducers/modal.reducer'; -export function Sessions ({newSession}) { +export function Sessions ({ newSession }) { const dispatch = useAppDispatch(); const notificationService = useNotificationService(dispatch); const auth = useAuth(); @@ -65,8 +65,18 @@ export function Sessions ({newSession}) { }] = useDeleteAllSessionsForUserMutation(); const [getConfiguration] = useLazyGetConfigurationQuery(); const [config, setConfig] = useState(); + const [searchQuery, setSearchQuery] = useState(''); const { data: sessions } = useListSessionsQuery(null, { refetchOnMountOrArgChange: 5 }); - const { items } = useCollection(sessions || [], { + // Filter sessions based on search query + const filteredSessions = useMemo(() => { + if (!searchQuery.trim()) { + return sessions || []; + } + return (sessions || []) + .filter((session) => getDisplayableMessage(session.firstHumanMessage ?? '').toLowerCase().includes(searchQuery.toLowerCase())); + }, [sessions, searchQuery]); + + const { items } = useCollection(filteredSessions, { sorting: { defaultState: { sortingColumn: { @@ -113,38 +123,67 @@ export function Sessions ({newSession}) {
- - + + + setSearchQuery(detail.value)} + placeholder='Search sessions by message content...' + clearAriaLabel='Clear search' + type='search' + /> + {searchQuery && ( + + Found {filteredSessions.length} session{filteredSessions.length !== 1 ? 's' : ''} + + )} + + } + > + + + + ariaLabel='Refresh Sessions' + > {config?.configuration.enabledComponents.deleteSessionHistory && - } + } } @@ -168,14 +207,14 @@ export function Sessions ({newSession}) { { - if (e.detail.id === 'delete-session'){ + if (e.detail.id === 'delete-session') { dispatch( setConfirmationModal({ action: 'Delete', @@ -184,7 +223,7 @@ export function Sessions ({newSession}) { description: `This will delete the Session: ${item.sessionId}.` }) ); - } else if (e.detail.id === 'download-session'){ + } else if (e.detail.id === 'download-session') { getSessionById(item.sessionId).then((resp) => { const sess: LisaChatSession = resp.data; const file = new Blob([JSON.stringify(sess, null, 2)], { type: 'application/json' }); @@ -209,7 +248,7 @@ export function Sessions ({newSession}) { const imagePromises = images.map(async (imageUrl, index) => { try { const blob = await fetchImage(imageUrl); - zip.file(`image_${index + 1}.png`, blob, {binary: true}); + zip.file(`image_${index + 1}.png`, blob, { binary: true }); } catch (error) { console.error(`Error processing image ${index + 1}:`, error); } @@ -217,7 +256,7 @@ export function Sessions ({newSession}) { // Wait for all images to be processed await Promise.all(imagePromises); - const content = await zip.generateAsync({type: 'blob'}); + const content = await zip.generateAsync({ type: 'blob' }); downloadFile(URL.createObjectURL(content), `${sess.sessionId}-images.zip`); } }); From 773968c6acc863d52a602a2602d59b8d14afb55a Mon Sep 17 00:00:00 2001 From: bedanley Date: Tue, 19 Aug 2025 15:11:29 -0600 Subject: [PATCH 05/13] Feature/model comparer (#364) * Add model comparer * Add memos * Add model comparison utility toggle * Add default model preference * Set default model in chat * Add model comparer badge to welcome * Make Model Comparer a standalone page --- lib/user-interface/react/src/App.tsx | 12 +- .../react/src/components/chatbot/Chat.tsx | 20 +- .../components/SessionConfiguration.tsx | 28 +- .../chatbot/components/Sessions.tsx | 97 ++++++- .../chatbot/components/WelcomeScreen.tsx | 86 +++--- .../chatbot/hooks/useModals.hooks.tsx | 2 + .../chatbot/hooks/useSession.hooks.tsx | 30 +- .../src/components/common/ButtonBadge.tsx | 41 +++ .../configuration/ActivatedUserComponents.tsx | 3 +- .../ModelManagementActions.tsx | 75 ++++- .../ModelManagementComponent.tsx | 40 ++- .../model-management/ModelManagementUtils.tsx | 10 +- .../components/ModelComparisonComponents.tsx | 233 ++++++++++++++++ .../config/modelComparison.config.ts | 58 ++++ .../hooks/useModelComparison.hook.tsx | 259 ++++++++++++++++++ .../react/src/pages/ModelComparison.tsx | 171 ++++++++++++ .../src/shared/model/configuration.model.ts | 16 +- .../shared/model/model-management.model.ts | 2 + 18 files changed, 1088 insertions(+), 95 deletions(-) create mode 100644 lib/user-interface/react/src/components/common/ButtonBadge.tsx create mode 100644 lib/user-interface/react/src/components/model-management/components/ModelComparisonComponents.tsx create mode 100644 lib/user-interface/react/src/components/model-management/config/modelComparison.config.ts create mode 100644 lib/user-interface/react/src/components/model-management/hooks/useModelComparison.hook.tsx create mode 100644 lib/user-interface/react/src/pages/ModelComparison.tsx diff --git a/lib/user-interface/react/src/App.tsx b/lib/user-interface/react/src/App.tsx index 6aa679161..f81fe615a 100644 --- a/lib/user-interface/react/src/App.tsx +++ b/lib/user-interface/react/src/App.tsx @@ -40,6 +40,7 @@ import BreadcrumbsDefaultChangeListener from './shared/breadcrumb/breadcrumbs-ch import PromptTemplatesLibrary from './pages/PromptTemplatesLibrary'; import { ConfigurationContext } from './shared/configuration.provider'; import McpServers from '@/pages/Mcp'; +import ModelComparisonPage from './pages/ModelComparison'; export type RouteProps = { @@ -80,7 +81,7 @@ function App () { const [nav, setNav] = useState(null); const confirmationModal: ConfirmationModalProps = useAppSelector((state) => state.modal.confirmationModal); const auth = useAuth(); - const [ getConfigurationQuery, {data: fullConfig} ] = useLazyGetConfigurationQuery(); + const [getConfigurationQuery, { data: fullConfig }] = useLazyGetConfigurationQuery(); const config = fullConfig?.[0]; useEffect(() => { @@ -189,6 +190,15 @@ function App () { } />} + {config?.configuration?.enabledComponents?.enableModelComparisonUtility && + + + } + /> + } } /> diff --git a/lib/user-interface/react/src/components/chatbot/Chat.tsx b/lib/user-interface/react/src/components/chatbot/Chat.tsx index 0cb46d537..fa675ca85 100644 --- a/lib/user-interface/react/src/components/chatbot/Chat.tsx +++ b/lib/user-interface/react/src/components/chatbot/Chat.tsx @@ -100,12 +100,14 @@ export default function Chat ({ sessionId }) { data: (state.data || []).filter((model) => (model.modelType === ModelType.textgen || model.modelType === ModelType.imagegen) && model.status === ModelStatus.InService), }) }); - const {data: userPreferences} = useGetUserPreferencesQuery(); - const { data: mcpServers } = useListMcpServersQuery(undefined, { refetchOnMountOrArgChange: true, + const { data: userPreferences } = useGetUserPreferencesQuery(); + const { data: mcpServers } = useListMcpServersQuery(undefined, { + refetchOnMountOrArgChange: true, selectFromResult: (state) => ({ isFetching: state.isFetching, data: (state.data?.Items || []).filter((server) => (server.status === McpServerStatus.Active)), - }) },); + }) + },); // State management const [userPrompt, setUserPrompt] = useState(''); @@ -143,7 +145,7 @@ export default function Chat ({ sessionId }) { if (userPreferences) { setPreferences(userPreferences); } else { - setPreferences({...DefaultUserPreferences, user: userName}); + setPreferences({ ...DefaultUserPreferences, user: userName }); } }, [userPreferences, userName]); @@ -263,7 +265,7 @@ export default function Chat ({ sessionId }) { const toggleToolAutoApproval = (toolName: string, enabled: boolean) => { - const existingMcpPrefs = preferences.preferences.mcp ?? {enabledServers: [], overrideAllApprovals: false}; + const existingMcpPrefs = preferences.preferences.mcp ?? { enabledServers: [], overrideAllApprovals: false }; const mcpPrefs: McpPreferences = { ...existingMcpPrefs, enabledServers: [...existingMcpPrefs.enabledServers] @@ -289,8 +291,10 @@ export default function Chat ({ sessionId }) { }; const updatePrefs = (mcpPrefs: McpPreferences) => { - const updated = {...preferences, - preferences: {...preferences.preferences, + const updated = { + ...preferences, + preferences: { + ...preferences.preferences, mcp: { ...preferences.preferences.mcp, ...mcpPrefs @@ -629,7 +633,7 @@ export default function Chat ({ sessionId }) { {JSON.stringify(toolApprovalModal.tool.args).replace('{', '').replace('}', '')}

Do you want to allow this tool execution?

-
+
toggleToolAutoApproval(toolApprovalModal.tool.name, detail.checked) diff --git a/lib/user-interface/react/src/components/chatbot/components/SessionConfiguration.tsx b/lib/user-interface/react/src/components/chatbot/components/SessionConfiguration.tsx index bf40e2c17..b5967dc09 100644 --- a/lib/user-interface/react/src/components/chatbot/components/SessionConfiguration.tsx +++ b/lib/user-interface/react/src/components/chatbot/components/SessionConfiguration.tsx @@ -33,6 +33,7 @@ import { IModel, ModelType } from '@/shared/model/model-management.model'; import { IConfiguration } from '@/shared/model/configuration.model'; export type SessionConfigurationProps = { + title?: string; chatConfiguration: IChatConfiguration; setChatConfiguration: (items: IChatConfiguration) => void; setVisible: (boolean) => void; @@ -40,9 +41,11 @@ export type SessionConfigurationProps = { selectedModel: IModel; isRunning: boolean; systemConfig: IConfiguration; + modelOnly?: boolean }; export default function SessionConfiguration ({ + title, chatConfiguration, setChatConfiguration, selectedModel, @@ -50,6 +53,7 @@ export default function SessionConfiguration ({ visible, setVisible, systemConfig, + modelOnly = false }: SessionConfigurationProps) { // Defaults based on https://huggingface.co/docs/transformers/main_classes/text_generation#transformers.GenerationConfig // Default stop sequences based on User/Assistant instruction prompting for Falcon, Mistral, etc. @@ -75,7 +79,7 @@ export default function SessionConfiguration ({ setVisible(false)} visible={visible} - header={
Session Configuration
} + header={
{title || 'Session Configuration'}
} footer='' size='large' > @@ -102,7 +106,7 @@ export default function SessionConfiguration ({ > Show Message Metadata } - {systemConfig && systemConfig.configuration.enabledComponents.editChatHistoryBuffer && !isImageModel && + {systemConfig && systemConfig.configuration.enabledComponents.editChatHistoryBuffer && !isImageModel && !modelOnly && @@ -119,7 +123,7 @@ export default function SessionConfiguration ({ options={oneThroughTenOptions} /> } - {systemConfig && systemConfig.configuration.enabledComponents.editNumOfRagDocument && !isImageModel && + {systemConfig && systemConfig.configuration.enabledComponents.editNumOfRagDocument && !isImageModel && !modelOnly && @@ -316,7 +320,7 @@ export default function SessionConfiguration ({ }} /> - - { updateSessionConfiguration('imageGenerationArgs', { ...chatConfiguration.sessionConfiguration.imageGenerationArgs, @@ -430,14 +436,14 @@ export default function SessionConfiguration ({ }); }} options={[ - { label: 'Standard', value: 'standard'}, + { label: 'Standard', value: 'standard' }, { label: 'HD', value: 'hd' }, ]} /> setNewSessionName(detail.value)} + placeholder='Enter session name...' + onKeyDown={(e) => { + if (e.detail.key === 'Enter' && newSessionName.trim() && !isUpdateSessionLoading) { + handleRenameConfirm(); + } + }} + /> + + +
); } diff --git a/lib/user-interface/react/src/components/chatbot/components/WelcomeScreen.tsx b/lib/user-interface/react/src/components/chatbot/components/WelcomeScreen.tsx index 832d1b362..073e4f751 100644 --- a/lib/user-interface/react/src/components/chatbot/components/WelcomeScreen.tsx +++ b/lib/user-interface/react/src/components/chatbot/components/WelcomeScreen.tsx @@ -15,11 +15,12 @@ */ import { v4 as uuidv4 } from 'uuid'; -import { Button, Header, SpaceBetween, TextContent } from '@cloudscape-design/components'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Header } from '@cloudscape-design/components'; import { faFileLines, faMessage, faPenToSquare, faComment } from '@fortawesome/free-regular-svg-icons'; import { PromptTemplateType } from '@/shared/reducers/prompt-templates.reducer'; import { IConfiguration } from '@/shared/model/configuration.model'; +import { ButtonBadge } from '@/components/common/ButtonBadge'; +import { faCodeCompare } from '@fortawesome/free-solid-svg-icons'; type WelcomeScreenProps = { navigate: (path: string) => void; @@ -54,52 +55,55 @@ export const WelcomeScreen = ({
- + { + navigate(`/ai-assistant/${uuidv4()}`); + modelSelectRef?.current?.focus(); + }} + /> - {config?.configuration?.enabledComponents?.showPromptTemplateLibrary && ( - <> - - - - )} + { + refreshPromptTemplate(); + setFilterPromptTemplateType(PromptTemplateType.Persona); + openModal('promptTemplate'); + }} + show={config?.configuration?.enabledComponents?.showPromptTemplateLibrary} + /> - + { + refreshPromptTemplate(); + setFilterPromptTemplateType(PromptTemplateType.Directive); + openModal('promptTemplate'); + }} + show={config?.configuration?.enabledComponents?.showPromptTemplateLibrary} + /> + + openModal('documentSummarization')} + /> + + navigate('/model-comparison')} + show={config?.configuration?.enabledComponents?.enableModelComparisonUtility} + />
); diff --git a/lib/user-interface/react/src/components/chatbot/hooks/useModals.hooks.tsx b/lib/user-interface/react/src/components/chatbot/hooks/useModals.hooks.tsx index 30ce5c4d3..8001cbca2 100644 --- a/lib/user-interface/react/src/components/chatbot/hooks/useModals.hooks.tsx +++ b/lib/user-interface/react/src/components/chatbot/hooks/useModals.hooks.tsx @@ -22,6 +22,7 @@ export type ModalState = { ragUpload: boolean; documentSummarization: boolean; promptTemplate: boolean; + modelComparison: boolean }; export const useModals = () => { @@ -31,6 +32,7 @@ export const useModals = () => { ragUpload: false, documentSummarization: false, promptTemplate: false, + modelComparison: false, }); const [promptTemplateKey, setPromptTemplateKey] = useState(new Date().toISOString()); diff --git a/lib/user-interface/react/src/components/chatbot/hooks/useSession.hooks.tsx b/lib/user-interface/react/src/components/chatbot/hooks/useSession.hooks.tsx index 27cf4bc49..f587ef649 100644 --- a/lib/user-interface/react/src/components/chatbot/hooks/useSession.hooks.tsx +++ b/lib/user-interface/react/src/components/chatbot/hooks/useSession.hooks.tsx @@ -14,7 +14,7 @@ limitations under the License. */ -import { useEffect, useState } from 'react'; +import { useContext, useEffect, useState } from 'react'; import { useAuth } from 'react-oidc-context'; import { v4 as uuidv4 } from 'uuid'; import { LisaChatSession } from '@/components/types'; @@ -23,10 +23,15 @@ import { RagConfig } from '../components/RagOptions'; import { IModel } from '@/shared/model/model-management.model'; import { useAppDispatch } from '@/config/store'; import { setBreadcrumbs } from '@/shared/reducers/breadcrumbs.reducer'; +import ConfigurationContext from '@/shared/configuration.provider'; +import { IConfiguration } from '@/shared/model/configuration.model'; +import { useGetAllModelsQuery } from '@/shared/reducers/model-management.reducer'; export const useSession = (sessionId: string, getSessionById: any) => { const dispatch = useAppDispatch(); const auth = useAuth(); + const config: IConfiguration = useContext(ConfigurationContext); + const { data: allModels } = useGetAllModelsQuery(); const [session, setSession] = useState({ history: [], @@ -39,6 +44,7 @@ export const useSession = (sessionId: string, getSessionById: any) => { const [chatConfiguration, setChatConfiguration] = useState(baseConfig); const [selectedModel, setSelectedModel] = useState(); const [ragConfig, setRagConfig] = useState({} as RagConfig); + const [hasUserInteractedWithModel, setHasUserInteractedWithModel] = useState(false); useEffect(() => { // always hide breadcrumbs @@ -63,6 +69,10 @@ export const useSession = (sessionId: string, getSessionById: any) => { setSession(sess); setChatConfiguration(sess.configuration ?? baseConfig); setSelectedModel(sess.configuration?.selectedModel ?? undefined); + // If session has a pre-selected model, consider it as user interaction + if (sess.configuration?.selectedModel) { + setHasUserInteractedWithModel(true); + } setRagConfig(sess.configuration?.ragConfig ?? {} as RagConfig); setLoadingSession(false); }); @@ -80,6 +90,22 @@ export const useSession = (sessionId: string, getSessionById: any) => { } }, [sessionId, dispatch, auth.user?.profile.sub, getSessionById]); + // Set default model if none is selected, default model is configured, and user hasn't interacted with model selection + useEffect(() => { + if (!selectedModel && !hasUserInteractedWithModel && config?.configuration?.global?.defaultModel && allModels) { + const defaultModel = allModels.find((model) => model.modelId === config.configuration.global.defaultModel); + if (defaultModel) { + setSelectedModel(defaultModel); + } + } + }, [selectedModel, hasUserInteractedWithModel, config?.configuration?.global?.defaultModel, allModels]); + + // Wrapper function to track user interaction with model selection + const handleSetSelectedModel = (model: IModel | undefined) => { + setHasUserInteractedWithModel(true); + setSelectedModel(model); + }; + return { session, setSession, @@ -89,7 +115,7 @@ export const useSession = (sessionId: string, getSessionById: any) => { chatConfiguration, setChatConfiguration, selectedModel, - setSelectedModel, + setSelectedModel: handleSetSelectedModel, ragConfig, setRagConfig, }; diff --git a/lib/user-interface/react/src/components/common/ButtonBadge.tsx b/lib/user-interface/react/src/components/common/ButtonBadge.tsx new file mode 100644 index 000000000..a764cd340 --- /dev/null +++ b/lib/user-interface/react/src/components/common/ButtonBadge.tsx @@ -0,0 +1,41 @@ +/** + 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. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License 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. +*/ + +import { Button, SpaceBetween, TextContent } from '@cloudscape-design/components'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { IconDefinition } from '@fortawesome/fontawesome-svg-core'; + +type ButtonBadgeProps = { + text: string; + icon: IconDefinition; + onClick: () => void; + show?: boolean; +}; + +export const ButtonBadge = ({ text, icon, onClick, show = true }: ButtonBadgeProps) => { + if (!show) { + return null; + } + + return ( + + ); +}; diff --git a/lib/user-interface/react/src/components/configuration/ActivatedUserComponents.tsx b/lib/user-interface/react/src/components/configuration/ActivatedUserComponents.tsx index 5af443ea1..6ddeb822e 100644 --- a/lib/user-interface/react/src/components/configuration/ActivatedUserComponents.tsx +++ b/lib/user-interface/react/src/components/configuration/ActivatedUserComponents.tsx @@ -36,7 +36,8 @@ const advancedOptions = { viewMetaData: 'View chat meta-data', deleteSessionHistory: 'Delete Session History', editChatHistoryBuffer: 'Edit chat history buffer', - showPromptTemplateLibrary: 'Show Prompt Template Library' + showPromptTemplateLibrary: 'Show Prompt Template Library', + enableModelComparisonUtility: 'Enable Model Comparison Utility' }; const configurableOperations = [{ diff --git a/lib/user-interface/react/src/components/model-management/ModelManagementActions.tsx b/lib/user-interface/react/src/components/model-management/ModelManagementActions.tsx index cd7b1d951..46b7131d7 100644 --- a/lib/user-interface/react/src/components/model-management/ModelManagementActions.tsx +++ b/lib/user-interface/react/src/components/model-management/ModelManagementActions.tsx @@ -14,25 +14,30 @@ limitations under the License. */ -import React, { ReactElement, useEffect } from 'react'; +import { ReactElement, useEffect } from 'react'; import { Button, ButtonDropdown, Icon, SpaceBetween } from '@cloudscape-design/components'; -import { useAppDispatch } from '../../config/store'; -import { IModel, ModelStatus } from '../../shared/model/model-management.model'; -import { useNotificationService } from '../../shared/util/hooks'; -import { INotificationService } from '../../shared/notification/notification.service'; +import { useAppDispatch, useAppSelector } from '@/config/store'; +import { IModel, ModelStatus } from '@/shared/model/model-management.model'; +import { useNotificationService } from '@/shared/util/hooks'; +import { INotificationService } from '@/shared/notification/notification.service'; import { modelManagementApi, useDeleteModelMutation, useUpdateModelMutation, -} from '../../shared/reducers/model-management.reducer'; +} from '@/shared/reducers/model-management.reducer'; import { MutationTrigger } from '@reduxjs/toolkit/dist/query/react/buildHooks'; import { Action, ThunkDispatch } from '@reduxjs/toolkit'; -import { setConfirmationModal } from '../../shared/reducers/modal.reducer'; +import { setConfirmationModal } from '@/shared/reducers/modal.reducer'; +import { IConfiguration, SystemConfiguration } from '@/shared/model/configuration.model'; +import { selectCurrentUsername } from '@/shared/reducers/user.reducer'; export type ModelActionProps = { selectedItems: IModel[]; setSelectedItems: (items: IModel[]) => void; setNewModelModelVisible: (state: boolean) => void; setEdit: (state: boolean) => void; + updateConfigMutation?: any; + currentDefaultModel?: string; + currentConfig?: any; }; function ModelActions (props: ModelActionProps): ReactElement { @@ -50,6 +55,7 @@ function ModelActions (props: ModelActionProps): ReactElement { > + {ModelActionButton(dispatch, notificationService, props)}
+ } + > + + {modelSelections.map((selection, index) => ( + + + + Model {index + 1} + + {modelSelections.length > MODEL_COMPARISON_CONFIG.MIN_MODELS && ( +