From a11c07a9a72bb6f83f7a6ea0a57e8e8fc3dc678c Mon Sep 17 00:00:00 2001 From: yl-nuwan Date: Fri, 5 Jun 2026 10:01:36 +0530 Subject: [PATCH 01/90] Adding initial scalable setup --- .github/integration-test-config.yaml | 145 +- ak-deployment/ak-aws/containerized/ecs.tf | 16 +- ak-deployment/ak-aws/containerized/outputs.tf | 25 + ak-deployment/ak-aws/containerized/sqs.tf | 366 ++ .../ak-aws/containerized/variables.tf | 132 +- .../agentkernel/deployment/aws/__init__.py | 1 + .../deployment/aws/containerized/__init__.py | 2 + .../aws/containerized/akagentrunner.py | 143 + .../aws/containerized/akrestservice.py | 265 + .../aws/containerized/sqs_poller.py | 119 + .../openai-dynamodb-scalable/.gitignore | 11 + .../openai-dynamodb-scalable/README.md | 80 + .../openai-dynamodb-scalable/app.py | 26 + .../app_agent_runner.py | 30 + .../app_rest_service.py | 11 + .../openai-dynamodb-scalable/app_test.py | 52 + .../openai-dynamodb-scalable/build.sh | 16 + .../openai-dynamodb-scalable/config.yaml | 25 + .../deploy/Dockerfile | 8 + .../deploy/Dockerfile.agent-runner | 6 + .../deploy/Dockerfile.rest-service | 8 + .../openai-dynamodb-scalable/deploy/deploy.sh | 41 + .../openai-dynamodb-scalable/deploy/main.tf | 106 + .../deploy/outputs.tf | 24 + .../deploy/terraform.tfvars | 4 + .../deploy/variables.tf | 40 + .../openai-dynamodb-scalable/pyproject.toml | 35 + .../openai-dynamodb-scalable/uv.lock | 4405 +++++++++++++++++ 28 files changed, 6066 insertions(+), 76 deletions(-) create mode 100644 ak-deployment/ak-aws/containerized/sqs.tf create mode 100644 ak-py/src/agentkernel/deployment/aws/containerized/__init__.py create mode 100644 ak-py/src/agentkernel/deployment/aws/containerized/akagentrunner.py create mode 100644 ak-py/src/agentkernel/deployment/aws/containerized/akrestservice.py create mode 100644 ak-py/src/agentkernel/deployment/aws/containerized/sqs_poller.py create mode 100644 examples/aws-containerized/openai-dynamodb-scalable/.gitignore create mode 100644 examples/aws-containerized/openai-dynamodb-scalable/README.md create mode 100644 examples/aws-containerized/openai-dynamodb-scalable/app.py create mode 100644 examples/aws-containerized/openai-dynamodb-scalable/app_agent_runner.py create mode 100644 examples/aws-containerized/openai-dynamodb-scalable/app_rest_service.py create mode 100644 examples/aws-containerized/openai-dynamodb-scalable/app_test.py create mode 100755 examples/aws-containerized/openai-dynamodb-scalable/build.sh create mode 100644 examples/aws-containerized/openai-dynamodb-scalable/config.yaml create mode 100644 examples/aws-containerized/openai-dynamodb-scalable/deploy/Dockerfile create mode 100644 examples/aws-containerized/openai-dynamodb-scalable/deploy/Dockerfile.agent-runner create mode 100644 examples/aws-containerized/openai-dynamodb-scalable/deploy/Dockerfile.rest-service create mode 100755 examples/aws-containerized/openai-dynamodb-scalable/deploy/deploy.sh create mode 100644 examples/aws-containerized/openai-dynamodb-scalable/deploy/main.tf create mode 100644 examples/aws-containerized/openai-dynamodb-scalable/deploy/outputs.tf create mode 100644 examples/aws-containerized/openai-dynamodb-scalable/deploy/terraform.tfvars create mode 100644 examples/aws-containerized/openai-dynamodb-scalable/deploy/variables.tf create mode 100644 examples/aws-containerized/openai-dynamodb-scalable/pyproject.toml create mode 100644 examples/aws-containerized/openai-dynamodb-scalable/uv.lock diff --git a/.github/integration-test-config.yaml b/.github/integration-test-config.yaml index 5625669dd..63ff4c764 100644 --- a/.github/integration-test-config.yaml +++ b/.github/integration-test-config.yaml @@ -12,24 +12,24 @@ nightly: # For AWS projects, only deploy.sh and pytest are executed (no terraform destroy) tests: # Social integrations - - type: api - path: examples/api/slack - - type: api - path: examples/api/telegram - - type: api - path: examples/api/messenger - - type: api - path: examples/api/whatsapp - - type: api - path: examples/api/instagram - - type: api - path: examples/api/gmail - - type: api - path: examples/api/multimodal/dynamodb - requires_aws: true - - type: api - path: examples/api/multimodal/redis - requires_redis: true + # - type: api + # path: examples/api/slack + # - type: api + # path: examples/api/telegram + # - type: api + # path: examples/api/messenger + # - type: api + # path: examples/api/whatsapp + # - type: api + # path: examples/api/instagram + # - type: api + # path: examples/api/gmail + # - type: api + # path: examples/api/multimodal/dynamodb + # requires_aws: true + # - type: api + # path: examples/api/multimodal/redis + # requires_redis: true weekly: # Tests that run once a week @@ -37,71 +37,74 @@ weekly: # then each project is deployed and tested tests: #AWS Containerized + # - type: aws-containerized + # path: examples/aws-containerized/adk + # deploy_dir: deploy + # - type: aws-containerized + # path: examples/aws-containerized/openai-dynamodb + # deploy_dir: deploy + # - type: aws-containerized + # path: examples/aws-containerized/crewai + # deploy_dir: deploy + # - type: aws-containerized + # path: examples/aws-containerized/mcp/multi + # deploy_dir: deploy - type: aws-containerized - path: examples/aws-containerized/adk - deploy_dir: deploy - - type: aws-containerized - path: examples/aws-containerized/openai-dynamodb - deploy_dir: deploy - - type: aws-containerized - path: examples/aws-containerized/crewai - deploy_dir: deploy - - type: aws-containerized - path: examples/aws-containerized/mcp/multi + path: examples/aws-containerized/openai-dynamodb-scalable deploy_dir: deploy # AWS Serverless - - type: aws-serverless - path: examples/aws-serverless/adk - deploy_dir: deploy - - type: aws-serverless - path: examples/aws-serverless/crewai - deploy_dir: deploy - - type: aws-serverless - path: examples/aws-serverless/langgraph - deploy_dir: deploy - - type: aws-serverless - path: examples/aws-serverless/openai-auth - deploy_dir: deploy - - type: aws-serverless - path: examples/aws-serverless/scalable-openai - deploy_dir: deploy + # - type: aws-serverless + # path: examples/aws-serverless/adk + # deploy_dir: deploy + # - type: aws-serverless + # path: examples/aws-serverless/crewai + # deploy_dir: deploy + # - type: aws-serverless + # path: examples/aws-serverless/langgraph + # deploy_dir: deploy + # - type: aws-serverless + # path: examples/aws-serverless/openai-auth + # deploy_dir: deploy + # - type: aws-serverless + # path: examples/aws-serverless/scalable-openai + # deploy_dir: deploy # Memory options - - type: aws-serverless - path: examples/memory/redis - - type: aws-serverless - path: examples/memory/dynamodb - - type: azure-serverless - path: examples/memory/cosmos - deploy_dir: deploy + # - type: aws-serverless + # path: examples/memory/redis + # - type: aws-serverless + # path: examples/memory/dynamodb + # - type: azure-serverless + # path: examples/memory/cosmos + # deploy_dir: deploy # Azure Serverless - - type: azure-serverless - path: examples/azure-serverless/openai - deploy_dir: deploy + # - type: azure-serverless + # path: examples/azure-serverless/openai + # deploy_dir: deploy # Azure Containerized - - type: azure-containerized - path: examples/azure-containerized/openai-cosmos - deploy_dir: deploy + # - type: azure-containerized + # path: examples/azure-containerized/openai-cosmos + # deploy_dir: deploy # GCP Serverless - - type: gcp-serverless - path: examples/gcp-serverless/openai-firestore - deploy_dir: deploy - - type: gcp-serverless - path: examples/gcp-serverless/openai - deploy_dir: deploy - - type: gcp-serverless - path: examples/gcp-serverless/openai-auth - deploy_dir: deploy + # - type: gcp-serverless + # path: examples/gcp-serverless/openai-firestore + # deploy_dir: deploy + # - type: gcp-serverless + # path: examples/gcp-serverless/openai + # deploy_dir: deploy + # - type: gcp-serverless + # path: examples/gcp-serverless/openai-auth + # deploy_dir: deploy # # GCP Containerized - - type: gcp-containerized - path: examples/gcp-containerized/openai - deploy_dir: deploy - - type: gcp-containerized - path: examples/gcp-containerized/openai-auth - deploy_dir: deploy + # - type: gcp-containerized + # path: examples/gcp-containerized/openai + # deploy_dir: deploy + # - type: gcp-containerized + # path: examples/gcp-containerized/openai-auth + # deploy_dir: deploy diff --git a/ak-deployment/ak-aws/containerized/ecs.tf b/ak-deployment/ak-aws/containerized/ecs.tf index f8bc92faa..0ab781182 100644 --- a/ak-deployment/ak-aws/containerized/ecs.tf +++ b/ak-deployment/ak-aws/containerized/ecs.tf @@ -95,11 +95,19 @@ module "ecs" { ] enable_cloudwatch_logging = true environment = [ - for k, v in merge(var.environment_variables, local.redis_url != null ? { - AK_SESSION__REDIS__URL = local.redis_url - } : {}, - local.dynamodb_memory_table_arn != null ? { + for k, v in merge( + var.environment_variables, + local.redis_url != null ? { + AK_SESSION__REDIS__URL = local.redis_url + } : {}, + local.dynamodb_memory_table_arn != null ? { AK_SESSION__DYNAMODB__TABLE_NAME = local.dynamodb_memory_table_name + } : {}, + # Queue mode — inject queue URLs and response store table name + var.enable_queue_mode ? { + AK_EXECUTION__QUEUES__INPUT__URL = module.input_queue[0].queue_url + AK_EXECUTION__QUEUES__OUTPUT__URL = module.output_queue[0].queue_url + AK_EXECUTION__RESPONSE_STORE__DYNAMODB__TABLE_NAME = aws_dynamodb_table.response_store[0].name } : {} ) : { name = k diff --git a/ak-deployment/ak-aws/containerized/outputs.tf b/ak-deployment/ak-aws/containerized/outputs.tf index 71d5c0476..ce28c7cfb 100644 --- a/ak-deployment/ak-aws/containerized/outputs.tf +++ b/ak-deployment/ak-aws/containerized/outputs.tf @@ -27,3 +27,28 @@ output "private_subnet_ids" { description = "Private subnet IDs used for the deployment" value = local.subnet_ids } + +output "input_queue_url" { + description = "URL of the SQS Input Queue (queue mode only)" + value = var.enable_queue_mode ? module.input_queue[0].queue_url : null +} + +output "output_queue_url" { + description = "URL of the SQS Output Queue (queue mode only)" + value = var.enable_queue_mode ? module.output_queue[0].queue_url : null +} + +output "response_store_table_name" { + description = "DynamoDB Response Store table name (queue mode only)" + value = var.enable_queue_mode ? aws_dynamodb_table.response_store[0].name : null +} + +output "agent_runner_service_name" { + description = "ECS Agent Runner service name (queue mode only)" + value = var.enable_queue_mode ? aws_ecs_service.agent_runner[0].name : null +} + +output "rest_service_image_uri" { + description = "Docker image URI used by the REST Service ECS task" + value = module.docker_image[0].docker_image_uri +} diff --git a/ak-deployment/ak-aws/containerized/sqs.tf b/ak-deployment/ak-aws/containerized/sqs.tf new file mode 100644 index 000000000..59ebaea66 --- /dev/null +++ b/ak-deployment/ak-aws/containerized/sqs.tf @@ -0,0 +1,366 @@ +# --------------------------------------------------------------------------- +# SQS Queue Mode — ECS Containerized +# +# Uses the shared common/modules/sqs module (same as the serverless stack). +# Enable with: var.enable_queue_mode = true +# --------------------------------------------------------------------------- + +data "aws_region" "current" {} + +# ---------- Input Queue ---------- + +module "input_queue" { + count = var.enable_queue_mode ? 1 : 0 + source = "../common/modules/sqs" + + product_alias = var.product_alias + env_alias = var.env_alias + module_name = var.module_name + queue_name = "input-queue" + region = data.aws_region.current.name + product_display_name = var.product_alias + is_production = var.env_alias == "prod" + + fifo_queue = true + content_based_deduplication = false + deduplication_scope = "messageGroup" + fifo_throughput_limit = "perMessageGroupId" + + visibility_timeout_seconds = var.sqs_input_visibility_timeout + message_retention_seconds = var.sqs_input_message_retention_seconds + max_message_size = var.sqs_max_message_size + receive_wait_time_seconds = var.sqs_receive_wait_time_seconds + max_receive_count = var.sqs_input_max_receive_count + create_dlq = var.sqs_input_create_dlq + dlq_message_retention_seconds = var.sqs_input_dlq_message_retention_seconds + + sqs_managed_sse_enabled = var.sqs_managed_sse_enabled + + # IAM access is managed via ECS task role policies below + enable_producer_access = false + enable_consumer_access = false + + tags = merge(var.tags, { Type = "InputQueue" }) +} + +# ---------- Output Queue ---------- + +module "output_queue" { + count = var.enable_queue_mode ? 1 : 0 + source = "../common/modules/sqs" + + product_alias = var.product_alias + env_alias = var.env_alias + module_name = var.module_name + queue_name = "output-queue" + region = data.aws_region.current.name + product_display_name = var.product_alias + is_production = var.env_alias == "prod" + + fifo_queue = true + content_based_deduplication = false + deduplication_scope = "messageGroup" + fifo_throughput_limit = "perMessageGroupId" + + visibility_timeout_seconds = var.sqs_output_visibility_timeout + message_retention_seconds = var.sqs_output_message_retention_seconds + max_message_size = var.sqs_max_message_size + receive_wait_time_seconds = var.sqs_receive_wait_time_seconds + max_receive_count = var.sqs_output_max_receive_count + create_dlq = var.sqs_output_create_dlq + dlq_message_retention_seconds = var.sqs_output_dlq_message_retention_seconds + + sqs_managed_sse_enabled = var.sqs_managed_sse_enabled + + enable_producer_access = false + enable_consumer_access = false + + tags = merge(var.tags, { Type = "OutputQueue" }) +} + +# ---------- DynamoDB Response Store ---------- +# Temporary response buffer. Thread 2 writes here; Thread 1 reads from here. + +resource "aws_dynamodb_table" "response_store" { + count = var.enable_queue_mode ? 1 : 0 + + name = "${local.prefix}-response-store" + billing_mode = "PAY_PER_REQUEST" + hash_key = "request_id" + + attribute { + name = "request_id" + type = "S" + } + + ttl { + attribute_name = "expiry_time" + enabled = true + } + + tags = merge(var.tags, { Type = "ResponseStore" }) +} + +# ---------- IAM — REST Service ECS Task Role ---------- + +resource "aws_iam_policy" "rest_service_sqs_policy" { + count = var.enable_queue_mode ? 1 : 0 + + name = "${local.prefix}-rest-svc-sqs" + description = "Allow REST Service ECS task to send to Input Queue and consume from Output Queue" + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Sid = "SendToInputQueue" + Effect = "Allow" + Action = [ + "sqs:SendMessage", + "sqs:GetQueueAttributes", + "sqs:GetQueueUrl" + ] + Resource = module.input_queue[0].queue_arn + }, + { + Sid = "ConsumeOutputQueue" + Effect = "Allow" + Action = [ + "sqs:ReceiveMessage", + "sqs:DeleteMessage", + "sqs:ChangeMessageVisibility", + "sqs:GetQueueAttributes", + "sqs:GetQueueUrl" + ] + Resource = module.output_queue[0].queue_arn + } + ] + }) + + tags = var.tags +} + +resource "aws_iam_policy" "rest_service_response_store_policy" { + count = var.enable_queue_mode ? 1 : 0 + + name = "${local.prefix}-rest-svc-response-store" + description = "Allow REST Service ECS task to read/write the DynamoDB response store" + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Action = [ + "dynamodb:DescribeTable", + "dynamodb:GetItem", + "dynamodb:PutItem", + "dynamodb:UpdateItem", + "dynamodb:DeleteItem", + "dynamodb:Query", + "dynamodb:Scan" + ] + Resource = [ + aws_dynamodb_table.response_store[0].arn, + "${aws_dynamodb_table.response_store[0].arn}/index/*" + ] + } + ] + }) + + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "rest_service_sqs_attachment" { + count = var.enable_queue_mode ? 1 : 0 + role = module.ecs.services[local.service_name].tasks_iam_role_name + policy_arn = aws_iam_policy.rest_service_sqs_policy[0].arn +} + +resource "aws_iam_role_policy_attachment" "rest_service_response_store_attachment" { + count = var.enable_queue_mode ? 1 : 0 + role = module.ecs.services[local.service_name].tasks_iam_role_name + policy_arn = aws_iam_policy.rest_service_response_store_policy[0].arn +} + +# ---------- IAM — Agent Runner ECS Task Role ---------- + +resource "aws_iam_role" "agent_runner_task_role" { + count = var.enable_queue_mode ? 1 : 0 + + name = "${local.prefix}-agent-runner-task-role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + Effect = "Allow" + Action = "sts:AssumeRole" + Principal = { Service = "ecs-tasks.amazonaws.com" } + }] + }) + + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "agent_runner_task_execution" { + count = var.enable_queue_mode ? 1 : 0 + role = aws_iam_role.agent_runner_task_role[0].name + policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" +} + +resource "aws_iam_policy" "agent_runner_sqs_policy" { + count = var.enable_queue_mode ? 1 : 0 + + name = "${local.prefix}-agent-runner-sqs" + description = "Allow Agent Runner ECS task to consume Input Queue and produce to Output Queue" + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Sid = "ConsumeInputQueue" + Effect = "Allow" + Action = [ + "sqs:ReceiveMessage", + "sqs:DeleteMessage", + "sqs:ChangeMessageVisibility", + "sqs:GetQueueAttributes", + "sqs:GetQueueUrl" + ] + Resource = module.input_queue[0].queue_arn + }, + { + Sid = "SendToOutputQueue" + Effect = "Allow" + Action = [ + "sqs:SendMessage", + "sqs:GetQueueAttributes", + "sqs:GetQueueUrl" + ] + Resource = module.output_queue[0].queue_arn + } + ] + }) + + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "agent_runner_sqs_attachment" { + count = var.enable_queue_mode ? 1 : 0 + role = aws_iam_role.agent_runner_task_role[0].name + policy_arn = aws_iam_policy.agent_runner_sqs_policy[0].arn +} + +# ---------- ECS Agent Runner Service ---------- + +resource "aws_security_group" "agent_runner" { + count = var.enable_queue_mode ? 1 : 0 + + name = "${local.prefix}-agent-runner-sg" + description = "Agent Runner ECS service SG - egress only (queue-polling)" + vpc_id = local.vpc_id + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = var.tags +} + +resource "aws_cloudwatch_log_group" "agent_runner" { + count = var.enable_queue_mode ? 1 : 0 + name = "/ecs/${local.prefix}-agent-runner" + retention_in_days = 90 + tags = var.tags +} + +resource "aws_ecs_task_definition" "agent_runner" { + count = var.enable_queue_mode ? 1 : 0 + + family = "${local.prefix}-agent-runner" + requires_compatibilities = ["FARGATE"] + network_mode = "awsvpc" + cpu = var.agent_runner_cpu + memory = var.agent_runner_memory + execution_role_arn = aws_iam_role.agent_runner_task_role[0].arn + task_role_arn = aws_iam_role.agent_runner_task_role[0].arn + + container_definitions = jsonencode([ + { + name = "${local.prefix}-agent-runner" + image = var.agent_runner_image_uri != null ? var.agent_runner_image_uri : module.docker_image[0].docker_image_uri + essential = true + + environment = [ + for k, v in merge( + var.environment_variables, + { + AK_EXECUTION__QUEUES__INPUT__URL = module.input_queue[0].queue_url + AK_EXECUTION__QUEUES__OUTPUT__URL = module.output_queue[0].queue_url + AK_EXECUTION__QUEUES__INPUT__MAX_RECEIVE_COUNT = tostring(max(1, var.sqs_input_max_receive_count - 1)) + }, + local.redis_url != null ? { AK_SESSION__REDIS__URL = local.redis_url } : {}, + local.dynamodb_memory_table_arn != null ? { + AK_SESSION__DYNAMODB__TABLE_NAME = local.dynamodb_memory_table_name + } : {} + ) : { name = k, value = v } + ] + + logConfiguration = { + logDriver = "awslogs" + options = { + "awslogs-group" = "/ecs/${local.prefix}-agent-runner" + "awslogs-region" = var.region + "awslogs-stream-prefix" = "ecs" + } + } + } + ]) + + tags = var.tags +} + +resource "aws_ecs_service" "agent_runner" { + count = var.enable_queue_mode ? 1 : 0 + + name = "${local.prefix}-agent-runner" + cluster = module.ecs.cluster_arn + task_definition = aws_ecs_task_definition.agent_runner[0].arn + desired_count = var.agent_runner_desired_count + launch_type = "FARGATE" + + network_configuration { + subnets = local.subnet_ids + security_groups = [aws_security_group.agent_runner[0].id] + assign_public_ip = false + } + + tags = var.tags +} + +# ---------- API Gateway — REST Async GET route ---------- +# Only needed for rest_async mode — adds GET /api/{version}/{endpoint}/{sessionId} + +resource "aws_apigatewayv2_integration" "async_get" { + count = var.enable_queue_mode && var.queue_mode_type == "async" ? 1 : 0 + api_id = aws_apigatewayv2_api.http_api.id + integration_type = "HTTP_PROXY" + integration_method = "ANY" + integration_uri = aws_lb_listener.http.arn + connection_type = "VPC_LINK" + connection_id = aws_apigatewayv2_vpc_link.ecs_alb.id + passthrough_behavior = "WHEN_NO_MATCH" + request_parameters = { + "overwrite:path" = "/api/v1/chat/{sessionId}" + } +} + +resource "aws_apigatewayv2_route" "async_get" { + count = var.enable_queue_mode && var.queue_mode_type == "async" ? 1 : 0 + api_id = aws_apigatewayv2_api.http_api.id + route_key = "GET ${local.api_base_segment_with_version}/${var.agent_endpoint}/{sessionId}" + target = "integrations/${aws_apigatewayv2_integration.async_get[0].id}" +} diff --git a/ak-deployment/ak-aws/containerized/variables.tf b/ak-deployment/ak-aws/containerized/variables.tf index e685a8d2f..2f69e25c1 100644 --- a/ak-deployment/ak-aws/containerized/variables.tf +++ b/ak-deployment/ak-aws/containerized/variables.tf @@ -227,4 +227,134 @@ variable "enable_mcp_server" { } data "aws_ecr_authorization_token" "token" {} -data "aws_caller_identity" "current" {} \ No newline at end of file +data "aws_caller_identity" "current" {} + +# --------------------------------------------------------------------------- +# Queue Mode Variables (ECS + SQS) +# --------------------------------------------------------------------------- + +variable "enable_queue_mode" { + type = bool + description = "Enable SQS queue mode. Creates Input/Output queues, DynamoDB response store, and Agent Runner ECS service." + default = false +} + +variable "queue_mode_type" { + type = string + description = "Queue mode type: 'sync' (client waits on same connection) or 'async' (client polls a separate GET endpoint)." + default = "sync" + validation { + condition = contains(["sync", "async"], var.queue_mode_type) + error_message = "queue_mode_type must be either 'sync' or 'async'." + } +} + +# --- SQS shared --- + +variable "sqs_managed_sse_enabled" { + type = bool + description = "Enable SQS-managed server-side encryption for both queues." + default = true +} + +variable "sqs_max_message_size" { + type = number + description = "Maximum SQS message size in bytes (applies to both queues)." + default = 262144 # 256 KB +} + +variable "sqs_receive_wait_time_seconds" { + type = number + description = "Long-poll wait time for ReceiveMessage calls (applies to both queues)." + default = 0 +} + +# --- Input Queue --- + +variable "sqs_input_visibility_timeout" { + type = number + description = "Visibility timeout (seconds) for the Input Queue. Should be >= agent processing time." + default = 60 +} + +variable "sqs_input_message_retention_seconds" { + type = number + description = "How long messages stay in the Input Queue before being automatically deleted." + default = 1800 # 30 minutes +} + +variable "sqs_input_max_receive_count" { + type = number + description = "Number of times a message can be received before being sent to the DLQ (if enabled)." + default = 5 +} + +variable "sqs_input_create_dlq" { + type = bool + description = "Create a dead-letter queue for the Input Queue." + default = false +} + +variable "sqs_input_dlq_message_retention_seconds" { + type = number + description = "How long messages stay in the Input DLQ." + default = 1800 +} + +# --- Output Queue --- + +variable "sqs_output_visibility_timeout" { + type = number + description = "Visibility timeout (seconds) for the Output Queue." + default = 60 +} + +variable "sqs_output_message_retention_seconds" { + type = number + description = "How long messages stay in the Output Queue before being automatically deleted." + default = 1800 +} + +variable "sqs_output_max_receive_count" { + type = number + description = "Number of times a message can be received before being sent to the DLQ (if enabled)." + default = 5 +} + +variable "sqs_output_create_dlq" { + type = bool + description = "Create a dead-letter queue for the Output Queue." + default = false +} + +variable "sqs_output_dlq_message_retention_seconds" { + type = number + description = "How long messages stay in the Output DLQ." + default = 1800 +} + +# --- Agent Runner ECS Service --- + +variable "agent_runner_cpu" { + type = number + description = "Fargate CPU units for the Agent Runner ECS task." + default = 512 +} + +variable "agent_runner_memory" { + type = number + description = "Fargate memory (MiB) for the Agent Runner ECS task." + default = 1024 +} + +variable "agent_runner_desired_count" { + type = number + description = "Desired number of Agent Runner ECS tasks." + default = 1 +} + +variable "agent_runner_image_uri" { + type = string + description = "Docker image URI for the Agent Runner ECS task. Defaults to the same image as the REST Service if not set." + default = null +} diff --git a/ak-py/src/agentkernel/deployment/aws/__init__.py b/ak-py/src/agentkernel/deployment/aws/__init__.py index 416f5e19d..f8b254d00 100644 --- a/ak-py/src/agentkernel/deployment/aws/__init__.py +++ b/ak-py/src/agentkernel/deployment/aws/__init__.py @@ -11,6 +11,7 @@ except importlib.metadata.PackageNotFoundError: __version__ = "0.1.0" +from .containerized import ECSAgentRunner, ECSRESTService from .core.sqs_handler import SQSHandler from .serverless import APIGatewayAuthorizer, Lambda, ResponseHandler, ServerlessAgentRunner, WebsocketConnectionHandler from .serverless.core import LambdaSQSConsumer diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/__init__.py b/ak-py/src/agentkernel/deployment/aws/containerized/__init__.py new file mode 100644 index 000000000..bdb06997c --- /dev/null +++ b/ak-py/src/agentkernel/deployment/aws/containerized/__init__.py @@ -0,0 +1,2 @@ +from .akagentrunner import ECSAgentRunner +from .akrestservice import ECSRESTService diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/akagentrunner.py b/ak-py/src/agentkernel/deployment/aws/containerized/akagentrunner.py new file mode 100644 index 000000000..ea5f83e92 --- /dev/null +++ b/ak-py/src/agentkernel/deployment/aws/containerized/akagentrunner.py @@ -0,0 +1,143 @@ +from __future__ import annotations + +import json +import logging + +from ....core.chat_service import ChatService +from ....core.config import AKConfig +from ....core.model import BaseRunRequest +from ..core.sqs_handler import SQSHandler +from .sqs_poller import SQSPoller + + +class ECSAgentRunner: + """ + ECS Agent Runner — polls the Input Queue, runs the agent, and puts + the result on the Output Queue. + + This is the ECS equivalent of ``ServerlessAgentRunner``. Instead of + being triggered by a Lambda Event Source Mapping, it runs a blocking + ``SQSPoller`` loop (meant to be the container's main process). + + Usage:: + + if __name__ == "__main__": + ECSAgentRunner.run() + """ + + _log = logging.getLogger("ak.ecs.agentrunner") + _chat_service: ChatService | None = None + _config = AKConfig.get() + + # ------------------------------------------------------------------ + # Public entrypoint + # ------------------------------------------------------------------ + + @classmethod + def run(cls) -> None: + """ + Start the blocking poll loop. Call this as the container entry-point. + """ + config = cls._config + input_queue_url = config.execution.queues.input.url + max_receive_count = config.execution.queues.input.max_receive_count + + if not input_queue_url: + raise ValueError( + "AK_EXECUTION__QUEUES__INPUT__URL is required for ECSAgentRunner" + ) + + cls._log.info(f"ECSAgentRunner starting — input queue: {input_queue_url}") + + poller = SQSPoller( + queue_url=input_queue_url, + process_fn=cls.process_message, + max_receive_count=max_receive_count, + on_permanent_failure_fn=cls.on_permanent_failure, + ) + poller.run() # blocks forever + + # ------------------------------------------------------------------ + # Message processing (same logic as ServerlessAgentRunner) + # ------------------------------------------------------------------ + + @classmethod + def _get_chat_service(cls) -> ChatService: + if cls._chat_service is None: + cls._chat_service = ChatService() + return cls._chat_service + + @classmethod + def _get_record_attributes(cls, raw_queue_message: dict) -> dict: + """ + Extract routing attributes from a raw SQS message. + + :param raw_queue_message: boto3 SQS message dict + :return: Extracted attributes dict + :raises ValueError: If request_id is missing + """ + attributes = SQSHandler.get_message_system_attributes(raw_queue_message) + message_attributes = SQSHandler.get_message_custom_attributes(raw_queue_message) + + request_id = message_attributes.get("request_id") + if not request_id: + raise ValueError("request_id is required in SQS message attributes") + + return { + "message_group_id": attributes.get("MessageGroupId"), + "message_deduplication_id": attributes.get("MessageDeduplicationId"), + "request_id": request_id, + "user_id": message_attributes.get("user_id"), + } + + @classmethod + def _send_to_output_queue(cls, message_body: dict, record_attributes: dict) -> None: + SQSHandler.send_message_to_output_queue( + message_group_id=record_attributes["message_group_id"], + message_deduplication_id=record_attributes["message_deduplication_id"], + message_body=message_body, + request_id=record_attributes["request_id"], + user_id=record_attributes["user_id"], + ) + + @classmethod + def process_message(cls, record: dict) -> None: + """ + Process a single SQS message: run the agent and put the response + on the Output Queue. + + :param record: boto3 SQS message dict + """ + cls._log.info(f"Processing message {record.get('MessageId')}") + body = BaseRunRequest.model_validate(json.loads(record["Body"])) + _, agent_response = cls._get_chat_service().process_chat_request(req=body) + record_attributes = cls._get_record_attributes(raw_queue_message=record) + cls._send_to_output_queue(message_body=agent_response, record_attributes=record_attributes) + cls._log.info( + f"Sent response to output queue: {SQSHandler.get_output_queue_url()}" + ) + + @classmethod + def on_permanent_failure(cls, record: dict) -> None: + """ + Handle a message that exceeded max retries — send an error response + to the Output Queue so the REST Service can return it to the caller. + + :param record: boto3 SQS message dict + """ + cls._log.error( + f"Permanent failure for message {record.get('MessageId')}" + ) + try: + record_attributes = cls._get_record_attributes(raw_queue_message=record) + error_body = { + "error": f"Failed to process message after " + f"{cls._config.execution.queues.input.max_receive_count} retries" + } + cls._send_to_output_queue( + message_body=error_body, record_attributes=record_attributes + ) + except Exception: + cls._log.exception( + "Failed to send permanent-failure error to output queue" + ) diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/akrestservice.py b/ak-py/src/agentkernel/deployment/aws/containerized/akrestservice.py new file mode 100644 index 000000000..4e7c574fd --- /dev/null +++ b/ak-py/src/agentkernel/deployment/aws/containerized/akrestservice.py @@ -0,0 +1,265 @@ +from __future__ import annotations + +import json +import logging +import threading +from typing import Any, Dict + +from ....api.http import RESTAPI +from ....core.config import AKConfig +from ....core.model import ExecutionMode +from ..core.response_store import ResponseDBHandler +from ..core.sqs_handler import SQSHandler +from ..core.websocket_service import WebSocketHandler +from .sqs_poller import SQSPoller + + +class ECSRESTService: + """ + ECS REST Service — handles all queue-based execution modes: + + - **REST Sync** (``execution.mode = rest_sync``) + Thread 2 writes output messages to the DynamoDB Response Store. + Thread 1 (FastAPI) waits on the same store and returns the response + on the original HTTP connection. + + - **REST Async** (``execution.mode = rest_async``) + Same as REST Sync for the output-queue side. Thread 1 exposes a + separate ``GET`` endpoint that the client polls. + + - **WebSocket / Async** (``execution.mode = async``) + Thread 2 pushes output messages directly to the client over the + still-open WebSocket connection via API Gateway Management API + (PostToConnection). No DynamoDB response store is used. + + Runs two threads: + + - **Thread 1** — FastAPI/uvicorn (via ``RESTAPI.run``). + - **Thread 2** — ``SQSPoller`` daemon on the Output Queue. + + Can be used standalone — just set ``execution.mode`` in ``config.yaml`` + (or via env vars) and call ``ECSRESTService.run()``. + + Usage:: + + from agentkernel.deployment.aws.containerized import ECSRESTService + + if __name__ == "__main__": + ECSRESTService.run() + """ + + _log = logging.getLogger("ak.ecs.restservice") + _config = AKConfig.get() + + # lazily initialised, shared across Thread 2 calls + _response_store = None + _websocket_handler = None + + # ------------------------------------------------------------------ + # Public entrypoint + # ------------------------------------------------------------------ + + @classmethod + def run(cls) -> None: + """ + Start the output-queue poller (Thread 2) as a daemon, then start + the REST API (Thread 1) in the main thread. Blocks until the + REST API exits. + """ + output_queue_url = cls._config.execution.queues.output.url + max_receive_count = cls._config.execution.queues.output.max_receive_count + + if not output_queue_url: + raise ValueError( + "AK_EXECUTION__QUEUES__OUTPUT__URL is required for ECSRESTService" + ) + + mode = cls._config.execution.mode + cls._log.info( + f"ECSRESTService starting — mode={mode} output_queue={output_queue_url}" + ) + + poller = SQSPoller( + queue_url=output_queue_url, + process_fn=cls.process_output_message, + max_receive_count=max_receive_count, + on_permanent_failure_fn=cls.on_permanent_failure, + ) + t2 = threading.Thread( + target=poller.run, name="output-queue-poller", daemon=True + ) + t2.start() + cls._log.info("ECSRESTService: Thread 2 (output-queue poller) started") + + cls._log.info("ECSRESTService: starting REST API (Thread 1)") + RESTAPI.run() + + # ------------------------------------------------------------------ + # Lazy initialisers + # ------------------------------------------------------------------ + + @classmethod + def _get_response_store(cls): + if cls._response_store is None: + cls._response_store = ResponseDBHandler().get_store() + return cls._response_store + + @classmethod + def _get_websocket_handler(cls) -> WebSocketHandler: + if cls._websocket_handler is None: + ws_config = cls._config.websocket_api + if not ws_config.connection_table or not ws_config.connection_table.table_name: + raise ValueError( + "websocket_api.connection_table.table_name is required " + "for ECSRESTService in WebSocket mode" + ) + cls._websocket_handler = WebSocketHandler( + conn_table_name=ws_config.connection_table.table_name, + ttl=ws_config.connection_table.ttl, + ) + return cls._websocket_handler + + # ------------------------------------------------------------------ + # Output-queue message handlers (Thread 2) + # These are public so callers can also invoke them directly if needed. + # ------------------------------------------------------------------ + + @classmethod + def process_output_message(cls, record: Dict[str, Any]) -> None: + """ + Process one message from the Output Queue. + + Dispatches based on ``execution.mode``: + + - ``async`` → push via WebSocket (PostToConnection) + - ``rest_sync`` / ``rest_async`` → write to DynamoDB Response Store + + :param record: boto3 SQS ``receive_message`` record + """ + cls._log.info(f"Processing output message {record.get('MessageId')}") + + if cls._config.execution.mode == ExecutionMode.ASYNC: + cls._broadcast_via_websocket(record) + else: + message = cls._construct_message_for_store(record) + cls._get_response_store().add_message(message) + cls._log.info( + f"Stored response — session_id={message['session_id']} " + f"request_id={message['request_id']}" + ) + + @classmethod + def on_permanent_failure(cls, record: Dict[str, Any]) -> None: + """ + Handle an output message that exceeded ``max_receive_count``. + + - ``async`` mode → broadcast error via WebSocket + - Other modes → write error entry to Response Store so the + waiting HTTP caller gets a response instead of + hanging indefinitely + + :param record: boto3 SQS ``receive_message`` record + """ + max_retries = cls._config.execution.queues.output.max_receive_count + cls._log.error( + f"Permanent failure for output message {record.get('MessageId')} " + f"after {max_retries} retries" + ) + + try: + message_attributes = SQSHandler.get_message_custom_attributes(record) + request_id = message_attributes.get("request_id") + error_payload = { + "error": f"Failed to process message after {max_retries} retries", + "request_id": request_id, + } + + if cls._config.execution.mode == ExecutionMode.ASYNC: + endpoint_url = message_attributes.get("endpoint_url") + user_id = message_attributes.get("user_id") + if endpoint_url and user_id: + cls._get_websocket_handler().broadcast( + endpoint_url=endpoint_url, + message=error_payload, + user_id=user_id, + ) + else: + cls._log.warning( + "Cannot broadcast permanent-failure error: " + "endpoint_url or user_id missing" + ) + else: + error_body = json.dumps(error_payload) + message = cls._construct_message_for_store(record, body=error_body) + cls._get_response_store().add_message(message) + cls._log.info( + f"Stored permanent-failure error — " + f"session_id={message['session_id']} " + f"request_id={message['request_id']}" + ) + except Exception: + cls._log.exception( + "Failed to handle permanent-failure output message" + ) + + # ------------------------------------------------------------------ + # Internal helpers + # ------------------------------------------------------------------ + + @classmethod + def _construct_message_for_store( + cls, record: Dict[str, Any], body: Any = None + ) -> Dict[str, Any]: + """ + Build the dict to write into the Response Store. + + :param record: boto3 SQS record + :param body: Override body (string or dict). Defaults to record["Body"]. + :raises ValueError: If request_id is missing from message attributes. + """ + message_body = body if body is not None else record.get("Body", "{}") + if isinstance(message_body, str): + message_body = json.loads(message_body) + + message_attributes = SQSHandler.get_message_custom_attributes(record) + request_id = message_attributes.get("request_id") + if not request_id: + raise ValueError("request_id is required in SQS message attributes") + + return { + "session_id": message_body.get("session_id"), + "request_id": request_id, + "body": message_body, + } + + @classmethod + def _broadcast_via_websocket(cls, record: Dict[str, Any]) -> None: + """ + Push an output message to the client over WebSocket. + + :param record: boto3 SQS record + :raises ValueError: If endpoint_url or user_id is missing. + """ + message_attributes = SQSHandler.get_message_custom_attributes(record) + endpoint_url = message_attributes.get("endpoint_url") + user_id = message_attributes.get("user_id") + + if not endpoint_url: + raise ValueError( + "endpoint_url is required in SQS message attributes for ASYNC mode" + ) + if not user_id: + raise ValueError( + "user_id is required in SQS message attributes for ASYNC mode" + ) + + message_body = record.get("Body", "{}") + if isinstance(message_body, str): + message_body = json.loads(message_body) + + cls._log.info( + f"Broadcasting via WebSocket — user_id={user_id} endpoint={endpoint_url}" + ) + cls._get_websocket_handler().broadcast( + endpoint_url=endpoint_url, message=message_body, user_id=user_id + ) diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/sqs_poller.py b/ak-py/src/agentkernel/deployment/aws/containerized/sqs_poller.py new file mode 100644 index 000000000..5964e6a5d --- /dev/null +++ b/ak-py/src/agentkernel/deployment/aws/containerized/sqs_poller.py @@ -0,0 +1,119 @@ +from __future__ import annotations + +import logging +import time +from typing import Callable + +import boto3 + +_log = logging.getLogger("ak.ecs.sqs_poller") + + +class SQSPoller: + """ + Polls an SQS queue in a tight loop and delegates each message to a + caller-supplied ``process_fn``. + + Intended to run as a background daemon thread (Thread 2 in the ECS + REST-Service, or the main loop in the Agent Runner service). + + On success the message is deleted from the queue. + On failure the message is left in the queue so that the visibility + timeout returns it for a retry — exactly mirroring the Lambda ESM + behaviour. + + :param queue_url: SQS queue URL to poll. + :param process_fn: Callable that receives a single raw boto3 SQS + message dict (same shape as a Lambda ``Records`` entry). + :param max_receive_count: Messages received more than this many times + are passed to ``on_permanent_failure_fn`` instead of + ``process_fn`` and then deleted. + :param on_permanent_failure_fn: Optional callback for permanently + failed messages. Receives the raw message dict. + :param wait_seconds: Long-poll duration (0–20 s). Defaults to 20. + :param batch_size: Number of messages to fetch per poll (1–10). + """ + + def __init__( + self, + queue_url: str, + process_fn: Callable[[dict], None], + max_receive_count: int = 3, + on_permanent_failure_fn: Callable[[dict], None] | None = None, + wait_seconds: int = 20, + batch_size: int = 10, + ) -> None: + self._queue_url = queue_url + self._process_fn = process_fn + self._max_receive_count = max_receive_count + self._on_permanent_failure_fn = on_permanent_failure_fn + self._wait_seconds = wait_seconds + self._batch_size = batch_size + self._client: object | None = None + + # ------------------------------------------------------------------ + # Public API + # ------------------------------------------------------------------ + + def run(self) -> None: + """Block forever, polling the queue. Call from a dedicated thread.""" + _log.info(f"SQSPoller starting — queue: {self._queue_url}") + client = self._get_client() + while True: + try: + self._poll_once(client) + except Exception: + _log.exception("Unexpected error in poll loop, sleeping 5 s before retry") + time.sleep(5) + + # ------------------------------------------------------------------ + # Internal + # ------------------------------------------------------------------ + + def _get_client(self): + if self._client is None: + self._client = boto3.client("sqs") + return self._client + + def _poll_once(self, client) -> None: + resp = client.receive_message( + QueueUrl=self._queue_url, + MaxNumberOfMessages=self._batch_size, + WaitTimeSeconds=self._wait_seconds, + AttributeNames=["All"], + MessageAttributeNames=["All"], + ) + for msg in resp.get("Messages", []): + self._handle_message(client, msg) + + def _handle_message(self, client, msg: dict) -> None: + message_id = msg.get("MessageId", "") + receive_count = int(msg.get("Attributes", {}).get("ApproximateReceiveCount", "1")) + + try: + if receive_count > self._max_receive_count: + _log.warning( + f"Message {message_id} exceeded max receive count " + f"({receive_count} > {self._max_receive_count})" + ) + if self._on_permanent_failure_fn: + self._on_permanent_failure_fn(msg) + # Delete so it stops cycling + self._delete_message(client, msg) + return + + self._process_fn(msg) + self._delete_message(client, msg) + + except Exception: + _log.exception( + f"Failed to process message {message_id} — " + "leaving in queue for visibility-timeout retry" + ) + # Do NOT delete — visibility timeout will return it for retry + + def _delete_message(self, client, msg: dict) -> None: + client.delete_message( + QueueUrl=self._queue_url, + ReceiptHandle=msg["ReceiptHandle"], + ) diff --git a/examples/aws-containerized/openai-dynamodb-scalable/.gitignore b/examples/aws-containerized/openai-dynamodb-scalable/.gitignore new file mode 100644 index 000000000..a48920cb5 --- /dev/null +++ b/examples/aws-containerized/openai-dynamodb-scalable/.gitignore @@ -0,0 +1,11 @@ +.terraform +requirements.txt +dist +dist.zip +*.tfstate +*.tfstate.backup + +# Auto-injected backend files (optional for CI/CD) +deploy/backend.tf +dist-agent-runner +dist-rest-service \ No newline at end of file diff --git a/examples/aws-containerized/openai-dynamodb-scalable/README.md b/examples/aws-containerized/openai-dynamodb-scalable/README.md new file mode 100644 index 000000000..3f72ea56b --- /dev/null +++ b/examples/aws-containerized/openai-dynamodb-scalable/README.md @@ -0,0 +1,80 @@ +# OpenAI Agents on ECS — Scalable (SQS Queue Mode) + +Agent Kernel running OpenAI Agents SDK agents on AWS ECS in **REST Sync queue mode**, +using SQS queues to decouple the HTTP request from agent processing and DynamoDB as +both agent memory (session store) and the response store. + +## Architecture + +``` +Client + │ POST /api/v1/chat + ▼ +API Gateway → ALB + ▼ +REST Service ECS task ──── sends to ──► Input SQS Queue + │ (Thread 1: FastAPI) │ + │ (Thread 2: output-queue poller) ▼ + │ Agent Runner ECS task + │ (polls Input Queue, + │ runs agent, + │ sends to Output Queue) + │ │ + │ ▼ + │ Output SQS Queue + │ │ + │◄─── Thread 2 writes ───────────────────────┘ + │ to DynamoDB Response Store + │ + └─── Thread 1 reads from DynamoDB Response Store + and returns response to client +``` + +Two Docker images are built: + +| Image | Source | Role | +|-------|--------|------| +| `dist-rest-service/` | `app_rest_service.py` | FastAPI API + output-queue poller | +| `dist-agent-runner/` | `app_agent_runner.py` | Input-queue consumer, runs the agent | + +## Deployed Resources + +- API Gateway (HTTP) + ALB → REST Service ECS task +- REST Service ECS service (Thread 1 + Thread 2) +- Agent Runner ECS service (separate Fargate task, no ALB) +- Input SQS FIFO Queue +- Output SQS FIFO Queue +- DynamoDB Response Store (TTL-enabled, temporary response buffer) +- DynamoDB Session Store (agent memory) + +## Prerequisites + +- AWS CLI configured with appropriate credentials +- Terraform `>= 1.9.5` +- `uv` Python package manager + +## Deployment + +1. Set environment variables: + ```bash + export TF_VAR_openai_api_key= + export TF_VAR_vpc_id= + export TF_VAR_private_subnet_ids='["subnet-xxx","subnet-yyy"]' + ``` + +2. Build and deploy: + ```bash + cd deploy && ./deploy.sh # from PyPI + cd deploy && ./deploy.sh local # from local ak-py build + ``` + +## Testing + +```bash +curl -X POST \ + -H "Content-Type: application/json" \ + -d '{"prompt": "What is 12 * 9?", "session_id": "test-1"}' +``` + +The request blocks on the same HTTP connection until the Agent Runner +finishes processing and the response is available in DynamoDB. diff --git a/examples/aws-containerized/openai-dynamodb-scalable/app.py b/examples/aws-containerized/openai-dynamodb-scalable/app.py new file mode 100644 index 000000000..9d20ccecf --- /dev/null +++ b/examples/aws-containerized/openai-dynamodb-scalable/app.py @@ -0,0 +1,26 @@ +from agentkernel.api import RESTAPI +from agentkernel.openai import OpenAIModule +from agents import Agent + +math_agent = Agent( + name="math", + handoff_description="Specialist agent for math questions", + instructions="You provide help with math problems. Explain your reasoning at each step and include examples. \ + If prompted for anything else you refuse to answer.", +) + +history_agent = Agent( + name="history", + handoff_description="Specialist agent for historical questions", + instructions="You provide assistance with historical queries. Explain important events and context clearly.", +) + +triage_agent = Agent( + name="triage", + instructions="You determine which agent to use based on the user's question.", + handoffs=[history_agent, math_agent], +) + +OpenAIModule([triage_agent, math_agent, history_agent]) + +runner = RESTAPI.run diff --git a/examples/aws-containerized/openai-dynamodb-scalable/app_agent_runner.py b/examples/aws-containerized/openai-dynamodb-scalable/app_agent_runner.py new file mode 100644 index 000000000..22fe8e479 --- /dev/null +++ b/examples/aws-containerized/openai-dynamodb-scalable/app_agent_runner.py @@ -0,0 +1,30 @@ +from agentkernel.deployment.aws.containerized import ECSAgentRunner +from agentkernel.openai import OpenAIModule +from agents import Agent + +math_agent = Agent( + name="math", + handoff_description="Specialist agent for math questions", + instructions="You provide help with math problems. Explain your reasoning at each step and include examples. \ + If prompted for anything else you refuse to answer.", +) + +history_agent = Agent( + name="history", + handoff_description="Specialist agent for historical questions", + instructions="You provide assistance with historical queries. Explain important events and context clearly.", +) + +triage_agent = Agent( + name="triage", + instructions="You determine which agent to use based on the user's question.", + handoffs=[history_agent, math_agent], +) + +OpenAIModule([triage_agent, math_agent, history_agent]) + +# Agent Runner entrypoint — polls Input Queue, runs agent, sends to Output Queue. +handler = ECSAgentRunner.run + +if __name__ == "__main__": + handler() diff --git a/examples/aws-containerized/openai-dynamodb-scalable/app_rest_service.py b/examples/aws-containerized/openai-dynamodb-scalable/app_rest_service.py new file mode 100644 index 000000000..a4947f137 --- /dev/null +++ b/examples/aws-containerized/openai-dynamodb-scalable/app_rest_service.py @@ -0,0 +1,11 @@ +from agentkernel.deployment.aws.containerized import ECSRESTService + +# REST Service entrypoint — no agent definitions needed here. +# Thread 1: FastAPI handles POST /chat (enqueues to Input Queue, waits on DynamoDB) +# and GET /chat/{sessionId} (REST Async mode only) +# Thread 2: Output-queue poller — writes responses to DynamoDB (sync/async) +# or pushes via WebSocket PostToConnection (async/WS mode) +runner = ECSRESTService.run + +if __name__ == "__main__": + runner() diff --git a/examples/aws-containerized/openai-dynamodb-scalable/app_test.py b/examples/aws-containerized/openai-dynamodb-scalable/app_test.py new file mode 100644 index 000000000..684c5f06f --- /dev/null +++ b/examples/aws-containerized/openai-dynamodb-scalable/app_test.py @@ -0,0 +1,52 @@ +import os +import uuid + +import httpx +import pytest +import pytest_asyncio +from agentkernel.test import Test + +pytestmark = pytest.mark.asyncio(loop_scope="session") # uses a single session for all tests + + +class APITestClient: + def __init__(self, url): + self.url = url + self.session_id = str(uuid.uuid4()) + + async def send(self, prompt, endpoint: str = "", additional_context=None, body=None): + payload = ( + { + "prompt": prompt, + "session_id": self.session_id, + "agent": "triage", + "additional_context": additional_context, + } + if body is None + else body + ) + async with httpx.AsyncClient(timeout=30.0) as client: + resp = await client.post(f"{self.url}{endpoint}", json=payload) + resp.raise_for_status() + data = resp.json() + return data.get("result", "") + + +@pytest_asyncio.fixture(scope="session", loop_scope="session") +async def http_client(): + endpoint = os.getenv("AK_TEST_ENDPOINT") + yield APITestClient(endpoint) + + +@pytest.mark.asyncio +@pytest.mark.order(1) +async def test_history_agent(http_client): + response = await http_client.send("Who won the 1996 cricket world cup?") + Test.compare(response, ["Sri Lanka won the 1996 cricket world cup."]) + + +@pytest.mark.asyncio +@pytest.mark.order(2) +async def test_history_agent_followup(http_client): + response = await http_client.send("Who hosted?") + Test.compare(response, ["Sri Lanka, India and Pakistan"]) diff --git a/examples/aws-containerized/openai-dynamodb-scalable/build.sh b/examples/aws-containerized/openai-dynamodb-scalable/build.sh new file mode 100755 index 000000000..09cc68e4e --- /dev/null +++ b/examples/aws-containerized/openai-dynamodb-scalable/build.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +set -euo pipefail +if command -v pyenv >/dev/null 2>&1; then + uv venv --python "$(pyenv which python)" --allow-existing +else + uv venv --allow-existing +fi + +if [[ ${1-} != "local" ]]; then + uv sync --all-extras +else + # For local development of agentkernel, you can force reinstall from local dist + uv sync --find-links ../../../ak-py/dist --all-extras + uv pip install --force-reinstall --find-links ../../../ak-py/dist agentkernel[api,openai,aws,test] || true +fi \ No newline at end of file diff --git a/examples/aws-containerized/openai-dynamodb-scalable/config.yaml b/examples/aws-containerized/openai-dynamodb-scalable/config.yaml new file mode 100644 index 000000000..35a959364 --- /dev/null +++ b/examples/aws-containerized/openai-dynamodb-scalable/config.yaml @@ -0,0 +1,25 @@ +logging: + ak: + level: DEBUG + +session: + type: dynamodb + dynamodb: + table_name: "ak-oai-scl-ecs-dev-scalable-session_store" + +execution: + mode: rest_sync + queues: + input: + url: "" # injected by Terraform via AK_EXECUTION__QUEUES__INPUT__URL + max_receive_count: 4 + output: + url: "" # injected by Terraform via AK_EXECUTION__QUEUES__OUTPUT__URL + max_receive_count: 4 + response_store: + type: dynamodb + retry_count: 10 + delay: 3 + dynamodb: + table_name: "" # injected by Terraform via AK_EXECUTION__RESPONSE_STORE__DYNAMODB__TABLE_NAME + ttl: 300 diff --git a/examples/aws-containerized/openai-dynamodb-scalable/deploy/Dockerfile b/examples/aws-containerized/openai-dynamodb-scalable/deploy/Dockerfile new file mode 100644 index 000000000..b69be5076 --- /dev/null +++ b/examples/aws-containerized/openai-dynamodb-scalable/deploy/Dockerfile @@ -0,0 +1,8 @@ +FROM python:3.12-slim + +WORKDIR /app +COPY data/ /app/ + +EXPOSE 8000 + +CMD ["python", "-c", "from app import runner; runner()"] \ No newline at end of file diff --git a/examples/aws-containerized/openai-dynamodb-scalable/deploy/Dockerfile.agent-runner b/examples/aws-containerized/openai-dynamodb-scalable/deploy/Dockerfile.agent-runner new file mode 100644 index 000000000..636d96f63 --- /dev/null +++ b/examples/aws-containerized/openai-dynamodb-scalable/deploy/Dockerfile.agent-runner @@ -0,0 +1,6 @@ +FROM python:3.12-slim + +WORKDIR /app +COPY data/ /app/ + +CMD ["python", "app_agent_runner.py"] diff --git a/examples/aws-containerized/openai-dynamodb-scalable/deploy/Dockerfile.rest-service b/examples/aws-containerized/openai-dynamodb-scalable/deploy/Dockerfile.rest-service new file mode 100644 index 000000000..512cacaeb --- /dev/null +++ b/examples/aws-containerized/openai-dynamodb-scalable/deploy/Dockerfile.rest-service @@ -0,0 +1,8 @@ +FROM python:3.12-slim + +WORKDIR /app +COPY data/ /app/ + +EXPOSE 8000 + +CMD ["python", "app_rest_service.py"] diff --git a/examples/aws-containerized/openai-dynamodb-scalable/deploy/deploy.sh b/examples/aws-containerized/openai-dynamodb-scalable/deploy/deploy.sh new file mode 100755 index 000000000..485704ce1 --- /dev/null +++ b/examples/aws-containerized/openai-dynamodb-scalable/deploy/deploy.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +set -e + +create_deployment_packages() { + local mode="${1-}" + pushd ../ + + uv export --no-hashes > requirements.txt + + for target in dist-rest-service dist-agent-runner; do + rm -rf "$target" + mkdir -p "$target/data" + + if [[ "$mode" != "local" ]]; then + uv pip install -r requirements.txt --target="$target/data" + else + uv pip install -r requirements.txt --target="$target/data" \ + --find-links ../../../ak-py/dist + uv pip install --force-reinstall --target="$target/data" \ + --find-links ../../../ak-py/dist \ + agentkernel[adk,api,aws,test] || true + fi + + cp config.yaml "$target/data/" + done + + cp app_rest_service.py dist-rest-service/data/ + cp deploy/Dockerfile.rest-service dist-rest-service/Dockerfile + + cp app_agent_runner.py dist-agent-runner/data/ + cp deploy/Dockerfile.agent-runner dist-agent-runner/Dockerfile + + rm -f requirements.txt + popd || exit 1 +} + +create_deployment_packages "$1" +exit 1 +terrafor init +terraform apply diff --git a/examples/aws-containerized/openai-dynamodb-scalable/deploy/main.tf b/examples/aws-containerized/openai-dynamodb-scalable/deploy/main.tf new file mode 100644 index 000000000..4f6b97d90 --- /dev/null +++ b/examples/aws-containerized/openai-dynamodb-scalable/deploy/main.tf @@ -0,0 +1,106 @@ +provider "aws" { + region = var.region +} + +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 6.11.0" + } + docker = { + source = "kreuzwerker/docker" + version = "3.6.2" + } + } + required_version = ">= 1.9.5" +} + +data "aws_ecr_authorization_token" "token" {} +data "aws_caller_identity" "current" {} + +provider "docker" { + registry_auth { + address = format("%v.dkr.ecr.%v.amazonaws.com", data.aws_caller_identity.current.account_id, var.region) + username = data.aws_ecr_authorization_token.token.user_name + password = data.aws_ecr_authorization_token.token.password + } +} + +# --------------------------------------------------------------------------- +# Agent Runner image — built from dist-agent-runner/ +# --------------------------------------------------------------------------- +module "agent_runner_image" { + # Points to the local ECR module. + # Switch to registry once published: + # source = "yaalalabs/ak-common/aws//modules/ecr" + # version = "0.5.0" + source = "../../../../ak-deployment/ak-aws/common/modules/ecr" + + env_alias = var.env_alias + module_name = "${var.module_name}-runner" + product_alias = var.product_alias + source_path = "../dist-agent-runner" +} + +# --------------------------------------------------------------------------- +# Main containerized deployment — REST Sync queue mode +# +# REST Service image → dist-rest-service/ (FastAPI + output-queue poller) +# Agent Runner image → dist-agent-runner/ (Input Queue poller) +# +# Flow: client → API GW → ALB → REST Service → Input Queue +# → Agent Runner → Output Queue → REST Service (Thread 2) +# → DynamoDB Response Store → REST Service (Thread 1) → client +# --------------------------------------------------------------------------- +module "containerized_agents" { + # Points to the local module which includes queue-mode support. + # Switch to the registry source once a new version is published: + # source = "yaalalabs/ak-containerized/aws" + # version = "0.5.0" + source = "../../../../ak-deployment/ak-aws/containerized" + + # ---- identity ---- + product_alias = var.product_alias + env_alias = var.env_alias + module_name = var.module_name + region = var.region + product_display_name = "OpenAI Agents — Scalable" + + # ---- networking ---- + vpc_id = var.vpc_id + private_subnet_ids = var.private_subnet_ids + + # ---- REST Service image (Thread 1 + Thread 2) ---- + package_path = "../dist-rest-service" + container_type = "ecs" + ecs_container_port = 8000 + + # ---- agent memory (session store) ---- + create_dynamodb_memory_table = true + + # ---- queue mode ---- + enable_queue_mode = true + queue_mode_type = "sync" + + # Agent Runner uses its own image (different CMD) + agent_runner_image_uri = module.agent_runner_image.docker_image_uri + + # SQS visibility timeout should exceed agent processing time + sqs_input_visibility_timeout = 120 + sqs_output_visibility_timeout = 60 + + # Agent Runner Fargate sizing + agent_runner_cpu = 1024 + agent_runner_memory = 2048 + agent_runner_desired_count = 1 + + # Environment variables for both containers + environment_variables = { + OPENAI_API_KEY = var.openai_api_key + } + + tags = { + Example = "openai-dynamodb-scalable" + } +} diff --git a/examples/aws-containerized/openai-dynamodb-scalable/deploy/outputs.tf b/examples/aws-containerized/openai-dynamodb-scalable/deploy/outputs.tf new file mode 100644 index 000000000..f7ba7d1ac --- /dev/null +++ b/examples/aws-containerized/openai-dynamodb-scalable/deploy/outputs.tf @@ -0,0 +1,24 @@ +output "agent_invoke_url" { + description = "POST to this URL to chat with the agent (REST Sync queue mode)" + value = module.containerized_agents.agent_invoke_url +} + +output "input_queue_url" { + description = "SQS Input Queue URL" + value = module.containerized_agents.input_queue_url +} + +output "output_queue_url" { + description = "SQS Output Queue URL" + value = module.containerized_agents.output_queue_url +} + +output "response_store_table_name" { + description = "DynamoDB Response Store table name" + value = module.containerized_agents.response_store_table_name +} + +output "agent_runner_service_name" { + description = "ECS Agent Runner service name" + value = module.containerized_agents.agent_runner_service_name +} diff --git a/examples/aws-containerized/openai-dynamodb-scalable/deploy/terraform.tfvars b/examples/aws-containerized/openai-dynamodb-scalable/deploy/terraform.tfvars new file mode 100644 index 000000000..eb222002a --- /dev/null +++ b/examples/aws-containerized/openai-dynamodb-scalable/deploy/terraform.tfvars @@ -0,0 +1,4 @@ +region = "ap-southeast-2" +product_alias = "ak-oai-scl-ecs" +env_alias = "dev" +module_name = "scalable" diff --git a/examples/aws-containerized/openai-dynamodb-scalable/deploy/variables.tf b/examples/aws-containerized/openai-dynamodb-scalable/deploy/variables.tf new file mode 100644 index 000000000..701e5adde --- /dev/null +++ b/examples/aws-containerized/openai-dynamodb-scalable/deploy/variables.tf @@ -0,0 +1,40 @@ +variable "region" { + type = string + description = "Region" +} + +variable "product_alias" { + type = string + description = "Product alias" +} + +variable "env_alias" { + type = string + description = "Environment alias" +} + +variable "module_name" { + type = string + description = "module name" +} + +variable "is_production" { + description = "Is production" + type = bool + default = false +} + +variable "openai_api_key" { + description = "OpenAI API Key" + type = string +} + +variable "vpc_id" { + description = "VPC ID for ECS deployment" + type = string +} + +variable "private_subnet_ids" { + description = "List of private subnet IDs for ECS deployment" + type = list(string) +} \ No newline at end of file diff --git a/examples/aws-containerized/openai-dynamodb-scalable/pyproject.toml b/examples/aws-containerized/openai-dynamodb-scalable/pyproject.toml new file mode 100644 index 000000000..76f71405c --- /dev/null +++ b/examples/aws-containerized/openai-dynamodb-scalable/pyproject.toml @@ -0,0 +1,35 @@ +[project] +name = "aws-containerized-openai" +version = "0.1.0" +description = "Demo of OpenAI Agents SDK based agents running in Agent Kernel on AWS ECS using DynamoDB as memory" +readme = "README.md" +requires-python = ">=3.12" +dependencies = [ + "agentkernel[openai,api,aws]>=0.4.0" +] + +[dependency-groups] +dev = [ + "agentkernel[test]>=0.4.0", + "black>=23.0.0", + "isort>=5.0.0", + "mypy>=1.0.0", +] + +[tool.uv] +package = false + +[tool.isort] +profile = "black" +line_length = 120 + +[tool.mypy] +python_version = "3.12" +warn_return_any = true +warn_unused_configs = true +disallow_untyped_defs = true +disallow_incomplete_defs = true + +[tool.black] +line-length = 120 +target-version = ["py312"] diff --git a/examples/aws-containerized/openai-dynamodb-scalable/uv.lock b/examples/aws-containerized/openai-dynamodb-scalable/uv.lock new file mode 100644 index 000000000..2ed28e83e --- /dev/null +++ b/examples/aws-containerized/openai-dynamodb-scalable/uv.lock @@ -0,0 +1,4405 @@ +version = 1 +revision = 3 +requires-python = ">=3.12" +resolution-markers = [ + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'win32'", + "python_full_version < '3.13' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", + "python_full_version < '3.13' and sys_platform == 'emscripten'", + "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version < '3.13' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] + +[[package]] +name = "agentkernel" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "deprecated" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "pyyaml" }, + { name = "singleton-type" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, +] + +[package.optional-dependencies] +api = [ + { name = "fastapi" }, + { name = "gunicorn" }, + { name = "uvicorn" }, +] +aws = [ + { name = "boto3" }, +] +openai = [ + { name = "openai-agents" }, + { name = "openai-guardrails" }, + { name = "openinference-instrumentation-openai-agents" }, +] +test = [ + { name = "datasets" }, + { name = "litellm" }, + { name = "pandas" }, + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "pytest-cov" }, + { name = "pytest-html" }, + { name = "pytest-order" }, + { name = "ragas" }, + { name = "rapidfuzz" }, +] + +[[package]] +name = "aiohappyeyeballs" +version = "2.6.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/33/c6/61a2d7b7572279226bb2e7f61d7a19ca7c90da0329c93fa0d560cbf288d8/aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64", size = 22591, upload-time = "2026-05-20T15:12:24.631Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/fc/a7bf5b6e4e617b45f90f2d9d2a68519c249c81dd4fc2658c7a2a61c4f4b7/aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4", size = 15062, upload-time = "2026-05-20T15:12:23.328Z" }, +] + +[[package]] +name = "aiohttp" +version = "3.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ee/ab/93ce242f899b68c51b0578c027aafa791ab3614cb9345fa5d37b5f5c8e3e/aiohttp-3.14.0.tar.gz", hash = "sha256:2882de819734c715fd1b9c11c97e09fa020d14438203d1d354d8ed1702791c9b", size = 7940674, upload-time = "2026-06-01T19:41:02.763Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/97/2b6889bfb6b6847520d50d95eb8c4307a45e28aaca39faf4a9454b3d1b2f/aiohttp-3.14.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b29518c9c2ec7e373e68259206a137c7f4f5439c58baaec4b5ab3ab799850a4e", size = 750194, upload-time = "2026-06-01T19:37:48.164Z" }, + { url = "https://files.pythonhosted.org/packages/21/e2/62634b7fff918ed98c3c6b2f0e70d520f7f28846cb412d451b04354c6459/aiohttp-3.14.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:dbec68ce61b64cb73cab4d33df9433427b1713c8bcccb181dce695c1b6f8e87c", size = 506966, upload-time = "2026-06-01T19:37:50.014Z" }, + { url = "https://files.pythonhosted.org/packages/dd/fb/5ce075150828c797a5106f1c2fb26034e709d4289b9d2bf8b07f1e59fac6/aiohttp-3.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3cdf534aa455593e589302990c5097aa5c92c06c4262a20da22934f9186a5fff", size = 507527, upload-time = "2026-06-01T19:37:51.96Z" }, + { url = "https://files.pythonhosted.org/packages/01/d5/405a0ae4e6b081754a3609c1c97c63a950e000a2def16046f1e736933a0e/aiohttp-3.14.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cb6c657104393b5fbff01a5f59b2023db74058a8077d94475d6c25d03882a108", size = 1762420, upload-time = "2026-06-01T19:37:53.839Z" }, + { url = "https://files.pythonhosted.org/packages/ae/1d/e05a7c896b15a6bc6fb8fc5319eb437861c2c49c34559ef928add6590315/aiohttp-3.14.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:46fbbec4e4fab7428d4396a3823f9320e4560aa3113b89eeebce712c27c9ed5a", size = 1733672, upload-time = "2026-06-01T19:37:55.791Z" }, + { url = "https://files.pythonhosted.org/packages/cc/22/a72f7c459e195fa41bf4f7abd1f925b91fe91f8097e51c654229ba144a33/aiohttp-3.14.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2c2c7e05dd5335b298085abf45ddf98673934c3ee1c083d0b9ea13d4186ad500", size = 1805064, upload-time = "2026-06-01T19:37:57.931Z" }, + { url = "https://files.pythonhosted.org/packages/80/50/e85bdaba0be59ca4838005ebfef4048fcdd5f35a02b07057a9a123394440/aiohttp-3.14.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3c7139100fbaae76515b73051d8f0aa3a3ff02e415eec8a8eee8e2223d9ba955", size = 1902125, upload-time = "2026-06-01T19:38:00.225Z" }, + { url = "https://files.pythonhosted.org/packages/19/d8/51de5c6b971c27bb1ef620293b8d1ca611ec78736b34b3f6ccf68e4c8785/aiohttp-3.14.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:78d6f9286a629ce52728430afe18f8ed2b6c39a1fddb3802d7244b9983910ad2", size = 1783112, upload-time = "2026-06-01T19:38:02.641Z" }, + { url = "https://files.pythonhosted.org/packages/73/ae/b4402bfde77e43dfb1b6ccff83c7b7ab63ed06b50c4754f0c5423fb374fe/aiohttp-3.14.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cc3c3e12cdaeb92d7dcf13db00e9f6b1956b910e47256e696df1cfa946d02159", size = 1586356, upload-time = "2026-06-01T19:38:04.637Z" }, + { url = "https://files.pythonhosted.org/packages/bc/05/750a3265ca4dc54a460bd0cb1121a8f2ce9171fce4a135fb47ea7fd594d2/aiohttp-3.14.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4d6a998191f5ebe3b8c28463ff72bc030250008b3193c402464efadd08b5ca02", size = 1723119, upload-time = "2026-06-01T19:38:06.713Z" }, + { url = "https://files.pythonhosted.org/packages/37/01/8c0812c50b3b1b1c37b323bf170d6be8847a8f234060485b7d1e71953f60/aiohttp-3.14.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0fc2b75ae8d169d853be2862d960be8550da6c5c65711d5476407eb3fdb006bd", size = 1757216, upload-time = "2026-06-01T19:38:08.736Z" }, + { url = "https://files.pythonhosted.org/packages/47/2a/50fb98028a26887cbe48dcc1df92a90825615bc73b5584301304090cded8/aiohttp-3.14.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:16eee56bcc72d04600bc56c1759982c2385ec0b41d3fd3521f836bf64a0957ef", size = 1770500, upload-time = "2026-06-01T19:38:11.111Z" }, + { url = "https://files.pythonhosted.org/packages/bd/32/0ffd598a2fa2b9a423daf242e700cfdabda35d6e602394ad9ae58972c1c7/aiohttp-3.14.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5a2e7ca615c3ddc15b82687e05a624e5f5cba3f1d6c20cb81172d70ea498451e", size = 1576224, upload-time = "2026-06-01T19:38:13.391Z" }, + { url = "https://files.pythonhosted.org/packages/0b/f9/b9fc381dd9b66afb33f2634c40e229d106467be0afcabe79648631ab6712/aiohttp-3.14.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:f0b7b8bbbec3ce9467ee0ebe334622fd90624f593edd3136c567811453fc4fae", size = 1794252, upload-time = "2026-06-01T19:38:15.498Z" }, + { url = "https://files.pythonhosted.org/packages/a8/fb/05d9214c975f23225a8cd5c439325e338c7c377b315480ef3871db51f54e/aiohttp-3.14.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5ba10966d4f03dd96a14365be4b8e37c327c76f11c3ca867116966cdd9f98066", size = 1760193, upload-time = "2026-06-01T19:38:17.624Z" }, + { url = "https://files.pythonhosted.org/packages/d9/4b/02992fc4fb9e1b6673ee3f888a8e587a6447afda1f6f4aca776c148c2876/aiohttp-3.14.0-cp312-cp312-win32.whl", hash = "sha256:101df7779c80c0636014a6b2c6642acd3efb5b355d48347c9d7dfb720aee9430", size = 448650, upload-time = "2026-06-01T19:38:19.545Z" }, + { url = "https://files.pythonhosted.org/packages/39/e9/246532214c3abda518477cbaaf16d420295ad8effa5233844cbb38f299ab/aiohttp-3.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:b0a5747586d4467efd1f932710b269131c9717a872dce082cd92a00c1c13123a", size = 476145, upload-time = "2026-06-01T19:38:21.505Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c3/63f8c20090048915711598b0adf475b149216d736157961de06480a45b15/aiohttp-3.14.0-cp312-cp312-win_arm64.whl", hash = "sha256:5f1c5be60add78fabb4aacd13c5a348ae79d2fcbfc7fa78da8f1eb192273b370", size = 444250, upload-time = "2026-06-01T19:38:24.027Z" }, + { url = "https://files.pythonhosted.org/packages/21/61/d11f7d9a3144bffe825247d6367cd93053666da50b94707c9129c78868d5/aiohttp-3.14.0-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:25400d710641a8040bf022a8a99f579e581ffa1c5bd42c33255d7d6f3957c127", size = 502399, upload-time = "2026-06-01T19:38:25.955Z" }, + { url = "https://files.pythonhosted.org/packages/4f/9b/a7e317625d36356844f8bb022cabd305b541f968856cc3c2e0b58e53ee6e/aiohttp-3.14.0-cp313-cp313-android_21_x86_64.whl", hash = "sha256:c5492b9929826e07cc3fcb9739ae87aab05dff6b5e67a9b73fd1700c6d008981", size = 510068, upload-time = "2026-06-01T19:38:27.828Z" }, + { url = "https://files.pythonhosted.org/packages/11/41/cc2d2cfbfbdc3126ba258f3cd27d1ac8a33492ae3c35a4583ee21f0ba7f1/aiohttp-3.14.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:3366751d68d237c621264233a32f3078bbc21b7904ab90a77e03d21390c742c6", size = 481670, upload-time = "2026-06-01T19:38:29.836Z" }, + { url = "https://files.pythonhosted.org/packages/3c/07/381f4023c3b08cb616e520f566d8c58957abad54e56441d41fe67cfb0195/aiohttp-3.14.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:57ea07d28695a7a40304d42251892a8df765e5588c10ee32afeddcd5df33c0a2", size = 487591, upload-time = "2026-06-01T19:38:31.704Z" }, + { url = "https://files.pythonhosted.org/packages/fb/4d/4506fdb7a022bdf70011a3bbb4ca00c5c570026ef6a3c5bd7bc70c39089c/aiohttp-3.14.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:076cb014191ae2e65d949e1ad01f1dcfe33e32789b5172510f3e79c79fc04d50", size = 496503, upload-time = "2026-06-01T19:38:33.6Z" }, + { url = "https://files.pythonhosted.org/packages/ef/7d/c814111e04894a45d9e2defc94443879a6f118d9633d5fedfe6e2e8af5f0/aiohttp-3.14.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2f3fc37054564dee64a855b5b092d87ec35dcddfaabf7dacb1c8a2b1f83dc0a9", size = 745870, upload-time = "2026-06-01T19:38:36.013Z" }, + { url = "https://files.pythonhosted.org/packages/c6/ee/80eee0efddfe187e7cd05027086b7ce1c0e492e82a4eda58f5c5543a44a0/aiohttp-3.14.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8fcaef74d2ab0f607d7ff85a0d15e21bb5a258c4a58df1908396eb50d7f4ed3c", size = 505588, upload-time = "2026-06-01T19:38:38.282Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f8/0f28f04eef75d52fc9c715dde7ce9c0abb810fd20cfeb0fea7afd2ab1e98/aiohttp-3.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e4c01b0bfc6209590960e68eac083cd22d5d87c21f974dd6208cafa5d3542bc8", size = 504492, upload-time = "2026-06-01T19:38:40.611Z" }, + { url = "https://files.pythonhosted.org/packages/ff/db/44c755232085545065c94378dfce38641b1aee647f4939fcd32f5b32e719/aiohttp-3.14.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f12eb7896e81caf403a2b18c9406426f1207361e7239c057ab29c076d4257e83", size = 1752111, upload-time = "2026-06-01T19:38:42.682Z" }, + { url = "https://files.pythonhosted.org/packages/5e/6a/42e030a46743841414402a3b00cd3d78419055e86c66fb5822c14b5abfc6/aiohttp-3.14.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6c79a044cacf360ec46738d863d2f41c9300d2a06ef4a7402ea0df306a350e61", size = 1729674, upload-time = "2026-06-01T19:38:44.79Z" }, + { url = "https://files.pythonhosted.org/packages/34/26/3199beb415202e3108e7b83ecebe10914d806d33fb9860c3e4aa60a19be3/aiohttp-3.14.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:85e0675f47be4eff0636bf88c02140ea89168ae0df3ff1f3f464e9de9610d277", size = 1798808, upload-time = "2026-06-01T19:38:47.01Z" }, + { url = "https://files.pythonhosted.org/packages/bd/94/b9b6fcf0ee17c21d0d19fb8c22bf83ad18f82e702a9c3bd901a868f5e446/aiohttp-3.14.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7b33e751cab03fdc960095b1e326cb5a03f5ee577d6ded59f3d1c100f8668882", size = 1891921, upload-time = "2026-06-01T19:38:49.233Z" }, + { url = "https://files.pythonhosted.org/packages/c5/a3/3800dbd095cb2bb165a7ea5d94d790914677e27f45638c7d80e3f34c8945/aiohttp-3.14.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:26d9224c6dd7f5c749aba4f61315a894601448b28d94d12f4dea0903e26d2096", size = 1777241, upload-time = "2026-06-01T19:38:52.04Z" }, + { url = "https://files.pythonhosted.org/packages/21/2a/45be91ad1b860508557448d4cc2e165a2ee68dd865657b73bf66cc5a00fb/aiohttp-3.14.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6281aecdf2732940f4fe06bd6adec5ae4d59b78b080b8e3a6b81467301010988", size = 1579554, upload-time = "2026-06-01T19:38:54.508Z" }, + { url = "https://files.pythonhosted.org/packages/b4/3d/dc94df99ed1511fdf28314f722643ed334112643cab00223577085e788c4/aiohttp-3.14.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:23e8314e7aed8576fbe33314d218bd81447a3adbc91dc36f1163bf583cd3084c", size = 1714864, upload-time = "2026-06-01T19:38:56.788Z" }, + { url = "https://files.pythonhosted.org/packages/ae/e4/1f1c8acbb3acd5c8f795473b92c9c3d44eb60a5692c6104256c8a1c83a0c/aiohttp-3.14.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3b54fbff46127aeafdd764cecd0d99fa2f24a0e37ea5c18a7c3a4ac450df1db3", size = 1749803, upload-time = "2026-06-01T19:38:59.367Z" }, + { url = "https://files.pythonhosted.org/packages/0b/c8/c45ea6e7ed84cebba939b9c334498a045ba19d79c61b0110df5f21580de3/aiohttp-3.14.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b27d89af91a555f58e08e4902dbcbc48862fd40095720ca705990476bd93b7ac", size = 1765023, upload-time = "2026-06-01T19:39:01.651Z" }, + { url = "https://files.pythonhosted.org/packages/a8/a1/a932941784432962fe390e1066823aaef64b4e5ac9fa595df57b5fe472a9/aiohttp-3.14.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:25d2326a4967bf705a9f9913a13005e93b6020ad8a9f6bd6bd78850d5171332e", size = 1571671, upload-time = "2026-06-01T19:39:04.044Z" }, + { url = "https://files.pythonhosted.org/packages/b0/01/e1280feac522597a4d46eb67a0cdfa053cfae263033030b761ab146f29fb/aiohttp-3.14.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:a1d209375c503472b3c0a340cdf3c55fcd82e84b46dda7caeaced59faba373ec", size = 1789904, upload-time = "2026-06-01T19:39:06.294Z" }, + { url = "https://files.pythonhosted.org/packages/fa/10/ab28818262f4d26bdb47ed5f1fc7999b69e2fc6e0370b02d0f49011f45ea/aiohttp-3.14.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:666c7c5036df57b693026398b69b41874a1931ac5b3485fd910e57bfac253869", size = 1754516, upload-time = "2026-06-01T19:39:08.788Z" }, + { url = "https://files.pythonhosted.org/packages/af/cc/c122eabd7a1b7e0c9bbdd6be60e4715905b858399145d9df872bb94f1427/aiohttp-3.14.0-cp313-cp313-win32.whl", hash = "sha256:23f094a1ef64823fd35854ddf5c7a80a078162f37f9d2f7c6142b51a6affa456", size = 448656, upload-time = "2026-06-01T19:39:11.171Z" }, + { url = "https://files.pythonhosted.org/packages/41/a5/bab07d79848a00eedd8ed979ccb302aaea3ac6eb9fa16bd0ed87135869b4/aiohttp-3.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:e03abdaa17d553f17e1d1d06bb266b3970106c78051d06795723e748d8e49d11", size = 475803, upload-time = "2026-06-01T19:39:13.439Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a0/f03ade8566c153666a3871afccbedf6d99911da006325e1fc6cf72a2de99/aiohttp-3.14.0-cp313-cp313-win_arm64.whl", hash = "sha256:acdb400538cf4769543548bb5d1eb23d39bed4f96554a6078cb728c7cb2c268b", size = 443889, upload-time = "2026-06-01T19:39:15.945Z" }, + { url = "https://files.pythonhosted.org/packages/28/03/5f36ab196a88ba5e9648ae5643e6531e67a3a8c0e96f9c6510ff41540fec/aiohttp-3.14.0-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:363ef9e91014e7891679bfb2ac0a7c6ea93435dbbfd10ecf41b9f06fcf506c5f", size = 503330, upload-time = "2026-06-01T19:39:18.195Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ce/8b49ec2f30f68e02f314f4832186cd45e583360a5a386058be36855d23b6/aiohttp-3.14.0-cp314-cp314-android_24_x86_64.whl", hash = "sha256:884a4edbdad77be9d0ef36142c8b504351b170df0bf62b51e784fadabf311c42", size = 509822, upload-time = "2026-06-01T19:39:20.396Z" }, + { url = "https://files.pythonhosted.org/packages/1a/fe/6edbf5d39bf29322b6816365b17ed8ede4dace164a3aea1abcd30110eb78/aiohttp-3.14.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:70ea956f6cc4a37620966b56c2e205d88ca3e6d85ec063277e414b1035cddad3", size = 483329, upload-time = "2026-06-01T19:39:22.607Z" }, + { url = "https://files.pythonhosted.org/packages/1b/5a/fae531bdbc6456fb6241f46b7b81e4d8a0dd3fc09118a0055dc7141ac1ec/aiohttp-3.14.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:ea3b9806c89f61da22fddf1f12dd524fb368e5e28f1261fbdafe5c3cd8ce893b", size = 489502, upload-time = "2026-06-01T19:39:24.881Z" }, + { url = "https://files.pythonhosted.org/packages/36/f4/48a7b0414db7fed77a03d5dde34508c026afd83510ab6bca08c313855776/aiohttp-3.14.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:a071be341c2bd9b0188e62d173509f024e0a35b1c342c53c50f8daaeda8c3bd8", size = 497357, upload-time = "2026-06-01T19:39:27.197Z" }, + { url = "https://files.pythonhosted.org/packages/75/75/e85a13a370acc007fca5feb1fd1b88ac2d8426e6dadd625479b7cadd55a3/aiohttp-3.14.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:198cfe61bf253b19da1fb3e0fa122249dc4f14c12709493fed8054aa0411cc76", size = 750898, upload-time = "2026-06-01T19:39:29.563Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e4/3d637f800c724eff0e2bed64df72557444482366fd0a35b0cec0e6968f6c/aiohttp-3.14.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9dc203d6ce6b9106d54e2a93f41dfdfebfbca2d99962ba503bfd3e5921a6549e", size = 506986, upload-time = "2026-06-01T19:39:31.872Z" }, + { url = "https://files.pythonhosted.org/packages/1d/df/35161f3598bf7501d2b2a805b41ab4f45a2e34150c421bcb4ef8c0d281a7/aiohttp-3.14.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9e19d17ab02bf16832a2c8c0d55a486792c5b1645665652ee9531aebcc30cb72", size = 508033, upload-time = "2026-06-01T19:39:34.137Z" }, + { url = "https://files.pythonhosted.org/packages/e5/39/b36e5d3d31e850fb4691dd3e941684ac490a2559249f6fa634b6b0fdf020/aiohttp-3.14.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d925fba0c14d5b498a8028b0107beebdfd16c5d48d702ff54f879cb017aaaca3", size = 1746213, upload-time = "2026-06-01T19:39:36.654Z" }, + { url = "https://files.pythonhosted.org/packages/b1/28/24e1409e605a9aa5d84abe0e2acb365354b70ae56d40948101cabe3341ab/aiohttp-3.14.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d33e61021222ce7f9792bcac870d6f58d8adfceda33ab857b01264f4560f2c5f", size = 1705862, upload-time = "2026-06-01T19:39:38.968Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d0/e5eb3ff1daeaf644c7e36a957517672494122628e067c38b263fa04eda77/aiohttp-3.14.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:44eca38755d0105bb32f47d085f5dd449846a449e1245fc105889e3279dcf8e3", size = 1798909, upload-time = "2026-06-01T19:39:41.334Z" }, + { url = "https://files.pythonhosted.org/packages/d3/ba/8943f906f0570342886ababb9a722a44e360f786a028c5e0b0e29e3f735b/aiohttp-3.14.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f13087e06f68fea4941c21a0c541c00553aa16e4f8fd7bbe2b198df761e964d6", size = 1868892, upload-time = "2026-06-01T19:39:43.807Z" }, + { url = "https://files.pythonhosted.org/packages/3a/05/27df32c844b2156e1675a8d8ec22d963e3c8ba469ed7ceb1863320c7b521/aiohttp-3.14.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ff82be7f1ef73634cb77890a770743239bc3d487b848669be1c599889336dc0a", size = 1751659, upload-time = "2026-06-01T19:39:46.398Z" }, + { url = "https://files.pythonhosted.org/packages/7f/62/da182e5910ab912b2e88aa919b61a16046a37a95714a5795b02eb57b2d18/aiohttp-3.14.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a150c0875ac8fd87f1c398650841308a30d65facf7416b12dbdb9cfdcbe5a48c", size = 1578775, upload-time = "2026-06-01T19:39:48.902Z" }, + { url = "https://files.pythonhosted.org/packages/66/e3/53c67097e8a5ce98625e91e3fa7f43c9c6940de680345d03b3509a72a078/aiohttp-3.14.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:edc01ea4e1ec5a1649a28866262bf24195889ff7b27bdd947029a6086741de9b", size = 1710090, upload-time = "2026-06-01T19:39:51.392Z" }, + { url = "https://files.pythonhosted.org/packages/dd/55/0e2732ca598c7a4dfe8a775662376d0ca2977cb1030e48386d4da5d9a456/aiohttp-3.14.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:540632bf882ff8fc88f2e1697be0761578e89e0d79fb4a8a6d65dc5da7e729d4", size = 1715016, upload-time = "2026-06-01T19:39:53.807Z" }, + { url = "https://files.pythonhosted.org/packages/5a/96/f0b73730798c9ca525afc30b39f1f81bbe24e245d9654c54d3b39d63212d/aiohttp-3.14.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:860a86bc2c80237f5dff52edcf427e10a8d8352271fd84845429a3e60199e02c", size = 1763810, upload-time = "2026-06-01T19:39:56.31Z" }, + { url = "https://files.pythonhosted.org/packages/71/cc/11acb6c4518f448323405a7312b6f255d0f974a34373ad1db7633c4aadc8/aiohttp-3.14.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5cbd50e6a50d6b99283a826b18cbdebf65b0797689a7535cb0e9dd37be0f63c3", size = 1573064, upload-time = "2026-06-01T19:39:58.718Z" }, + { url = "https://files.pythonhosted.org/packages/de/2d/28c31dde0a7dc98c0ee7d0da2ddcec3f7688c4fc131e5989e278d0c03c0a/aiohttp-3.14.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:20144819e99db593e22bbd2f3f2691a5e149f879142d6b8670254708853ff4fb", size = 1775765, upload-time = "2026-06-01T19:40:01.195Z" }, + { url = "https://files.pythonhosted.org/packages/b8/69/155c4ef3aec96417d47024800472b33b16c5d8a665371dcd044c2afdf25d/aiohttp-3.14.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:26b6d79aa54cb4ed50cc7d41ed14e99e0f1fc8e7c2d42f2e05b37aea897b2b52", size = 1733716, upload-time = "2026-06-01T19:40:03.631Z" }, + { url = "https://files.pythonhosted.org/packages/5f/44/6126116fd8a316b712bb615660b855c78466bb67ba1bb1742427eafcf7ac/aiohttp-3.14.0-cp314-cp314-win32.whl", hash = "sha256:106ed074a856f3e21d186b8579e2c8afb6da598e267cdaab01059e13db2fc44d", size = 453684, upload-time = "2026-06-01T19:40:06.277Z" }, + { url = "https://files.pythonhosted.org/packages/a2/d7/eff4c58a88c5cac5e38b55f44fb8a6d3929c3cbd77356e383e094d3220bd/aiohttp-3.14.0-cp314-cp314-win_amd64.whl", hash = "sha256:4f770846edae8f00ecc57af825bce811f787f87a7dcf0e90d191790efe5b31f7", size = 481758, upload-time = "2026-06-01T19:40:08.653Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ed/17b5bd9fbcb46e688f02e572f517754a9a75831e7b54702f027761dc4fa5/aiohttp-3.14.0-cp314-cp314-win_arm64.whl", hash = "sha256:acf1581c4f21ed4b80a2dded504d87b055a071a84d5737ea966435f768275ac6", size = 450557, upload-time = "2026-06-01T19:40:11.03Z" }, + { url = "https://files.pythonhosted.org/packages/12/34/6180103ce9aabc8ebff3f7bb55a1228ffe60f61042823031d9692cb7b101/aiohttp-3.14.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:6aa1a40f9cbb3da9f80714c5966b8946c21e6a2530d809b9498b33161e3c8733", size = 787878, upload-time = "2026-06-01T19:40:13.401Z" }, + { url = "https://files.pythonhosted.org/packages/92/e9/08954a40e8b7baa3d8beadd2b074b186e9b1e9c8ddabc288678a6265de50/aiohttp-3.14.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b62af5a8cc96a194eaa01a9ed7b34a3ffa58d3d8daaa1a0d7a749353ad12d228", size = 524400, upload-time = "2026-06-01T19:40:15.972Z" }, + { url = "https://files.pythonhosted.org/packages/08/6a/b5965a634ac4d5ba99a463314cf4ab214ca073fcdc38a15e0294273701fc/aiohttp-3.14.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6eb63b1417efaf7d1002a6ad034a40d44376afcc16508a57f8e74b49ad26a095", size = 527904, upload-time = "2026-06-01T19:40:18.28Z" }, + { url = "https://files.pythonhosted.org/packages/06/b4/932bcdd850c354d9bcca30f360e475d7852e30413fbbd44b182782ed5432/aiohttp-3.14.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c20b9ad156a79eb97be5cf9e069eec01d2f0dc8472ffbd75299a8b2d4c2cbbde", size = 1912162, upload-time = "2026-06-01T19:40:20.825Z" }, + { url = "https://files.pythonhosted.org/packages/c6/85/ce79bab0310d2e3fd2d7bc7e44412abeff7c8338f8a21dd0f2f1714989e5/aiohttp-3.14.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:40ae7b0642c25632c7eabc4a04754012691864d2a1b93becf7cddb76027b838a", size = 1778813, upload-time = "2026-06-01T19:40:23.726Z" }, + { url = "https://files.pythonhosted.org/packages/05/54/ba62ac2d1bc87e010aad23751e383b8794e45d931df67677313a2da78823/aiohttp-3.14.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:95f5217e76a046b9f228a101717ef8d42b1eb3d9d196d15202db5bf41df88936", size = 1899969, upload-time = "2026-06-01T19:40:26.406Z" }, + { url = "https://files.pythonhosted.org/packages/dc/82/7cc7907725d83a19f31551334061e1ab8e108b1d7ac52632a2a844a4acb5/aiohttp-3.14.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1a4a9f17e85b80878c176695c1998c790e83731d8271881e5d356488652a1f9e", size = 1991771, upload-time = "2026-06-01T19:40:29.061Z" }, + { url = "https://files.pythonhosted.org/packages/d0/1c/a57de71a4508c93a830b77c28af3d08cd97f606dedfc6b94275347744508/aiohttp-3.14.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:145262119b07d7f95abc1839add35ba2bfc84551d4b4660ca11542c0b215455b", size = 1868606, upload-time = "2026-06-01T19:40:31.843Z" }, + { url = "https://files.pythonhosted.org/packages/9c/ae/3839726cd49150a53ed340cc24ce5ba09d4c2117020ef9d45542bec5eb2f/aiohttp-3.14.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:49a33ded29b0b2fa7a367a02cf0fb89af602bb87542a16177ec8ce1c9c51d12a", size = 1665437, upload-time = "2026-06-01T19:40:35.01Z" }, + { url = "https://files.pythonhosted.org/packages/35/1e/c237923232c7da7f0392ea25d89fc5e60c0e93f685f4ebca8e7bcdd5271c/aiohttp-3.14.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2cc736a9c9fc2bc4dd71fd404815741b6573df27c3f985948ec4076989ac57de", size = 1834090, upload-time = "2026-06-01T19:40:37.733Z" }, + { url = "https://files.pythonhosted.org/packages/98/02/a5a7a2524f92d3911761b405a7c067c751891942144adc13e2ad79611e39/aiohttp-3.14.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:b4141a3e5342ee3053a9cab54d25b64ed28289c1041e4c54b3d99839314d90ce", size = 1816907, upload-time = "2026-06-01T19:40:40.46Z" }, + { url = "https://files.pythonhosted.org/packages/fa/76/a8b9f0d09234d516af9f2d7dd715557f33b5da3b0b56ead41d1170e86e3c/aiohttp-3.14.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:e30871b2d58996cb81aac52d2b1d15ac05257131ef0f90f18c2115a380fbfe7c", size = 1840382, upload-time = "2026-06-01T19:40:43.48Z" }, + { url = "https://files.pythonhosted.org/packages/c9/8e/140e715a0a4bbc211979ea30ec8396ad2ed5bf90ab87d8058fc4668b1923/aiohttp-3.14.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:667b881d083ccae3900ea5a241e17e5007ca78844c53ed389bb63d48f729d9c7", size = 1659497, upload-time = "2026-06-01T19:40:46.265Z" }, + { url = "https://files.pythonhosted.org/packages/10/c7/7ba5de8af9650b9767b063c675427b8685f43fa7ce563673a7bc3af60f08/aiohttp-3.14.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:b584dfe615d151e9b8f0a8ecb3aee6147f2927ec5b95ba25fe621f5377510928", size = 1870829, upload-time = "2026-06-01T19:40:49.583Z" }, + { url = "https://files.pythonhosted.org/packages/cc/bc/2aaab2f85cadb26ea59c091fa2b8e370d625154b5c14b478f1b489d07551/aiohttp-3.14.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6199707cc40e0e9cd39c36fbc97bec416c704e1d0ddce03412bb3b3e6a90ccd0", size = 1832281, upload-time = "2026-06-01T19:40:52.303Z" }, + { url = "https://files.pythonhosted.org/packages/39/98/31b9ad9fbc01f0075ee7221002df5fd2d10b647f451ca5f30edc802d9dd6/aiohttp-3.14.0-cp314-cp314t-win32.whl", hash = "sha256:a8d93334d4961c9d566b1f046c81dee475b7c21eb730728d38237bfa70d1c8e6", size = 490597, upload-time = "2026-06-01T19:40:54.937Z" }, + { url = "https://files.pythonhosted.org/packages/59/1f/299b21441c8de42ff70fddc7cfe65e92f810abcf740739a09b56f7835364/aiohttp-3.14.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2d2ffe9b614f50f069068b3b52e73414e4107fc10b7efc939a76acff9251fdd2", size = 525789, upload-time = "2026-06-01T19:40:57.306Z" }, + { url = "https://files.pythonhosted.org/packages/70/11/7f83fcba9ee05d4c54d61b3f8104da0d43a59adac44dd28effc0c9a10422/aiohttp-3.14.0-cp314-cp314t-win_arm64.whl", hash = "sha256:7a3fc4358e65826c515350f199c210de747cf669998211b1ee6c2e46de364b24", size = 467399, upload-time = "2026-06-01T19:40:59.993Z" }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, +] + +[[package]] +name = "annotated-doc" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anyio" +version = "4.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" }, +] + +[[package]] +name = "appdirs" +version = "1.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/d8/05696357e0311f5b5c316d7b95f46c669dd9c15aaeecbb48c7d0aeb88c40/appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", size = 13470, upload-time = "2020-05-11T07:59:51.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/00/2344469e2084fb287c2e0b57b72910309874c3245463acd6cf5e3db69324/appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128", size = 9566, upload-time = "2020-05-11T07:59:49.499Z" }, +] + +[[package]] +name = "ast-serialize" +version = "0.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/81/9d/09e27731bd5864a9ce04e3244074e674bb8936bf62b45e0357248717adac/ast_serialize-0.5.0.tar.gz", hash = "sha256:5880091bfe6f4f986f22866375c2e884843e7a0b6343ae41aeea659613d879b6", size = 61157, upload-time = "2026-05-17T17:48:29.429Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/9a/13dde51ba9e15f8b97957ab7cb0120d0e381524d651c6bd630b9c359227f/ast_serialize-0.5.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8f5c14f169eb0972c0c21bada5358b23d6047c76583b005234f865b11f1fa00a", size = 1183520, upload-time = "2026-05-17T17:47:30.831Z" }, + { url = "https://files.pythonhosted.org/packages/37/de/5a7f0a9fe68944f536632a5af84676739c7d2582be42deb082634bf3a754/ast_serialize-0.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7d1a2de9de5be04652f0ed60738356ef94f66db37924a9499fffe98dc491aa0b", size = 1175779, upload-time = "2026-05-17T17:47:32.551Z" }, + { url = "https://files.pythonhosted.org/packages/9c/81/0bb853e76e4f6e9a1855d569003c59e19ffac45f7079d91505d1bb212f92/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be5173fb66f9b49026d9d5a2ff0fc7c7009077107c0eb285b2d60fdf1fe10bd1", size = 1233750, upload-time = "2026-05-17T17:47:34.731Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d3/4cf705beeccc08754d0bbda99aefff26110e209b9a07ac8a6b60eec48531/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f8015cd071ac1339924ee2b8098c93e00e155f30a16f40ec9816fcf84f4753f6", size = 1235942, upload-time = "2026-05-17T17:47:36.287Z" }, + { url = "https://files.pythonhosted.org/packages/26/c8/ee097e437ea27dd2b8b227865c875492b585650a5802a22d82b304c8201b/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5499e8797edff2a9186aa313ed382c6b422e798e9332d9953badcee6e69a88f2", size = 1442517, upload-time = "2026-05-17T17:47:38.17Z" }, + { url = "https://files.pythonhosted.org/packages/ff/bd/68063442838f1ba68ec72b5436430bc75b3bb17a1a3c3063f09b0c05ae2b/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6848f2a093fb5548751a9a09bff8fcd229e2bbeb0e3331f391b6ae6d26cd9903", size = 1254081, upload-time = "2026-05-17T17:47:39.826Z" }, + { url = "https://files.pythonhosted.org/packages/50/e2/1e520793bc6a4e4524a6ab022391e827825eaa0c3811828bfdc6852eca26/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:832d4c998e0b091fd60a6d6bceee535483c4d490de9ba85003af835225719261", size = 1259910, upload-time = "2026-05-17T17:47:41.369Z" }, + { url = "https://files.pythonhosted.org/packages/4e/e1/49b60f467979979cfe6913b43948ff25bca971ad0591d181812f163a988e/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:16db7c62ec0b8efe1d7afd283a388d8f74f2605d56032e5a37747d2de8dba027", size = 1250678, upload-time = "2026-05-17T17:47:43.702Z" }, + { url = "https://files.pythonhosted.org/packages/74/ba/66ab9555de6275677566f6574e5ef6c29cb185ea866f643bc06f8280a8ee/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:baf5eb061eb5bccade4128ad42da33787d72f6013809cd1b590376ece8b3c937", size = 1301603, upload-time = "2026-05-17T17:47:46.256Z" }, + { url = "https://files.pythonhosted.org/packages/66/42/6aca9b9abc710014b2be9059689e5dd1679339e78f567ffb4d255a9e2050/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:104e4a35bd7c124173c41760ef9aaea17ddb3f86c65cb643671d59afbe3ee94c", size = 1410332, upload-time = "2026-05-17T17:47:47.899Z" }, + { url = "https://files.pythonhosted.org/packages/47/68/2f76594432a22581ecf878b5e75a9b8601c24b2241cf0bbeb1e21fcf370c/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:36be371028fc1675acb38a331bde160dbab7ff907fdf00b67eb6911aa106951b", size = 1509979, upload-time = "2026-05-17T17:47:50.942Z" }, + { url = "https://files.pythonhosted.org/packages/40/ac/a93c9b58292653f6c595752f677a08e608f903b710594909e9231a389b3b/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:061ee58bdb52341c8201a6df41182a977736bae3b7ded87ca7176ca25a8a47ab", size = 1505002, upload-time = "2026-05-17T17:47:54.093Z" }, + { url = "https://files.pythonhosted.org/packages/14/2e/b278f68c497ee2f1d1576cbbef8db5281cd4a5f2db040537592ac9c8862e/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b15219e9cdc9f53f6f4cb51c009203507228226148c05c5e8fe451c28b435eb3", size = 1456231, upload-time = "2026-05-17T17:47:56.311Z" }, + { url = "https://files.pythonhosted.org/packages/0b/43/419be1c566a4c504cd8fd60ce2f84e790f295495c0f327cfaeadf3d51012/ast_serialize-0.5.0-cp314-cp314t-win32.whl", hash = "sha256:842d1c004bb466c7df036f95fabef789570541922b10976b12f5592a69cf0b38", size = 1058668, upload-time = "2026-05-17T17:47:58.305Z" }, + { url = "https://files.pythonhosted.org/packages/03/6f/c9d4d549295ed05111aeb8853232d1afd9d0a179fddb01eeffbb3a4a6842/ast_serialize-0.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b0c06d760909b095cc466356dfccd05a1c7233a6ca191c020dca2c6a6f16c24c", size = 1101075, upload-time = "2026-05-17T17:48:00.35Z" }, + { url = "https://files.pythonhosted.org/packages/d0/8e/d00c5ab30c58222e07d62956fca86c59d91b9ad32997e633c38b526623a3/ast_serialize-0.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:787baedb0262cc49e8ce37cc15c00ae818e46a165a3b36f5e21ed174998104cb", size = 1075347, upload-time = "2026-05-17T17:48:01.753Z" }, + { url = "https://files.pythonhosted.org/packages/e0/9e/dc2530acb3a60dc6e46d65abf27d1d9f86721694757906a148d90a6860de/ast_serialize-0.5.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:0668aa9459cfa8c9c49ddd2163ebcf43088ba045ef7492af6fe22e0098303101", size = 1191380, upload-time = "2026-05-17T17:48:03.738Z" }, + { url = "https://files.pythonhosted.org/packages/26/0a/bd3d18a582f273d6c843d16bb9e22e9e16365ff7991e92f18f798e9f1224/ast_serialize-0.5.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:bf683d6363edf2b39eed6b6d4fe22d34b6203867a67e27134d9e2a2680c4bc4a", size = 1183879, upload-time = "2026-05-17T17:48:05.463Z" }, + { url = "https://files.pythonhosted.org/packages/40/ae/1f919100f8620887af58fcc381c61a1f218cdf89c6e155f87b213e61010a/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc22cf0c9be65e71cf88fda130af60d61eb4a79370ad4cfe7900d48a4aa2211", size = 1244529, upload-time = "2026-05-17T17:48:07.008Z" }, + { url = "https://files.pythonhosted.org/packages/c6/ca/6376559dcce707cdbc1d0d9a13c8d3baaaa501e949ce0ebdc4230cd881aa/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f66173891548c9f2726bf27957b41cabce12fa679dc6da505ddbde4d4b3b31cf", size = 1240560, upload-time = "2026-05-17T17:48:08.46Z" }, + { url = "https://files.pythonhosted.org/packages/35/b2/a620e206b5aeb7efbf2710336df57d457cffbb3991076bbcc1147ef9abd4/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e42d729ef2be96a14efbad355093284739e3670ece3e534f82cc8832790911d9", size = 1451172, upload-time = "2026-05-17T17:48:09.922Z" }, + { url = "https://files.pythonhosted.org/packages/fa/e0/4ad5c04c24a40481b2935ce9a0ccdb6023dc8b667167d06ae530cc3512f2/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b725026bafa801dbd7310eb13a75f0a2e370e7e51b2cb225f9d21fcfadf919ee", size = 1265072, upload-time = "2026-05-17T17:48:11.469Z" }, + { url = "https://files.pythonhosted.org/packages/b2/71/4d1d479aa56d0101c40e17720c3d6ac2af7269ea0487a80b18e7bfd1a5b7/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b54f60c1d78767a53b67eaa663f0dfac3afe606aa07f1301572f588b73d64809", size = 1270488, upload-time = "2026-05-17T17:48:13.575Z" }, + { url = "https://files.pythonhosted.org/packages/6d/4f/0de1bbe06f6edef9fde4ed12ca8e7b3ec7e6e2bd4e672c5af487f7957665/ast_serialize-0.5.0-cp39-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:27d51654fc240a1e87e742d353d98eb45b75f62f129086b3596ab53df2ac2a43", size = 1260702, upload-time = "2026-05-17T17:48:15.141Z" }, + { url = "https://files.pythonhosted.org/packages/75/61/e00872439cfdddcc3c1b6cdaa6e5d904ba8e26a18807c67c4e14409d0ca8/ast_serialize-0.5.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c36237c46dd1674542f2109740ea5ea485a169bf1431939ada0434e17934", size = 1311182, upload-time = "2026-05-17T17:48:16.779Z" }, + { url = "https://files.pythonhosted.org/packages/76/8e/699a5b955f7926956c95e9e1d74132acad73c2fe7a426f94da89123c20aa/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1943db345233cc7194a470f13afa9c59772c0b123dea0c9414c4d4ca54369759", size = 1421410, upload-time = "2026-05-17T17:48:18.527Z" }, + { url = "https://files.pythonhosted.org/packages/a9/ae/d5b7626874478997adc7a29ab28accf21e596fb590c944290401dfd0b29e/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:df1c00022cbbcb064bfaa505aa9c9295362443ce5dacb459d1331d3da353f887", size = 1516587, upload-time = "2026-05-17T17:48:20.133Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ce/b59e02a82d9c4244d64cde502e0b00e83e38816abe19155ceb5437402c7f/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:cae65289fc456fde04af979a2be09302ef5d8ab92ef23e596d6746dc267ada27", size = 1515171, upload-time = "2026-05-17T17:48:21.921Z" }, + { url = "https://files.pythonhosted.org/packages/8b/38/d8d90042747d05aa08d4efcf1c99035a5f670a6bf4c214d31644392afbca/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:239a4c354e8d676e9d94631d1d4a64edc6b266f86ff3a5a80aedd344f342c01d", size = 1464668, upload-time = "2026-05-17T17:48:23.544Z" }, + { url = "https://files.pythonhosted.org/packages/dd/51/5b840c4df7334104cecffa28f23904fe81ca89ca223d2450e288de39fd3c/ast_serialize-0.5.0-cp39-abi3-win32.whl", hash = "sha256:143a4ef63285a075871908fda3672dc21864b83a8ec3ee12304aa3e4c5387b9a", size = 1068311, upload-time = "2026-05-17T17:48:25.027Z" }, + { url = "https://files.pythonhosted.org/packages/41/11/ca5672c7d491825bc4cd6702dea106a6b60d928707712ec257c7833ae476/ast_serialize-0.5.0-cp39-abi3-win_amd64.whl", hash = "sha256:cf25572c526add400f26a4750dc6ce0c3bb93fc1f75e7ae0cad4ce4f2cd5c590", size = 1108931, upload-time = "2026-05-17T17:48:26.591Z" }, + { url = "https://files.pythonhosted.org/packages/45/19/cc8bd127d28a43da249aa955cfd164cf8fd534e79e42cea96c4854d72fd0/ast_serialize-0.5.0-cp39-abi3-win_arm64.whl", hash = "sha256:92a31c9c20d25a076edaeec76b128a3535d74a24f340b9a8a7e96c9b86dc9642", size = 1081181, upload-time = "2026-05-17T17:48:28.122Z" }, +] + +[[package]] +name = "attrs" +version = "26.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, +] + +[[package]] +name = "aws-containerized-openai" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "agentkernel", extra = ["api", "aws", "openai"] }, +] + +[package.dev-dependencies] +dev = [ + { name = "agentkernel", extra = ["test"] }, + { name = "black" }, + { name = "isort" }, + { name = "mypy" }, +] + +[package.metadata] +requires-dist = [{ name = "agentkernel", extras = ["openai", "api", "aws"], specifier = ">=0.4.0" }] + +[package.metadata.requires-dev] +dev = [ + { name = "agentkernel", extras = ["test"], specifier = ">=0.4.0" }, + { name = "black", specifier = ">=23.0.0" }, + { name = "isort", specifier = ">=5.0.0" }, + { name = "mypy", specifier = ">=1.0.0" }, +] + +[[package]] +name = "black" +version = "26.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "mypy-extensions" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "platformdirs" }, + { name = "pytokens" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/37/5628dd55bf2b34257fc7603f0fe97c40e3aaf24265f416a9c85c95ca1436/black-26.5.1.tar.gz", hash = "sha256:dd321f668053961824bcc1be1cc1df748b2d7e4fa28086b08331e577b0100a73", size = 679439, upload-time = "2026-05-18T16:53:36.107Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/99/7744b906703228264ef73bdd534df88ec1ef3de45c4e78f6d31b9e32d0c9/black-26.5.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4ad6fa01f941920f54f2bbb35f3df7673428a0ef98a0b0840c2eaef3b110efa8", size = 2012518, upload-time = "2026-05-18T17:05:20.108Z" }, + { url = "https://files.pythonhosted.org/packages/b7/c0/c5a3b1636dfd09c42534f2b3cf33506814f6d3e066fb0879ffa16c1ae860/black-26.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3915f256e75a2d7cf88d8953d37f780455dc586cc72dee059c528fe77f581217", size = 1816016, upload-time = "2026-05-18T17:05:21.84Z" }, + { url = "https://files.pythonhosted.org/packages/1f/0e/36044316b65ca471d3bb6d3703fd06fb50c6b727c3562f6a5a3153634f88/black-26.5.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d98d4137277c75dfb898ec8d846c4fd68ba1e9cf77f95e2865c203dc18f4c3d", size = 1884150, upload-time = "2026-05-18T17:05:23.546Z" }, + { url = "https://files.pythonhosted.org/packages/b3/33/dafc5808c2af43672912111d7c3354af1615f7e2be3bed7a878461abbe4d/black-26.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:a1dca32d9f1784af512a13410ec204c6f7f0aa9797a111c42e1c03449821c264", size = 1486825, upload-time = "2026-05-18T17:05:25.004Z" }, + { url = "https://files.pythonhosted.org/packages/82/14/b965ee6ad2a311f28bdbf692def3ee9848d2ae289dab28b27657fcee3e78/black-26.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:1037d5ac7b7b310b2632ad867ec8d0e4c4819dcdb0b820f63135da746a24e418", size = 1288646, upload-time = "2026-05-18T17:05:26.477Z" }, + { url = "https://files.pythonhosted.org/packages/3f/5c/c384363980e11e25ca6b93205949bb331fbf35f4e0dbec376dfa6326cec8/black-26.5.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2b36cf2ddf5566e205f6535f782a62194a184d33e175b64ae8c40b1737522be3", size = 2009020, upload-time = "2026-05-18T17:05:28.132Z" }, + { url = "https://files.pythonhosted.org/packages/0b/df/9f31c5e0babbfed77d505fc5d120beb98b21b33feaeded3924ea941fe360/black-26.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f7ea64ebfa01b50f693508fc39f875e264446d3b097088f84f203b9d09618a0", size = 1813335, upload-time = "2026-05-18T17:05:31.266Z" }, + { url = "https://files.pythonhosted.org/packages/fb/24/8e7b9a2fa61b0afd82209efe937557d180a1fa055bd7f6161eb9defc3719/black-26.5.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecb3e624844c798144e9bd986954e0adc81d8911a1f30f375e1252fe26e8c294", size = 1881614, upload-time = "2026-05-18T17:05:32.718Z" }, + { url = "https://files.pythonhosted.org/packages/49/ad/b4e0d9365ba8ac34f6bbab62a4b1b2dd5d618fac3fa1b8db968c844201b5/black-26.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:e1a26503279b6b310669fb0b219c39e4820b77e8189fe80f522bb511f247db0a", size = 1488925, upload-time = "2026-05-18T17:05:34.259Z" }, + { url = "https://files.pythonhosted.org/packages/a1/4b/652b859bf5df88a751c30451b09338f7fd26a77d1271c666992f836b7711/black-26.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:5c34b25da232ead53a6f335b76dbea124f4d152ad568b9080d6f944bc2b34b52", size = 1289883, upload-time = "2026-05-18T17:05:36.019Z" }, + { url = "https://files.pythonhosted.org/packages/a6/16/a8da8eb208c51c7f4ce74609a45d0dcc6d8a2141e45e81ee5289d1bb0d59/black-26.5.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e88976690a64b0af98312ca958415849cb42423423c5f2ee74af4b49a97a2168", size = 2004800, upload-time = "2026-05-18T17:05:38.182Z" }, + { url = "https://files.pythonhosted.org/packages/11/8a/a479296a19e383b70a725882a6cf3d786540601ff03cabbaaf1cce864c5a/black-26.5.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32d5ea7f6c8bdfa6e648326ebca1f02b0764e2a029edc6f8dce2627e19d468c3", size = 1815576, upload-time = "2026-05-18T17:05:40.309Z" }, + { url = "https://files.pythonhosted.org/packages/81/6b/cfaf3d39f25132c156a068f6b805576c9103a84086019507c70e1911ee7d/black-26.5.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ea8d16dc41655aa113cd64665e7219446cd7e4ff2248d7178eaa905190c86b18", size = 1877927, upload-time = "2026-05-18T17:05:42.463Z" }, + { url = "https://files.pythonhosted.org/packages/66/76/302e313964bcff7e28df329d39f84f5270095730d85ff0acc260610a0d82/black-26.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:577f21094ea469ef92ec1adaf2c9441a226d2144d01a5be2fa823cecf6543e50", size = 1511860, upload-time = "2026-05-18T17:05:43.943Z" }, + { url = "https://files.pythonhosted.org/packages/27/4e/a3827e35e0e567f9f9ee59e2a0ab979267dca98718f25547ca8c6733afd4/black-26.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:ed1a20af114c301a0269bf01163d51dbef72737fd65f850001e7cbe7f3c7abae", size = 1316632, upload-time = "2026-05-18T17:05:45.521Z" }, + { url = "https://files.pythonhosted.org/packages/94/51/f975cae76d44274cc2868dc9040ac5d58d464784610234455b4e7b19c6ef/black-26.5.1-py3-none-any.whl", hash = "sha256:4ed7f7da04046d2e488437170797d3b4a4ad83906683bcb7dfc68b673bbce5e2", size = 213693, upload-time = "2026-05-18T16:53:33.964Z" }, +] + +[[package]] +name = "blis" +version = "1.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d0/d0/d8cc8c9a4488a787e7fa430f6055e5bd1ddb22c340a751d9e901b82e2efe/blis-1.3.3.tar.gz", hash = "sha256:034d4560ff3cc43e8aa37e188451b0440e3261d989bb8a42ceee865607715ecd", size = 2644873, upload-time = "2025-11-17T12:28:30.511Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/d1/429cf0cf693d4c7dc2efed969bd474e315aab636e4a95f66c4ed7264912d/blis-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2a1c74e100665f8e918ebdbae2794576adf1f691680b5cdb8b29578432f623ef", size = 6929663, upload-time = "2025-11-17T12:27:44.482Z" }, + { url = "https://files.pythonhosted.org/packages/11/69/363c8df8d98b3cc97be19aad6aabb2c9c53f372490d79316bdee92d476e7/blis-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3f6c595185176ce021316263e1a1d636a3425b6c48366c1fd712d08d0b71849a", size = 1230939, upload-time = "2025-11-17T12:27:46.19Z" }, + { url = "https://files.pythonhosted.org/packages/96/2a/fbf65d906d823d839076c5150a6f8eb5ecbc5f9135e0b6510609bda1e6b7/blis-1.3.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d734b19fba0be7944f272dfa7b443b37c61f9476d9ab054a9ac53555ceadd2e0", size = 2818835, upload-time = "2025-11-17T12:27:48.167Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ad/58deaa3ad856dd3cc96493e40ffd2ed043d18d4d304f85a65cde1ccbf644/blis-1.3.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ef6d6e2b599a3a2788eb6d9b443533961265aa4ec49d574ed4bb846e548dcdb", size = 11366550, upload-time = "2025-11-17T12:27:49.958Z" }, + { url = "https://files.pythonhosted.org/packages/78/82/816a7adfe1f7acc8151f01ec86ef64467a3c833932d8f19f8e06613b8a4e/blis-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8c888438ae99c500422d50698e3028b65caa8ebb44e24204d87fda2df64058f7", size = 3023686, upload-time = "2025-11-17T12:27:52.062Z" }, + { url = "https://files.pythonhosted.org/packages/1e/e2/0e93b865f648b5519360846669a35f28ee8f4e1d93d054f6850d8afbabde/blis-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8177879fd3590b5eecdd377f9deafb5dc8af6d684f065bd01553302fb3fcf9a7", size = 14250939, upload-time = "2025-11-17T12:27:53.847Z" }, + { url = "https://files.pythonhosted.org/packages/20/07/fb43edc2ff0a6a367e4a94fc39eb3b85aa1e55e24cc857af2db145ce9f0d/blis-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:f20f7ad69aaffd1ce14fe77de557b6df9b61e0c9e582f75a843715d836b5c8af", size = 6192759, upload-time = "2025-11-17T12:27:56.176Z" }, + { url = "https://files.pythonhosted.org/packages/e6/f7/d26e62d9be3d70473a63e0a5d30bae49c2fe138bebac224adddcdef8a7ce/blis-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1e647341f958421a86b028a2efe16ce19c67dba2a05f79e8f7e80b1ff45328aa", size = 6928322, upload-time = "2025-11-17T12:27:57.965Z" }, + { url = "https://files.pythonhosted.org/packages/4a/78/750d12da388f714958eb2f2fd177652323bbe7ec528365c37129edd6eb84/blis-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d563160f874abb78a57e346f07312c5323f7ad67b6370052b6b17087ef234a8e", size = 1229635, upload-time = "2025-11-17T12:28:00.118Z" }, + { url = "https://files.pythonhosted.org/packages/e8/36/eac4199c5b200a5f3e93cad197da8d26d909f218eb444c4f552647c95240/blis-1.3.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:30b8a5b90cb6cb81d1ada9ae05aa55fb8e70d9a0ae9db40d2401bb9c1c8f14c4", size = 2815650, upload-time = "2025-11-17T12:28:02.544Z" }, + { url = "https://files.pythonhosted.org/packages/bf/51/472e7b36a6bedb5242a9757e7486f702c3619eff76e256735d0c8b1679c6/blis-1.3.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e9f5c53b277f6ac5b3ca30bc12ebab7ea16c8f8c36b14428abb56924213dc127", size = 11359008, upload-time = "2025-11-17T12:28:04.589Z" }, + { url = "https://files.pythonhosted.org/packages/84/da/d0dfb6d6e6321ae44df0321384c32c322bd07b15740d7422727a1a49fc5d/blis-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6297e7616c158b305c9a8a4e47ca5fc9b0785194dd96c903b1a1591a7ca21ddf", size = 3011959, upload-time = "2025-11-17T12:28:06.862Z" }, + { url = "https://files.pythonhosted.org/packages/20/c5/2b0b5e556fa0364ed671051ea078a6d6d7b979b1cfef78d64ad3ca5f0c7f/blis-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3f966ca74f89f8a33e568b9a1d71992fc9a0d29a423e047f0a212643e21b5458", size = 14232456, upload-time = "2025-11-17T12:28:08.779Z" }, + { url = "https://files.pythonhosted.org/packages/31/07/4cdc81a47bf862c0b06d91f1bc6782064e8b69ac9b5d4ff51d97e4ff03da/blis-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:7a0fc4b237a3a453bdc3c7ab48d91439fcd2d013b665c46948d9eaf9c3e45a97", size = 6192624, upload-time = "2025-11-17T12:28:14.197Z" }, + { url = "https://files.pythonhosted.org/packages/5f/8a/80f7c68fbc24a76fc9c18522c46d6d69329c320abb18e26a707a5d874083/blis-1.3.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c3e33cfbf22a418373766816343fcfcd0556012aa3ffdf562c29cddec448a415", size = 6934081, upload-time = "2025-11-17T12:28:16.436Z" }, + { url = "https://files.pythonhosted.org/packages/e5/52/d1aa3a51a7fc299b0c89dcaa971922714f50b1202769eebbdaadd1b5cff7/blis-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6f165930e8d3a85c606d2003211497e28d528c7416fbfeafb6b15600963f7c9b", size = 1231486, upload-time = "2025-11-17T12:28:18.008Z" }, + { url = "https://files.pythonhosted.org/packages/99/4f/badc7bd7f74861b26c10123bba7b9d16f99cd9535ad0128780360713820f/blis-1.3.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:878d4d96d8f2c7a2459024f013f2e4e5f46d708b23437dae970d998e7bff14a0", size = 2814944, upload-time = "2025-11-17T12:28:19.654Z" }, + { url = "https://files.pythonhosted.org/packages/72/a6/f62a3bd814ca19ec7e29ac889fd354adea1217df3183e10217de51e2eb8b/blis-1.3.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f36c0ca84a05ee5d3dbaa38056c4423c1fc29948b17a7923dd2fed8967375d74", size = 11345825, upload-time = "2025-11-17T12:28:21.354Z" }, + { url = "https://files.pythonhosted.org/packages/d4/6c/671af79ee42bc4c968cae35c091ac89e8721c795bfa4639100670dc59139/blis-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e5a662c48cd4aad5dae1a950345df23957524f071315837a4c6feb7d3b288990", size = 3008771, upload-time = "2025-11-17T12:28:23.637Z" }, + { url = "https://files.pythonhosted.org/packages/be/92/7cd7f8490da7c98ee01557f2105885cc597217b0e7fd2eeb9e22cdd4ef23/blis-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9de26fbd72bac900c273b76d46f0b45b77a28eace2e01f6ac6c2239531a413bb", size = 14219213, upload-time = "2025-11-17T12:28:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/0a/de/acae8e9f9a1f4bb393d41c8265898b0f29772e38eac14e9f69d191e2c006/blis-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:9e5fdf4211b1972400f8ff6dafe87cb689c5d84f046b4a76b207c0bd2270faaf", size = 6324695, upload-time = "2025-11-17T12:28:28.401Z" }, +] + +[[package]] +name = "boto3" +version = "1.43.22" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, + { name = "jmespath" }, + { name = "s3transfer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/64/31/32388d5ec332ffe81d8f3650860f94b66294009172d188c390cad58c6f5f/boto3-1.43.22.tar.gz", hash = "sha256:2a7fe12d8e0731bb8aa7c1e59b4ccc770fda031b8659c2f6f497393bdcec3051", size = 113203, upload-time = "2026-06-03T19:33:13.39Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/6b/c2fb3b91e849882df5426e68cc15eb2b5ba6ac28325ab2aa3a1065da5884/boto3-1.43.22-py3-none-any.whl", hash = "sha256:0597fb9fe1613e636ac55219a5a54ad0fcb7c15e6be32c799301f7fb53ff04e1", size = 140536, upload-time = "2026-06-03T19:33:10.939Z" }, +] + +[[package]] +name = "botocore" +version = "1.43.22" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jmespath" }, + { name = "python-dateutil" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/cf/840f1b8db16d45e3807c23d1ea779723eed1cd9cf3b6c49e16f372d2a777/botocore-1.43.22.tar.gz", hash = "sha256:b00de525e538289ed4a7a85263f1be4e47473c124cec87be6b23be49356bf745", size = 15458781, upload-time = "2026-06-03T19:33:02.882Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/6b/576a1b0f915871e35f14a33104f2bcae635f19c6a72486ba639db0d1fc70/botocore-1.43.22-py3-none-any.whl", hash = "sha256:ceec9f81d0891abe7b28ca2b2ee47e32de7b3360ad11e80d351470f015217379", size = 15141375, upload-time = "2026-06-03T19:32:58.324Z" }, +] + +[[package]] +name = "catalogue" +version = "2.0.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/38/b4/244d58127e1cdf04cf2dc7d9566f0d24ef01d5ce21811bab088ecc62b5ea/catalogue-2.0.10.tar.gz", hash = "sha256:4f56daa940913d3f09d589c191c74e5a6d51762b3a9e37dd53b7437afd6cda15", size = 19561, upload-time = "2023-09-25T06:29:24.962Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/96/d32b941a501ab566a16358d68b6eb4e4acc373fab3c3c4d7d9e649f7b4bb/catalogue-2.0.10-py3-none-any.whl", hash = "sha256:58c2de0020aa90f4a2da7dfad161bf7b3b054c86a5f09fcedc0b2b740c109a9f", size = 17325, upload-time = "2023-09-25T06:29:23.337Z" }, +] + +[[package]] +name = "certifi" +version = "2026.5.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/ce/ee2ecad540810a79593028e88299baeae54d346cc7a0d94b6199988b89b1/certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d", size = 135422, upload-time = "2026-05-20T11:46:50.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/8c/57e832b7af6d7c5abe66eb3fbe3a3a32f4d11ea23a1aa7131371035be991/certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897", size = 134134, upload-time = "2026-05-20T11:46:48.578Z" }, +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328, upload-time = "2026-04-02T09:26:24.331Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061, upload-time = "2026-04-02T09:26:25.568Z" }, + { url = "https://files.pythonhosted.org/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031, upload-time = "2026-04-02T09:26:26.865Z" }, + { url = "https://files.pythonhosted.org/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", size = 225239, upload-time = "2026-04-02T09:26:28.044Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f8/d0118a2f5f23b02cd166fa385c60f9b0d4f9194f574e2b31cef350ad7223/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116", size = 216589, upload-time = "2026-04-02T09:26:29.239Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f1/6d2b0b261b6c4ceef0fcb0d17a01cc5bc53586c2d4796fa04b5c540bc13d/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb", size = 202733, upload-time = "2026-04-02T09:26:30.5Z" }, + { url = "https://files.pythonhosted.org/packages/6f/c0/7b1f943f7e87cc3db9626ba17807d042c38645f0a1d4415c7a14afb5591f/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1", size = 212652, upload-time = "2026-04-02T09:26:31.709Z" }, + { url = "https://files.pythonhosted.org/packages/38/dd/5a9ab159fe45c6e72079398f277b7d2b523e7f716acc489726115a910097/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15", size = 211229, upload-time = "2026-04-02T09:26:33.282Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ff/531a1cad5ca855d1c1a8b69cb71abfd6d85c0291580146fda7c82857caa1/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5", size = 203552, upload-time = "2026-04-02T09:26:34.845Z" }, + { url = "https://files.pythonhosted.org/packages/c1/4c/a5fb52d528a8ca41f7598cb619409ece30a169fbdf9cdce592e53b46c3a6/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d", size = 230806, upload-time = "2026-04-02T09:26:36.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", size = 212316, upload-time = "2026-04-02T09:26:37.672Z" }, + { url = "https://files.pythonhosted.org/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464", size = 227274, upload-time = "2026-04-02T09:26:38.93Z" }, + { url = "https://files.pythonhosted.org/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", size = 218468, upload-time = "2026-04-02T09:26:40.17Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", size = 148460, upload-time = "2026-04-02T09:26:41.416Z" }, + { url = "https://files.pythonhosted.org/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", size = 159330, upload-time = "2026-04-02T09:26:42.554Z" }, + { url = "https://files.pythonhosted.org/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", size = 147828, upload-time = "2026-04-02T09:26:44.075Z" }, + { url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" }, + { url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" }, + { url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" }, + { url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" }, + { url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" }, + { url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" }, + { url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" }, + { url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" }, + { url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" }, + { url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" }, + { url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" }, + { url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" }, + { url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" }, + { url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" }, + { url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" }, + { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" }, + { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" }, + { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" }, + { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" }, + { url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" }, + { url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" }, + { url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" }, + { url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" }, + { url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" }, + { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" }, + { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" }, + { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" }, + { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" }, + { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" }, + { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" }, + { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" }, + { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" }, + { url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" }, + { url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" }, + { url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" }, + { url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" }, + { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" }, + { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" }, + { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" }, + { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" }, + { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, +] + +[[package]] +name = "click" +version = "8.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9b/98/518d8e5081007684232226f475082b30087d0f585e8457db087298259f49/click-8.4.1.tar.gz", hash = "sha256:918b5633eddf6b41c32d4f454bf0de810065c74e3f7dbf8ee5452f8be88d3e96", size = 353007, upload-time = "2026-05-22T04:08:37.769Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/0d/67e5b4109ea4a837e80daa87c2c696711955e40449a97e8926672534def2/click-8.4.1-py3-none-any.whl", hash = "sha256:482be17c6991b8c19c5429a1e995d9b0efdbb63172824c41f99965dc0ade8ec2", size = 116639, upload-time = "2026-05-22T04:08:35.26Z" }, +] + +[[package]] +name = "cloudpathlib" +version = "0.24.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/06/19/58bc6b5d7d0f81c7209b05445af477e147c486552f96665a5912211839b9/cloudpathlib-0.24.0.tar.gz", hash = "sha256:c521a984e77b47e656fe78e20a7e3e260e0ab45fc69e33ac01094227c979e34a", size = 53600, upload-time = "2026-04-30T00:54:43.265Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/5b/ba933f896d9b0b07608d575a8501e2b4e32166b60d84c430a4a7285ebe64/cloudpathlib-0.24.0-py3-none-any.whl", hash = "sha256:b1c51e2d2ec7dc4fed6538991f4aea849d6cf11a7e6b9069f86e461aa1f9b5b4", size = 63214, upload-time = "2026-04-30T00:54:42.06Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "confection" +version = "1.3.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/65/efd0fe8a936fc8ca2978cb7b82581fb20d901c6039e746a808f746b7647b/confection-1.3.3.tar.gz", hash = "sha256:f0f6810d567ff73993fe74d218ca5e1ffb6a44fb03f391257fc5d033546cbfaa", size = 54895, upload-time = "2026-03-24T18:45:24.331Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/e4/d66708bdf0d92fb4d49b22cdff4b10cec38aca5dcd7e81d909bb55c65cd7/confection-1.3.3-py3-none-any.whl", hash = "sha256:b9fef9ee84b237ef4611ec3eb5797b70e13063e6310ad9f15536373f5e313c82", size = 35902, upload-time = "2026-03-24T18:45:22.664Z" }, +] + +[[package]] +name = "coverage" +version = "7.14.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/fd/0ab2772530e946e1be1abd0bc09e647ec9b02e88f0867857601fefca8953/coverage-7.14.1.tar.gz", hash = "sha256:30c08f7d90415aa98b3c990385dea2939b0da55f38515e5b369b83655f8523be", size = 920132, upload-time = "2026-05-26T20:41:36.783Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/b7/bdbb725ba02c5b42825b200c940f38b7a54fcad24627b7192f78f8110d76/coverage-7.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a06c76364a9360e33d6d23769aefdf7f66f38e2ffb60ceb1baaa4989d83b695c", size = 220022, upload-time = "2026-05-26T20:39:03.702Z" }, + { url = "https://files.pythonhosted.org/packages/72/81/fdc0898a55c6219223291ec1a1fe89966ef212ce82276aa0899df84b5de0/coverage-7.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fad54e871165f6ec2f536063ac74c3104508a12963e64072ba44bd822de52b0c", size = 220379, upload-time = "2026-05-26T20:39:05.381Z" }, + { url = "https://files.pythonhosted.org/packages/de/72/de048c4a25e13bce59ac6a339351c10bdf2515e07459afcdaf04dc3143a2/coverage-7.14.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:84b535f00655ecafe1d929d1fb00ed5d6fa3051ea643ab2c161a3887b86f294b", size = 251888, upload-time = "2026-05-26T20:39:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/28/30/300c343f68beb9d4cbb64ec81e58c5b6b80b56927f72d2b38654ac26e013/coverage-7.14.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6b6b0853b895fe0e98cbfc580d1ec3393d9302b4b1e96a77b3f5c91fdab899e6", size = 254624, upload-time = "2026-05-26T20:39:09.037Z" }, + { url = "https://files.pythonhosted.org/packages/b1/ed/7b25642496e8170b6bac14adce00537c6e5fa2d586159401a4de3e8b49e6/coverage-7.14.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:442cc9c952b2df400cda54bb04ab87330cf2cd08a8692cbbea36773531eb6f37", size = 255739, upload-time = "2026-05-26T20:39:10.889Z" }, + { url = "https://files.pythonhosted.org/packages/7f/a2/abd210b8c4e29c24e4624916db97bb519097a91034aaeb767f937e7da794/coverage-7.14.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8270544c361ed405a27a060dbc9ed2c124b084d96dfdc2d9a2510482aef981ad", size = 257998, upload-time = "2026-05-26T20:39:12.722Z" }, + { url = "https://files.pythonhosted.org/packages/7f/24/7c50beed3792fe62f6ce0545c6686ce83379719e2c0276179333d97eae92/coverage-7.14.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:48b283b1dd6372e8de2a7a9a4c4d5dc06f4d4fd209b876f3c88a7a205a0c8f84", size = 252296, upload-time = "2026-05-26T20:39:14.259Z" }, + { url = "https://files.pythonhosted.org/packages/15/05/0f874628ebcbfc77ead559ff210281ef06a97db08481832e7dd39274a135/coverage-7.14.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5b0c99ba93a07d56f6df340bb79be53202a082b2fdb81bfe6190b741a3470d54", size = 253658, upload-time = "2026-05-26T20:39:15.923Z" }, + { url = "https://files.pythonhosted.org/packages/99/6f/ca6ad067364b337ef997802115e7ecad2abd2248b05471464b0dea02b4d4/coverage-7.14.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e471bc5769ff073b058cfadb0d736b56ce067c8560eabeb0da88462df98c23e7", size = 251803, upload-time = "2026-05-26T20:39:17.537Z" }, + { url = "https://files.pythonhosted.org/packages/c0/30/b9b4d377cd9f40baf228068f5a81faf8450c6228503011bd499708483a50/coverage-7.14.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f497a1ea81d4cd7c10ddcaa685135b9aabd291af3d55775a9ddf3cb7a364cdd9", size = 255873, upload-time = "2026-05-26T20:39:19.414Z" }, + { url = "https://files.pythonhosted.org/packages/3c/21/7c721a9e5e6bb88547d30a787aefb97512d3f54c1324c7488d9b3743f7f9/coverage-7.14.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:2222be86d0b54f5dd5a38f45f17f315f737245e857bf0bdedc70734f84a13c02", size = 251372, upload-time = "2026-05-26T20:39:21.169Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f8ae5a2200130e1503cd7661a6cd3b2b7bacef98277fbf3571fb13f8b766/coverage-7.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:85e85586565842f6932abebd4c18bcb1074223dc0b3576e7d173ca710622813a", size = 253245, upload-time = "2026-05-26T20:39:23.097Z" }, + { url = "https://files.pythonhosted.org/packages/34/62/70a9024672a5f6910517d9628c52c9afbdd3cf8f46426af52bb148a56fff/coverage-7.14.1-cp312-cp312-win32.whl", hash = "sha256:4a28fd227808366b196a75476dced2eb35b351d6766ba9c858dc93319e87f4f1", size = 222567, upload-time = "2026-05-26T20:39:24.868Z" }, + { url = "https://files.pythonhosted.org/packages/f6/81/8b7cd386839b039ebe1855733b9f9449a8dec5d79564018234f185a7fa70/coverage-7.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:54acdb6674a4661768d7bf7db32dfb9f46ab1d764f8aba6df75ce1a6a088724e", size = 223372, upload-time = "2026-05-26T20:39:26.603Z" }, + { url = "https://files.pythonhosted.org/packages/ae/ba/b44d472022f620d289d95fa830143235c0c36461c6f2437ea8d51e5481ed/coverage-7.14.1-cp312-cp312-win_arm64.whl", hash = "sha256:99cd41ff91afd94896fea3bc002706b6ae4ce95727d06e4a0f39c0a8d8bd8b1a", size = 221989, upload-time = "2026-05-26T20:39:28.242Z" }, + { url = "https://files.pythonhosted.org/packages/8a/9e/5f6d56327c62b185225d145191c607e07515294a0aa6338e58805cd4a5ac/coverage-7.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:be9f2c802dcfce3f71298303aa5dad0dce440a76c52f2f60dacd8656dab78793", size = 220044, upload-time = "2026-05-26T20:39:29.902Z" }, + { url = "https://files.pythonhosted.org/packages/75/92/e82aca356744cbbc0f77a0b623e38918c1872361963413a3bab5d0340393/coverage-7.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6223a72fd0e4c7156353ec0f08a5f93623e1d3034d0e2683b9bb8ea674131b1d", size = 220412, upload-time = "2026-05-26T20:39:31.561Z" }, + { url = "https://files.pythonhosted.org/packages/27/c9/385bde0bf7ed0f4bf3a7ee5367060a86b5d218718cfd6fb943c0f836b34f/coverage-7.14.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7279d2110a28cebc738b6459ecda2771735a4c18465fbbd36b3288fe5ed92247", size = 251412, upload-time = "2026-05-26T20:39:33.337Z" }, + { url = "https://files.pythonhosted.org/packages/51/8c/23faf6a2343a0d17f960a4bd56c43bc7eb4cf312f774dd6ceebd82c7d8fc/coverage-7.14.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9eeb3fcbc13ba40dfbdb22d01d196a28e9cef9ed4c29b60061a1e0e823a9929d", size = 254008, upload-time = "2026-05-26T20:39:35.009Z" }, + { url = "https://files.pythonhosted.org/packages/42/06/36f4aa9ca8a815e6036156e80706a67828bb97bd826948244f6996dda957/coverage-7.14.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f0cfc27c539f07cf5c0a4cfe211d0b6cae039f8f40526dbaa71944e64b50a7b", size = 255241, upload-time = "2026-05-26T20:39:36.71Z" }, + { url = "https://files.pythonhosted.org/packages/ca/79/95266316352f90f6b1c6736bb413302edfde2453fb32422d3911642691b3/coverage-7.14.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:221c70f316241a78e77e607c227cefc8808d4e08f28d99c04f35694690e940be", size = 257373, upload-time = "2026-05-26T20:39:38.412Z" }, + { url = "https://files.pythonhosted.org/packages/e3/9c/58316d1f66c488b5fca8a0eb3e98348807813efa8a0d0833b9021be27488/coverage-7.14.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:da028256b04ec30e5e0114b6f76172938c313991f0a2d3d894271315cf5d5e43", size = 251635, upload-time = "2026-05-26T20:39:40.268Z" }, + { url = "https://files.pythonhosted.org/packages/ef/5a/ca2398a568e16fed7bb713e84ba3603a7164fb65779abe645c565ec890d5/coverage-7.14.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76a085d7005236a767e3426148b2c407e53ad61695c562f8a81da2d373324901", size = 253373, upload-time = "2026-05-26T20:39:42.145Z" }, + { url = "https://files.pythonhosted.org/packages/6e/2c/0396562c32deaebe7be51d865b3a41e9a87d7561acafe1a28f53b07e019a/coverage-7.14.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b553d04b5e778a8e56d57eb134aff42a92718ecba45e79c4764ecfa40efd92ff", size = 251341, upload-time = "2026-05-26T20:39:43.907Z" }, + { url = "https://files.pythonhosted.org/packages/fd/8f/a94f9221184c9cae1ee115820e3798e48b6b17777a9f19e46fb9a0c8dc74/coverage-7.14.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:46f714d2fb8ae2f4f29f23ada7f1e79b759fff5a70f94a1dac23af204c3ec9e4", size = 255497, upload-time = "2026-05-26T20:39:46.166Z" }, + { url = "https://files.pythonhosted.org/packages/71/69/505d70e47db1eaebcd002c39759707621ef184cd6b1ae084d9f41293f323/coverage-7.14.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:1896f5e19ff3f0431c7ce2172adc54890fd97f86b59ced8ca1649145d9ffe35d", size = 251159, upload-time = "2026-05-26T20:39:48.03Z" }, + { url = "https://files.pythonhosted.org/packages/e0/aa/58681c383aa33a9d2ed40a02d7a22fbf780d1fa4d575396365777828198c/coverage-7.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:62fd185ef9df3c33d1c8178c5af105f762afbad96038de9a4ae100aa6297ca33", size = 252934, upload-time = "2026-05-26T20:39:49.872Z" }, + { url = "https://files.pythonhosted.org/packages/eb/fd/11c928cd6bdffc7074bb5965c173d9ebf517fb00205e1da524b98d29ef92/coverage-7.14.1-cp313-cp313-win32.whl", hash = "sha256:ab4af6352741a604c431c6072fce5bee33bf0f20dc7a56618d6bf6bb89e9810c", size = 222584, upload-time = "2026-05-26T20:39:51.68Z" }, + { url = "https://files.pythonhosted.org/packages/6f/92/fb416fc26d340dcba19518c418d6048e913186e17243982c5e435e41fa7a/coverage-7.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:7af486dabe8954d03b087f0021540897afe084f04e16ff5579e08cc46f871416", size = 223394, upload-time = "2026-05-26T20:39:53.472Z" }, + { url = "https://files.pythonhosted.org/packages/73/c6/02d56e3867972f77d5036de924643f26c056e848f00452cafb4dbc3c29b4/coverage-7.14.1-cp313-cp313-win_arm64.whl", hash = "sha256:2224f89ffd0c5605ccce1ed7a584da162bc7c55f601ab1c946bc9de31a486b42", size = 222015, upload-time = "2026-05-26T20:39:55.374Z" }, + { url = "https://files.pythonhosted.org/packages/4d/9e/fcc77914050df73f7662fa1f00902774c79c075a8388ab334074574bf77e/coverage-7.14.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:de286598cc65d2b489411174b1faec2f5a7775fb3201fd925db2a76b4030f37d", size = 220733, upload-time = "2026-05-26T20:39:57.189Z" }, + { url = "https://files.pythonhosted.org/packages/f7/67/2963cbdaf5cbadec44efa3a1e39eaa1f02df4079585f05387607a221e126/coverage-7.14.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:042c46ded7c288aeb07cf14a28b6c1e10b78fcba40171c3fa1e939377eeef0b5", size = 221086, upload-time = "2026-05-26T20:39:59.019Z" }, + { url = "https://files.pythonhosted.org/packages/c8/c5/8701645574e11881f2f47d8930f98bc48b5d43b25eb5b4430dfc4a2f9f48/coverage-7.14.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f4ddbe407477f04c45115d1a4e5bc480f753553b534d338d4c3358b1cdd0ea52", size = 262381, upload-time = "2026-05-26T20:40:00.822Z" }, + { url = "https://files.pythonhosted.org/packages/7c/28/7a64d73598263e0c5abd5084211a8474488d31b3c552ff531c719dfcff62/coverage-7.14.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d13e6725992e2d2fd7d81d4f5241952d13740121dfd501da09201be39b2c003a", size = 264458, upload-time = "2026-05-26T20:40:02.506Z" }, + { url = "https://files.pythonhosted.org/packages/fa/d8/4969179db9f7eb4df218e69540adf829d1c835f59452513d065d15446802/coverage-7.14.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f747dc8edcfe740130f28f32f3995e955494285717e86ee25af51db2219df08a", size = 266884, upload-time = "2026-05-26T20:40:04.421Z" }, + { url = "https://files.pythonhosted.org/packages/a6/78/a45d5794dbc9bafd97afc96a4377c86c7820d78b6cf51b89bc1d4e919275/coverage-7.14.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ced2f09ef276fd58611a1ef502164ad266d2b75174e5a40cabbdb4033f9f6cf2", size = 268022, upload-time = "2026-05-26T20:40:06.298Z" }, + { url = "https://files.pythonhosted.org/packages/21/cb/4f5e354e9e3e67af96bd4e57113e6db6b22298c7168b13eec408a549903d/coverage-7.14.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b84800013769a78ccb9ef4659402e26d06867e337b61ec365f77ad008adea80e", size = 261631, upload-time = "2026-05-26T20:40:08.226Z" }, + { url = "https://files.pythonhosted.org/packages/ec/49/eced49af4cb996d5d8b7e94e736175c513e4facd3398507b89892b4326d8/coverage-7.14.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ea8cd6ca0ee9f616aaef3afc6882e32c2cbf18b00d96313ffd76af650574034d", size = 264443, upload-time = "2026-05-26T20:40:10.137Z" }, + { url = "https://files.pythonhosted.org/packages/f1/d8/5603a88a7c5913a6b54f6cb1a8c46f7b39cbb30f27cd3f492908da09b2d7/coverage-7.14.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:aa5e304a873fabddc11e484e9b6b738bd38bd7bed17b09aa84eecf5332e8b8bb", size = 262069, upload-time = "2026-05-26T20:40:11.999Z" }, + { url = "https://files.pythonhosted.org/packages/f0/59/2ae3cb79da554a06c8619d6c88ea19dd1e4aed4b834b6a83bb1fa243bdc5/coverage-7.14.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:5a1c5215be81035e629d5bc756650634d0bf31991038db7a0eccb90f025ce16d", size = 265780, upload-time = "2026-05-26T20:40:13.858Z" }, + { url = "https://files.pythonhosted.org/packages/af/5f/b130c1dc999031f2648bd25317fbce505ad8d5562079b4ed81e736a84967/coverage-7.14.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:79058c47dae6788504b5effb319961bcd72d7240551464b91d474bc0ed186d69", size = 260970, upload-time = "2026-05-26T20:40:16.142Z" }, + { url = "https://files.pythonhosted.org/packages/87/d1/ec13ccddeb48ec963bdfa72a11224bac2584bd045ba13beca82f8113e9c7/coverage-7.14.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:370c5afae3fa0658e11694a32b24c2778f6bc2d17718121f94ee185e69f26b54", size = 263157, upload-time = "2026-05-26T20:40:18.382Z" }, + { url = "https://files.pythonhosted.org/packages/cf/c2/cd91ead503045161092d3845f7bb95ea2f25131ce96d3e314dd835d91b9c/coverage-7.14.1-cp313-cp313t-win32.whl", hash = "sha256:3758dd0a7f1fa57365ef2e781df0f0731d38b6e3772259d13dae4bd8a958d4b1", size = 223259, upload-time = "2026-05-26T20:40:20.381Z" }, + { url = "https://files.pythonhosted.org/packages/71/9f/1e28d97e6bd2c76b07f38b7c02870f1371255ff6717f54eca578fcbbdd0e/coverage-7.14.1-cp313-cp313t-win_amd64.whl", hash = "sha256:6ff665fb023a77386fe11685190cee1f60a7d635994a30d9b0a061533d470fce", size = 224320, upload-time = "2026-05-26T20:40:22.316Z" }, + { url = "https://files.pythonhosted.org/packages/a9/e0/d936e908f0e1efa55e52b91e01b52f1055cef5e1ab2718493390ed8e2fb8/coverage-7.14.1-cp313-cp313t-win_arm64.whl", hash = "sha256:17a5a241e5997621a956a7f402a7433ef4221e5152809b785bec79e2323799f1", size = 222577, upload-time = "2026-05-26T20:40:24.894Z" }, + { url = "https://files.pythonhosted.org/packages/d6/34/fc2f101b151af3799a101f0550b0454aa008afdc0add677394ec4aa8ea10/coverage-7.14.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d5ed429d0b8edaac649e889b4ffcedb6c80b06629a3f93050e3dddfb99235bee", size = 220091, upload-time = "2026-05-26T20:40:27.249Z" }, + { url = "https://files.pythonhosted.org/packages/3d/a7/1ebae2ab5b961b5c79bb09fe7b3ac99edb190d8be4a8c510b2cf66f46468/coverage-7.14.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8011224a62280e50dab346960c03cf47aca1a1e09e608c0fb33fd6e0cc8e9500", size = 220421, upload-time = "2026-05-26T20:40:30.084Z" }, + { url = "https://files.pythonhosted.org/packages/5e/90/92aca9cf0acc95123c96cd1eb1f08917897a7f5dee01e15738922971ec31/coverage-7.14.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:12c42ec1e14f553c4f817e989365982e646e27211f10a0f717855b94a79c8906", size = 251466, upload-time = "2026-05-26T20:40:32.542Z" }, + { url = "https://files.pythonhosted.org/packages/26/2b/78048cbe3b999f6cbf9cc0d90abba6a88a3e0863a8c1c6cbc762f3f8802f/coverage-7.14.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:06144cd511cf2624873a035c5069cf297144f6e77a73ee3d7a55b605ec5efb42", size = 253973, upload-time = "2026-05-26T20:40:34.473Z" }, + { url = "https://files.pythonhosted.org/packages/8e/21/c2e33b29d1cfde484a19d437afc343c6cd30b08d78cbbf9f5aff14e57b2b/coverage-7.14.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a311d8e1da24be5c1ccf85cbfb06315dbaa1703d5a1eab3f6432c72b837917c8", size = 255318, upload-time = "2026-05-26T20:40:38.154Z" }, + { url = "https://files.pythonhosted.org/packages/8e/ee/aad2f108d63b769121005302f16bf66db8625c88ceaba466942e09a2607e/coverage-7.14.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c79cead5b5bc584d9c71451cb984d0e3a84e0c0937379c8efcbf27c8d661b851", size = 257633, upload-time = "2026-05-26T20:40:40.164Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f8/11a2c29b4fd76d9849f81d0bb812ec0017a9396df3217214e38934a8c837/coverage-7.14.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dcbf65f1f66a26cdd88c35cf68fb4729c5d1cd2e88added72420541dfb212034", size = 251488, upload-time = "2026-05-26T20:40:42.631Z" }, + { url = "https://files.pythonhosted.org/packages/c9/b8/9a5820de4b8ac2b71d85e3b5fb49108d7469c665f0e2ad0dd7569023e305/coverage-7.14.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fd86572566fb40189a8260446158235159bc7a82dfbc87a3b39cf4fb57fcec1c", size = 253329, upload-time = "2026-05-26T20:40:45.208Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ff/f33e4823667e27548e8fd8df44217515303f9808d0ff29817db56f87d990/coverage-7.14.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:7771b601718fdde84832c3a434ca9bbf4ae9adbc49d84198b4110700c3c77c36", size = 251291, upload-time = "2026-05-26T20:40:47.502Z" }, + { url = "https://files.pythonhosted.org/packages/68/9b/489db0ebb209054766b90a9014a45f6d26eb724c02ec21311c3733b5a644/coverage-7.14.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:39b21e212c55af06fa375e3dbf90a8a8e38792f3a910c580066d23563830ddd5", size = 255564, upload-time = "2026-05-26T20:40:49.372Z" }, + { url = "https://files.pythonhosted.org/packages/27/b5/16bc2d4c2409b23c7737edb68c83bc89e345f378050549fe1d75ac7d34d5/coverage-7.14.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f2302660e32562a532b442480121aef8aa61a5bdb20b30bf0adab29f10a5a4b4", size = 251107, upload-time = "2026-05-26T20:40:51.677Z" }, + { url = "https://files.pythonhosted.org/packages/7d/0c/2629997469a00cd069d588a41c9dc887610f2775ae89d250c4791e65272a/coverage-7.14.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:03a6f93c1ec3b7f2e77b5dbcc5573a2c21f12529a5c6bbe0f16f72303cc2fa4d", size = 252764, upload-time = "2026-05-26T20:40:54.267Z" }, + { url = "https://files.pythonhosted.org/packages/d2/ee/f78d63c8f079e0d7211c7e2401fa17e311514534ba61bae03e4b287ce4ab/coverage-7.14.1-cp314-cp314-win32.whl", hash = "sha256:8a3ce026d73290f42f08dafecbd82c193a74df280461fbf97300fec51fd133ee", size = 222837, upload-time = "2026-05-26T20:40:56.496Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b9/be539854f93a70dfbeec69117f33ec70dc42ff0b65b5b07ab8d40d04228e/coverage-7.14.1-cp314-cp314-win_amd64.whl", hash = "sha256:114c95ef29302423b87d159075805f4ab973254a2638a5d7d046c94887cc87d7", size = 223650, upload-time = "2026-05-26T20:40:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/fe/9e/24e2842fef40f35ac82ba3a7719c8023d011bf3bf652d0675316a9d088a1/coverage-7.14.1-cp314-cp314-win_arm64.whl", hash = "sha256:a07891c3f4805442b31b71e84ba3cf29ed1aa9a428284e06deeb4b23e5b46343", size = 222218, upload-time = "2026-05-26T20:41:00.321Z" }, + { url = "https://files.pythonhosted.org/packages/0a/1d/ac0a9df5fe31c1e8bdd658074905fc12844a05c1a7e3fdb8417e97c31e23/coverage-7.14.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1101a5ebb083aecb625ebb6209d4105b58f647b093cb2dc8122d7b33f743cfe1", size = 220822, upload-time = "2026-05-26T20:41:02.281Z" }, + { url = "https://files.pythonhosted.org/packages/32/cf/f964fd9aff20323f9f1a726c97135f8a76bcd87b92dad141a456a43f3c64/coverage-7.14.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:851b9e1e4e8a4608e77c79714b2e77c0970d2ed7202a05e92ae407817481887b", size = 221084, upload-time = "2026-05-26T20:41:04.593Z" }, + { url = "https://files.pythonhosted.org/packages/d8/5e/7e5ef2aba844de2b80d678619fcf0841b42e3f37f16411226f3fe4c1016f/coverage-7.14.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d5b89cdfb2ee051b71e8c3c70bd81a9eff81100f736a269136fe1a68efe00474", size = 262454, upload-time = "2026-05-26T20:41:06.641Z" }, + { url = "https://files.pythonhosted.org/packages/64/62/75809bded87015cc4935524218a2a8ed8dd1a8498bfed30a2f4f7a4b4d34/coverage-7.14.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0177614a0370f227888b4e436a7c55686d6a9f90eb1ade2b624ba685a1686e86", size = 264578, upload-time = "2026-05-26T20:41:08.556Z" }, + { url = "https://files.pythonhosted.org/packages/f3/42/d33392dc14633525012d2d504fa1a33b05538bf535f5c1d64675e5754b78/coverage-7.14.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d69af5dea2de76fc485a83032a630523f985198b7e25be901ec60181587b01e", size = 266981, upload-time = "2026-05-26T20:41:10.824Z" }, + { url = "https://files.pythonhosted.org/packages/2a/49/0157c4428c2aca7f1e09d5565930586fd5ae36f1655f08b0daa7cf1fcae1/coverage-7.14.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:35ab22d91de736e8966b980dc355cbcdd2c6dbbcfe275f9a2991bc8a91b3df65", size = 268112, upload-time = "2026-05-26T20:41:12.966Z" }, + { url = "https://files.pythonhosted.org/packages/96/26/86b9ce71f4092b1ed325ce1421698081df1286b833400b6836912834d6e0/coverage-7.14.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:357d4e32935c36588aaba057d734fa32428c360c9fc2e4442afbf1b646beee6e", size = 261558, upload-time = "2026-05-26T20:41:15Z" }, + { url = "https://files.pythonhosted.org/packages/20/4c/c311210c5472cf5401d8422b0d7812cdd520f24417673afabda6c323faca/coverage-7.14.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:51bd64741cc6fa065abd300ede1afe5a5291ece9c31da8b24884deda48bcc3f8", size = 264447, upload-time = "2026-05-26T20:41:17.369Z" }, + { url = "https://files.pythonhosted.org/packages/fb/71/59513f8710ed3e6b0ac0a050a5b7e977bb9c9e880354863b5d00d8809256/coverage-7.14.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:9132cd363a68a4c3daa7c8704a654b1e39d3360f6f5b8ddd470608a945236c07", size = 262048, upload-time = "2026-05-26T20:41:19.309Z" }, + { url = "https://files.pythonhosted.org/packages/84/8d/bceed32dc494f5bbf50f775cd2e78ca814953942b5ea28d3c1c3ac316f14/coverage-7.14.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:07c6290b1697b862c0478eab545eec949a0d0e4d6d03497f446d706da3b4f2de", size = 265781, upload-time = "2026-05-26T20:41:21.559Z" }, + { url = "https://files.pythonhosted.org/packages/e7/c5/9348fe40dbfd4991aaf78df2c6c3098bfb2cc834d1fd362a64b4efef855a/coverage-7.14.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:5ea0c297e27133853b4d8a3eb799bff5a2dbd9f2f41537a240d337ac9b4df890", size = 260896, upload-time = "2026-05-26T20:41:23.428Z" }, + { url = "https://files.pythonhosted.org/packages/ca/92/1ea0f03929da7cf87206b1fa24f4c8e9c158be0455481af29ec0a1f3503f/coverage-7.14.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:01b7733daad0237daa01ef80fe2dfceffc911e6a17fa7b55d14aa8214eaaaecd", size = 263214, upload-time = "2026-05-26T20:41:25.419Z" }, + { url = "https://files.pythonhosted.org/packages/f6/a9/b2493c054c0e01a643266742ab45e15744e60743f9260cd930c7142b1124/coverage-7.14.1-cp314-cp314t-win32.whl", hash = "sha256:6adc5a36984624a70bf11d7184e20fa0a49aa7c47ffab43804106a1a695ea22e", size = 223624, upload-time = "2026-05-26T20:41:27.795Z" }, + { url = "https://files.pythonhosted.org/packages/fc/bd/3e1e6a57fccd2d7c83fcdf338e93ba98eb85c6e877dd34731ac585375490/coverage-7.14.1-cp314-cp314t-win_amd64.whl", hash = "sha256:ddf799247318f34dbcd2efa8c95a8d0642674e926bb1774cf9b63dfd2a389d1c", size = 224728, upload-time = "2026-05-26T20:41:30.098Z" }, + { url = "https://files.pythonhosted.org/packages/bb/d7/31066cf1d2f0c6c797fce911bcfa01dd35642dc6da992a950256097c5860/coverage-7.14.1-cp314-cp314t-win_arm64.whl", hash = "sha256:145986fe66647eb489f18d9a997567a3fd358584c4b5a808769113abc07466af", size = 222752, upload-time = "2026-05-26T20:41:32.123Z" }, + { url = "https://files.pythonhosted.org/packages/8a/3c/1a983b9a745d7f83d53f057bcc5bf79ba6a2bbc08266b3f0c7d6fe630c9b/coverage-7.14.1-py3-none-any.whl", hash = "sha256:a252f21c27e38347e60111a3266b03827422a7d5525951aceee313aa68bab1d2", size = 211815, upload-time = "2026-05-26T20:41:34.078Z" }, +] + +[[package]] +name = "cryptography" +version = "48.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/a9/db8f313fdcd85d767d4973515e1db101f9c71f95fced83233de224673757/cryptography-48.0.0.tar.gz", hash = "sha256:5c3932f4436d1cccb036cb0eaef46e6e2db91035166f1ad6505c3c9d5a635920", size = 832984, upload-time = "2026-05-04T22:59:38.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/3d/01f6dd9190170a5a241e0e98c2d04be3664a9e6f5b9b872cde63aff1c3dd/cryptography-48.0.0-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:0c558d2cdffd8f4bbb30fc7134c74d2ca9a476f830bb053074498fbc86f41ed6", size = 8001587, upload-time = "2026-05-04T22:57:36.803Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6e/e90527eef33f309beb811cf7c982c3aeffcce8e3edb178baa4ca3ae4a6fa/cryptography-48.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f5333311663ea94f75dd408665686aaf426563556bb5283554a3539177e03b8c", size = 4690433, upload-time = "2026-05-04T22:57:40.373Z" }, + { url = "https://files.pythonhosted.org/packages/90/04/673510ed51ddff56575f306cf1617d80411ee76831ccd3097599140efdfe/cryptography-48.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7995ef305d7165c3f11ae07f2517e5a4f1d5c18da1376a0a9ed496336b69e5f3", size = 4710620, upload-time = "2026-05-04T22:57:42.935Z" }, + { url = "https://files.pythonhosted.org/packages/14/d5/e9c4ef932c8d800490c34d8bd589d64a31d5890e27ec9e9ad532be893294/cryptography-48.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:40ba1f85eaa6959837b1d51c9767e230e14612eea4ef110ee8854ada22da1bf5", size = 4696283, upload-time = "2026-05-04T22:57:45.294Z" }, + { url = "https://files.pythonhosted.org/packages/0c/29/174b9dfb60b12d59ecfc6cfa04bc88c21b42a54f01b8aae09bb6e51e4c7f/cryptography-48.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:369a6348999f94bbd53435c894377b20ab95f25a9065c283570e70150d8abc3c", size = 5296573, upload-time = "2026-05-04T22:57:47.933Z" }, + { url = "https://files.pythonhosted.org/packages/95/38/0d29a6fd7d0d1373f0c0c88a04ba20e359b257753ac497564cd660fc1d55/cryptography-48.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a0e692c683f4df67815a2d258b324e66f4738bd7a96a218c826dce4f4bd05d8f", size = 4743677, upload-time = "2026-05-04T22:57:50.067Z" }, + { url = "https://files.pythonhosted.org/packages/30/be/eef653013d5c63b6a490529e0316f9ac14a37602965d4903efed1399f32b/cryptography-48.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:18349bbc56f4743c8b12dc32e2bccb2cf83ee8b69a3bba74ef8ae857e26b3d25", size = 4330808, upload-time = "2026-05-04T22:57:52.301Z" }, + { url = "https://files.pythonhosted.org/packages/84/9e/500463e87abb7a0a0f9f256ec21123ecde0a7b5541a15e840ea54551fd81/cryptography-48.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:7e8eac43dfca5c4cccc6dad9a80504436fca53bb9bc3100a2386d730fbe6b602", size = 4695941, upload-time = "2026-05-04T22:57:54.603Z" }, + { url = "https://files.pythonhosted.org/packages/e3/dc/7303087450c2ec9e7fbb750e17c2abfbc658f23cbd0e54009509b7cc4091/cryptography-48.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9ccdac7d40688ecb5a3b4a604b8a88c8002e3442d6c60aead1db2a89a041560c", size = 5252579, upload-time = "2026-05-04T22:57:57.207Z" }, + { url = "https://files.pythonhosted.org/packages/d0/c0/7101d3b7215edcdc90c45da544961fd8ed2d6448f77577460fa75a8443f7/cryptography-48.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:bd72e68b06bb1e96913f97dd4901119bc17f39d4586a5adf2d3e47bc2b9d58b5", size = 4743326, upload-time = "2026-05-04T22:57:59.535Z" }, + { url = "https://files.pythonhosted.org/packages/ac/d8/5b833bad13016f562ab9d063d68199a4bd121d18458e439515601d3357ec/cryptography-48.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:59baa2cb386c4f0b9905bd6eb4c2a79a69a128408fd31d32ca4d7102d4156321", size = 4826672, upload-time = "2026-05-04T22:58:01.996Z" }, + { url = "https://files.pythonhosted.org/packages/98/e1/7074eb8bf3c135558c73fc2bcf0f5633f912e6fb87e868a55c454080ef09/cryptography-48.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9249e3cd978541d665967ac2cb2787fd6a62bddf1e75b3e347a594d7dacf4f74", size = 4972574, upload-time = "2026-05-04T22:58:03.968Z" }, + { url = "https://files.pythonhosted.org/packages/04/70/e5a1b41d325f797f39427aa44ef8baf0be500065ab6d8e10369d850d4a4f/cryptography-48.0.0-cp311-abi3-win32.whl", hash = "sha256:9c459db21422be75e2809370b829a87eb37f74cd785fc4aa9ea1e5f43b47cda4", size = 3294868, upload-time = "2026-05-04T22:58:06.467Z" }, + { url = "https://files.pythonhosted.org/packages/f4/ac/8ac51b4a5fc5932eb7ee5c517ba7dc8cd834f0048962b6b352f00f41ebf9/cryptography-48.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:5b012212e08b8dd5edc78ef54da83dd9892fd9105323b3993eff6bea65dc21d7", size = 3817107, upload-time = "2026-05-04T22:58:08.845Z" }, + { url = "https://files.pythonhosted.org/packages/6b/84/70e3feea9feea87fd7cbe77efb2712ae1e3e6edf10749dc6e95f4e60e455/cryptography-48.0.0-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:3cb07a3ed6431663cd321ea8a000a1314c74211f823e4177fefa2255e057d1ec", size = 7986556, upload-time = "2026-05-04T22:58:11.172Z" }, + { url = "https://files.pythonhosted.org/packages/89/6e/18e07a618bb5442ba10cf4df16e99c071365528aa570dfcb8c02e25a303b/cryptography-48.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8c7378637d7d88016fa6791c159f698b3d3eed28ebf844ac36b9dc04a14dae18", size = 4684776, upload-time = "2026-05-04T22:58:13.712Z" }, + { url = "https://files.pythonhosted.org/packages/be/6a/4ea3b4c6c6759794d5ee2103c304a5076dc4b19ae1f9fe47dba439e159e9/cryptography-48.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc90c0b39b2e3c65ef52c804b72e3c58f8a04ab2a1871272798e5f9572c17d20", size = 4698121, upload-time = "2026-05-04T22:58:16.448Z" }, + { url = "https://files.pythonhosted.org/packages/2f/59/6ff6ad6cae03bb887da2a5860b2c9805f8dac969ef01ce563336c49bd1d1/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:76341972e1eff8b4bea859f09c0d3e64b96ce931b084f9b9b7db8ef364c30eff", size = 4690042, upload-time = "2026-05-04T22:58:18.544Z" }, + { url = "https://files.pythonhosted.org/packages/ca/b4/fc334ed8cfd705aca282fe4d8f5ae64a8e0f74932e9feecb344610cf6e4d/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:55b7718303bf06a5753dcdccf2f3945cf18ad7bffde41b61226e4db31ab89a9c", size = 5282526, upload-time = "2026-05-04T22:58:20.75Z" }, + { url = "https://files.pythonhosted.org/packages/11/08/9f8c5386cc4cd90d8255c7cdd0f5baf459a08502a09de30dc51f553d38dc/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:a64697c641c7b1b2178e573cbc31c7c6684cd56883a478d75143dbb7118036db", size = 4733116, upload-time = "2026-05-04T22:58:23.627Z" }, + { url = "https://files.pythonhosted.org/packages/b8/77/99307d7574045699f8805aa500fa0fb83422d115b5400a064ddd306d7750/cryptography-48.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:561215ea3879cb1cbbf272867e2efda62476f240fb58c64de6b393ae19246741", size = 4316030, upload-time = "2026-05-04T22:58:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/fd/36/a608b98337af3cb2aff4818e406649d30572b7031918b04c87d979495348/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:ad64688338ed4bc1a6618076ba75fd7194a5f1797ac60b47afe926285adb3166", size = 4689640, upload-time = "2026-05-04T22:58:27.747Z" }, + { url = "https://files.pythonhosted.org/packages/dd/a6/825010a291b4438aecc1f568bc428189fc1175515223632477c07dc0a6df/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:906cbf0670286c6e0044156bc7d4af9cbb0ef6db9f73e52c3ec56ba6bdde5336", size = 5237657, upload-time = "2026-05-04T22:58:29.848Z" }, + { url = "https://files.pythonhosted.org/packages/b9/09/4e76a09b4caa29aad535ddc806f5d4c5d01885bd978bd984fbc6ca032cae/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:ea8990436d914540a40ab24b6a77c0969695ed52f4a4874c5137ccf7045a7057", size = 4732362, upload-time = "2026-05-04T22:58:32.009Z" }, + { url = "https://files.pythonhosted.org/packages/18/78/444fa04a77d0cb95f417dda20d450e13c56ba8e5220fc892a1658f44f882/cryptography-48.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c18684a7f0cc9a3cb60328f496b8e3372def7c5d2df39ac267878b05565aaaae", size = 4819580, upload-time = "2026-05-04T22:58:34.254Z" }, + { url = "https://files.pythonhosted.org/packages/38/85/ea67067c70a1fd4be2c63d35eeed82658023021affccc7b17705f8527dd2/cryptography-48.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9be5aafa5736574f8f15f262adc81b2a9869e2cfe9014d52a44633905b40d52c", size = 4963283, upload-time = "2026-05-04T22:58:36.376Z" }, + { url = "https://files.pythonhosted.org/packages/75/54/cc6d0f3deac3e81c7f847e8a189a12b6cdd65059b43dad25d4316abd849a/cryptography-48.0.0-cp314-cp314t-win32.whl", hash = "sha256:c17dfe85494deaeddc5ce251aebd1d60bbe6afc8b62071bb0b469431a000124f", size = 3270954, upload-time = "2026-05-04T22:58:38.791Z" }, + { url = "https://files.pythonhosted.org/packages/49/67/cc947e288c0758a4e5473d1dcb743037ab7785541265a969240b8885441a/cryptography-48.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27241b1dc9962e056062a8eef1991d02c3a24569c95975bd2322a8a52c6e5e12", size = 3797313, upload-time = "2026-05-04T22:58:40.746Z" }, + { url = "https://files.pythonhosted.org/packages/f2/63/61d4a4e1c6b6bab6ce1e213cd36a24c415d90e76d78c5eb8577c5541d2e8/cryptography-48.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:58d00498e8933e4a194f3076aee1b4a97dfec1a6da444535755822fe5d8b0b86", size = 7983482, upload-time = "2026-05-04T22:58:43.769Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ac/f5b5995b87770c693e2596559ffafe195b4033a57f14a82268a2842953f3/cryptography-48.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:614d0949f4790582d2cc25553abd09dd723025f0c0e7c67376a1d77196743d6e", size = 4683266, upload-time = "2026-05-04T22:58:46.064Z" }, + { url = "https://files.pythonhosted.org/packages/ec/c6/8b14f67e18338fbc4adb76f66c001f5c3610b3e2d1837f268f47a347dbbb/cryptography-48.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7ce4bfae76319a532a2dc68f82cc32f5676ee792a983187dac07183690e5c66f", size = 4696228, upload-time = "2026-05-04T22:58:48.22Z" }, + { url = "https://files.pythonhosted.org/packages/ea/73/f808fbae9514bd91b47875b003f13e284c8c6bdfd904b7944e803937eec1/cryptography-48.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:2eb992bbd4661238c5a397594c83f5b4dc2bc5b848c365c8f991b6780efcc5c7", size = 4689097, upload-time = "2026-05-04T22:58:50.9Z" }, + { url = "https://files.pythonhosted.org/packages/93/01/d86632d7d28db8ae83221995752eeb6639ffb374c2d22955648cf8d52797/cryptography-48.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:22a5cb272895dce158b2cacdfdc3debd299019659f42947dbdac6f32d68fe832", size = 5283582, upload-time = "2026-05-04T22:58:53.017Z" }, + { url = "https://files.pythonhosted.org/packages/02/e1/50edc7a50334807cc4791fc4a0ce7468b4a1416d9138eab358bfc9a3d70b/cryptography-48.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2b4d59804e8408e2fea7d1fbaf218e5ec984325221db76e6a241a9abd6cdd95c", size = 4730479, upload-time = "2026-05-04T22:58:55.611Z" }, + { url = "https://files.pythonhosted.org/packages/6f/af/99a582b1b1641ff5911ac559beb45097cf79efd4ead4657f578ef1af2d47/cryptography-48.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:984a20b0f62a26f48a3396c72e4bc34c66e356d356bf370053066b3b6d54634a", size = 4326481, upload-time = "2026-05-04T22:58:57.607Z" }, + { url = "https://files.pythonhosted.org/packages/90/ee/89aa26a06ef0a7d7611788ffd571a7c50e368cc6a4d5eef8b4884e866edb/cryptography-48.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5a5ed8fde7a1d09376ca0b40e68cd59c69fe23b1f9768bd5824f54681626032a", size = 4688713, upload-time = "2026-05-04T22:59:00.077Z" }, + { url = "https://files.pythonhosted.org/packages/70/ba/bcb1b0bb7a33d4c7c0c4d4c7874b4a62ae4f56113a5f4baefa362dfb1f0f/cryptography-48.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:8cd666227ef7af430aa5914a9910e0ddd703e75f039cef0825cd0da71b6b711a", size = 5238165, upload-time = "2026-05-04T22:59:02.317Z" }, + { url = "https://files.pythonhosted.org/packages/c9/70/ca4003b1ce5ca3dc3186ada51908c8a9b9ff7d5cab83cc0d43ee14ec144f/cryptography-48.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9071196d81abc88b3516ac8cdfad32e2b66dd4a5393a8e68a961e9161ddc6239", size = 4729947, upload-time = "2026-05-04T22:59:05.255Z" }, + { url = "https://files.pythonhosted.org/packages/44/a0/4ec7cf774207905aef1a8d11c3750d5a1db805eb380ee4e16df317870128/cryptography-48.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1e2d54c8be6152856a36f0882ab231e70f8ec7f14e93cf87db8a2ed056bf160c", size = 4822059, upload-time = "2026-05-04T22:59:07.802Z" }, + { url = "https://files.pythonhosted.org/packages/1e/75/a2e55f99c16fcac7b5d6c1eb19ad8e00799854d6be5ca845f9259eae1681/cryptography-48.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a5da777e32ffed6f85a7b2b3f7c5cbc88c146bfcd0a1d7baf5fcc6c52ee35dd4", size = 4960575, upload-time = "2026-05-04T22:59:09.851Z" }, + { url = "https://files.pythonhosted.org/packages/b8/23/6e6f32143ab5d8b36ca848a502c4bcd477ae75b9e1677e3530d669062578/cryptography-48.0.0-cp39-abi3-win32.whl", hash = "sha256:77a2ccbbe917f6710e05ba9adaa25fb5075620bf3ea6fb751997875aff4ae4bd", size = 3279117, upload-time = "2026-05-04T22:59:12.019Z" }, + { url = "https://files.pythonhosted.org/packages/9d/9a/0fea98a70cf1749d41d738836f6349d97945f7c89433a259a6c2642eefeb/cryptography-48.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:16cd65b9330583e4619939b3a3843eec1e6e789744bb01e7c7e2e62e33c239c8", size = 3792100, upload-time = "2026-05-04T22:59:14.884Z" }, +] + +[[package]] +name = "cymem" +version = "2.0.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/2f0fbb32535c3731b7c2974c569fb9325e0a38ed5565a08e1139a3b71e82/cymem-2.0.13.tar.gz", hash = "sha256:1c91a92ae8c7104275ac26bd4d29b08ccd3e7faff5893d3858cb6fadf1bc1588", size = 12320, upload-time = "2025-11-14T14:58:36.902Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/52/478a2911ab5028cb710b4900d64aceba6f4f882fcb13fd8d40a456a1b6dc/cymem-2.0.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e8afbc5162a0fe14b6463e1c4e45248a1b2fe2cbcecc8a5b9e511117080da0eb", size = 43745, upload-time = "2025-11-14T14:57:32.52Z" }, + { url = "https://files.pythonhosted.org/packages/f9/71/f0f8adee945524774b16af326bd314a14a478ed369a728a22834e6785a18/cymem-2.0.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9251d889348fe79a75e9b3e4d1b5fa651fca8a64500820685d73a3acc21b6a8", size = 42927, upload-time = "2025-11-14T14:57:33.827Z" }, + { url = "https://files.pythonhosted.org/packages/62/6d/159780fe162ff715d62b809246e5fc20901cef87ca28b67d255a8d741861/cymem-2.0.13-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:742fc19764467a49ed22e56a4d2134c262d73a6c635409584ae3bf9afa092c33", size = 258346, upload-time = "2025-11-14T14:57:34.917Z" }, + { url = "https://files.pythonhosted.org/packages/eb/12/678d16f7aa1996f947bf17b8cfb917ea9c9674ef5e2bd3690c04123d5680/cymem-2.0.13-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f190a92fe46197ee64d32560eb121c2809bb843341733227f51538ce77b3410d", size = 260843, upload-time = "2025-11-14T14:57:36.503Z" }, + { url = "https://files.pythonhosted.org/packages/31/5d/0dd8c167c08cd85e70d274b7235cfe1e31b3cebc99221178eaf4bbb95c6f/cymem-2.0.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d670329ee8dbbbf241b7c08069fe3f1d3a1a3e2d69c7d05ea008a7010d826298", size = 254607, upload-time = "2025-11-14T14:57:38.036Z" }, + { url = "https://files.pythonhosted.org/packages/b7/c9/d6514a412a1160aa65db539836b3d47f9b59f6675f294ec34ae32f867c82/cymem-2.0.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a84ba3178d9128b9ffb52ce81ebab456e9fe959125b51109f5b73ebdfc6b60d6", size = 262421, upload-time = "2025-11-14T14:57:39.265Z" }, + { url = "https://files.pythonhosted.org/packages/dd/fe/3ee37d02ca4040f2fb22d34eb415198f955862b5dd47eee01df4c8f5454c/cymem-2.0.13-cp312-cp312-win_amd64.whl", hash = "sha256:2ff1c41fd59b789579fdace78aa587c5fc091991fa59458c382b116fc36e30dc", size = 40176, upload-time = "2025-11-14T14:57:40.706Z" }, + { url = "https://files.pythonhosted.org/packages/94/fb/1b681635bfd5f2274d0caa8f934b58435db6c091b97f5593738065ddb786/cymem-2.0.13-cp312-cp312-win_arm64.whl", hash = "sha256:6bbd701338df7bf408648191dff52472a9b334f71bcd31a21a41d83821050f67", size = 35959, upload-time = "2025-11-14T14:57:41.682Z" }, + { url = "https://files.pythonhosted.org/packages/ce/0f/95a4d1e3bebfdfa7829252369357cf9a764f67569328cd9221f21e2c952e/cymem-2.0.13-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:891fd9030293a8b652dc7fb9fdc79a910a6c76fc679cd775e6741b819ffea476", size = 43478, upload-time = "2025-11-14T14:57:42.682Z" }, + { url = "https://files.pythonhosted.org/packages/bf/a0/8fc929cc29ae466b7b4efc23ece99cbd3ea34992ccff319089c624d667fd/cymem-2.0.13-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:89c4889bd16513ce1644ccfe1e7c473ba7ca150f0621e66feac3a571bde09e7e", size = 42695, upload-time = "2025-11-14T14:57:43.741Z" }, + { url = "https://files.pythonhosted.org/packages/4a/b3/deeb01354ebaf384438083ffe0310209ef903db3e7ba5a8f584b06d28387/cymem-2.0.13-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:45dcaba0f48bef9cc3d8b0b92058640244a95a9f12542210b51318da97c2cf28", size = 250573, upload-time = "2025-11-14T14:57:44.81Z" }, + { url = "https://files.pythonhosted.org/packages/36/36/bc980b9a14409f3356309c45a8d88d58797d02002a9d794dd6c84e809d3a/cymem-2.0.13-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e96848faaafccc0abd631f1c5fb194eac0caee4f5a8777fdbb3e349d3a21741c", size = 254572, upload-time = "2025-11-14T14:57:46.023Z" }, + { url = "https://files.pythonhosted.org/packages/fd/dd/a12522952624685bd0f8968e26d2ed6d059c967413ce6eb52292f538f1b0/cymem-2.0.13-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e02d3e2c3bfeb21185d5a4a70790d9df40629a87d8d7617dc22b4e864f665fa3", size = 248060, upload-time = "2025-11-14T14:57:47.605Z" }, + { url = "https://files.pythonhosted.org/packages/08/11/5dc933ddfeb2dfea747a0b935cb965b9a7580b324d96fc5f5a1b5ff8df29/cymem-2.0.13-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fece5229fd5ecdcd7a0738affb8c59890e13073ae5626544e13825f26c019d3c", size = 254601, upload-time = "2025-11-14T14:57:48.861Z" }, + { url = "https://files.pythonhosted.org/packages/70/66/d23b06166864fa94e13a98e5922986ce774832936473578febce64448d75/cymem-2.0.13-cp313-cp313-win_amd64.whl", hash = "sha256:38aefeb269597c1a0c2ddf1567dd8605489b661fa0369c6406c1acd433b4c7ba", size = 40103, upload-time = "2025-11-14T14:57:50.396Z" }, + { url = "https://files.pythonhosted.org/packages/2f/9e/c7b21271ab88a21760f3afdec84d2bc09ffa9e6c8d774ad9d4f1afab0416/cymem-2.0.13-cp313-cp313-win_arm64.whl", hash = "sha256:717270dcfd8c8096b479c42708b151002ff98e434a7b6f1f916387a6c791e2ad", size = 36016, upload-time = "2025-11-14T14:57:51.611Z" }, + { url = "https://files.pythonhosted.org/packages/7f/28/d3b03427edc04ae04910edf1c24b993881c3ba93a9729a42bcbb816a1808/cymem-2.0.13-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7e1a863a7f144ffb345397813701509cfc74fc9ed360a4d92799805b4b865dd1", size = 46429, upload-time = "2025-11-14T14:57:52.582Z" }, + { url = "https://files.pythonhosted.org/packages/35/a9/7ed53e481f47ebfb922b0b42e980cec83e98ccb2137dc597ea156642440c/cymem-2.0.13-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c16cb80efc017b054f78998c6b4b013cef509c7b3d802707ce1f85a1d68361bf", size = 46205, upload-time = "2025-11-14T14:57:53.64Z" }, + { url = "https://files.pythonhosted.org/packages/61/39/a3d6ad073cf7f0fbbb8bbf09698c3c8fac11be3f791d710239a4e8dd3438/cymem-2.0.13-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0d78a27c88b26c89bd1ece247d1d5939dba05a1dae6305aad8fd8056b17ddb51", size = 296083, upload-time = "2025-11-14T14:57:55.922Z" }, + { url = "https://files.pythonhosted.org/packages/36/0c/20697c8bc19f624a595833e566f37d7bcb9167b0ce69de896eba7cfc9c2d/cymem-2.0.13-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6d36710760f817194dacb09d9fc45cb6a5062ed75e85f0ef7ad7aeeb13d80cc3", size = 286159, upload-time = "2025-11-14T14:57:57.106Z" }, + { url = "https://files.pythonhosted.org/packages/82/d4/9326e3422d1c2d2b4a8fb859bdcce80138f6ab721ddafa4cba328a505c71/cymem-2.0.13-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c8f30971cadd5dcf73bcfbbc5849b1f1e1f40db8cd846c4aa7d3b5e035c7b583", size = 288186, upload-time = "2025-11-14T14:57:58.334Z" }, + { url = "https://files.pythonhosted.org/packages/ed/bc/68da7dd749b72884dc22e898562f335002d70306069d496376e5ff3b6153/cymem-2.0.13-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9d441d0e45798ec1fd330373bf7ffa6b795f229275f64016b6a193e6e2a51522", size = 290353, upload-time = "2025-11-14T14:58:00.562Z" }, + { url = "https://files.pythonhosted.org/packages/50/23/dbf2ad6ecd19b99b3aab6203b1a06608bbd04a09c522d836b854f2f30f73/cymem-2.0.13-cp313-cp313t-win_amd64.whl", hash = "sha256:d1c950eebb9f0f15e3ef3591313482a5a611d16fc12d545e2018cd607f40f472", size = 44764, upload-time = "2025-11-14T14:58:01.793Z" }, + { url = "https://files.pythonhosted.org/packages/54/3f/35701c13e1fc7b0895198c8b20068c569a841e0daf8e0b14d1dc0816b28f/cymem-2.0.13-cp313-cp313t-win_arm64.whl", hash = "sha256:042e8611ef862c34a97b13241f5d0da86d58aca3cecc45c533496678e75c5a1f", size = 38964, upload-time = "2025-11-14T14:58:02.87Z" }, + { url = "https://files.pythonhosted.org/packages/a7/2e/f0e1596010a9a57fa9ebd124a678c07c5b2092283781ae51e79edcf5cb98/cymem-2.0.13-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d2a4bf67db76c7b6afc33de44fb1c318207c3224a30da02c70901936b5aafdf1", size = 43812, upload-time = "2025-11-14T14:58:04.227Z" }, + { url = "https://files.pythonhosted.org/packages/bc/45/8ccc21df08fcbfa6aa3efeb7efc11a1c81c90e7476e255768bb9c29ba02a/cymem-2.0.13-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:92a2ce50afa5625fb5ce7c9302cee61e23a57ccac52cd0410b4858e572f8614b", size = 42951, upload-time = "2025-11-14T14:58:05.424Z" }, + { url = "https://files.pythonhosted.org/packages/01/8c/fe16531631f051d3d1226fa42e2d76fd2c8d5cfa893ec93baee90c7a9d90/cymem-2.0.13-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bc116a70cc3a5dc3d1684db5268eff9399a0be8603980005e5b889564f1ea42f", size = 249878, upload-time = "2025-11-14T14:58:06.95Z" }, + { url = "https://files.pythonhosted.org/packages/47/4b/39d67b80ffb260457c05fcc545de37d82e9e2dbafc93dd6b64f17e09b933/cymem-2.0.13-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:68489bf0035c4c280614067ab6a82815b01dc9fcd486742a5306fe9f68deb7ef", size = 252571, upload-time = "2025-11-14T14:58:08.232Z" }, + { url = "https://files.pythonhosted.org/packages/53/0e/76f6531f74dfdfe7107899cce93ab063bb7ee086ccd3910522b31f623c08/cymem-2.0.13-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:03cb7bdb55718d5eb6ef0340b1d2430ba1386db30d33e9134d01ba9d6d34d705", size = 248555, upload-time = "2025-11-14T14:58:09.429Z" }, + { url = "https://files.pythonhosted.org/packages/c7/7c/eee56757db81f0aefc2615267677ae145aff74228f529838425057003c0d/cymem-2.0.13-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1710390e7fb2510a8091a1991024d8ae838fd06b02cdfdcd35f006192e3c6b0e", size = 254177, upload-time = "2025-11-14T14:58:10.594Z" }, + { url = "https://files.pythonhosted.org/packages/77/e0/a4b58ec9e53c836dce07ef39837a64a599f4a21a134fc7ca57a3a8f9a4b5/cymem-2.0.13-cp314-cp314-win_amd64.whl", hash = "sha256:ac699c8ec72a3a9de8109bd78821ab22f60b14cf2abccd970b5ff310e14158ed", size = 40853, upload-time = "2025-11-14T14:58:12.116Z" }, + { url = "https://files.pythonhosted.org/packages/61/81/9931d1f83e5aeba175440af0b28f0c2e6f71274a5a7b688bc3e907669388/cymem-2.0.13-cp314-cp314-win_arm64.whl", hash = "sha256:90c2d0c04bcda12cd5cebe9be93ce3af6742ad8da96e1b1907e3f8e00291def1", size = 36970, upload-time = "2025-11-14T14:58:13.114Z" }, + { url = "https://files.pythonhosted.org/packages/b7/ef/af447c2184dec6dec973be14614df8ccb4d16d1c74e0784ab4f02538433c/cymem-2.0.13-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:ff036bbc1464993552fd1251b0a83fe102af334b301e3896d7aa05a4999ad042", size = 46804, upload-time = "2025-11-14T14:58:14.113Z" }, + { url = "https://files.pythonhosted.org/packages/8c/95/e10f33a8d4fc17f9b933d451038218437f9326c2abb15a3e7f58ce2a06ec/cymem-2.0.13-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:fb8291691ba7ff4e6e000224cc97a744a8d9588418535c9454fd8436911df612", size = 46254, upload-time = "2025-11-14T14:58:15.156Z" }, + { url = "https://files.pythonhosted.org/packages/e7/7a/5efeb2d2ea6ebad2745301ad33a4fa9a8f9a33b66623ee4d9185683007a6/cymem-2.0.13-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d8d06ea59006b1251ad5794bcc00121e148434826090ead0073c7b7fedebe431", size = 296061, upload-time = "2025-11-14T14:58:16.254Z" }, + { url = "https://files.pythonhosted.org/packages/0b/28/2a3f65842cc8443c2c0650cf23d525be06c8761ab212e0a095a88627be1b/cymem-2.0.13-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c0046a619ecc845ccb4528b37b63426a0cbcb4f14d7940add3391f59f13701e6", size = 285784, upload-time = "2025-11-14T14:58:17.412Z" }, + { url = "https://files.pythonhosted.org/packages/98/73/dd5f9729398f0108c2e71d942253d0d484d299d08b02e474d7cfc43ed0b0/cymem-2.0.13-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:18ad5b116a82fa3674bc8838bd3792891b428971e2123ae8c0fd3ca472157c5e", size = 288062, upload-time = "2025-11-14T14:58:20.225Z" }, + { url = "https://files.pythonhosted.org/packages/5a/01/ffe51729a8f961a437920560659073e47f575d4627445216c1177ecd4a41/cymem-2.0.13-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:666ce6146bc61b9318aa70d91ce33f126b6344a25cf0b925621baed0c161e9cc", size = 290465, upload-time = "2025-11-14T14:58:21.815Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ac/c9e7d68607f71ef978c81e334ab2898b426944c71950212b1467186f69f9/cymem-2.0.13-cp314-cp314t-win_amd64.whl", hash = "sha256:84c1168c563d9d1e04546cb65e3e54fde2bf814f7c7faf11fc06436598e386d1", size = 46665, upload-time = "2025-11-14T14:58:23.512Z" }, + { url = "https://files.pythonhosted.org/packages/66/66/150e406a2db5535533aa3c946de58f0371f2e412e23f050c704588023e6e/cymem-2.0.13-cp314-cp314t-win_arm64.whl", hash = "sha256:e9027764dc5f1999fb4b4cabee1d0322c59e330c0a6485b436a68275f614277f", size = 39715, upload-time = "2025-11-14T14:58:24.773Z" }, +] + +[[package]] +name = "datasets" +version = "4.8.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dill" }, + { name = "filelock" }, + { name = "fsspec", extra = ["http"] }, + { name = "httpx" }, + { name = "huggingface-hub" }, + { name = "multiprocess" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "pyarrow" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "tqdm" }, + { name = "xxhash" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/34/14cd8e76f907f7d4dca2334cfeec9f81d30fd15c25a015f99aaea694eaed/datasets-4.8.5.tar.gz", hash = "sha256:0f0c1c3d56ffff2c93b2f4c63c95bac94f3d7e8621aea2a2a576275233bba772", size = 605649, upload-time = "2026-04-27T15:43:57.384Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/99/00f3196036501b53032c4b1ab8337a0b978dee832ed276dae3815df4e8b5/datasets-4.8.5-py3-none-any.whl", hash = "sha256:5079900781719c0e063a8efdd2cd95a31ad0c63209178669cd23cf1b926149ff", size = 528973, upload-time = "2026-04-27T15:43:53.702Z" }, +] + +[[package]] +name = "deprecated" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/85/12f0a49a7c4ffb70572b6c2ef13c90c88fd190debda93b23f026b25f9634/deprecated-1.3.1.tar.gz", hash = "sha256:b1b50e0ff0c1fddaa5708a2c6b0a6588bb09b892825ab2b214ac9ea9d92a5223", size = 2932523, upload-time = "2025-10-30T08:19:02.757Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/d0/205d54408c08b13550c733c4b85429e7ead111c7f0014309637425520a9a/deprecated-1.3.1-py2.py3-none-any.whl", hash = "sha256:597bfef186b6f60181535a29fbe44865ce137a5079f295b479886c82729d5f3f", size = 11298, upload-time = "2025-10-30T08:19:00.758Z" }, +] + +[[package]] +name = "dill" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/81/e1/56027a71e31b02ddc53c7d65b01e68edf64dea2932122fe7746a516f75d5/dill-0.4.1.tar.gz", hash = "sha256:423092df4182177d4d8ba8290c8a5b640c66ab35ec7da59ccfa00f6fa3eea5fa", size = 187315, upload-time = "2026-01-19T02:36:56.85Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl", hash = "sha256:1e1ce33e978ae97fcfcff5638477032b801c46c7c65cf717f95fbc2248f79a9d", size = 120019, upload-time = "2026-01-19T02:36:55.663Z" }, +] + +[[package]] +name = "diskcache" +version = "5.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/21/1c1ffc1a039ddcc459db43cc108658f32c57d271d7289a2794e401d0fdb6/diskcache-5.6.3.tar.gz", hash = "sha256:2c3a3fa2743d8535d832ec61c2054a1641f41775aa7c556758a109941e33e4fc", size = 67916, upload-time = "2023-08-31T06:12:00.316Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/27/4570e78fc0bf5ea0ca45eb1de3818a23787af9b390c0b0a0033a1b8236f9/diskcache-5.6.3-py3-none-any.whl", hash = "sha256:5e31b2d5fbad117cc363ebaf6b689474db18a1f6438bc82358b024abd4c2ca19", size = 45550, upload-time = "2023-08-31T06:11:58.822Z" }, +] + +[[package]] +name = "distro" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, +] + +[[package]] +name = "docstring-parser" +version = "0.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/4d/f332313098c1de1b2d2ff91cf2674415cc7cddab2ca1b01ae29774bd5fdf/docstring_parser-0.18.0.tar.gz", hash = "sha256:292510982205c12b1248696f44959db3cdd1740237a968ea1e2e7a900eeb2015", size = 29341, upload-time = "2026-04-14T04:09:19.867Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/5f/ed01f9a3cdffbd5a008556fc7b2a08ddb1cc6ace7effa7340604b1d16699/docstring_parser-0.18.0-py3-none-any.whl", hash = "sha256:b3fcbed555c47d8479be0796ef7e19c2670d428d72e96da63f3a40122860374b", size = 22484, upload-time = "2026-04-14T04:09:18.638Z" }, +] + +[[package]] +name = "fastapi" +version = "0.136.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/81/2d/ff8d91d7b564d464629a0fd50a4489c97fcb836ac230bf3a7269232a9b1f/fastapi-0.136.3.tar.gz", hash = "sha256:e487fae93ad408e6f47641ee4dfe389864fd7bec92e547ea8498fc13f43e83ab", size = 396410, upload-time = "2026-05-23T18:53:15.192Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/82/45359b62a067409bd929ae8a56b8ed13e5a8c8a61194b3c236920999ab83/fastapi-0.136.3-py3-none-any.whl", hash = "sha256:3d2a69bdf04b7e9f3afa292c3bc7a98816bbfafa10bc9b45f3f3700d2f761620", size = 117481, upload-time = "2026-05-23T18:53:16.924Z" }, +] + +[[package]] +name = "fastuuid" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/7d/d9daedf0f2ebcacd20d599928f8913e9d2aea1d56d2d355a93bfa2b611d7/fastuuid-0.14.0.tar.gz", hash = "sha256:178947fc2f995b38497a74172adee64fdeb8b7ec18f2a5934d037641ba265d26", size = 18232, upload-time = "2025-10-19T22:19:22.402Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/a2/e78fcc5df65467f0d207661b7ef86c5b7ac62eea337c0c0fcedbeee6fb13/fastuuid-0.14.0-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:77e94728324b63660ebf8adb27055e92d2e4611645bf12ed9d88d30486471d0a", size = 510164, upload-time = "2025-10-19T22:31:45.635Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b3/c846f933f22f581f558ee63f81f29fa924acd971ce903dab1a9b6701816e/fastuuid-0.14.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:caa1f14d2102cb8d353096bc6ef6c13b2c81f347e6ab9d6fbd48b9dea41c153d", size = 261837, upload-time = "2025-10-19T22:38:38.53Z" }, + { url = "https://files.pythonhosted.org/packages/54/ea/682551030f8c4fa9a769d9825570ad28c0c71e30cf34020b85c1f7ee7382/fastuuid-0.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d23ef06f9e67163be38cece704170486715b177f6baae338110983f99a72c070", size = 251370, upload-time = "2025-10-19T22:40:26.07Z" }, + { url = "https://files.pythonhosted.org/packages/14/dd/5927f0a523d8e6a76b70968e6004966ee7df30322f5fc9b6cdfb0276646a/fastuuid-0.14.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c9ec605ace243b6dbe3bd27ebdd5d33b00d8d1d3f580b39fdd15cd96fd71796", size = 277766, upload-time = "2025-10-19T22:37:23.779Z" }, + { url = "https://files.pythonhosted.org/packages/16/6e/c0fb547eef61293153348f12e0f75a06abb322664b34a1573a7760501336/fastuuid-0.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:808527f2407f58a76c916d6aa15d58692a4a019fdf8d4c32ac7ff303b7d7af09", size = 278105, upload-time = "2025-10-19T22:26:56.821Z" }, + { url = "https://files.pythonhosted.org/packages/2d/b1/b9c75e03b768f61cf2e84ee193dc18601aeaf89a4684b20f2f0e9f52b62c/fastuuid-0.14.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2fb3c0d7fef6674bbeacdd6dbd386924a7b60b26de849266d1ff6602937675c8", size = 301564, upload-time = "2025-10-19T22:30:31.604Z" }, + { url = "https://files.pythonhosted.org/packages/fc/fa/f7395fdac07c7a54f18f801744573707321ca0cee082e638e36452355a9d/fastuuid-0.14.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab3f5d36e4393e628a4df337c2c039069344db5f4b9d2a3c9cea48284f1dd741", size = 459659, upload-time = "2025-10-19T22:31:32.341Z" }, + { url = "https://files.pythonhosted.org/packages/66/49/c9fd06a4a0b1f0f048aacb6599e7d96e5d6bc6fa680ed0d46bf111929d1b/fastuuid-0.14.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:b9a0ca4f03b7e0b01425281ffd44e99d360e15c895f1907ca105854ed85e2057", size = 478430, upload-time = "2025-10-19T22:26:22.962Z" }, + { url = "https://files.pythonhosted.org/packages/be/9c/909e8c95b494e8e140e8be6165d5fc3f61fdc46198c1554df7b3e1764471/fastuuid-0.14.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3acdf655684cc09e60fb7e4cf524e8f42ea760031945aa8086c7eae2eeeabeb8", size = 450894, upload-time = "2025-10-19T22:27:01.647Z" }, + { url = "https://files.pythonhosted.org/packages/90/eb/d29d17521976e673c55ef7f210d4cdd72091a9ec6755d0fd4710d9b3c871/fastuuid-0.14.0-cp312-cp312-win32.whl", hash = "sha256:9579618be6280700ae36ac42c3efd157049fe4dd40ca49b021280481c78c3176", size = 154374, upload-time = "2025-10-19T22:29:19.879Z" }, + { url = "https://files.pythonhosted.org/packages/cc/fc/f5c799a6ea6d877faec0472d0b27c079b47c86b1cdc577720a5386483b36/fastuuid-0.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:d9e4332dc4ba054434a9594cbfaf7823b57993d7d8e7267831c3e059857cf397", size = 156550, upload-time = "2025-10-19T22:27:49.658Z" }, + { url = "https://files.pythonhosted.org/packages/a5/83/ae12dd39b9a39b55d7f90abb8971f1a5f3c321fd72d5aa83f90dc67fe9ed/fastuuid-0.14.0-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:77a09cb7427e7af74c594e409f7731a0cf887221de2f698e1ca0ebf0f3139021", size = 510720, upload-time = "2025-10-19T22:42:34.633Z" }, + { url = "https://files.pythonhosted.org/packages/53/b0/a4b03ff5d00f563cc7546b933c28cb3f2a07344b2aec5834e874f7d44143/fastuuid-0.14.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:9bd57289daf7b153bfa3e8013446aa144ce5e8c825e9e366d455155ede5ea2dc", size = 262024, upload-time = "2025-10-19T22:30:25.482Z" }, + { url = "https://files.pythonhosted.org/packages/9c/6d/64aee0a0f6a58eeabadd582e55d0d7d70258ffdd01d093b30c53d668303b/fastuuid-0.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ac60fc860cdf3c3f327374db87ab8e064c86566ca8c49d2e30df15eda1b0c2d5", size = 251679, upload-time = "2025-10-19T22:36:14.096Z" }, + { url = "https://files.pythonhosted.org/packages/60/f5/a7e9cda8369e4f7919d36552db9b2ae21db7915083bc6336f1b0082c8b2e/fastuuid-0.14.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab32f74bd56565b186f036e33129da77db8be09178cd2f5206a5d4035fb2a23f", size = 277862, upload-time = "2025-10-19T22:36:23.302Z" }, + { url = "https://files.pythonhosted.org/packages/f0/d3/8ce11827c783affffd5bd4d6378b28eb6cc6d2ddf41474006b8d62e7448e/fastuuid-0.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33e678459cf4addaedd9936bbb038e35b3f6b2061330fd8f2f6a1d80414c0f87", size = 278278, upload-time = "2025-10-19T22:29:43.809Z" }, + { url = "https://files.pythonhosted.org/packages/a2/51/680fb6352d0bbade04036da46264a8001f74b7484e2fd1f4da9e3db1c666/fastuuid-0.14.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1e3cc56742f76cd25ecb98e4b82a25f978ccffba02e4bdce8aba857b6d85d87b", size = 301788, upload-time = "2025-10-19T22:36:06.825Z" }, + { url = "https://files.pythonhosted.org/packages/fa/7c/2014b5785bd8ebdab04ec857635ebd84d5ee4950186a577db9eff0fb8ff6/fastuuid-0.14.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cb9a030f609194b679e1660f7e32733b7a0f332d519c5d5a6a0a580991290022", size = 459819, upload-time = "2025-10-19T22:35:31.623Z" }, + { url = "https://files.pythonhosted.org/packages/01/d2/524d4ceeba9160e7a9bc2ea3e8f4ccf1ad78f3bde34090ca0c51f09a5e91/fastuuid-0.14.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:09098762aad4f8da3a888eb9ae01c84430c907a297b97166b8abc07b640f2995", size = 478546, upload-time = "2025-10-19T22:26:03.023Z" }, + { url = "https://files.pythonhosted.org/packages/bc/17/354d04951ce114bf4afc78e27a18cfbd6ee319ab1829c2d5fb5e94063ac6/fastuuid-0.14.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:1383fff584fa249b16329a059c68ad45d030d5a4b70fb7c73a08d98fd53bcdab", size = 450921, upload-time = "2025-10-19T22:31:02.151Z" }, + { url = "https://files.pythonhosted.org/packages/fb/be/d7be8670151d16d88f15bb121c5b66cdb5ea6a0c2a362d0dcf30276ade53/fastuuid-0.14.0-cp313-cp313-win32.whl", hash = "sha256:a0809f8cc5731c066c909047f9a314d5f536c871a7a22e815cc4967c110ac9ad", size = 154559, upload-time = "2025-10-19T22:36:36.011Z" }, + { url = "https://files.pythonhosted.org/packages/22/1d/5573ef3624ceb7abf4a46073d3554e37191c868abc3aecd5289a72f9810a/fastuuid-0.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:0df14e92e7ad3276327631c9e7cec09e32572ce82089c55cb1bb8df71cf394ed", size = 156539, upload-time = "2025-10-19T22:33:35.898Z" }, + { url = "https://files.pythonhosted.org/packages/16/c9/8c7660d1fe3862e3f8acabd9be7fc9ad71eb270f1c65cce9a2b7a31329ab/fastuuid-0.14.0-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:b852a870a61cfc26c884af205d502881a2e59cc07076b60ab4a951cc0c94d1ad", size = 510600, upload-time = "2025-10-19T22:43:44.17Z" }, + { url = "https://files.pythonhosted.org/packages/4c/f4/a989c82f9a90d0ad995aa957b3e572ebef163c5299823b4027986f133dfb/fastuuid-0.14.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:c7502d6f54cd08024c3ea9b3514e2d6f190feb2f46e6dbcd3747882264bb5f7b", size = 262069, upload-time = "2025-10-19T22:43:38.38Z" }, + { url = "https://files.pythonhosted.org/packages/da/6c/a1a24f73574ac995482b1326cf7ab41301af0fabaa3e37eeb6b3df00e6e2/fastuuid-0.14.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1ca61b592120cf314cfd66e662a5b54a578c5a15b26305e1b8b618a6f22df714", size = 251543, upload-time = "2025-10-19T22:32:22.537Z" }, + { url = "https://files.pythonhosted.org/packages/1a/20/2a9b59185ba7a6c7b37808431477c2d739fcbdabbf63e00243e37bd6bf49/fastuuid-0.14.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa75b6657ec129d0abded3bec745e6f7ab642e6dba3a5272a68247e85f5f316f", size = 277798, upload-time = "2025-10-19T22:33:53.821Z" }, + { url = "https://files.pythonhosted.org/packages/ef/33/4105ca574f6ded0af6a797d39add041bcfb468a1255fbbe82fcb6f592da2/fastuuid-0.14.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8a0dfea3972200f72d4c7df02c8ac70bad1bb4c58d7e0ec1e6f341679073a7f", size = 278283, upload-time = "2025-10-19T22:29:02.812Z" }, + { url = "https://files.pythonhosted.org/packages/fe/8c/fca59f8e21c4deb013f574eae05723737ddb1d2937ce87cb2a5d20992dc3/fastuuid-0.14.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1bf539a7a95f35b419f9ad105d5a8a35036df35fdafae48fb2fd2e5f318f0d75", size = 301627, upload-time = "2025-10-19T22:35:54.985Z" }, + { url = "https://files.pythonhosted.org/packages/cb/e2/f78c271b909c034d429218f2798ca4e89eeda7983f4257d7865976ddbb6c/fastuuid-0.14.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:9a133bf9cc78fdbd1179cb58a59ad0100aa32d8675508150f3658814aeefeaa4", size = 459778, upload-time = "2025-10-19T22:28:00.999Z" }, + { url = "https://files.pythonhosted.org/packages/1e/f0/5ff209d865897667a2ff3e7a572267a9ced8f7313919f6d6043aed8b1caa/fastuuid-0.14.0-cp314-cp314-musllinux_1_1_i686.whl", hash = "sha256:f54d5b36c56a2d5e1a31e73b950b28a0d83eb0c37b91d10408875a5a29494bad", size = 478605, upload-time = "2025-10-19T22:36:21.764Z" }, + { url = "https://files.pythonhosted.org/packages/e0/c8/2ce1c78f983a2c4987ea865d9516dbdfb141a120fd3abb977ae6f02ba7ca/fastuuid-0.14.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:ec27778c6ca3393ef662e2762dba8af13f4ec1aaa32d08d77f71f2a70ae9feb8", size = 450837, upload-time = "2025-10-19T22:34:37.178Z" }, + { url = "https://files.pythonhosted.org/packages/df/60/dad662ec9a33b4a5fe44f60699258da64172c39bd041da2994422cdc40fe/fastuuid-0.14.0-cp314-cp314-win32.whl", hash = "sha256:e23fc6a83f112de4be0cc1990e5b127c27663ae43f866353166f87df58e73d06", size = 154532, upload-time = "2025-10-19T22:35:18.217Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f6/da4db31001e854025ffd26bc9ba0740a9cbba2c3259695f7c5834908b336/fastuuid-0.14.0-cp314-cp314-win_amd64.whl", hash = "sha256:df61342889d0f5e7a32f7284e55ef95103f2110fee433c2ae7c2c0956d76ac8a", size = 156457, upload-time = "2025-10-19T22:33:44.579Z" }, +] + +[[package]] +name = "filelock" +version = "3.29.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/f9/f38573ed5844586db374d085911740a501ccfa373b455fc9413f09f85237/filelock-3.29.1.tar.gz", hash = "sha256:d97e6b1b9757569626c58caa07dc4beb1613f4a2938b1e8cc81afca398906c9e", size = 59335, upload-time = "2026-06-03T15:19:04.053Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/a0/614c5fe402fd88951df45f4dda2fa3b4e17a99ecd92340771929169b3b95/filelock-3.29.1-py3-none-any.whl", hash = "sha256:85199dfd706869641b72b2e8955d5416a4b2b7dc4b0e8e6d97b4cc1299a6983b", size = 40750, upload-time = "2026-06-03T15:19:02.959Z" }, +] + +[[package]] +name = "frozenlist" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1", size = 87782, upload-time = "2025-10-06T05:36:06.649Z" }, + { url = "https://files.pythonhosted.org/packages/64/80/4f6e318ee2a7c0750ed724fa33a4bdf1eacdc5a39a7a24e818a773cd91af/frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b", size = 50594, upload-time = "2025-10-06T05:36:07.69Z" }, + { url = "https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4", size = 50448, upload-time = "2025-10-06T05:36:08.78Z" }, + { url = "https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383", size = 242411, upload-time = "2025-10-06T05:36:09.801Z" }, + { url = "https://files.pythonhosted.org/packages/8f/83/f61505a05109ef3293dfb1ff594d13d64a2324ac3482be2cedc2be818256/frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4", size = 243014, upload-time = "2025-10-06T05:36:11.394Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cb/cb6c7b0f7d4023ddda30cf56b8b17494eb3a79e3fda666bf735f63118b35/frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8", size = 234909, upload-time = "2025-10-06T05:36:12.598Z" }, + { url = "https://files.pythonhosted.org/packages/31/c5/cd7a1f3b8b34af009fb17d4123c5a778b44ae2804e3ad6b86204255f9ec5/frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b", size = 250049, upload-time = "2025-10-06T05:36:14.065Z" }, + { url = "https://files.pythonhosted.org/packages/c0/01/2f95d3b416c584a1e7f0e1d6d31998c4a795f7544069ee2e0962a4b60740/frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52", size = 256485, upload-time = "2025-10-06T05:36:15.39Z" }, + { url = "https://files.pythonhosted.org/packages/ce/03/024bf7720b3abaebcff6d0793d73c154237b85bdf67b7ed55e5e9596dc9a/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29", size = 237619, upload-time = "2025-10-06T05:36:16.558Z" }, + { url = "https://files.pythonhosted.org/packages/69/fa/f8abdfe7d76b731f5d8bd217827cf6764d4f1d9763407e42717b4bed50a0/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3", size = 250320, upload-time = "2025-10-06T05:36:17.821Z" }, + { url = "https://files.pythonhosted.org/packages/f5/3c/b051329f718b463b22613e269ad72138cc256c540f78a6de89452803a47d/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143", size = 246820, upload-time = "2025-10-06T05:36:19.046Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ae/58282e8f98e444b3f4dd42448ff36fa38bef29e40d40f330b22e7108f565/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608", size = 250518, upload-time = "2025-10-06T05:36:20.763Z" }, + { url = "https://files.pythonhosted.org/packages/8f/96/007e5944694d66123183845a106547a15944fbbb7154788cbf7272789536/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa", size = 239096, upload-time = "2025-10-06T05:36:22.129Z" }, + { url = "https://files.pythonhosted.org/packages/66/bb/852b9d6db2fa40be96f29c0d1205c306288f0684df8fd26ca1951d461a56/frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf", size = 39985, upload-time = "2025-10-06T05:36:23.661Z" }, + { url = "https://files.pythonhosted.org/packages/b8/af/38e51a553dd66eb064cdf193841f16f077585d4d28394c2fa6235cb41765/frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746", size = 44591, upload-time = "2025-10-06T05:36:24.958Z" }, + { url = "https://files.pythonhosted.org/packages/a7/06/1dc65480ab147339fecc70797e9c2f69d9cea9cf38934ce08df070fdb9cb/frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd", size = 40102, upload-time = "2025-10-06T05:36:26.333Z" }, + { url = "https://files.pythonhosted.org/packages/2d/40/0832c31a37d60f60ed79e9dfb5a92e1e2af4f40a16a29abcc7992af9edff/frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a", size = 85717, upload-time = "2025-10-06T05:36:27.341Z" }, + { url = "https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7", size = 49651, upload-time = "2025-10-06T05:36:28.855Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40", size = 49417, upload-time = "2025-10-06T05:36:29.877Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027", size = 234391, upload-time = "2025-10-06T05:36:31.301Z" }, + { url = "https://files.pythonhosted.org/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822", size = 233048, upload-time = "2025-10-06T05:36:32.531Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c0/8746afb90f17b73ca5979c7a3958116e105ff796e718575175319b5bb4ce/frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121", size = 226549, upload-time = "2025-10-06T05:36:33.706Z" }, + { url = "https://files.pythonhosted.org/packages/7e/eb/4c7eefc718ff72f9b6c4893291abaae5fbc0c82226a32dcd8ef4f7a5dbef/frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5", size = 239833, upload-time = "2025-10-06T05:36:34.947Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/e5c02187cf704224f8b21bee886f3d713ca379535f16893233b9d672ea71/frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e", size = 245363, upload-time = "2025-10-06T05:36:36.534Z" }, + { url = "https://files.pythonhosted.org/packages/1f/96/cb85ec608464472e82ad37a17f844889c36100eed57bea094518bf270692/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11", size = 229314, upload-time = "2025-10-06T05:36:38.582Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6f/4ae69c550e4cee66b57887daeebe006fe985917c01d0fff9caab9883f6d0/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1", size = 243365, upload-time = "2025-10-06T05:36:40.152Z" }, + { url = "https://files.pythonhosted.org/packages/7a/58/afd56de246cf11780a40a2c28dc7cbabbf06337cc8ddb1c780a2d97e88d8/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1", size = 237763, upload-time = "2025-10-06T05:36:41.355Z" }, + { url = "https://files.pythonhosted.org/packages/cb/36/cdfaf6ed42e2644740d4a10452d8e97fa1c062e2a8006e4b09f1b5fd7d63/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8", size = 240110, upload-time = "2025-10-06T05:36:42.716Z" }, + { url = "https://files.pythonhosted.org/packages/03/a8/9ea226fbefad669f11b52e864c55f0bd57d3c8d7eb07e9f2e9a0b39502e1/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed", size = 233717, upload-time = "2025-10-06T05:36:44.251Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0b/1b5531611e83ba7d13ccc9988967ea1b51186af64c42b7a7af465dcc9568/frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496", size = 39628, upload-time = "2025-10-06T05:36:45.423Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231", size = 43882, upload-time = "2025-10-06T05:36:46.796Z" }, + { url = "https://files.pythonhosted.org/packages/c1/17/502cd212cbfa96eb1388614fe39a3fc9ab87dbbe042b66f97acb57474834/frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62", size = 39676, upload-time = "2025-10-06T05:36:47.8Z" }, + { url = "https://files.pythonhosted.org/packages/d2/5c/3bbfaa920dfab09e76946a5d2833a7cbdf7b9b4a91c714666ac4855b88b4/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94", size = 89235, upload-time = "2025-10-06T05:36:48.78Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d6/f03961ef72166cec1687e84e8925838442b615bd0b8854b54923ce5b7b8a/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c", size = 50742, upload-time = "2025-10-06T05:36:49.837Z" }, + { url = "https://files.pythonhosted.org/packages/1e/bb/a6d12b7ba4c3337667d0e421f7181c82dda448ce4e7ad7ecd249a16fa806/frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52", size = 51725, upload-time = "2025-10-06T05:36:50.851Z" }, + { url = "https://files.pythonhosted.org/packages/bc/71/d1fed0ffe2c2ccd70b43714c6cab0f4188f09f8a67a7914a6b46ee30f274/frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51", size = 284533, upload-time = "2025-10-06T05:36:51.898Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/fb1685a7b009d89f9bf78a42d94461bc06581f6e718c39344754a5d9bada/frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65", size = 292506, upload-time = "2025-10-06T05:36:53.101Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3b/b991fe1612703f7e0d05c0cf734c1b77aaf7c7d321df4572e8d36e7048c8/frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82", size = 274161, upload-time = "2025-10-06T05:36:54.309Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ec/c5c618767bcdf66e88945ec0157d7f6c4a1322f1473392319b7a2501ded7/frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714", size = 294676, upload-time = "2025-10-06T05:36:55.566Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ce/3934758637d8f8a88d11f0585d6495ef54b2044ed6ec84492a91fa3b27aa/frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d", size = 300638, upload-time = "2025-10-06T05:36:56.758Z" }, + { url = "https://files.pythonhosted.org/packages/fc/4f/a7e4d0d467298f42de4b41cbc7ddaf19d3cfeabaf9ff97c20c6c7ee409f9/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506", size = 283067, upload-time = "2025-10-06T05:36:57.965Z" }, + { url = "https://files.pythonhosted.org/packages/dc/48/c7b163063d55a83772b268e6d1affb960771b0e203b632cfe09522d67ea5/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51", size = 292101, upload-time = "2025-10-06T05:36:59.237Z" }, + { url = "https://files.pythonhosted.org/packages/9f/d0/2366d3c4ecdc2fd391e0afa6e11500bfba0ea772764d631bbf82f0136c9d/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e", size = 289901, upload-time = "2025-10-06T05:37:00.811Z" }, + { url = "https://files.pythonhosted.org/packages/b8/94/daff920e82c1b70e3618a2ac39fbc01ae3e2ff6124e80739ce5d71c9b920/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0", size = 289395, upload-time = "2025-10-06T05:37:02.115Z" }, + { url = "https://files.pythonhosted.org/packages/e3/20/bba307ab4235a09fdcd3cc5508dbabd17c4634a1af4b96e0f69bfe551ebd/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41", size = 283659, upload-time = "2025-10-06T05:37:03.711Z" }, + { url = "https://files.pythonhosted.org/packages/fd/00/04ca1c3a7a124b6de4f8a9a17cc2fcad138b4608e7a3fc5877804b8715d7/frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b", size = 43492, upload-time = "2025-10-06T05:37:04.915Z" }, + { url = "https://files.pythonhosted.org/packages/59/5e/c69f733a86a94ab10f68e496dc6b7e8bc078ebb415281d5698313e3af3a1/frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888", size = 48034, upload-time = "2025-10-06T05:37:06.343Z" }, + { url = "https://files.pythonhosted.org/packages/16/6c/be9d79775d8abe79b05fa6d23da99ad6e7763a1d080fbae7290b286093fd/frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042", size = 41749, upload-time = "2025-10-06T05:37:07.431Z" }, + { url = "https://files.pythonhosted.org/packages/f1/c8/85da824b7e7b9b6e7f7705b2ecaf9591ba6f79c1177f324c2735e41d36a2/frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0", size = 86127, upload-time = "2025-10-06T05:37:08.438Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e8/a1185e236ec66c20afd72399522f142c3724c785789255202d27ae992818/frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f", size = 49698, upload-time = "2025-10-06T05:37:09.48Z" }, + { url = "https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c", size = 49749, upload-time = "2025-10-06T05:37:10.569Z" }, + { url = "https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2", size = 231298, upload-time = "2025-10-06T05:37:11.993Z" }, + { url = "https://files.pythonhosted.org/packages/3a/3b/d9b1e0b0eed36e70477ffb8360c49c85c8ca8ef9700a4e6711f39a6e8b45/frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8", size = 232015, upload-time = "2025-10-06T05:37:13.194Z" }, + { url = "https://files.pythonhosted.org/packages/dc/94/be719d2766c1138148564a3960fc2c06eb688da592bdc25adcf856101be7/frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686", size = 225038, upload-time = "2025-10-06T05:37:14.577Z" }, + { url = "https://files.pythonhosted.org/packages/e4/09/6712b6c5465f083f52f50cf74167b92d4ea2f50e46a9eea0523d658454ae/frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e", size = 240130, upload-time = "2025-10-06T05:37:15.781Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d4/cd065cdcf21550b54f3ce6a22e143ac9e4836ca42a0de1022da8498eac89/frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a", size = 242845, upload-time = "2025-10-06T05:37:17.037Z" }, + { url = "https://files.pythonhosted.org/packages/62/c3/f57a5c8c70cd1ead3d5d5f776f89d33110b1addae0ab010ad774d9a44fb9/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128", size = 229131, upload-time = "2025-10-06T05:37:18.221Z" }, + { url = "https://files.pythonhosted.org/packages/6c/52/232476fe9cb64f0742f3fde2b7d26c1dac18b6d62071c74d4ded55e0ef94/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f", size = 240542, upload-time = "2025-10-06T05:37:19.771Z" }, + { url = "https://files.pythonhosted.org/packages/5f/85/07bf3f5d0fb5414aee5f47d33c6f5c77bfe49aac680bfece33d4fdf6a246/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7", size = 237308, upload-time = "2025-10-06T05:37:20.969Z" }, + { url = "https://files.pythonhosted.org/packages/11/99/ae3a33d5befd41ac0ca2cc7fd3aa707c9c324de2e89db0e0f45db9a64c26/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30", size = 238210, upload-time = "2025-10-06T05:37:22.252Z" }, + { url = "https://files.pythonhosted.org/packages/b2/60/b1d2da22f4970e7a155f0adde9b1435712ece01b3cd45ba63702aea33938/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7", size = 231972, upload-time = "2025-10-06T05:37:23.5Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ab/945b2f32de889993b9c9133216c068b7fcf257d8595a0ac420ac8677cab0/frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806", size = 40536, upload-time = "2025-10-06T05:37:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0", size = 44330, upload-time = "2025-10-06T05:37:26.928Z" }, + { url = "https://files.pythonhosted.org/packages/82/13/e6950121764f2676f43534c555249f57030150260aee9dcf7d64efda11dd/frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b", size = 40627, upload-time = "2025-10-06T05:37:28.075Z" }, + { url = "https://files.pythonhosted.org/packages/c0/c7/43200656ecc4e02d3f8bc248df68256cd9572b3f0017f0a0c4e93440ae23/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d", size = 89238, upload-time = "2025-10-06T05:37:29.373Z" }, + { url = "https://files.pythonhosted.org/packages/d1/29/55c5f0689b9c0fb765055629f472c0de484dcaf0acee2f7707266ae3583c/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed", size = 50738, upload-time = "2025-10-06T05:37:30.792Z" }, + { url = "https://files.pythonhosted.org/packages/ba/7d/b7282a445956506fa11da8c2db7d276adcbf2b17d8bb8407a47685263f90/frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930", size = 51739, upload-time = "2025-10-06T05:37:32.127Z" }, + { url = "https://files.pythonhosted.org/packages/62/1c/3d8622e60d0b767a5510d1d3cf21065b9db874696a51ea6d7a43180a259c/frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c", size = 284186, upload-time = "2025-10-06T05:37:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/2d/14/aa36d5f85a89679a85a1d44cd7a6657e0b1c75f61e7cad987b203d2daca8/frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24", size = 292196, upload-time = "2025-10-06T05:37:36.107Z" }, + { url = "https://files.pythonhosted.org/packages/05/23/6bde59eb55abd407d34f77d39a5126fb7b4f109a3f611d3929f14b700c66/frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37", size = 273830, upload-time = "2025-10-06T05:37:37.663Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3f/22cff331bfad7a8afa616289000ba793347fcd7bc275f3b28ecea2a27909/frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a", size = 294289, upload-time = "2025-10-06T05:37:39.261Z" }, + { url = "https://files.pythonhosted.org/packages/a4/89/5b057c799de4838b6c69aa82b79705f2027615e01be996d2486a69ca99c4/frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2", size = 300318, upload-time = "2025-10-06T05:37:43.213Z" }, + { url = "https://files.pythonhosted.org/packages/30/de/2c22ab3eb2a8af6d69dc799e48455813bab3690c760de58e1bf43b36da3e/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef", size = 282814, upload-time = "2025-10-06T05:37:45.337Z" }, + { url = "https://files.pythonhosted.org/packages/59/f7/970141a6a8dbd7f556d94977858cfb36fa9b66e0892c6dd780d2219d8cd8/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe", size = 291762, upload-time = "2025-10-06T05:37:46.657Z" }, + { url = "https://files.pythonhosted.org/packages/c1/15/ca1adae83a719f82df9116d66f5bb28bb95557b3951903d39135620ef157/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8", size = 289470, upload-time = "2025-10-06T05:37:47.946Z" }, + { url = "https://files.pythonhosted.org/packages/ac/83/dca6dc53bf657d371fbc88ddeb21b79891e747189c5de990b9dfff2ccba1/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a", size = 289042, upload-time = "2025-10-06T05:37:49.499Z" }, + { url = "https://files.pythonhosted.org/packages/96/52/abddd34ca99be142f354398700536c5bd315880ed0a213812bc491cff5e4/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e", size = 283148, upload-time = "2025-10-06T05:37:50.745Z" }, + { url = "https://files.pythonhosted.org/packages/af/d3/76bd4ed4317e7119c2b7f57c3f6934aba26d277acc6309f873341640e21f/frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df", size = 44676, upload-time = "2025-10-06T05:37:52.222Z" }, + { url = "https://files.pythonhosted.org/packages/89/76/c615883b7b521ead2944bb3480398cbb07e12b7b4e4d073d3752eb721558/frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd", size = 49451, upload-time = "2025-10-06T05:37:53.425Z" }, + { url = "https://files.pythonhosted.org/packages/e0/a3/5982da14e113d07b325230f95060e2169f5311b1017ea8af2a29b374c289/frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79", size = 42507, upload-time = "2025-10-06T05:37:54.513Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, +] + +[[package]] +name = "fsspec" +version = "2026.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/51/7c/f60c259dcbf4f0c47cc4ddb8f7720d2dcdc8888c8e5ad84c73ea4531cc5b/fsspec-2026.2.0.tar.gz", hash = "sha256:6544e34b16869f5aacd5b90bdf1a71acb37792ea3ddf6125ee69a22a53fb8bff", size = 313441, upload-time = "2026-02-05T21:50:53.743Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl", hash = "sha256:98de475b5cb3bd66bedd5c4679e87b4fdfe1a3bf4d707b151b3c07e58c9a2437", size = 202505, upload-time = "2026-02-05T21:50:51.819Z" }, +] + +[package.optional-dependencies] +http = [ + { name = "aiohttp" }, +] + +[[package]] +name = "greenlet" +version = "3.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/6e/802acd792aebb2256fbbee8cacf2727faaeb6f240ac11008f09eae4414bc/greenlet-3.5.1.tar.gz", hash = "sha256:5a56aeb7d5d9cc4b3a735efb5095bd4b4f6f0e4f93e5ca876d0e2315137b7829", size = 197356, upload-time = "2026-05-20T15:05:03.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/37/4549f149c9797c21b32c2683c33522af22522099de128b2406672526d005/greenlet-3.5.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:fa4f98af3a528f0c3fd592a26df7f376f93329c8f4d987f6bb979057af8bf5e2", size = 286220, upload-time = "2026-05-20T13:07:28.463Z" }, + { url = "https://files.pythonhosted.org/packages/38/ff/a4f436709716965eaab9f36ea7b906c8a927fbe32fb1372a2071d964f6b1/greenlet-3.5.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ffea73584b216150eab159b6d12348fb253e68757974de1e2c40d8a318ac89ed", size = 601585, upload-time = "2026-05-20T14:00:06.141Z" }, + { url = "https://files.pythonhosted.org/packages/65/ad/54bc3fcee3ad368a61b19b67d88117f7a8c29727bf71fffdeda81fbd946e/greenlet-3.5.1-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1072b4f9edcc1e192d9283a66a3e68d6b84c561de33a83d7858beb9ba1effe10", size = 614215, upload-time = "2026-05-20T14:05:42.675Z" }, + { url = "https://files.pythonhosted.org/packages/40/69/b91cda0647df839483201545913514c2827ebea5e5ccdf931842763bc127/greenlet-3.5.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:add5217d68b31130f0beca584d7fef4878327d2e31642b66618a14eef312b63b", size = 611358, upload-time = "2026-05-20T13:14:26.37Z" }, + { url = "https://files.pythonhosted.org/packages/59/90/3cf77e080350cd02fa307bb2abf05df48f4482c240275bbd2c203ba8bb1c/greenlet-3.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a5ea42a752d47a145eae922b605cd1634665ac3d5ec1e72402d5048e8d60d207", size = 1570475, upload-time = "2026-05-20T14:02:25.29Z" }, + { url = "https://files.pythonhosted.org/packages/65/2c/18cece62045e74598c3c393f70dce4a63f56222015ba29a5d4eeb04f764c/greenlet-3.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c5551170cf4f5ff5623e9af81323751979fee2c731e2287b61f73cd27257b823", size = 1635625, upload-time = "2026-05-20T13:14:34.027Z" }, + { url = "https://files.pythonhosted.org/packages/30/f5/310d104ddf41eb5a70f4c268d22508dfb0c3c8e86fec152be34d0d2ed819/greenlet-3.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:3c8bb982ad117d29478ef8f5533e97df21f1e2befd17a299257b0c96d1371c0b", size = 238791, upload-time = "2026-05-20T13:10:39.018Z" }, + { url = "https://files.pythonhosted.org/packages/62/90/ceca11f504cd23a8047a3dea31919adc48df9b626dd0c13f0d858734fdfd/greenlet-3.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:80eb4b04dadc4e67df3fae179a32c4706a3f495bc7f22fc8a81115d5f5512188", size = 235580, upload-time = "2026-05-20T13:08:45.056Z" }, + { url = "https://files.pythonhosted.org/packages/27/69/7f7e5372d998b81001899b1c0823c957aa413ba0f2662e65821611cc31e4/greenlet-3.5.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:51518ff74664078fc51bffcc6fc529b0df5ae58da192691cee765d45ce944a2b", size = 285060, upload-time = "2026-05-20T13:08:51.899Z" }, + { url = "https://files.pythonhosted.org/packages/b1/bf/387f9b6b865fd2ae0d0be09e0004827295a01b71be76ed350dd1e28a91a4/greenlet-3.5.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ffdb3c0bb002c99cd8f298957e046c3dbf6006b5b7cdf11a4e19194624a0a0a", size = 604370, upload-time = "2026-05-20T14:00:07.492Z" }, + { url = "https://files.pythonhosted.org/packages/32/f5/169ce3d4e4c67291bd18f8cbe0299c9f3e45102c7f1fb3c14780c93e4532/greenlet-3.5.1-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7715a5a2c3378ba602c3a440558261e13a820bb53a82693aacd7b7f6d964e283", size = 616987, upload-time = "2026-05-20T14:05:44.237Z" }, + { url = "https://files.pythonhosted.org/packages/ee/e5/7f2e41d5273be07e77560d61ea4e56485b4d6c316d2a84518c62d1364061/greenlet-3.5.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc71ff466927a201b08305acac451ebe1aedfcea002f62f1f2f2ac2ac1e6a135", size = 613911, upload-time = "2026-05-20T13:14:27.539Z" }, + { url = "https://files.pythonhosted.org/packages/c5/a4/fbdc67579b73615a1f91615e814303cc71e06128f7baaba87be79b8fb90c/greenlet-3.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cd443683db272ebaaca03af98c0b063ab30db70ea8a31a1559f35e3f7b744ccd", size = 1570689, upload-time = "2026-05-20T14:02:27.225Z" }, + { url = "https://files.pythonhosted.org/packages/e6/b4/77abbe35078be39718a46cd49caf16bceb35662f97a34101dca28aa98e47/greenlet-3.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:089fff7a6ce8d9316d1f65ebc00273a56be258c1725b32b94de90a3a979557e1", size = 1635602, upload-time = "2026-05-20T13:14:36.344Z" }, + { url = "https://files.pythonhosted.org/packages/37/f7/129f27ca700845b8ee8ca88ce7f43435a1239c2eddb7677fc938822762cf/greenlet-3.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:110a1ca7b49b014b097f6078272c3f4ed31af45b254de5228b79adba879f6af9", size = 238683, upload-time = "2026-05-20T13:11:50.57Z" }, + { url = "https://files.pythonhosted.org/packages/6d/5c/a485a36e87df8d8fd0632ee01511244f5156a20ed3746cc6599340326395/greenlet-3.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:f16ba1efc0715b680a18b8123d90dad887c6112ae3555b4b5c32c149540c6b4e", size = 235499, upload-time = "2026-05-20T13:12:42.028Z" }, + { url = "https://files.pythonhosted.org/packages/8a/cb/c62454606daf5640369c94d8a9dd540599b1bfc090e2d2180cb77f4038d2/greenlet-3.5.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d8ab31c9de8651a2facdd5c5bb0011f2380dd1a7af78ce2adf4b56095294fc07", size = 285579, upload-time = "2026-05-20T13:08:56.396Z" }, + { url = "https://files.pythonhosted.org/packages/ec/71/c4270398c2eba968a6071af1dfbdcaeee6ec1c24bc8b435b8cc452700da6/greenlet-3.5.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e300185139abc337ade480c327183adf42a875ac7181bfe66d7d4efea31fbea", size = 651106, upload-time = "2026-05-20T14:00:09.448Z" }, + { url = "https://files.pythonhosted.org/packages/1a/ab/71e34b78a44ec271fb5f550c17bc46d301ddc5953890d935f270b0dcdb5a/greenlet-3.5.1-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7ffdb990dcaa0234cf9845aead5df2e3c3a8b6507d409274dd87e0d5ab05ffc2", size = 663478, upload-time = "2026-05-20T14:05:45.88Z" }, + { url = "https://files.pythonhosted.org/packages/77/96/4efd6fa5c62c85426a0c19077a586258ebc3a2a146ff2493e4312a697a22/greenlet-3.5.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2f82b3597e9d83b63408affed0b48fd0f54935edac4302237b9a837be0dae33c", size = 660800, upload-time = "2026-05-20T13:14:29.129Z" }, + { url = "https://files.pythonhosted.org/packages/7a/e0/6c71401a25cac7000261304e866a2f2cc04dc74810d40e2f118aa4799495/greenlet-3.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c0141e37414c10164e702b8fb1473304221ad98f71600850c6ef7ff4880feba0", size = 1617518, upload-time = "2026-05-20T14:02:28.662Z" }, + { url = "https://files.pythonhosted.org/packages/41/26/c5c06643e8c0af9e7bf18e16cb51d0ab7625155f0392e1c9015d66d556cd/greenlet-3.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:50ae25a67bea74ea41fb14b960bc532df73eb713417b2d61892dced82fe8d3bc", size = 1681593, upload-time = "2026-05-20T13:14:39.417Z" }, + { url = "https://files.pythonhosted.org/packages/8a/bd/e11a108317485075e68af9d23039619b86b28130c3b50d227d42edece64b/greenlet-3.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:8a17c42330e261299766b75ac1ea32caa437a9453c8f65d16a13140db378ecd3", size = 239800, upload-time = "2026-05-20T13:09:30.128Z" }, + { url = "https://files.pythonhosted.org/packages/47/f8/8e8e8417b7bf28639a5a56356ef934d0375e1d0c70a57e04d7701e870ffe/greenlet-3.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:7b5f5fae05b8ac6d176a61b60c394a8cbdc2b5b91b81793066e68745cf165e54", size = 236862, upload-time = "2026-05-20T13:09:10.498Z" }, + { url = "https://files.pythonhosted.org/packages/90/12/41bf27fde4d3605d3773ae57751eda182b8be2f5398011c041173b1d9534/greenlet-3.5.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:ea8da1e900d758d078810d4255d8c6aa572181896a31ec79d779eb79c3adc9ad", size = 293637, upload-time = "2026-05-20T13:12:35.529Z" }, + { url = "https://files.pythonhosted.org/packages/44/44/ba14b23e9757707050c2f397d305bbcae62e5d7cad122f8b6baec5ae4a1f/greenlet-3.5.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a19570c52a21420dcbc94e661994bc325c0b5b11304540fed514586da5dc8f2e", size = 650840, upload-time = "2026-05-20T14:00:11.079Z" }, + { url = "https://files.pythonhosted.org/packages/a8/37/5ddc2b686a6844f91abecef43411842426da2e1573f60b49ecf2547f4ae1/greenlet-3.5.1-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3d955c89b75eeca4723d7cc14135f393cd47c32e2a6cb4a8e4c6e760a26b0986", size = 656416, upload-time = "2026-05-20T14:05:47.118Z" }, + { url = "https://files.pythonhosted.org/packages/e1/f0/d17510297c35a2992712f0bf84de3779749999f7d3d63aa1f09db7c62dbe/greenlet-3.5.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2daaaebd1a5aa88c49045b6baf9310b3263796bd88db713edf37cf53e7bb4e", size = 654397, upload-time = "2026-05-20T13:14:30.696Z" }, + { url = "https://files.pythonhosted.org/packages/37/eb/147387705bb89092645b012586e7273cb5ed3c90ef7eaf3a69173eaf0209/greenlet-3.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3bfbd69cc349e43bf3a8ae1c85548ff0718efc887615c2db16c3833d7b0b072d", size = 1614469, upload-time = "2026-05-20T14:02:30.192Z" }, + { url = "https://files.pythonhosted.org/packages/a6/4e/37ee0da7732b7aa9896f17e15579a9df34b9fcb9dd494f0adfa749af6623/greenlet-3.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4378720dd888136c27215a0214d32a4d37c3852765d45bc37aad0623423cfd78", size = 1675115, upload-time = "2026-05-20T13:14:40.972Z" }, + { url = "https://files.pythonhosted.org/packages/57/f3/97dfcf4a6eb5077f8a672234216fb5923eb89f2cab7081cb10b2cf75b605/greenlet-3.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:45718441607f9325d948db98cbc691276059316d0358c188c246da4e1d4d23d2", size = 245246, upload-time = "2026-05-20T13:12:22.646Z" }, + { url = "https://files.pythonhosted.org/packages/5d/73/d7f72e34b582f694f4a9b248162db7b09cc458a259ba8f0c0bfa1a34ea7d/greenlet-3.5.1-cp315-cp315-macosx_11_0_universal2.whl", hash = "sha256:2baee5ca02031757ffe8cc3d69f0cc0aec7065ce362622da74f32d3bcab1c541", size = 285575, upload-time = "2026-05-20T13:12:07.043Z" }, + { url = "https://files.pythonhosted.org/packages/df/59/fa9c6e87dc8ad27a95dabe2f29f372b733d05a8a67470f6c901ed9975655/greenlet-3.5.1-cp315-cp315-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b1ec3274918a81d3ea778b9e75b56b72b33f300edb6cf7f3a7fe1dae56683de", size = 656428, upload-time = "2026-05-20T14:00:12.556Z" }, + { url = "https://files.pythonhosted.org/packages/f6/f9/e753408871eaa61dfe35e619cfc67512b036fde99893685d50eea9e07146/greenlet-3.5.1-cp315-cp315-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:111e2390ffffc47d5840b01711dd7fac07d4c09283d0283e7f3264b14e284c64", size = 667064, upload-time = "2026-05-20T14:05:48.662Z" }, + { url = "https://files.pythonhosted.org/packages/96/27/5565b5b40389f1c7753003a07e21892fda8660926787036d5bc0308b8113/greenlet-3.5.1-cp315-cp315-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e630136e905fe5ff43e86945ae41220b6d1470956a39220e708110ac48d01ea5", size = 665697, upload-time = "2026-05-20T13:14:32.943Z" }, + { url = "https://files.pythonhosted.org/packages/cf/82/e7de4178c0c2d1c9a5a3be3cc0b33e46a85b3ee4a77c071bf7ad8600e079/greenlet-3.5.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:975eac34b44a7077ca4d421348455b94f0f518246a7f14bc6d2fdcfe5b584368", size = 1621256, upload-time = "2026-05-20T14:02:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/00/10/f2dddcf7dacac17dfc68691809589adad06135eb28930429cf58a6467a2f/greenlet-3.5.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:9ab3c3a0b2ae6198e67c898dad5215a49f9ae0d0081b3c3ec59f333e39eeca26", size = 1685956, upload-time = "2026-05-20T13:14:42.55Z" }, + { url = "https://files.pythonhosted.org/packages/22/17/4a232b32133230ada52f70e9d7f5b65b0caef8772f01849bd8d149e7e4ca/greenlet-3.5.1-cp315-cp315-win_amd64.whl", hash = "sha256:cbfc69be86e10dcfef5b1e6269d1d6926552aa89ee39e1de3353360c1b6989ab", size = 239802, upload-time = "2026-05-20T13:13:15.481Z" }, + { url = "https://files.pythonhosted.org/packages/c2/ae/4e623a7e6d4d2a5f4cb8e4c82de4169fc637942caae68d6e676b8a128ac5/greenlet-3.5.1-cp315-cp315-win_arm64.whl", hash = "sha256:92fd6d44ac5e5a887c8a5dc4a8ba0ba908527c31c12f78c6bc7dcfe8aab279f6", size = 236853, upload-time = "2026-05-20T13:15:37.301Z" }, + { url = "https://files.pythonhosted.org/packages/7a/57/816d9cff29119da3505b3d6a5e14a8af89006ac36f47f891ff293ee05af1/greenlet-3.5.1-cp315-cp315t-macosx_11_0_universal2.whl", hash = "sha256:a6fdf2433a5441ef9a95464f7c3e674775da1c8c1177fff311cee1acad4626ed", size = 293877, upload-time = "2026-05-20T13:10:19.078Z" }, + { url = "https://files.pythonhosted.org/packages/23/a1/59b0a7c7d140ff1a75626680b9a9899b79a9176cab298b394968fb023295/greenlet-3.5.1-cp315-cp315t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7546556f0d649f99f6a361098a55f761181bb2ea12ff150bb16d26092ad88244", size = 655333, upload-time = "2026-05-20T14:00:14.758Z" }, + { url = "https://files.pythonhosted.org/packages/72/1b/5efe127597625042218939d01855109f352779050768b670b52edcc16a6c/greenlet-3.5.1-cp315-cp315t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d5ee3ea898009fa898f85f9982255d35278c477bebe185beca249cab42d4526c", size = 659443, upload-time = "2026-05-20T14:05:50.159Z" }, + { url = "https://files.pythonhosted.org/packages/6c/6d/c404246ea4d22d097a7426d0efb5b781bd7eb67715f09e79001bd552ab18/greenlet-3.5.1-cp315-cp315t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5c81f74d204d3edd136ebfd50dce53acbb776995d721a0fe801626cfc93b8cd", size = 658356, upload-time = "2026-05-20T13:14:35.091Z" }, + { url = "https://files.pythonhosted.org/packages/51/02/f8ee37fb6d2219329f350af241c27fcf12df57e723d11f6fc6d3bacdadaa/greenlet-3.5.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:2c18ef16bf6d4dd410e4dd52996888ea1497be26892fe5bbc73580aba4287b8e", size = 1619216, upload-time = "2026-05-20T14:02:33.403Z" }, + { url = "https://files.pythonhosted.org/packages/93/c5/3dc9475ace2c7a3680da12372cddd7f1ac874eb410a1ac48d3e9dab83782/greenlet-3.5.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:17d86354f0ae6b61bf9be5148d0dd34e06c3cb7c602c671f79f29ac3b150e659", size = 1678427, upload-time = "2026-05-20T13:14:43.71Z" }, + { url = "https://files.pythonhosted.org/packages/df/4e/750c15c317a41ffb36f0bf40b933e3d744a7dede61889f74443ea69690cf/greenlet-3.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:e7516cf6ae6b8a582c2770a0caed47b8a48373ed732c33d69a72913ae6ac923e", size = 245225, upload-time = "2026-05-20T13:13:59.366Z" }, + { url = "https://files.pythonhosted.org/packages/4f/fd/d3baea2eeb7b617efd47e87ca06e2ec2c6118d303aa9e918e0ce16eadc10/greenlet-3.5.1-cp315-cp315t-win_arm64.whl", hash = "sha256:5028648bf2253ec4745add746129d3904121fa7fe871a76bed23c5720573ce0a", size = 239590, upload-time = "2026-05-20T13:13:37.382Z" }, +] + +[[package]] +name = "griffelib" +version = "2.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/82/74f4a3310cdabfbb10da554c3a672847f1ed33c6f61dd472681ce7f1fe67/griffelib-2.0.2.tar.gz", hash = "sha256:3cf20b3bc470e83763ffbf236e0076b1211bac1bc67de13daf494640f2de707e", size = 166461, upload-time = "2026-03-27T11:34:51.091Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/8c/c9138d881c79aa0ea9ed83cbd58d5ca75624378b38cee225dcf5c42cc91f/griffelib-2.0.2-py3-none-any.whl", hash = "sha256:925c857658fb1ba40c0772c37acbc2ab650bd794d9c1b9726922e36ea4117ea1", size = 142357, upload-time = "2026-03-27T11:34:46.275Z" }, +] + +[[package]] +name = "gunicorn" +version = "26.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/b7/a4a3f632f823e432ce6bc65f62961b7980c898c77f075a2f7118cb3846fe/gunicorn-26.0.0.tar.gz", hash = "sha256:ca9346f85e3a4aeeb64d491045c16b9a35647abd37ea15efe53080eb8b090baf", size = 727286, upload-time = "2026-05-05T06:38:25.529Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/40/9c2384fc2be4ad25dd4a49decd5ad9ea5a3639814c11bd40ab77cb9f0a14/gunicorn-26.0.0-py3-none-any.whl", hash = "sha256:40233d26a5f0d1872916188c276e21641155111c2853f0c2cd55260aec0d24fc", size = 212009, upload-time = "2026-05-05T06:38:23.007Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "hf-xet" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/74/d8/5c06fc76461418326a7decf8367480c35be11a41fd938633929c60a9ec6b/hf_xet-1.5.0.tar.gz", hash = "sha256:e0fb0a34d9f406eed88233e829a67ec016bec5af19e480eac65a233ea289a948", size = 837196, upload-time = "2026-05-06T06:18:15.583Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/9b/6912c99070915a4f28119e3c5b52a9abd1eec0ad5cb293b8c967a0c6f5a2/hf_xet-1.5.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:7d70fe2ce97b9db73b9c9b9c81fe3693640aec83416a966c446afea54acfae3c", size = 4023383, upload-time = "2026-05-06T06:17:53.947Z" }, + { url = "https://files.pythonhosted.org/packages/0f/6d/9563cfde59b5d8128a9c7ec972a087f4c782e4f7bac5a85234edfd5d5e49/hf_xet-1.5.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:73a0dae8c71de3b0633a45c73f4a4a5ed09e94b43441d82981a781d4f12baa42", size = 3792751, upload-time = "2026-05-06T06:17:51.791Z" }, + { url = "https://files.pythonhosted.org/packages/07/a5/ed5a0cf35b49a0571af5a8f53416dad1877a718c021c9937c3a53cb45781/hf_xet-1.5.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a60290ec57e9b71767fba7c3645ddafdd0759974b540441510c629c6db6db24a", size = 4456058, upload-time = "2026-05-06T06:17:40.735Z" }, + { url = "https://files.pythonhosted.org/packages/60/fb/3ae8bf2a7a37a4197d0195d7247fd25b3952e15cb8a599e285dfaa6f52b3/hf_xet-1.5.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:e5de0f6deada0dada870bb376a11bcd1f08abf3a968a6d118f33e72d1b1eb480", size = 4250783, upload-time = "2026-05-06T06:17:38.412Z" }, + { url = "https://files.pythonhosted.org/packages/a2/9b/8bae40d4d91525085137196e84eb0ed49cf65b5e96e5c3ecdadd8bd0fac2/hf_xet-1.5.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c799d49f1a5544a0ef7591c0ee75e0d6b93d6f56dc7a4979f59f7518d2872216", size = 4445594, upload-time = "2026-05-06T06:18:04.219Z" }, + { url = "https://files.pythonhosted.org/packages/13/59/c74efbbd4e8728172b2cc72a2bc014d2947a4b7bdced932fbd3f5da1a4e5/hf_xet-1.5.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2baea1b0b989e5c152fe81425f7745ddc8901280ba3d97c98d8cdece7b706c60", size = 4663995, upload-time = "2026-05-06T06:18:06.1Z" }, + { url = "https://files.pythonhosted.org/packages/73/32/8e1e0410af64cda9b139d1dcebdc993a8ff9c8c7c0e2696ae356d75ccc0d/hf_xet-1.5.0-cp313-cp313t-win_amd64.whl", hash = "sha256:526345b3ed45f374f6317349df489167606736c876241ba984105afe7fd4839d", size = 3966608, upload-time = "2026-05-06T06:18:19.74Z" }, + { url = "https://files.pythonhosted.org/packages/fc/34/a8febc8f4edbea8b3e21b02ebc8b628679b84ba7e45cde624a7736b51500/hf_xet-1.5.0-cp313-cp313t-win_arm64.whl", hash = "sha256:786d28e2eb8315d5035544b9d137b4a842d600c434bb91bf7d0d953cce906ad4", size = 3796946, upload-time = "2026-05-06T06:18:17.568Z" }, + { url = "https://files.pythonhosted.org/packages/2a/20/8fc8996afe5815fa1a6be8e9e5c02f24500f409d599e905800d498a4e14d/hf_xet-1.5.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:872d5601e6deea30d15865ede55d29eac6daf5a534ab417b99b6ef6b076dd96c", size = 4023495, upload-time = "2026-05-06T06:18:01.94Z" }, + { url = "https://files.pythonhosted.org/packages/32/6a/93d84463c00cecb561a7508aa6303e35ee2894294eac14245526924415fe/hf_xet-1.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9929561f5abf4581c8ea79587881dfef6b8abb2a0d8a51915936fc2a614f4e73", size = 3792731, upload-time = "2026-05-06T06:18:00.021Z" }, + { url = "https://files.pythonhosted.org/packages/9d/5a/8ec8e0c863b382d00b3c2e2af6ded6b06371be617144a625903a6d562f4b/hf_xet-1.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f7b7bbae318e583a86fb21e5a4a175d6721d628a2874f4bd022d0e660c32a682", size = 4456738, upload-time = "2026-05-06T06:17:49.574Z" }, + { url = "https://files.pythonhosted.org/packages/c5/ca/f7effa1a67717da2bcc6b6c28f71c6ca648c77acaec4e2c32f40cbe16d85/hf_xet-1.5.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:cf7b2dc6f31a4ea754bb50f74cde482dcf5d366d184076d8530b9872787f3761", size = 4251622, upload-time = "2026-05-06T06:17:47.096Z" }, + { url = "https://files.pythonhosted.org/packages/65/f2/19247dba3e231cf77dec59ddfb878f00057635ff773d099c9b59d37812c3/hf_xet-1.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8dbcbab554c9ef158ef2c991545c3e970ddd8cc7acdcd0a78c5a41095dab4ded", size = 4445667, upload-time = "2026-05-06T06:18:11.983Z" }, + { url = "https://files.pythonhosted.org/packages/7f/64/6f116801a3bcfb6f59f5c251f48cadc47ea54026441c4a385079286a94fa/hf_xet-1.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5906bf7718d3636dc13402914736abe723492cb730f744834f5f5b67d3a12702", size = 4664619, upload-time = "2026-05-06T06:18:13.771Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e8/069542d37946ed08669b127e1496fa99e78196d71de8d41eda5e9f1b7a58/hf_xet-1.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:5f3dc2248fc01cc0a00cd392ab497f1ca373fcbc7e3f2da1f452480b384e839e", size = 3966802, upload-time = "2026-05-06T06:18:28.162Z" }, + { url = "https://files.pythonhosted.org/packages/f9/91/fc6fdec27b14d04e88c386ac0a0129732b53fa23f7c4a78f4b83a039c567/hf_xet-1.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:b285cea1b5bab46b758772716ba8d6854a1a0310fed1c249d678a8b38601e5a0", size = 3797168, upload-time = "2026-05-06T06:18:26.287Z" }, + { url = "https://files.pythonhosted.org/packages/3d/fb/69ff198a82cae7eb1a69fb84d93b3a3e4816564d76817fe541ddc96874eb/hf_xet-1.5.0-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:dad0dc84e941b8ba3c860659fe1fdc35c049d47cce293f003287757e971a8f56", size = 4030814, upload-time = "2026-05-06T06:17:57.933Z" }, + { url = "https://files.pythonhosted.org/packages/9b/ff/edcc2b40162bef3ff78e14ab637e5f3b89243d6aee72f5949d3bb6a5af83/hf_xet-1.5.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:fd6e5a9b0fdac4ed03ed45ef79254a655b1aaab514a02202617fbf643f5fdf7a", size = 3798444, upload-time = "2026-05-06T06:17:55.79Z" }, + { url = "https://files.pythonhosted.org/packages/49/4d/103f76b04310e5e57656696cc184690d20c466af0bca3ca88f8c8ea5d4f3/hf_xet-1.5.0-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3531b1823a0e6d77d80f9ed15ca0e00f0d115094f8ac033d5cae88f4564cc949", size = 4465986, upload-time = "2026-05-06T06:17:44.886Z" }, + { url = "https://files.pythonhosted.org/packages/c4/a2/546f47f464737b3edbab6f8ddb57f2599b93d2cbb66f06abb475ccb48651/hf_xet-1.5.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:9a0ee58cd18d5ea799f7ed11290bbccbe56bdd8b1d97ca74b9cc49a3945d7a3b", size = 4259865, upload-time = "2026-05-06T06:17:42.639Z" }, + { url = "https://files.pythonhosted.org/packages/95/7f/1be593c1f28613be2e196473481cd81bfc5910795e30a34e8f744f6cac4f/hf_xet-1.5.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1e60df5a42e9bed8628b6416af2cba4cba57ae9f02de226a06b020d98e1aab18", size = 4459835, upload-time = "2026-05-06T06:18:08.026Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b2/703569fc881f3284487e68cda7b42179978480da3c438042a6bbbb4a671c/hf_xet-1.5.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4b35549ce62601b84da4ff9b24d970032ace3d4430f52d91bcbb26c901d6c690", size = 4672414, upload-time = "2026-05-06T06:18:09.864Z" }, + { url = "https://files.pythonhosted.org/packages/af/37/1b6def445c567286b50aa3b33828158e135b1be44938dde59f11382a500c/hf_xet-1.5.0-cp37-abi3-win_amd64.whl", hash = "sha256:2806c7c17b4d23f8d88f7c4814f838c3b6150773fe339c20af23e1cfaf2797e4", size = 3977238, upload-time = "2026-05-06T06:18:23.621Z" }, + { url = "https://files.pythonhosted.org/packages/62/94/3b66b148778ee100dcfd69c2ca22b57b41b44d3063ceec934f209e9184ce/hf_xet-1.5.0-cp37-abi3-win_arm64.whl", hash = "sha256:b6c9df403040248c76d808d3e047d64db2d923bae593eb244c41e425cf6cd7be", size = 3806916, upload-time = "2026-05-06T06:18:21.7Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "httpx-sse" +version = "0.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/4c/751061ffa58615a32c31b2d82e8482be8dd4a89154f003147acee90f2be9/httpx_sse-0.4.3.tar.gz", hash = "sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d", size = 15943, upload-time = "2025-10-10T21:48:22.271Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/fd/6668e5aec43ab844de6fc74927e155a3b37bf40d7c3790e49fc0406b6578/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc", size = 8960, upload-time = "2025-10-10T21:48:21.158Z" }, +] + +[[package]] +name = "huggingface-hub" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "filelock" }, + { name = "fsspec" }, + { name = "hf-xet", marker = "platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, + { name = "httpx" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "tqdm" }, + { name = "typer" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bd/65/9826515abb600b5722bcf53f8b4a2fb58340b1f8bfcaee19f83561c13a44/huggingface_hub-1.17.0.tar.gz", hash = "sha256:fad842b6763ef70ebc3919665b1b9273645203185400a7d6c5eddc2323cc3435", size = 797082, upload-time = "2026-05-28T15:12:13.347Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/28/d7cef5e477b855c25d415b8f57e5bc7347c7a90cad3acf1725d0c92ca294/huggingface_hub-1.17.0-py3-none-any.whl", hash = "sha256:3b8156d23118e87f6a587648bfbc04f04a12a757ccb4ed298b35c4ae638bf24c", size = 671546, upload-time = "2026-05-28T15:12:11.441Z" }, +] + +[[package]] +name = "idna" +version = "3.18" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, +] + +[[package]] +name = "importlib-metadata" +version = "8.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e7/72/c600ae4f68c28fc19f9c31b9403053e5dbb8cace2e6842c7b7c3e4d42fe9/importlib_metadata-8.9.0.tar.gz", hash = "sha256:58850626cef4bd2df100378b0f2aea9724a7b92f10770d547725b047078f99ee", size = 56140, upload-time = "2026-03-20T16:56:26.362Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/f9/97f2ca8bb3ec6e4b1d64f983ebe98b9a192faddff67fac3d6303a537e670/importlib_metadata-8.9.0-py3-none-any.whl", hash = "sha256:e0f761b6ea91ced3b0844c14c9d955224d538105921f8e6754c00f6ca79fba7f", size = 27220, upload-time = "2026-03-20T16:56:25.07Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "instructor" +version = "1.15.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "docstring-parser" }, + { name = "jinja2" }, + { name = "jiter" }, + { name = "openai" }, + { name = "pydantic" }, + { name = "pydantic-core" }, + { name = "requests" }, + { name = "rich" }, + { name = "tenacity" }, + { name = "typer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/a4/832cfb15420360e26d2d85bd9d5fe1e4b839d52587574d389bc31284bf6f/instructor-1.15.1.tar.gz", hash = "sha256:c72406469d9025b742e83cf0c13e914b317db2089d08d889944e74fcd659ef94", size = 69948370, upload-time = "2026-04-03T01:51:30.107Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/c8/36c5d9b80aaf40ba9a7084a8fc18c967db6bf248a4cc8d0f0816b14284be/instructor-1.15.1-py3-none-any.whl", hash = "sha256:be81d17ba2b154a04ab4720808f24f9d6b598f80992f82eaf9cc79006099cf6c", size = 178156, upload-time = "2026-04-03T01:51:23.098Z" }, +] + +[[package]] +name = "isort" +version = "8.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ef/7c/ec4ab396d31b3b395e2e999c8f46dec78c5e29209fac49d1f4dace04041d/isort-8.0.1.tar.gz", hash = "sha256:171ac4ff559cdc060bcfff550bc8404a486fee0caab245679c2abe7cb253c78d", size = 769592, upload-time = "2026-02-28T10:08:20.685Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/95/c7c34aa53c16353c56d0b802fba48d5f5caa2cdee7958acbcb795c830416/isort-8.0.1-py3-none-any.whl", hash = "sha256:28b89bc70f751b559aeca209e6120393d43fbe2490de0559662be7a9787e3d75", size = 89733, upload-time = "2026-02-28T10:08:19.466Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "jiter" +version = "0.13.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/5e/4ec91646aee381d01cdb9974e30882c9cd3b8c5d1079d6b5ff4af522439a/jiter-0.13.0.tar.gz", hash = "sha256:f2839f9c2c7e2dffc1bc5929a510e14ce0a946be9365fd1219e7ef342dae14f4", size = 164847, upload-time = "2026-02-02T12:37:56.441Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/30/7687e4f87086829955013ca12a9233523349767f69653ebc27036313def9/jiter-0.13.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0a2bd69fc1d902e89925fc34d1da51b2128019423d7b339a45d9e99c894e0663", size = 307958, upload-time = "2026-02-02T12:35:57.165Z" }, + { url = "https://files.pythonhosted.org/packages/c3/27/e57f9a783246ed95481e6749cc5002a8a767a73177a83c63ea71f0528b90/jiter-0.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f917a04240ef31898182f76a332f508f2cc4b57d2b4d7ad2dbfebbfe167eb505", size = 318597, upload-time = "2026-02-02T12:35:58.591Z" }, + { url = "https://files.pythonhosted.org/packages/cf/52/e5719a60ac5d4d7c5995461a94ad5ef962a37c8bf5b088390e6fad59b2ff/jiter-0.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1e2b199f446d3e82246b4fd9236d7cb502dc2222b18698ba0d986d2fecc6152", size = 348821, upload-time = "2026-02-02T12:36:00.093Z" }, + { url = "https://files.pythonhosted.org/packages/61/db/c1efc32b8ba4c740ab3fc2d037d8753f67685f475e26b9d6536a4322bcdd/jiter-0.13.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04670992b576fa65bd056dbac0c39fe8bd67681c380cb2b48efa885711d9d726", size = 364163, upload-time = "2026-02-02T12:36:01.937Z" }, + { url = "https://files.pythonhosted.org/packages/55/8a/fb75556236047c8806995671a18e4a0ad646ed255276f51a20f32dceaeec/jiter-0.13.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a1aff1fbdb803a376d4d22a8f63f8e7ccbce0b4890c26cc7af9e501ab339ef0", size = 483709, upload-time = "2026-02-02T12:36:03.41Z" }, + { url = "https://files.pythonhosted.org/packages/7e/16/43512e6ee863875693a8e6f6d532e19d650779d6ba9a81593ae40a9088ff/jiter-0.13.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b3fb8c2053acaef8580809ac1d1f7481a0a0bdc012fd7f5d8b18fb696a5a089", size = 370480, upload-time = "2026-02-02T12:36:04.791Z" }, + { url = "https://files.pythonhosted.org/packages/f8/4c/09b93e30e984a187bc8aaa3510e1ec8dcbdcd71ca05d2f56aac0492453aa/jiter-0.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdaba7d87e66f26a2c45d8cbadcbfc4bf7884182317907baf39cfe9775bb4d93", size = 360735, upload-time = "2026-02-02T12:36:06.994Z" }, + { url = "https://files.pythonhosted.org/packages/1a/1b/46c5e349019874ec5dfa508c14c37e29864ea108d376ae26d90bee238cd7/jiter-0.13.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7b88d649135aca526da172e48083da915ec086b54e8e73a425ba50999468cc08", size = 391814, upload-time = "2026-02-02T12:36:08.368Z" }, + { url = "https://files.pythonhosted.org/packages/15/9e/26184760e85baee7162ad37b7912797d2077718476bf91517641c92b3639/jiter-0.13.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e404ea551d35438013c64b4f357b0474c7abf9f781c06d44fcaf7a14c69ff9e2", size = 513990, upload-time = "2026-02-02T12:36:09.993Z" }, + { url = "https://files.pythonhosted.org/packages/e9/34/2c9355247d6debad57a0a15e76ab1566ab799388042743656e566b3b7de1/jiter-0.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1f4748aad1b4a93c8bdd70f604d0f748cdc0e8744c5547798acfa52f10e79228", size = 548021, upload-time = "2026-02-02T12:36:11.376Z" }, + { url = "https://files.pythonhosted.org/packages/ac/4a/9f2c23255d04a834398b9c2e0e665382116911dc4d06b795710503cdad25/jiter-0.13.0-cp312-cp312-win32.whl", hash = "sha256:0bf670e3b1445fc4d31612199f1744f67f889ee1bbae703c4b54dc097e5dd394", size = 203024, upload-time = "2026-02-02T12:36:12.682Z" }, + { url = "https://files.pythonhosted.org/packages/09/ee/f0ae675a957ae5a8f160be3e87acea6b11dc7b89f6b7ab057e77b2d2b13a/jiter-0.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:15db60e121e11fe186c0b15236bd5d18381b9ddacdcf4e659feb96fc6c969c92", size = 205424, upload-time = "2026-02-02T12:36:13.93Z" }, + { url = "https://files.pythonhosted.org/packages/1b/02/ae611edf913d3cbf02c97cdb90374af2082c48d7190d74c1111dde08bcdd/jiter-0.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:41f92313d17989102f3cb5dd533a02787cdb99454d494344b0361355da52fcb9", size = 186818, upload-time = "2026-02-02T12:36:15.308Z" }, + { url = "https://files.pythonhosted.org/packages/91/9c/7ee5a6ff4b9991e1a45263bfc46731634c4a2bde27dfda6c8251df2d958c/jiter-0.13.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1f8a55b848cbabf97d861495cd65f1e5c590246fabca8b48e1747c4dfc8f85bf", size = 306897, upload-time = "2026-02-02T12:36:16.748Z" }, + { url = "https://files.pythonhosted.org/packages/7c/02/be5b870d1d2be5dd6a91bdfb90f248fbb7dcbd21338f092c6b89817c3dbf/jiter-0.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f556aa591c00f2c45eb1b89f68f52441a016034d18b65da60e2d2875bbbf344a", size = 317507, upload-time = "2026-02-02T12:36:18.351Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/b25d2ec333615f5f284f3a4024f7ce68cfa0604c322c6808b2344c7f5d2b/jiter-0.13.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7e1d61da332ec412350463891923f960c3073cf1aae93b538f0bb4c8cd46efb", size = 350560, upload-time = "2026-02-02T12:36:19.746Z" }, + { url = "https://files.pythonhosted.org/packages/be/ec/74dcb99fef0aca9fbe56b303bf79f6bd839010cb18ad41000bf6cc71eec0/jiter-0.13.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3097d665a27bc96fd9bbf7f86178037db139f319f785e4757ce7ccbf390db6c2", size = 363232, upload-time = "2026-02-02T12:36:21.243Z" }, + { url = "https://files.pythonhosted.org/packages/1b/37/f17375e0bb2f6a812d4dd92d7616e41917f740f3e71343627da9db2824ce/jiter-0.13.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d01ecc3a8cbdb6f25a37bd500510550b64ddf9f7d64a107d92f3ccb25035d0f", size = 483727, upload-time = "2026-02-02T12:36:22.688Z" }, + { url = "https://files.pythonhosted.org/packages/77/d2/a71160a5ae1a1e66c1395b37ef77da67513b0adba73b993a27fbe47eb048/jiter-0.13.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed9bbc30f5d60a3bdf63ae76beb3f9db280d7f195dfcfa61af792d6ce912d159", size = 370799, upload-time = "2026-02-02T12:36:24.106Z" }, + { url = "https://files.pythonhosted.org/packages/01/99/ed5e478ff0eb4e8aa5fd998f9d69603c9fd3f32de3bd16c2b1194f68361c/jiter-0.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98fbafb6e88256f4454de33c1f40203d09fc33ed19162a68b3b257b29ca7f663", size = 359120, upload-time = "2026-02-02T12:36:25.519Z" }, + { url = "https://files.pythonhosted.org/packages/16/be/7ffd08203277a813f732ba897352797fa9493faf8dc7995b31f3d9cb9488/jiter-0.13.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5467696f6b827f1116556cb0db620440380434591e93ecee7fd14d1a491b6daa", size = 390664, upload-time = "2026-02-02T12:36:26.866Z" }, + { url = "https://files.pythonhosted.org/packages/d1/84/e0787856196d6d346264d6dcccb01f741e5f0bd014c1d9a2ebe149caf4f3/jiter-0.13.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:2d08c9475d48b92892583df9da592a0e2ac49bcd41fae1fec4f39ba6cf107820", size = 513543, upload-time = "2026-02-02T12:36:28.217Z" }, + { url = "https://files.pythonhosted.org/packages/65/50/ecbd258181c4313cf79bca6c88fb63207d04d5bf5e4f65174114d072aa55/jiter-0.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:aed40e099404721d7fcaf5b89bd3b4568a4666358bcac7b6b15c09fb6252ab68", size = 547262, upload-time = "2026-02-02T12:36:29.678Z" }, + { url = "https://files.pythonhosted.org/packages/27/da/68f38d12e7111d2016cd198161b36e1f042bd115c169255bcb7ec823a3bf/jiter-0.13.0-cp313-cp313-win32.whl", hash = "sha256:36ebfbcffafb146d0e6ffb3e74d51e03d9c35ce7c625c8066cdbfc7b953bdc72", size = 200630, upload-time = "2026-02-02T12:36:31.808Z" }, + { url = "https://files.pythonhosted.org/packages/25/65/3bd1a972c9a08ecd22eb3b08a95d1941ebe6938aea620c246cf426ae09c2/jiter-0.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:8d76029f077379374cf0dbc78dbe45b38dec4a2eb78b08b5194ce836b2517afc", size = 202602, upload-time = "2026-02-02T12:36:33.679Z" }, + { url = "https://files.pythonhosted.org/packages/15/fe/13bd3678a311aa67686bb303654792c48206a112068f8b0b21426eb6851e/jiter-0.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:bb7613e1a427cfcb6ea4544f9ac566b93d5bf67e0d48c787eca673ff9c9dff2b", size = 185939, upload-time = "2026-02-02T12:36:35.065Z" }, + { url = "https://files.pythonhosted.org/packages/49/19/a929ec002ad3228bc97ca01dbb14f7632fffdc84a95ec92ceaf4145688ae/jiter-0.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fa476ab5dd49f3bf3a168e05f89358c75a17608dbabb080ef65f96b27c19ab10", size = 316616, upload-time = "2026-02-02T12:36:36.579Z" }, + { url = "https://files.pythonhosted.org/packages/52/56/d19a9a194afa37c1728831e5fb81b7722c3de18a3109e8f282bfc23e587a/jiter-0.13.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ade8cb6ff5632a62b7dbd4757d8c5573f7a2e9ae285d6b5b841707d8363205ef", size = 346850, upload-time = "2026-02-02T12:36:38.058Z" }, + { url = "https://files.pythonhosted.org/packages/36/4a/94e831c6bf287754a8a019cb966ed39ff8be6ab78cadecf08df3bb02d505/jiter-0.13.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9950290340acc1adaded363edd94baebcee7dabdfa8bee4790794cd5cfad2af6", size = 358551, upload-time = "2026-02-02T12:36:39.417Z" }, + { url = "https://files.pythonhosted.org/packages/a2/ec/a4c72c822695fa80e55d2b4142b73f0012035d9fcf90eccc56bc060db37c/jiter-0.13.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2b4972c6df33731aac0742b64fd0d18e0a69bc7d6e03108ce7d40c85fd9e3e6d", size = 201950, upload-time = "2026-02-02T12:36:40.791Z" }, + { url = "https://files.pythonhosted.org/packages/b6/00/393553ec27b824fbc29047e9c7cd4a3951d7fbe4a76743f17e44034fa4e4/jiter-0.13.0-cp313-cp313t-win_arm64.whl", hash = "sha256:701a1e77d1e593c1b435315ff625fd071f0998c5f02792038a5ca98899261b7d", size = 185852, upload-time = "2026-02-02T12:36:42.077Z" }, + { url = "https://files.pythonhosted.org/packages/6e/f5/f1997e987211f6f9bd71b8083047b316208b4aca0b529bb5f8c96c89ef3e/jiter-0.13.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:cc5223ab19fe25e2f0bf2643204ad7318896fe3729bf12fde41b77bfc4fafff0", size = 308804, upload-time = "2026-02-02T12:36:43.496Z" }, + { url = "https://files.pythonhosted.org/packages/cd/8f/5482a7677731fd44881f0204981ce2d7175db271f82cba2085dd2212e095/jiter-0.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9776ebe51713acf438fd9b4405fcd86893ae5d03487546dae7f34993217f8a91", size = 318787, upload-time = "2026-02-02T12:36:45.071Z" }, + { url = "https://files.pythonhosted.org/packages/f3/b9/7257ac59778f1cd025b26a23c5520a36a424f7f1b068f2442a5b499b7464/jiter-0.13.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:879e768938e7b49b5e90b7e3fecc0dbec01b8cb89595861fb39a8967c5220d09", size = 353880, upload-time = "2026-02-02T12:36:47.365Z" }, + { url = "https://files.pythonhosted.org/packages/c3/87/719eec4a3f0841dad99e3d3604ee4cba36af4419a76f3cb0b8e2e691ad67/jiter-0.13.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:682161a67adea11e3aae9038c06c8b4a9a71023228767477d683f69903ebc607", size = 366702, upload-time = "2026-02-02T12:36:48.871Z" }, + { url = "https://files.pythonhosted.org/packages/d2/65/415f0a75cf6921e43365a1bc227c565cb949caca8b7532776e430cbaa530/jiter-0.13.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a13b68cd1cd8cc9de8f244ebae18ccb3e4067ad205220ef324c39181e23bbf66", size = 486319, upload-time = "2026-02-02T12:36:53.006Z" }, + { url = "https://files.pythonhosted.org/packages/54/a2/9e12b48e82c6bbc6081fd81abf915e1443add1b13d8fc586e1d90bb02bb8/jiter-0.13.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87ce0f14c6c08892b610686ae8be350bf368467b6acd5085a5b65441e2bf36d2", size = 372289, upload-time = "2026-02-02T12:36:54.593Z" }, + { url = "https://files.pythonhosted.org/packages/4e/c1/e4693f107a1789a239c759a432e9afc592366f04e901470c2af89cfd28e1/jiter-0.13.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c365005b05505a90d1c47856420980d0237adf82f70c4aff7aebd3c1cc143ad", size = 360165, upload-time = "2026-02-02T12:36:56.112Z" }, + { url = "https://files.pythonhosted.org/packages/17/08/91b9ea976c1c758240614bd88442681a87672eebc3d9a6dde476874e706b/jiter-0.13.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1317fdffd16f5873e46ce27d0e0f7f4f90f0cdf1d86bf6abeaea9f63ca2c401d", size = 389634, upload-time = "2026-02-02T12:36:57.495Z" }, + { url = "https://files.pythonhosted.org/packages/18/23/58325ef99390d6d40427ed6005bf1ad54f2577866594bcf13ce55675f87d/jiter-0.13.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c05b450d37ba0c9e21c77fef1f205f56bcee2330bddca68d344baebfc55ae0df", size = 514933, upload-time = "2026-02-02T12:36:58.909Z" }, + { url = "https://files.pythonhosted.org/packages/5b/25/69f1120c7c395fd276c3996bb8adefa9c6b84c12bb7111e5c6ccdcd8526d/jiter-0.13.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:775e10de3849d0631a97c603f996f518159272db00fdda0a780f81752255ee9d", size = 548842, upload-time = "2026-02-02T12:37:00.433Z" }, + { url = "https://files.pythonhosted.org/packages/18/05/981c9669d86850c5fbb0d9e62bba144787f9fba84546ba43d624ee27ef29/jiter-0.13.0-cp314-cp314-win32.whl", hash = "sha256:632bf7c1d28421c00dd8bbb8a3bac5663e1f57d5cd5ed962bce3c73bf62608e6", size = 202108, upload-time = "2026-02-02T12:37:01.718Z" }, + { url = "https://files.pythonhosted.org/packages/8d/96/cdcf54dd0b0341db7d25413229888a346c7130bd20820530905fdb65727b/jiter-0.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:f22ef501c3f87ede88f23f9b11e608581c14f04db59b6a801f354397ae13739f", size = 204027, upload-time = "2026-02-02T12:37:03.075Z" }, + { url = "https://files.pythonhosted.org/packages/fb/f9/724bcaaab7a3cd727031fe4f6995cb86c4bd344909177c186699c8dec51a/jiter-0.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:07b75fe09a4ee8e0c606200622e571e44943f47254f95e2436c8bdcaceb36d7d", size = 187199, upload-time = "2026-02-02T12:37:04.414Z" }, + { url = "https://files.pythonhosted.org/packages/62/92/1661d8b9fd6a3d7a2d89831db26fe3c1509a287d83ad7838831c7b7a5c7e/jiter-0.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:964538479359059a35fb400e769295d4b315ae61e4105396d355a12f7fef09f0", size = 318423, upload-time = "2026-02-02T12:37:05.806Z" }, + { url = "https://files.pythonhosted.org/packages/4f/3b/f77d342a54d4ebcd128e520fc58ec2f5b30a423b0fd26acdfc0c6fef8e26/jiter-0.13.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e104da1db1c0991b3eaed391ccd650ae8d947eab1480c733e5a3fb28d4313e40", size = 351438, upload-time = "2026-02-02T12:37:07.189Z" }, + { url = "https://files.pythonhosted.org/packages/76/b3/ba9a69f0e4209bd3331470c723c2f5509e6f0482e416b612431a5061ed71/jiter-0.13.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e3a5f0cde8ff433b8e88e41aa40131455420fb3649a3c7abdda6145f8cb7202", size = 364774, upload-time = "2026-02-02T12:37:08.579Z" }, + { url = "https://files.pythonhosted.org/packages/b3/16/6cdb31fa342932602458dbb631bfbd47f601e03d2e4950740e0b2100b570/jiter-0.13.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:57aab48f40be1db920a582b30b116fe2435d184f77f0e4226f546794cedd9cf0", size = 487238, upload-time = "2026-02-02T12:37:10.066Z" }, + { url = "https://files.pythonhosted.org/packages/ed/b1/956cc7abaca8d95c13aa8d6c9b3f3797241c246cd6e792934cc4c8b250d2/jiter-0.13.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7772115877c53f62beeb8fd853cab692dbc04374ef623b30f997959a4c0e7e95", size = 372892, upload-time = "2026-02-02T12:37:11.656Z" }, + { url = "https://files.pythonhosted.org/packages/26/c4/97ecde8b1e74f67b8598c57c6fccf6df86ea7861ed29da84629cdbba76c4/jiter-0.13.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1211427574b17b633cfceba5040de8081e5abf114f7a7602f73d2e16f9fdaa59", size = 360309, upload-time = "2026-02-02T12:37:13.244Z" }, + { url = "https://files.pythonhosted.org/packages/4b/d7/eabe3cf46715854ccc80be2cd78dd4c36aedeb30751dbf85a1d08c14373c/jiter-0.13.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7beae3a3d3b5212d3a55d2961db3c292e02e302feb43fce6a3f7a31b90ea6dfe", size = 389607, upload-time = "2026-02-02T12:37:14.881Z" }, + { url = "https://files.pythonhosted.org/packages/df/2d/03963fc0804e6109b82decfb9974eb92df3797fe7222428cae12f8ccaa0c/jiter-0.13.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:e5562a0f0e90a6223b704163ea28e831bd3a9faa3512a711f031611e6b06c939", size = 514986, upload-time = "2026-02-02T12:37:16.326Z" }, + { url = "https://files.pythonhosted.org/packages/f6/6c/8c83b45eb3eb1c1e18d841fe30b4b5bc5619d781267ca9bc03e005d8fd0a/jiter-0.13.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:6c26a424569a59140fb51160a56df13f438a2b0967365e987889186d5fc2f6f9", size = 548756, upload-time = "2026-02-02T12:37:17.736Z" }, + { url = "https://files.pythonhosted.org/packages/47/66/eea81dfff765ed66c68fd2ed8c96245109e13c896c2a5015c7839c92367e/jiter-0.13.0-cp314-cp314t-win32.whl", hash = "sha256:24dc96eca9f84da4131cdf87a95e6ce36765c3b156fc9ae33280873b1c32d5f6", size = 201196, upload-time = "2026-02-02T12:37:19.101Z" }, + { url = "https://files.pythonhosted.org/packages/ff/32/4ac9c7a76402f8f00d00842a7f6b83b284d0cf7c1e9d4227bc95aa6d17fa/jiter-0.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0a8d76c7524087272c8ae913f5d9d608bd839154b62c4322ef65723d2e5bb0b8", size = 204215, upload-time = "2026-02-02T12:37:20.495Z" }, + { url = "https://files.pythonhosted.org/packages/f9/8e/7def204fea9f9be8b3c21a6f2dd6c020cf56c7d5ff753e0e23ed7f9ea57e/jiter-0.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2c26cf47e2cad140fa23b6d58d435a7c0161f5c514284802f25e87fddfe11024", size = 187152, upload-time = "2026-02-02T12:37:22.124Z" }, + { url = "https://files.pythonhosted.org/packages/80/60/e50fa45dd7e2eae049f0ce964663849e897300433921198aef94b6ffa23a/jiter-0.13.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:3d744a6061afba08dd7ae375dcde870cffb14429b7477e10f67e9e6d68772a0a", size = 305169, upload-time = "2026-02-02T12:37:50.376Z" }, + { url = "https://files.pythonhosted.org/packages/d2/73/a009f41c5eed71c49bec53036c4b33555afcdee70682a18c6f66e396c039/jiter-0.13.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:ff732bd0a0e778f43d5009840f20b935e79087b4dc65bd36f1cd0f9b04b8ff7f", size = 303808, upload-time = "2026-02-02T12:37:52.092Z" }, + { url = "https://files.pythonhosted.org/packages/c4/10/528b439290763bff3d939268085d03382471b442f212dca4ff5f12802d43/jiter-0.13.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab44b178f7981fcaea7e0a5df20e773c663d06ffda0198f1a524e91b2fde7e59", size = 337384, upload-time = "2026-02-02T12:37:53.582Z" }, + { url = "https://files.pythonhosted.org/packages/67/8a/a342b2f0251f3dac4ca17618265d93bf244a2a4d089126e81e4c1056ac50/jiter-0.13.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bb00b6d26db67a05fe3e12c76edc75f32077fb51deed13822dc648fa373bc19", size = 343768, upload-time = "2026-02-02T12:37:55.055Z" }, +] + +[[package]] +name = "jmespath" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/59/322338183ecda247fb5d1763a6cbe46eff7222eaeebafd9fa65d4bf5cb11/jmespath-1.1.0.tar.gz", hash = "sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d", size = 27377, upload-time = "2026-01-22T16:35:26.279Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64", size = 20419, upload-time = "2026-01-22T16:35:24.919Z" }, +] + +[[package]] +name = "jsonpatch" +version = "1.33" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonpointer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/78/18813351fe5d63acad16aec57f94ec2b70a09e53ca98145589e185423873/jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c", size = 21699, upload-time = "2023-06-26T12:07:29.144Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/07/02e16ed01e04a374e644b575638ec7987ae846d25ad97bcc9945a3ee4b0e/jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade", size = 12898, upload-time = "2023-06-16T21:01:28.466Z" }, +] + +[[package]] +name = "jsonpointer" +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/c7/af399a2e7a67fd18d63c40c5e62d3af4e67b836a2107468b6a5ea24c4304/jsonpointer-3.1.1.tar.gz", hash = "sha256:0b801c7db33a904024f6004d526dcc53bbb8a4a0f4e32bfd10beadf60adf1900", size = 9068, upload-time = "2026-03-23T22:32:32.458Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl", hash = "sha256:8ff8b95779d071ba472cf5bc913028df06031797532f08a7d5b602d8b2a488ca", size = 7659, upload-time = "2026-03-23T22:32:31.568Z" }, +] + +[[package]] +name = "jsonschema" +version = "4.26.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, +] + +[[package]] +name = "langchain" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "langgraph" }, + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d5/d0/c7f9d3d26c0e3f8bb146c6d707ee0fc1d30d8da65a59626e8a580085e929/langchain-1.3.2.tar.gz", hash = "sha256:ffd5f204a46b5fa1a38bf89ba3b45ca0902c02d18fa7d2a2eaeaeb1f5bf19d0a", size = 600598, upload-time = "2026-05-26T18:17:57.715Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/82/a54edcd1c48163de5642eb10fa2cb58b13a8889c659964f63f0306b58b1e/langchain-1.3.2-py3-none-any.whl", hash = "sha256:900f6b3f4ee08b9ba3cdbe667dbf42525bd6f66a4a07a7f1db26262673e41ed6", size = 121225, upload-time = "2026-05-26T18:17:56.075Z" }, +] + +[[package]] +name = "langchain-classic" +version = "1.0.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "langchain-text-splitters" }, + { name = "langsmith" }, + { name = "pydantic" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "sqlalchemy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9b/78/84b5065816f348c39fefa4316f209f0135e8410216340a953bec17d9e4e4/langchain_classic-1.0.7.tar.gz", hash = "sha256:debbec8065e69b95108d2652e8d5c44f4516e19aa8d716c02ed2211c3aee099d", size = 10554118, upload-time = "2026-05-07T15:46:56.8Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/78/2d9980d028ff0523eea503a77c200e2ff252a3a75eb6e7842bcf5f9c979b/langchain_classic-1.0.7-py3-none-any.whl", hash = "sha256:d9d9be38f7aa534ed0259c2410432e34a1f80b1d491e686749bb55af56479be3", size = 1041386, upload-time = "2026-05-07T15:46:54.845Z" }, +] + +[[package]] +name = "langchain-community" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "httpx-sse" }, + { name = "langchain-classic" }, + { name = "langchain-core" }, + { name = "langsmith" }, + { name = "numpy" }, + { name = "pydantic-settings" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "sqlalchemy" }, + { name = "tenacity" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ea/0c/e3aca1f2b1c5b95f8b87cb2b6e81a6f20d538c07a128419dc01cef0617b6/langchain_community-0.4.2.tar.gz", hash = "sha256:a99308160d53d7e9b5965ee665e5173709914338210089fd5788ad724432c21e", size = 33268708, upload-time = "2026-05-22T19:42:59.374Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/39/5d97e42a3e95dc2a6d71b2f902a3fae71786131e11d01bddb604accb0ebe/langchain_community-0.4.2-py3-none-any.whl", hash = "sha256:84dd8c5122532394d5b6849a5fc9995ef28e4f77227daeb09f24b3d942e9e466", size = 2364406, upload-time = "2026-05-22T19:42:57.103Z" }, +] + +[[package]] +name = "langchain-core" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonpatch" }, + { name = "langchain-protocol" }, + { name = "langsmith" }, + { name = "packaging" }, + { name = "pydantic" }, + { name = "pyyaml" }, + { name = "tenacity" }, + { name = "typing-extensions" }, + { name = "uuid-utils" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/59/de/679a53472c25860837e32c0442c962fa86e95317a36460e2c9d5c91b17c2/langchain_core-1.4.0.tar.gz", hash = "sha256:1dc341eed802ed9c117c0df3923c991e5e9e226571e5725c194eeb5bd93d1a7f", size = 920260, upload-time = "2026-05-11T18:42:35.919Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/1a/86c38c27b81913a1c6c12448cab55defb5a1097c7dc9a4cea83f55477a2d/langchain_core-1.4.0-py3-none-any.whl", hash = "sha256:23cbbdb46e38ddd1dd5247e6167e96013eae74bea4c5949c550809970a9e565c", size = 548120, upload-time = "2026-05-11T18:42:33.992Z" }, +] + +[[package]] +name = "langchain-openai" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "openai" }, + { name = "tiktoken" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f7/1b/c506c7f41156d3a6b4582b4c487f480001b8741deecc6e2d4931fdf4cf2c/langchain_openai-1.2.2.tar.gz", hash = "sha256:8698ffcee9a086e91ab6d207f0026181a03effcbf86bf9aee1808ee35af69dcc", size = 1147539, upload-time = "2026-05-21T22:08:31.123Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/8e/7406c99afacafc8c2ce0fa4152f9f8b9598c93ceb291959821abd053b982/langchain_openai-1.2.2-py3-none-any.whl", hash = "sha256:7da39a3c70cbafa93853456199e39a264dc70651be79b12ac49b4f6a448bce2d", size = 99631, upload-time = "2026-05-21T22:08:29.527Z" }, +] + +[[package]] +name = "langchain-protocol" +version = "0.0.16" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/36/e7/8300ba22d968653051fd06e3117d783872dddf3dcebdd6b1d386836eb43c/langchain_protocol-0.0.16.tar.gz", hash = "sha256:806c7cdd951b1c4f692fa40fce60821ff0f221d4360e27673ddf2c2b99c2b7ff", size = 5969, upload-time = "2026-05-28T23:05:11.121Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/9c/06dfcc88d02a6364e8d864c421ddd3736305cb0a6c853f75c302c80fe17c/langchain_protocol-0.0.16-py3-none-any.whl", hash = "sha256:3658c142c5d0fb3a023a4be442ce4c15c6d626aab6135eb79a76dc64ad19c3c3", size = 7037, upload-time = "2026-05-28T23:05:10.163Z" }, +] + +[[package]] +name = "langchain-text-splitters" +version = "1.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/26/9f/6c545900fefb7b00ddfa3f16b80d61338a0ec68c31c5451eeeab99082760/langchain_text_splitters-1.1.2.tar.gz", hash = "sha256:782a723db0a4746ac91e251c7c1d57fd23636e4f38ed733074e28d7a86f41627", size = 293580, upload-time = "2026-04-16T14:20:39.162Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/26/1ef06f56198d631296d646a6223de35bcc6cf9795ceb2442816bc963b84c/langchain_text_splitters-1.1.2-py3-none-any.whl", hash = "sha256:a2de0d799ff31886429fd6e2e0032df275b60ec817c19059a7b46181cc1c2f10", size = 35903, upload-time = "2026-04-16T14:20:38.243Z" }, +] + +[[package]] +name = "langgraph" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "langgraph-checkpoint" }, + { name = "langgraph-prebuilt" }, + { name = "langgraph-sdk" }, + { name = "pydantic" }, + { name = "xxhash" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e6/5a/ffc12434ee8aecab830d58b4d204ddea45073eae7639c963310f671a5bf5/langgraph-1.2.2.tar.gz", hash = "sha256:f54a98458976b3ff0774683867df125fb52d8dbedeb2441d0b0656a51331cee5", size = 695730, upload-time = "2026-05-26T18:07:28.49Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/9b/b08d578bba73e25351152dfd3d6d21e81210a5fff1b6f26e56f33197c8f5/langgraph-1.2.2-py3-none-any.whl", hash = "sha256:0a851bf4ba5939c5474a2fd57e6b439b5315283e254e42943bd392c2d71a5e03", size = 236376, upload-time = "2026-05-26T18:07:26.577Z" }, +] + +[[package]] +name = "langgraph-checkpoint" +version = "4.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "ormsgpack" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/83/47/886af6f886f0bff2273164a45f008694e48a96ff3cd25ff0228f2aa9480e/langgraph_checkpoint-4.1.1.tar.gz", hash = "sha256:6c2bdb530c91f91d7d9c1bd100925d0fc4f498d418c17f3587d1526279482a25", size = 184020, upload-time = "2026-05-22T16:57:38.503Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/b4/71425e3e38be92611300b9cc5e46a5bf98ab23f5ea8a75b73d02a2f1413c/langgraph_checkpoint-4.1.1-py3-none-any.whl", hash = "sha256:25d29144b082827218e7bc3f1e9b0566a4bb007895cd6cc26f66a8428739f56e", size = 56212, upload-time = "2026-05-22T16:57:37.203Z" }, +] + +[[package]] +name = "langgraph-prebuilt" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "langgraph-checkpoint" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/29/66/ed9b93f56bc17ef22d551892f0ac2b225a97fe0fcf23a511b857f70d590b/langgraph_prebuilt-1.1.0.tar.gz", hash = "sha256:3c579cf6eed2d17f9c157c2d0fcaddcd8688524e7022d3b22b37a3bf4589d528", size = 178833, upload-time = "2026-05-12T03:37:49.332Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/43/3fe1a700b8490ed02679cdbbc8c915eb23a092faf496c9c1118abcd10be3/langgraph_prebuilt-1.1.0-py3-none-any.whl", hash = "sha256:51e311747d755b751d5c6b39b0c1446124d3a7643d2515017e6714b323508fc9", size = 41043, upload-time = "2026-05-12T03:37:48.007Z" }, +] + +[[package]] +name = "langgraph-sdk" +version = "0.3.15" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "orjson" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/af/cdd4d6f3c05b3c1112ed3f12ef830faf15951b21d22cbc622a4becbbe25c/langgraph_sdk-0.3.15.tar.gz", hash = "sha256:29e805003d2c6e296823dd71992610976fd0428cefaa8b3304fd91f2247037de", size = 201924, upload-time = "2026-05-22T16:54:27.678Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/a5/0196d9c05749c25bc198e4909d68c998bc3120297e14944921baf2f4c384/langgraph_sdk-0.3.15-py3-none-any.whl", hash = "sha256:3838773acf7456d158165385d49f48f1e856f28b56ccd99ea139a8f27004815d", size = 98166, upload-time = "2026-05-22T16:54:26.013Z" }, +] + +[[package]] +name = "langsmith" +version = "0.8.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "orjson", marker = "platform_python_implementation != 'PyPy'" }, + { name = "packaging" }, + { name = "pydantic" }, + { name = "requests" }, + { name = "requests-toolbelt" }, + { name = "uuid-utils" }, + { name = "websockets" }, + { name = "xxhash" }, + { name = "zstandard" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/dd/f4c8a12987318e505b10760d30c3c2d45e8dc87ba8f47a004c753a9e7b35/langsmith-0.8.9.tar.gz", hash = "sha256:f16e37fcd5a8a2d4db30eae0e399a866a65ce5cc86218825c59409ed57a3bf53", size = 4428684, upload-time = "2026-06-03T17:56:09.448Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/2f/a701663c9fb4d9630448622a684bc372b4905b9a6dbe2297d55a70fde04e/langsmith-0.8.9-py3-none-any.whl", hash = "sha256:c9519cabc75568d088df045710d1b86eae9780c91054528b2aa7e6cb1fc80c52", size = 403165, upload-time = "2026-06-03T17:56:07.226Z" }, +] + +[[package]] +name = "librt" +version = "0.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/40/08/9e7f6b5d2b5bed6ad055cdd5925f192bb403a51280f86b56554d9d0699a2/librt-0.11.0.tar.gz", hash = "sha256:075dc3ef4458a278e0195cbf6ac9d38808d9b906c5a6c7f7f79c3888276a3fb1", size = 200139, upload-time = "2026-05-10T18:17:25.138Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/d0/07c77e067f0838949b43bd89232c29d72efebb9d2801a9750184eb706b71/librt-0.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b87504f1690a23b9a2cca841191a04f83895d4fc2dd04df91d82b1a04ca2ad46", size = 144147, upload-time = "2026-05-10T18:15:53.227Z" }, + { url = "https://files.pythonhosted.org/packages/7a/24/8493538fa4f62f982686398a5b8f68008138a75086abdea19ade64bf4255/librt-0.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40071fc5fe0ce8daa6de616702314a01e1250711682b0523d6ab8d4525910cb3", size = 143614, upload-time = "2026-05-10T18:15:54.657Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1e/f8bad050810d9171f34a1648ed910e56814c2ba61639f2bd53c6377ae24b/librt-0.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:137e79445c896a0ea7b265f52d23954e05b64222ee1af69e2cb34219067cbb67", size = 485538, upload-time = "2026-05-10T18:15:56.117Z" }, + { url = "https://files.pythonhosted.org/packages/c0/fe/3594ebfbaf03084ba4b120c9ba5c3183fd938a48725e9bbe6ff0a5159ad8/librt-0.11.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:cca6644054e78746d8d4ef238681f9c34ff8b584fe6b988ecebb8db3b15e622a", size = 479623, upload-time = "2026-05-10T18:15:57.544Z" }, + { url = "https://files.pythonhosted.org/packages/b0/da/5d1876984b3746c85dbd219dbfcb73c85f54ee263fd32e5b2a632ec14571/librt-0.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5b0eea49f5562861ee8d757a32ef7d559c1d35be2aaaa1ec28941d74c9ffc8a", size = 513082, upload-time = "2026-05-10T18:15:58.805Z" }, + { url = "https://files.pythonhosted.org/packages/19/6e/55bdf5d5ca00c3e18430690bf2c953d8d3ffd3c337418173d33dec985dc9/librt-0.11.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0d1029d7e1ae1a7e647ed6fb5df8c4ce2dffefb7a9f5fd1376a4554d96dac09f", size = 508105, upload-time = "2026-05-10T18:16:00.2Z" }, + { url = "https://files.pythonhosted.org/packages/07/10/f1f23a7c595ee90ece4d35c851e5d104b1311a887ed1b4ac4c35bbd13da8/librt-0.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bc3ce6b33c5828d9e80592011a5c584cb2ce86edbc4088405f70da47dc1d1b3b", size = 522268, upload-time = "2026-05-10T18:16:01.708Z" }, + { url = "https://files.pythonhosted.org/packages/b6/02/5720f5697a7f54b78b3aefbe20df3a48cedcff1276618c4aa481177942ed/librt-0.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:936c5995f3514a42111f20099397d8177c79b4d7e70961e396c6f5a0a3566766", size = 527348, upload-time = "2026-05-10T18:16:03.496Z" }, + { url = "https://files.pythonhosted.org/packages/50/db/b4a47c6f91db4ff76348a0b3dd0cc65e090a078b765a810a62ff9434c3d3/librt-0.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9bc0ca6ad9381cbe8e4aa6e5726e4c80c78115a6e9723c599ed1d73e092bc49d", size = 516294, upload-time = "2026-05-10T18:16:05.173Z" }, + { url = "https://files.pythonhosted.org/packages/9e/58/9384b2f4eb1ed1d273d40948a7c5c4b2360213b402ef3be4641c06299f9c/librt-0.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:070aa8c26c0a74774317a72df8851facc7f0f012a5b406557ac56992d92e1ec8", size = 553608, upload-time = "2026-05-10T18:16:06.839Z" }, + { url = "https://files.pythonhosted.org/packages/21/7b/5aa8848a7c6a9278c79375146da1812e695754ceec5f005e6043461a7315/librt-0.11.0-cp312-cp312-win32.whl", hash = "sha256:6bf14feb84b05ae945277395451998c89c54d0def4070eb5c08de544930b245a", size = 101879, upload-time = "2026-05-10T18:16:08.103Z" }, + { url = "https://files.pythonhosted.org/packages/37/33/8a745436944947575b584231750a41417de1a38cf6a2e9251d1065651c09/librt-0.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:75672f0bc524ede266287d532d7923dbce94c7514ad07627bac3d0c6d92cc4d9", size = 119831, upload-time = "2026-05-10T18:16:09.174Z" }, + { url = "https://files.pythonhosted.org/packages/59/67/a6739ac96e28b7855808bdb0370e250606104a859750d209e5a0716fe7ab/librt-0.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:2f10cf143e4a9bb0f4f5af568a00df94a2d69ef41c2579584454bb0fe5cc642c", size = 103470, upload-time = "2026-05-10T18:16:10.369Z" }, + { url = "https://files.pythonhosted.org/packages/82/61/e59168d4d0bf2bf90f4f0caf7a001bfc60254c3af4586013b04dc3ef517b/librt-0.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:78dc31f7fdfe9c9d0eb0e8f42d139db230e826415bbcabd9f0e9faaaee909894", size = 144119, upload-time = "2026-05-10T18:16:11.771Z" }, + { url = "https://files.pythonhosted.org/packages/61/fd/caa1d60b12f7dd79ccea23054e06eeaebe266a5f52c40a6b651069200ce5/librt-0.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fa475675db22290c3158e1d42326d0f5a65f04f44a0e68c3630a25b53560fb9c", size = 143565, upload-time = "2026-05-10T18:16:13.334Z" }, + { url = "https://files.pythonhosted.org/packages/b8/a9/dc744f5c2b4978d48db970be29f22716d3413d28b14ad99740817315cf2c/librt-0.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:621db29691044bdeda22e789e482e1b0f3a985d90e3426c9c6d17606416205ea", size = 485395, upload-time = "2026-05-10T18:16:14.729Z" }, + { url = "https://files.pythonhosted.org/packages/8f/21/7f8e97a1e4dae952a5a95948f6f8507a173bc1e669f54340bba6ca1ca31b/librt-0.11.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:a9010e2ed5b3a9e158c5fd966b3ab7e834bb3d3aacc8f66c91dd4b57a3799230", size = 479383, upload-time = "2026-05-10T18:16:16.321Z" }, + { url = "https://files.pythonhosted.org/packages/a6/6d/d8ee9c114bebf2c50e29ec2aa940826fccb62a645c3e4c18760987d0e16d/librt-0.11.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c39513d8b7477a2e1ed8c43fc21c524e8d5a0f8d4e8b7b074dbdbe7820a08e2", size = 513010, upload-time = "2026-05-10T18:16:17.647Z" }, + { url = "https://files.pythonhosted.org/packages/f0/43/0b5708af2bd30a46400e72ba6bdaa8f066f15fb9a688527e34220e8d6c06/librt-0.11.0-cp313-cp313-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7aef3cf1d5af86e770ab04bfd993dfc4ae8b8c17f66fb77dd4a7d50de7bbb1a3", size = 508433, upload-time = "2026-05-10T18:16:19.309Z" }, + { url = "https://files.pythonhosted.org/packages/4a/50/356187247d09013490481033183b3532b58acf8028bcb34b2b56a375c9b2/librt-0.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:557183ddc36babe46b27dd60facbd5adb4492181a5be887587d57cda6e092f21", size = 522595, upload-time = "2026-05-10T18:16:20.642Z" }, + { url = "https://files.pythonhosted.org/packages/40/e7/c6ac4240899c7f3248079d5a9900debe0dadb3fdeaf856684c987105ba47/librt-0.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:83d3e1f72bd42f6c5c0b7daec530c3f829bd02db42c70b8ddf0c2d90a2459930", size = 527255, upload-time = "2026-05-10T18:16:22.352Z" }, + { url = "https://files.pythonhosted.org/packages/eb/b5/a81322dbeedeeaf9c1ee6f001734d28a09d8383ac9e6779bc24bbd0743c6/librt-0.11.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:4ce1f21fbe589bc1afd7872dece84fb0e1144f794a288e58a10d2c54a55c43be", size = 516847, upload-time = "2026-05-10T18:16:23.627Z" }, + { url = "https://files.pythonhosted.org/packages/ae/66/6e6323787d592b55204a42595ff1102da5115601b53a7e9ddebc889a6da5/librt-0.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b09f7044ea2b64c9da42fd3d335666518cfd1c6e8a182c95da73d0214b41e", size = 553920, upload-time = "2026-05-10T18:16:25.025Z" }, + { url = "https://files.pythonhosted.org/packages/9c/21/623f8ca230857102066d9ca8c6c1734995908c4d0d1bee7bb2ef0021cb33/librt-0.11.0-cp313-cp313-win32.whl", hash = "sha256:78fddc31cd4d3caa897ad5d31f856b1faadc9474021ad6cb182b9018793e254e", size = 101898, upload-time = "2026-05-10T18:16:26.649Z" }, + { url = "https://files.pythonhosted.org/packages/b3/1d/b4ebd44dd723f768469007515cb92251e0ae286c94c140f374801140fa74/librt-0.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ca8aa88751a775870b764e93bad5135385f563cb8dcee399abf034ea4d3cb47", size = 119812, upload-time = "2026-05-10T18:16:27.859Z" }, + { url = "https://files.pythonhosted.org/packages/3b/e4/b2f4ca7965ca373b491cdb4bc25cdb30c1649ca81a8782056a83850292a9/librt-0.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:96f044bb325fd9cf1a723015638c219e9143f0dfbc0ca54c565df2b7fc748b44", size = 103448, upload-time = "2026-05-10T18:16:29.066Z" }, + { url = "https://files.pythonhosted.org/packages/29/eb/dbce197da4e227779e56b5735f2decc3eb36e55a1cdbf1bd65d6639d76c1/librt-0.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4a017a95e5837dc15a8c5661d60e05daa96b90908b1aa6b7acdf443cd25c8ebd", size = 143345, upload-time = "2026-05-10T18:16:30.674Z" }, + { url = "https://files.pythonhosted.org/packages/76/a3/254bebd0c11c8ba684018efb8006ff22e466abce445215cca6c778e7d9de/librt-0.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b1ecbd9819deccc39b7542bf4d2a740d8a620694d39989e58661d3763458f8d4", size = 143131, upload-time = "2026-05-10T18:16:32.037Z" }, + { url = "https://files.pythonhosted.org/packages/f1/3f/f77d6122d21ac7bf6ae8a7dfced1bd2a7ac545d3273ebdcaf8042f6d619f/librt-0.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7da327dacd7be8f8ec36547373550744a3cc0e536d54665cd83f8bcd961200e8", size = 477024, upload-time = "2026-05-10T18:16:33.493Z" }, + { url = "https://files.pythonhosted.org/packages/ac/0a/2c996dadebaa7d9bbbd43ef2d4f3e66b6da545f838a41694ef6172cebec8/librt-0.11.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:0dc56b1f8d06e60db362cc3fdae206681817f86ce4725d34511473487f12a34b", size = 474221, upload-time = "2026-05-10T18:16:34.864Z" }, + { url = "https://files.pythonhosted.org/packages/0a/7e/f5d92af8486b8272c23b3e686b46ff72d89c8169585eb61eef01a2ac7147/librt-0.11.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05fb8fb2ab90e21c8d12ea240d744ad514da9baf381ebfa70d91d20d21713175", size = 505174, upload-time = "2026-05-10T18:16:36.705Z" }, + { url = "https://files.pythonhosted.org/packages/af/1a/cb0734fe86398eb33193ab753b7326255c74cac5eb09e76b9b16536e7adb/librt-0.11.0-cp314-cp314-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cae74872be221df4374d10fec61f93ed1513b9546ea84f2c0bf73ab3e9bd0b03", size = 497216, upload-time = "2026-05-10T18:16:38.418Z" }, + { url = "https://files.pythonhosted.org/packages/18/06/094820f91558b66e29943c0ec41c9914f460f48dd51fc503c3101e10842d/librt-0.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:32bcc918c0148eb7e3d57385125bac7e5f9e4359d05f07448b09f6f778c2f31c", size = 513921, upload-time = "2026-05-10T18:16:39.848Z" }, + { url = "https://files.pythonhosted.org/packages/0b/c2/00de9018871a282f530cacb457d5ec0428f6ac7e6fedde9aff7468d9fb04/librt-0.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:f9743fc99135d5f78d2454435615f6dec0473ca507c26ce9d92b10b562a280d3", size = 520850, upload-time = "2026-05-10T18:16:41.471Z" }, + { url = "https://files.pythonhosted.org/packages/51/9d/64631832348fd1834fb3a61b996434edddaaf25a31d03b0a76273159d2cf/librt-0.11.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5ba067f4aadae8fda802d91d2124c90c42195ff32d9161d3549e6d05cfe26f96", size = 504237, upload-time = "2026-05-10T18:16:43.15Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ec/ae5525eb16edc827a044e7bb8777a455ff95d4bca9379e7e6bddd7383647/librt-0.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:de3bf945454d032f9e390b85c4072e0a0570bf825421c8be0e71209fa65e1abe", size = 546261, upload-time = "2026-05-10T18:16:44.408Z" }, + { url = "https://files.pythonhosted.org/packages/5a/09/adce371f27ca039411da9659f7430fcc2ba6cd0c7b3e4467a0f091be7fa9/librt-0.11.0-cp314-cp314-win32.whl", hash = "sha256:d2277a05f6dcb9fd13db9566aac4fabd68c3ea1ea46ee5567d4eef8efa495a2f", size = 96965, upload-time = "2026-05-10T18:16:46.039Z" }, + { url = "https://files.pythonhosted.org/packages/d6/ee/8ac720d98548f173c7ce2e632a7ca94673f74cacd5c8162a84af5b35958a/librt-0.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:ab73e8db5e3f564d812c1f5c3a175930a5f9bc96ccb5e3b22a34d7858b401cf7", size = 115151, upload-time = "2026-05-10T18:16:47.133Z" }, + { url = "https://files.pythonhosted.org/packages/94/20/c900cf14efeb09b6bef2b2dff20779f73464b97fd58d1c6bccc379588ae3/librt-0.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:aea3caa317752e3a466fa8af45d91ee0ea8c7fdd96e42b0a8dd9b76a7931eba1", size = 98850, upload-time = "2026-05-10T18:16:48.597Z" }, + { url = "https://files.pythonhosted.org/packages/0c/71/944bfe4b64e12abffcd3c15e1cce07f72f3d55655083786285f4dedeb532/librt-0.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d1b36540d7aaf9b9101b3a6f376c8d8e9f7a9aec93ed05918f2c69d493ffef72", size = 151138, upload-time = "2026-05-10T18:16:49.839Z" }, + { url = "https://files.pythonhosted.org/packages/b6/10/99e64a5c86989357fda078c8143c533389585f6473b7439172dd8f3b3b2d/librt-0.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:efbb343ab2ce3540f4ecbe6315d677ed70f37cd9a72b1e58066c918ca83acbaa", size = 151976, upload-time = "2026-05-10T18:16:51.062Z" }, + { url = "https://files.pythonhosted.org/packages/21/31/5072ad880946d83e5ea4147d6d018c78eefce85b77819b19bdd0ee229435/librt-0.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa0dd688aab3f7914d3e6e5e3554978e0383312fb8e771d84be008a35b9ee548", size = 557927, upload-time = "2026-05-10T18:16:52.632Z" }, + { url = "https://files.pythonhosted.org/packages/5e/8d/70b5fb7cfbab60edbe7381614ab985da58e144fbf465c86d44c95f43cdca/librt-0.11.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:f5fb36b8c6c63fdcbb1d526d94c0d1331610d43f4118cc1beb4efef4f3faacb2", size = 539698, upload-time = "2026-05-10T18:16:53.934Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a3/ba3495a0b3edbd24a4cae0d1d3c64f39a9fc45d06e812101289b50c1a619/librt-0.11.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4a9a237d13addb93715b6fee74023d5ee3469b53fce527626c0e088aa585805f", size = 577162, upload-time = "2026-05-10T18:16:55.589Z" }, + { url = "https://files.pythonhosted.org/packages/f7/db/36e25fb81f99937ff1b96612a1dc9fd66f039cb9cc3aee12c01fac31aab9/librt-0.11.0-cp314-cp314t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5ddd17bd87b2c56ddd60e546a7984a2e64c4e8eab92fb4cf3830a48ad5469d51", size = 566494, upload-time = "2026-05-10T18:16:56.975Z" }, + { url = "https://files.pythonhosted.org/packages/33/0d/3f622b47f0b013eeb9cf4cc07ae9bfe378d832a4eec998b2b209fe84244d/librt-0.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bd43992b4473d42f12ff9e68326079f0696d9d4e6000e8f39a0238d482ba6ee2", size = 596858, upload-time = "2026-05-10T18:16:58.374Z" }, + { url = "https://files.pythonhosted.org/packages/a9/02/71b90bc93039c46a2000651f6ad60122b114c8f54c4ad306e0e96f5b75ad/librt-0.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:f8e3e8056dd674e279741485e2e512d6e9a751c7455809d0114e6ebf8d781085", size = 590318, upload-time = "2026-05-10T18:16:59.676Z" }, + { url = "https://files.pythonhosted.org/packages/04/04/418cb3f75621e2b761fb1ab0f017f4d70a1a72a6e7c74ee4f7e8d198c2f3/librt-0.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:c1f708d8ae9c56cf38a903c44297243d2ec83fd82b396b977e0144a3e76217e3", size = 575115, upload-time = "2026-05-10T18:17:01.007Z" }, + { url = "https://files.pythonhosted.org/packages/cc/2c/5a2183ac58dd911f26b5d7e7d7d8f1d87fcecdddd99d6c12169a258ff62c/librt-0.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0add982e0e7b9fc14cf4b33789d5f13f66581889b88c2f58099f6ce8f92617bd", size = 617918, upload-time = "2026-05-10T18:17:02.682Z" }, + { url = "https://files.pythonhosted.org/packages/15/1f/dc6771a52592a4451be6effa200cbfc9cec61e4393d3033d81a9d307961d/librt-0.11.0-cp314-cp314t-win32.whl", hash = "sha256:2b481d846ac894c4e8403c5fd0e87c5d11d6499e404b474602508a224ff531c8", size = 103562, upload-time = "2026-05-10T18:17:03.99Z" }, + { url = "https://files.pythonhosted.org/packages/62/4a/7d1415567027286a75ba1093ec4aca11f073e0f559c530cf3e0a757ad55c/librt-0.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:28edb433edde181112a908c78907af28f964eabc15f4dd16c9d66c834302677c", size = 124327, upload-time = "2026-05-10T18:17:05.465Z" }, + { url = "https://files.pythonhosted.org/packages/ce/62/b40b382fa0c66fee1478073eb8db352a4a6beda4a1adccf1df911d8c289c/librt-0.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dee008f20b542e3cd162ba338a7f9ec0f6d23d395f66fe8aeeec3c9d067ea253", size = 102572, upload-time = "2026-05-10T18:17:06.809Z" }, +] + +[[package]] +name = "litellm" +version = "1.87.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "click" }, + { name = "fastuuid" }, + { name = "httpx" }, + { name = "importlib-metadata" }, + { name = "jinja2" }, + { name = "jsonschema" }, + { name = "openai" }, + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "tiktoken" }, + { name = "tokenizers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/77/0d/ccdf682ccfd7f18bf0e179c39d85616b8f8ef05a798588285310412db13d/litellm-1.87.0.tar.gz", hash = "sha256:cafc1882cb0cbab8374c41180af86e4a067796e4524e15f59e99f6e689cd1bd8", size = 15453755, upload-time = "2026-06-02T03:53:29.076Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/20/88a372fa7e50fc2c33458c6eef94a79afcf7bdfa43610079531b82b484a3/litellm-1.87.0-py3-none-any.whl", hash = "sha256:fbbba7e47ae29b55f878fe1acc80effb92761bc168f6236bd81a0cb6e147d855", size = 17103948, upload-time = "2026-06-02T03:53:25.677Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + +[[package]] +name = "mcp" +version = "1.27.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "httpx" }, + { name = "httpx-sse" }, + { name = "jsonschema" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "pyjwt", extra = ["crypto"] }, + { name = "python-multipart" }, + { name = "pywin32", marker = "sys_platform == 'win32'" }, + { name = "sse-starlette" }, + { name = "starlette" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, + { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/27/3c/347cf965d313f5d41764e7d46bea6ffe7d9ef13b983cc429b0340962a082/mcp-1.27.2.tar.gz", hash = "sha256:8e02db104096d1c25b28e64bde29a5c32b31bc241710213e12fd4d84985bdfef", size = 621116, upload-time = "2026-05-29T17:16:04.039Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/11/252c6f971dc4f16af1d98a1c469d8ba523aab00d1bb76b4d3bc1ff32eacc/mcp-1.27.2-py3-none-any.whl", hash = "sha256:d6ff5160c6ca65d93013626efb3fc249de683c30b2d8570755ceddd490344de5", size = 220498, upload-time = "2026-05-29T17:16:02.442Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "multidict" +version = "6.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1a/c2/c2d94cbe6ac1753f3fc980da97b3d930efe1da3af3c9f5125354436c073d/multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d", size = 102010, upload-time = "2026-01-26T02:46:45.979Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/9c/f20e0e2cf80e4b2e4b1c365bf5fe104ee633c751a724246262db8f1a0b13/multidict-6.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a90f75c956e32891a4eda3639ce6dd86e87105271f43d43442a3aedf3cddf172", size = 76893, upload-time = "2026-01-26T02:43:52.754Z" }, + { url = "https://files.pythonhosted.org/packages/fe/cf/18ef143a81610136d3da8193da9d80bfe1cb548a1e2d1c775f26b23d024a/multidict-6.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fccb473e87eaa1382689053e4a4618e7ba7b9b9b8d6adf2027ee474597128cd", size = 45456, upload-time = "2026-01-26T02:43:53.893Z" }, + { url = "https://files.pythonhosted.org/packages/a9/65/1caac9d4cd32e8433908683446eebc953e82d22b03d10d41a5f0fefe991b/multidict-6.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0fa96985700739c4c7853a43c0b3e169360d6855780021bfc6d0f1ce7c123e7", size = 43872, upload-time = "2026-01-26T02:43:55.041Z" }, + { url = "https://files.pythonhosted.org/packages/cf/3b/d6bd75dc4f3ff7c73766e04e705b00ed6dbbaccf670d9e05a12b006f5a21/multidict-6.7.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb2a55f408c3043e42b40cc8eecd575afa27b7e0b956dfb190de0f8499a57a53", size = 251018, upload-time = "2026-01-26T02:43:56.198Z" }, + { url = "https://files.pythonhosted.org/packages/fd/80/c959c5933adedb9ac15152e4067c702a808ea183a8b64cf8f31af8ad3155/multidict-6.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb0ce7b2a32d09892b3dd6cc44877a0d02a33241fafca5f25c8b6b62374f8b75", size = 258883, upload-time = "2026-01-26T02:43:57.499Z" }, + { url = "https://files.pythonhosted.org/packages/86/85/7ed40adafea3d4f1c8b916e3b5cc3a8e07dfcdcb9cd72800f4ed3ca1b387/multidict-6.7.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c3a32d23520ee37bf327d1e1a656fec76a2edd5c038bf43eddfa0572ec49c60b", size = 242413, upload-time = "2026-01-26T02:43:58.755Z" }, + { url = "https://files.pythonhosted.org/packages/d2/57/b8565ff533e48595503c785f8361ff9a4fde4d67de25c207cd0ba3befd03/multidict-6.7.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9c90fed18bffc0189ba814749fdcc102b536e83a9f738a9003e569acd540a733", size = 268404, upload-time = "2026-01-26T02:44:00.216Z" }, + { url = "https://files.pythonhosted.org/packages/e0/50/9810c5c29350f7258180dfdcb2e52783a0632862eb334c4896ac717cebcb/multidict-6.7.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:da62917e6076f512daccfbbde27f46fed1c98fee202f0559adec8ee0de67f71a", size = 269456, upload-time = "2026-01-26T02:44:02.202Z" }, + { url = "https://files.pythonhosted.org/packages/f3/8d/5e5be3ced1d12966fefb5c4ea3b2a5b480afcea36406559442c6e31d4a48/multidict-6.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfde23ef6ed9db7eaee6c37dcec08524cb43903c60b285b172b6c094711b3961", size = 256322, upload-time = "2026-01-26T02:44:03.56Z" }, + { url = "https://files.pythonhosted.org/packages/31/6e/d8a26d81ac166a5592782d208dd90dfdc0a7a218adaa52b45a672b46c122/multidict-6.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3758692429e4e32f1ba0df23219cd0b4fc0a52f476726fff9337d1a57676a582", size = 253955, upload-time = "2026-01-26T02:44:04.845Z" }, + { url = "https://files.pythonhosted.org/packages/59/4c/7c672c8aad41534ba619bcd4ade7a0dc87ed6b8b5c06149b85d3dd03f0cd/multidict-6.7.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:398c1478926eca669f2fd6a5856b6de9c0acf23a2cb59a14c0ba5844fa38077e", size = 251254, upload-time = "2026-01-26T02:44:06.133Z" }, + { url = "https://files.pythonhosted.org/packages/7b/bd/84c24de512cbafbdbc39439f74e967f19570ce7924e3007174a29c348916/multidict-6.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c102791b1c4f3ab36ce4101154549105a53dc828f016356b3e3bcae2e3a039d3", size = 252059, upload-time = "2026-01-26T02:44:07.518Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ba/f5449385510825b73d01c2d4087bf6d2fccc20a2d42ac34df93191d3dd03/multidict-6.7.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a088b62bd733e2ad12c50dad01b7d0166c30287c166e137433d3b410add807a6", size = 263588, upload-time = "2026-01-26T02:44:09.382Z" }, + { url = "https://files.pythonhosted.org/packages/d7/11/afc7c677f68f75c84a69fe37184f0f82fce13ce4b92f49f3db280b7e92b3/multidict-6.7.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3d51ff4785d58d3f6c91bdbffcb5e1f7ddfda557727043aa20d20ec4f65e324a", size = 259642, upload-time = "2026-01-26T02:44:10.73Z" }, + { url = "https://files.pythonhosted.org/packages/2b/17/ebb9644da78c4ab36403739e0e6e0e30ebb135b9caf3440825001a0bddcb/multidict-6.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc5907494fccf3e7d3f94f95c91d6336b092b5fc83811720fae5e2765890dfba", size = 251377, upload-time = "2026-01-26T02:44:12.042Z" }, + { url = "https://files.pythonhosted.org/packages/ca/a4/840f5b97339e27846c46307f2530a2805d9d537d8b8bd416af031cad7fa0/multidict-6.7.1-cp312-cp312-win32.whl", hash = "sha256:28ca5ce2fd9716631133d0e9a9b9a745ad7f60bac2bccafb56aa380fc0b6c511", size = 41887, upload-time = "2026-01-26T02:44:14.245Z" }, + { url = "https://files.pythonhosted.org/packages/80/31/0b2517913687895f5904325c2069d6a3b78f66cc641a86a2baf75a05dcbb/multidict-6.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcee94dfbd638784645b066074b338bc9cc155d4b4bffa4adce1615c5a426c19", size = 46053, upload-time = "2026-01-26T02:44:15.371Z" }, + { url = "https://files.pythonhosted.org/packages/0c/5b/aba28e4ee4006ae4c7df8d327d31025d760ffa992ea23812a601d226e682/multidict-6.7.1-cp312-cp312-win_arm64.whl", hash = "sha256:ba0a9fb644d0c1a2194cf7ffb043bd852cea63a57f66fbd33959f7dae18517bf", size = 43307, upload-time = "2026-01-26T02:44:16.852Z" }, + { url = "https://files.pythonhosted.org/packages/f2/22/929c141d6c0dba87d3e1d38fbdf1ba8baba86b7776469f2bc2d3227a1e67/multidict-6.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2b41f5fed0ed563624f1c17630cb9941cf2309d4df00e494b551b5f3e3d67a23", size = 76174, upload-time = "2026-01-26T02:44:18.509Z" }, + { url = "https://files.pythonhosted.org/packages/c7/75/bc704ae15fee974f8fccd871305e254754167dce5f9e42d88a2def741a1d/multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84e61e3af5463c19b67ced91f6c634effb89ef8bfc5ca0267f954451ed4bb6a2", size = 45116, upload-time = "2026-01-26T02:44:19.745Z" }, + { url = "https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:935434b9853c7c112eee7ac891bc4cb86455aa631269ae35442cb316790c1445", size = 43524, upload-time = "2026-01-26T02:44:21.571Z" }, + { url = "https://files.pythonhosted.org/packages/e9/3c/414842ef8d5a1628d68edee29ba0e5bcf235dbfb3ccd3ea303a7fe8c72ff/multidict-6.7.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:432feb25a1cb67fe82a9680b4d65fb542e4635cb3166cd9c01560651ad60f177", size = 249368, upload-time = "2026-01-26T02:44:22.803Z" }, + { url = "https://files.pythonhosted.org/packages/f6/32/befed7f74c458b4a525e60519fe8d87eef72bb1e99924fa2b0f9d97a221e/multidict-6.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e82d14e3c948952a1a85503817e038cba5905a3352de76b9a465075d072fba23", size = 256952, upload-time = "2026-01-26T02:44:24.306Z" }, + { url = "https://files.pythonhosted.org/packages/03/d6/c878a44ba877f366630c860fdf74bfb203c33778f12b6ac274936853c451/multidict-6.7.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4cfb48c6ea66c83bcaaf7e4dfa7ec1b6bbcf751b7db85a328902796dfde4c060", size = 240317, upload-time = "2026-01-26T02:44:25.772Z" }, + { url = "https://files.pythonhosted.org/packages/68/49/57421b4d7ad2e9e60e25922b08ceb37e077b90444bde6ead629095327a6f/multidict-6.7.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d540e51b7e8e170174555edecddbd5538105443754539193e3e1061864d444d", size = 267132, upload-time = "2026-01-26T02:44:27.648Z" }, + { url = "https://files.pythonhosted.org/packages/b7/fe/ec0edd52ddbcea2a2e89e174f0206444a61440b40f39704e64dc807a70bd/multidict-6.7.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:273d23f4b40f3dce4d6c8a821c741a86dec62cded82e1175ba3d99be128147ed", size = 268140, upload-time = "2026-01-26T02:44:29.588Z" }, + { url = "https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d624335fd4fa1c08a53f8b4be7676ebde19cd092b3895c421045ca87895b429", size = 254277, upload-time = "2026-01-26T02:44:30.902Z" }, + { url = "https://files.pythonhosted.org/packages/6a/b2/5fb8c124d7561a4974c342bc8c778b471ebbeb3cc17df696f034a7e9afe7/multidict-6.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:12fad252f8b267cc75b66e8fc51b3079604e8d43a75428ffe193cd9e2195dfd6", size = 252291, upload-time = "2026-01-26T02:44:32.31Z" }, + { url = "https://files.pythonhosted.org/packages/5a/96/51d4e4e06bcce92577fcd488e22600bd38e4fd59c20cb49434d054903bd2/multidict-6.7.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:03ede2a6ffbe8ef936b92cb4529f27f42be7f56afcdab5ab739cd5f27fb1cbf9", size = 250156, upload-time = "2026-01-26T02:44:33.734Z" }, + { url = "https://files.pythonhosted.org/packages/db/6b/420e173eec5fba721a50e2a9f89eda89d9c98fded1124f8d5c675f7a0c0f/multidict-6.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:90efbcf47dbe33dcf643a1e400d67d59abeac5db07dc3f27d6bdeae497a2198c", size = 249742, upload-time = "2026-01-26T02:44:35.222Z" }, + { url = "https://files.pythonhosted.org/packages/44/a3/ec5b5bd98f306bc2aa297b8c6f11a46714a56b1e6ef5ebda50a4f5d7c5fb/multidict-6.7.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c4b9bfc148f5a91be9244d6264c53035c8a0dcd2f51f1c3c6e30e30ebaa1c84", size = 262221, upload-time = "2026-01-26T02:44:36.604Z" }, + { url = "https://files.pythonhosted.org/packages/cd/f7/e8c0d0da0cd1e28d10e624604e1a36bcc3353aaebdfdc3a43c72bc683a12/multidict-6.7.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:401c5a650f3add2472d1d288c26deebc540f99e2fb83e9525007a74cd2116f1d", size = 258664, upload-time = "2026-01-26T02:44:38.008Z" }, + { url = "https://files.pythonhosted.org/packages/52/da/151a44e8016dd33feed44f730bd856a66257c1ee7aed4f44b649fb7edeb3/multidict-6.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:97891f3b1b3ffbded884e2916cacf3c6fc87b66bb0dde46f7357404750559f33", size = 249490, upload-time = "2026-01-26T02:44:39.386Z" }, + { url = "https://files.pythonhosted.org/packages/87/af/a3b86bf9630b732897f6fc3f4c4714b90aa4361983ccbdcd6c0339b21b0c/multidict-6.7.1-cp313-cp313-win32.whl", hash = "sha256:e1c5988359516095535c4301af38d8a8838534158f649c05dd1050222321bcb3", size = 41695, upload-time = "2026-01-26T02:44:41.318Z" }, + { url = "https://files.pythonhosted.org/packages/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:960c83bf01a95b12b08fd54324a4eb1d5b52c88932b5cba5d6e712bb3ed12eb5", size = 45884, upload-time = "2026-01-26T02:44:42.488Z" }, + { url = "https://files.pythonhosted.org/packages/ca/61/42d3e5dbf661242a69c97ea363f2d7b46c567da8eadef8890022be6e2ab0/multidict-6.7.1-cp313-cp313-win_arm64.whl", hash = "sha256:563fe25c678aaba333d5399408f5ec3c383ca5b663e7f774dd179a520b8144df", size = 43122, upload-time = "2026-01-26T02:44:43.664Z" }, + { url = "https://files.pythonhosted.org/packages/6d/b3/e6b21c6c4f314bb956016b0b3ef2162590a529b84cb831c257519e7fde44/multidict-6.7.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c76c4bec1538375dad9d452d246ca5368ad6e1c9039dadcf007ae59c70619ea1", size = 83175, upload-time = "2026-01-26T02:44:44.894Z" }, + { url = "https://files.pythonhosted.org/packages/fb/76/23ecd2abfe0957b234f6c960f4ade497f55f2c16aeb684d4ecdbf1c95791/multidict-6.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:57b46b24b5d5ebcc978da4ec23a819a9402b4228b8a90d9c656422b4bdd8a963", size = 48460, upload-time = "2026-01-26T02:44:46.106Z" }, + { url = "https://files.pythonhosted.org/packages/c4/57/a0ed92b23f3a042c36bc4227b72b97eca803f5f1801c1ab77c8a212d455e/multidict-6.7.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e954b24433c768ce78ab7929e84ccf3422e46deb45a4dc9f93438f8217fa2d34", size = 46930, upload-time = "2026-01-26T02:44:47.278Z" }, + { url = "https://files.pythonhosted.org/packages/b5/66/02ec7ace29162e447f6382c495dc95826bf931d3818799bbef11e8f7df1a/multidict-6.7.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3bd231490fa7217cc832528e1cd8752a96f0125ddd2b5749390f7c3ec8721b65", size = 242582, upload-time = "2026-01-26T02:44:48.604Z" }, + { url = "https://files.pythonhosted.org/packages/58/18/64f5a795e7677670e872673aca234162514696274597b3708b2c0d276cce/multidict-6.7.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:253282d70d67885a15c8a7716f3a73edf2d635793ceda8173b9ecc21f2fb8292", size = 250031, upload-time = "2026-01-26T02:44:50.544Z" }, + { url = "https://files.pythonhosted.org/packages/c8/ed/e192291dbbe51a8290c5686f482084d31bcd9d09af24f63358c3d42fd284/multidict-6.7.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b4c48648d7649c9335cf1927a8b87fa692de3dcb15faa676c6a6f1f1aabda43", size = 228596, upload-time = "2026-01-26T02:44:51.951Z" }, + { url = "https://files.pythonhosted.org/packages/1e/7e/3562a15a60cf747397e7f2180b0a11dc0c38d9175a650e75fa1b4d325e15/multidict-6.7.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98bc624954ec4d2c7cb074b8eefc2b5d0ce7d482e410df446414355d158fe4ca", size = 257492, upload-time = "2026-01-26T02:44:53.902Z" }, + { url = "https://files.pythonhosted.org/packages/24/02/7d0f9eae92b5249bb50ac1595b295f10e263dd0078ebb55115c31e0eaccd/multidict-6.7.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1b99af4d9eec0b49927b4402bcbb58dea89d3e0db8806a4086117019939ad3dd", size = 255899, upload-time = "2026-01-26T02:44:55.316Z" }, + { url = "https://files.pythonhosted.org/packages/00/e3/9b60ed9e23e64c73a5cde95269ef1330678e9c6e34dd4eb6b431b85b5a10/multidict-6.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6aac4f16b472d5b7dc6f66a0d49dd57b0e0902090be16594dc9ebfd3d17c47e7", size = 247970, upload-time = "2026-01-26T02:44:56.783Z" }, + { url = "https://files.pythonhosted.org/packages/3e/06/538e58a63ed5cfb0bd4517e346b91da32fde409d839720f664e9a4ae4f9d/multidict-6.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:21f830fe223215dffd51f538e78c172ed7c7f60c9b96a2bf05c4848ad49921c3", size = 245060, upload-time = "2026-01-26T02:44:58.195Z" }, + { url = "https://files.pythonhosted.org/packages/b2/2f/d743a3045a97c895d401e9bd29aaa09b94f5cbdf1bd561609e5a6c431c70/multidict-6.7.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f5dd81c45b05518b9aa4da4aa74e1c93d715efa234fd3e8a179df611cc85e5f4", size = 235888, upload-time = "2026-01-26T02:44:59.57Z" }, + { url = "https://files.pythonhosted.org/packages/38/83/5a325cac191ab28b63c52f14f1131f3b0a55ba3b9aa65a6d0bf2a9b921a0/multidict-6.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:eb304767bca2bb92fb9c5bd33cedc95baee5bb5f6c88e63706533a1c06ad08c8", size = 243554, upload-time = "2026-01-26T02:45:01.054Z" }, + { url = "https://files.pythonhosted.org/packages/20/1f/9d2327086bd15da2725ef6aae624208e2ef828ed99892b17f60c344e57ed/multidict-6.7.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c9035dde0f916702850ef66460bc4239d89d08df4d02023a5926e7446724212c", size = 252341, upload-time = "2026-01-26T02:45:02.484Z" }, + { url = "https://files.pythonhosted.org/packages/e8/2c/2a1aa0280cf579d0f6eed8ee5211c4f1730bd7e06c636ba2ee6aafda302e/multidict-6.7.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:af959b9beeb66c822380f222f0e0a1889331597e81f1ded7f374f3ecb0fd6c52", size = 246391, upload-time = "2026-01-26T02:45:03.862Z" }, + { url = "https://files.pythonhosted.org/packages/e5/03/7ca022ffc36c5a3f6e03b179a5ceb829be9da5783e6fe395f347c0794680/multidict-6.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:41f2952231456154ee479651491e94118229844dd7226541788be783be2b5108", size = 243422, upload-time = "2026-01-26T02:45:05.296Z" }, + { url = "https://files.pythonhosted.org/packages/dc/1d/b31650eab6c5778aceed46ba735bd97f7c7d2f54b319fa916c0f96e7805b/multidict-6.7.1-cp313-cp313t-win32.whl", hash = "sha256:df9f19c28adcb40b6aae30bbaa1478c389efd50c28d541d76760199fc1037c32", size = 47770, upload-time = "2026-01-26T02:45:06.754Z" }, + { url = "https://files.pythonhosted.org/packages/ac/5b/2d2d1d522e51285bd61b1e20df8f47ae1a9d80839db0b24ea783b3832832/multidict-6.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d54ecf9f301853f2c5e802da559604b3e95bb7a3b01a9c295c6ee591b9882de8", size = 53109, upload-time = "2026-01-26T02:45:08.044Z" }, + { url = "https://files.pythonhosted.org/packages/3d/a3/cc409ba012c83ca024a308516703cf339bdc4b696195644a7215a5164a24/multidict-6.7.1-cp313-cp313t-win_arm64.whl", hash = "sha256:5a37ca18e360377cfda1d62f5f382ff41f2b8c4ccb329ed974cc2e1643440118", size = 45573, upload-time = "2026-01-26T02:45:09.349Z" }, + { url = "https://files.pythonhosted.org/packages/91/cc/db74228a8be41884a567e88a62fd589a913708fcf180d029898c17a9a371/multidict-6.7.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8f333ec9c5eb1b7105e3b84b53141e66ca05a19a605368c55450b6ba208cb9ee", size = 75190, upload-time = "2026-01-26T02:45:10.651Z" }, + { url = "https://files.pythonhosted.org/packages/d5/22/492f2246bb5b534abd44804292e81eeaf835388901f0c574bac4eeec73c5/multidict-6.7.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a407f13c188f804c759fc6a9f88286a565c242a76b27626594c133b82883b5c2", size = 44486, upload-time = "2026-01-26T02:45:11.938Z" }, + { url = "https://files.pythonhosted.org/packages/f1/4f/733c48f270565d78b4544f2baddc2fb2a245e5a8640254b12c36ac7ac68e/multidict-6.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0e161ddf326db5577c3a4cc2d8648f81456e8a20d40415541587a71620d7a7d1", size = 43219, upload-time = "2026-01-26T02:45:14.346Z" }, + { url = "https://files.pythonhosted.org/packages/24/bb/2c0c2287963f4259c85e8bcbba9182ced8d7fca65c780c38e99e61629d11/multidict-6.7.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1e3a8bb24342a8201d178c3b4984c26ba81a577c80d4d525727427460a50c22d", size = 245132, upload-time = "2026-01-26T02:45:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/a7/f9/44d4b3064c65079d2467888794dea218d1601898ac50222ab8a9a8094460/multidict-6.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97231140a50f5d447d3164f994b86a0bed7cd016e2682f8650d6a9158e14fd31", size = 252420, upload-time = "2026-01-26T02:45:17.293Z" }, + { url = "https://files.pythonhosted.org/packages/8b/13/78f7275e73fa17b24c9a51b0bd9d73ba64bb32d0ed51b02a746eb876abe7/multidict-6.7.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b10359683bd8806a200fd2909e7c8ca3a7b24ec1d8132e483d58e791d881048", size = 233510, upload-time = "2026-01-26T02:45:19.356Z" }, + { url = "https://files.pythonhosted.org/packages/4b/25/8167187f62ae3cbd52da7893f58cb036b47ea3fb67138787c76800158982/multidict-6.7.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:283ddac99f7ac25a4acadbf004cb5ae34480bbeb063520f70ce397b281859362", size = 264094, upload-time = "2026-01-26T02:45:20.834Z" }, + { url = "https://files.pythonhosted.org/packages/a1/e7/69a3a83b7b030cf283fb06ce074a05a02322359783424d7edf0f15fe5022/multidict-6.7.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:538cec1e18c067d0e6103aa9a74f9e832904c957adc260e61cd9d8cf0c3b3d37", size = 260786, upload-time = "2026-01-26T02:45:22.818Z" }, + { url = "https://files.pythonhosted.org/packages/fe/3b/8ec5074bcfc450fe84273713b4b0a0dd47c0249358f5d82eb8104ffe2520/multidict-6.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eee46ccb30ff48a1e35bb818cc90846c6be2b68240e42a78599166722cea709", size = 248483, upload-time = "2026-01-26T02:45:24.368Z" }, + { url = "https://files.pythonhosted.org/packages/48/5a/d5a99e3acbca0e29c5d9cba8f92ceb15dce78bab963b308ae692981e3a5d/multidict-6.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa263a02f4f2dd2d11a7b1bb4362aa7cb1049f84a9235d31adf63f30143469a0", size = 248403, upload-time = "2026-01-26T02:45:25.982Z" }, + { url = "https://files.pythonhosted.org/packages/35/48/e58cd31f6c7d5102f2a4bf89f96b9cf7e00b6c6f3d04ecc44417c00a5a3c/multidict-6.7.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2e1425e2f99ec5bd36c15a01b690a1a2456209c5deed58f95469ffb46039ccbb", size = 240315, upload-time = "2026-01-26T02:45:27.487Z" }, + { url = "https://files.pythonhosted.org/packages/94/33/1cd210229559cb90b6786c30676bb0c58249ff42f942765f88793b41fdce/multidict-6.7.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:497394b3239fc6f0e13a78a3e1b61296e72bf1c5f94b4c4eb80b265c37a131cd", size = 245528, upload-time = "2026-01-26T02:45:28.991Z" }, + { url = "https://files.pythonhosted.org/packages/64/f2/6e1107d226278c876c783056b7db43d800bb64c6131cec9c8dfb6903698e/multidict-6.7.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:233b398c29d3f1b9676b4b6f75c518a06fcb2ea0b925119fb2c1bc35c05e1601", size = 258784, upload-time = "2026-01-26T02:45:30.503Z" }, + { url = "https://files.pythonhosted.org/packages/4d/c1/11f664f14d525e4a1b5327a82d4de61a1db604ab34c6603bb3c2cc63ad34/multidict-6.7.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:93b1818e4a6e0930454f0f2af7dfce69307ca03cdcfb3739bf4d91241967b6c1", size = 251980, upload-time = "2026-01-26T02:45:32.603Z" }, + { url = "https://files.pythonhosted.org/packages/e1/9f/75a9ac888121d0c5bbd4ecf4eead45668b1766f6baabfb3b7f66a410e231/multidict-6.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f33dc2a3abe9249ea5d8360f969ec7f4142e7ac45ee7014d8f8d5acddf178b7b", size = 243602, upload-time = "2026-01-26T02:45:34.043Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e7/50bf7b004cc8525d80dbbbedfdc7aed3e4c323810890be4413e589074032/multidict-6.7.1-cp314-cp314-win32.whl", hash = "sha256:3ab8b9d8b75aef9df299595d5388b14530839f6422333357af1339443cff777d", size = 40930, upload-time = "2026-01-26T02:45:36.278Z" }, + { url = "https://files.pythonhosted.org/packages/e0/bf/52f25716bbe93745595800f36fb17b73711f14da59ed0bb2eba141bc9f0f/multidict-6.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:5e01429a929600e7dab7b166062d9bb54a5eed752384c7384c968c2afab8f50f", size = 45074, upload-time = "2026-01-26T02:45:37.546Z" }, + { url = "https://files.pythonhosted.org/packages/97/ab/22803b03285fa3a525f48217963da3a65ae40f6a1b6f6cf2768879e208f9/multidict-6.7.1-cp314-cp314-win_arm64.whl", hash = "sha256:4885cb0e817aef5d00a2e8451d4665c1808378dc27c2705f1bf4ef8505c0d2e5", size = 42471, upload-time = "2026-01-26T02:45:38.889Z" }, + { url = "https://files.pythonhosted.org/packages/e0/6d/f9293baa6146ba9507e360ea0292b6422b016907c393e2f63fc40ab7b7b5/multidict-6.7.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0458c978acd8e6ea53c81eefaddbbee9c6c5e591f41b3f5e8e194780fe026581", size = 82401, upload-time = "2026-01-26T02:45:40.254Z" }, + { url = "https://files.pythonhosted.org/packages/7a/68/53b5494738d83558d87c3c71a486504d8373421c3e0dbb6d0db48ad42ee0/multidict-6.7.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c0abd12629b0af3cf590982c0b413b1e7395cd4ec026f30986818ab95bfaa94a", size = 48143, upload-time = "2026-01-26T02:45:41.635Z" }, + { url = "https://files.pythonhosted.org/packages/37/e8/5284c53310dcdc99ce5d66563f6e5773531a9b9fe9ec7a615e9bc306b05f/multidict-6.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:14525a5f61d7d0c94b368a42cff4c9a4e7ba2d52e2672a7b23d84dc86fb02b0c", size = 46507, upload-time = "2026-01-26T02:45:42.99Z" }, + { url = "https://files.pythonhosted.org/packages/e4/fc/6800d0e5b3875568b4083ecf5f310dcf91d86d52573160834fb4bfcf5e4f/multidict-6.7.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17307b22c217b4cf05033dabefe68255a534d637c6c9b0cc8382718f87be4262", size = 239358, upload-time = "2026-01-26T02:45:44.376Z" }, + { url = "https://files.pythonhosted.org/packages/41/75/4ad0973179361cdf3a113905e6e088173198349131be2b390f9fa4da5fc6/multidict-6.7.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a7e590ff876a3eaf1c02a4dfe0724b6e69a9e9de6d8f556816f29c496046e59", size = 246884, upload-time = "2026-01-26T02:45:47.167Z" }, + { url = "https://files.pythonhosted.org/packages/c3/9c/095bb28b5da139bd41fb9a5d5caff412584f377914bd8787c2aa98717130/multidict-6.7.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5fa6a95dfee63893d80a34758cd0e0c118a30b8dcb46372bf75106c591b77889", size = 225878, upload-time = "2026-01-26T02:45:48.698Z" }, + { url = "https://files.pythonhosted.org/packages/07/d0/c0a72000243756e8f5a277b6b514fa005f2c73d481b7d9e47cd4568aa2e4/multidict-6.7.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0543217a6a017692aa6ae5cc39adb75e587af0f3a82288b1492eb73dd6cc2a4", size = 253542, upload-time = "2026-01-26T02:45:50.164Z" }, + { url = "https://files.pythonhosted.org/packages/c0/6b/f69da15289e384ecf2a68837ec8b5ad8c33e973aa18b266f50fe55f24b8c/multidict-6.7.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f99fe611c312b3c1c0ace793f92464d8cd263cc3b26b5721950d977b006b6c4d", size = 252403, upload-time = "2026-01-26T02:45:51.779Z" }, + { url = "https://files.pythonhosted.org/packages/a2/76/b9669547afa5a1a25cd93eaca91c0da1c095b06b6d2d8ec25b713588d3a1/multidict-6.7.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9004d8386d133b7e6135679424c91b0b854d2d164af6ea3f289f8f2761064609", size = 244889, upload-time = "2026-01-26T02:45:53.27Z" }, + { url = "https://files.pythonhosted.org/packages/7e/a9/a50d2669e506dad33cfc45b5d574a205587b7b8a5f426f2fbb2e90882588/multidict-6.7.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e628ef0e6859ffd8273c69412a2465c4be4a9517d07261b33334b5ec6f3c7489", size = 241982, upload-time = "2026-01-26T02:45:54.919Z" }, + { url = "https://files.pythonhosted.org/packages/c5/bb/1609558ad8b456b4827d3c5a5b775c93b87878fd3117ed3db3423dfbce1b/multidict-6.7.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:841189848ba629c3552035a6a7f5bf3b02eb304e9fea7492ca220a8eda6b0e5c", size = 232415, upload-time = "2026-01-26T02:45:56.981Z" }, + { url = "https://files.pythonhosted.org/packages/d8/59/6f61039d2aa9261871e03ab9dc058a550d240f25859b05b67fd70f80d4b3/multidict-6.7.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce1bbd7d780bb5a0da032e095c951f7014d6b0a205f8318308140f1a6aba159e", size = 240337, upload-time = "2026-01-26T02:45:58.698Z" }, + { url = "https://files.pythonhosted.org/packages/a1/29/fdc6a43c203890dc2ae9249971ecd0c41deaedfe00d25cb6564b2edd99eb/multidict-6.7.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b26684587228afed0d50cf804cc71062cc9c1cdf55051c4c6345d372947b268c", size = 248788, upload-time = "2026-01-26T02:46:00.862Z" }, + { url = "https://files.pythonhosted.org/packages/a9/14/a153a06101323e4cf086ecee3faadba52ff71633d471f9685c42e3736163/multidict-6.7.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9f9af11306994335398293f9958071019e3ab95e9a707dc1383a35613f6abcb9", size = 242842, upload-time = "2026-01-26T02:46:02.824Z" }, + { url = "https://files.pythonhosted.org/packages/41/5f/604ae839e64a4a6efc80db94465348d3b328ee955e37acb24badbcd24d83/multidict-6.7.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b4938326284c4f1224178a560987b6cf8b4d38458b113d9b8c1db1a836e640a2", size = 240237, upload-time = "2026-01-26T02:46:05.898Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/c3a5187bf66f6fb546ff4ab8fb5a077cbdd832d7b1908d4365c7f74a1917/multidict-6.7.1-cp314-cp314t-win32.whl", hash = "sha256:98655c737850c064a65e006a3df7c997cd3b220be4ec8fe26215760b9697d4d7", size = 48008, upload-time = "2026-01-26T02:46:07.468Z" }, + { url = "https://files.pythonhosted.org/packages/0c/f7/addf1087b860ac60e6f382240f64fb99f8bfb532bb06f7c542b83c29ca61/multidict-6.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:497bde6223c212ba11d462853cfa4f0ae6ef97465033e7dc9940cdb3ab5b48e5", size = 53542, upload-time = "2026-01-26T02:46:08.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/81/4629d0aa32302ef7b2ec65c75a728cc5ff4fa410c50096174c1632e70b3e/multidict-6.7.1-cp314-cp314t-win_arm64.whl", hash = "sha256:2bbd113e0d4af5db41d5ebfe9ccaff89de2120578164f86a5d17d5a576d1e5b2", size = 44719, upload-time = "2026-01-26T02:46:11.146Z" }, + { url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" }, +] + +[[package]] +name = "multiprocess" +version = "0.70.19" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dill" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a2/f2/e783ac7f2aeeed14e9e12801f22529cc7e6b7ab80928d6dcce4e9f00922d/multiprocess-0.70.19.tar.gz", hash = "sha256:952021e0e6c55a4a9fe4cd787895b86e239a40e76802a789d6305398d3975897", size = 2079989, upload-time = "2026-01-19T06:47:39.744Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/45/8004d1e6b9185c1a444d6b55ac5682acf9d98035e54386d967366035a03a/multiprocess-0.70.19-py310-none-any.whl", hash = "sha256:97404393419dcb2a8385910864eedf47a3cadf82c66345b44f036420eb0b5d87", size = 134948, upload-time = "2026-01-19T06:47:32.325Z" }, + { url = "https://files.pythonhosted.org/packages/86/c2/dec9722dc3474c164a0b6bcd9a7ed7da542c98af8cabce05374abab35edd/multiprocess-0.70.19-py311-none-any.whl", hash = "sha256:928851ae7973aea4ce0eaf330bbdafb2e01398a91518d5c8818802845564f45c", size = 144457, upload-time = "2026-01-19T06:47:33.711Z" }, + { url = "https://files.pythonhosted.org/packages/71/70/38998b950a97ea279e6bd657575d22d1a2047256caf707d9a10fbce4f065/multiprocess-0.70.19-py312-none-any.whl", hash = "sha256:3a56c0e85dd5025161bac5ce138dcac1e49174c7d8e74596537e729fd5c53c28", size = 150281, upload-time = "2026-01-19T06:47:35.037Z" }, + { url = "https://files.pythonhosted.org/packages/7f/74/d2c27e03cb84251dfe7249b8e82923643c6d48fa4883b9476b025e7dc7eb/multiprocess-0.70.19-py313-none-any.whl", hash = "sha256:8d5eb4ec5017ba2fab4e34a747c6d2c2b6fecfe9e7236e77988db91580ada952", size = 156414, upload-time = "2026-01-19T06:47:35.915Z" }, + { url = "https://files.pythonhosted.org/packages/a0/61/af9115673a5870fd885247e2f1b68c4f1197737da315b520a91c757a861a/multiprocess-0.70.19-py314-none-any.whl", hash = "sha256:e8cc7fbdff15c0613f0a1f1f8744bef961b0a164c0ca29bdff53e9d2d93c5e5f", size = 160318, upload-time = "2026-01-19T06:47:37.497Z" }, + { url = "https://files.pythonhosted.org/packages/7e/82/69e539c4c2027f1e1697e09aaa2449243085a0edf81ae2c6341e84d769b6/multiprocess-0.70.19-py39-none-any.whl", hash = "sha256:0d4b4397ed669d371c81dcd1ef33fd384a44d6c3de1bd0ca7ac06d837720d3c5", size = 133477, upload-time = "2026-01-19T06:47:38.619Z" }, +] + +[[package]] +name = "murmurhash" +version = "1.0.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/23/2e/88c147931ea9725d634840d538622e94122bceaf346233349b7b5c62964b/murmurhash-1.0.15.tar.gz", hash = "sha256:58e2b27b7847f9e2a6edf10b47a8c8dd70a4705f45dccb7bf76aeadacf56ba01", size = 13291, upload-time = "2025-11-14T09:51:15.272Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/46/be8522d3456fdccf1b8b049c6d82e7a3c1114c4fc2cfe14b04cba4b3e701/murmurhash-1.0.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d37e3ae44746bca80b1a917c2ea625cf216913564ed43f69d2888e5df97db0cb", size = 27884, upload-time = "2025-11-14T09:50:13.133Z" }, + { url = "https://files.pythonhosted.org/packages/ed/cc/630449bf4f6178d7daf948ce46ad00b25d279065fc30abd8d706be3d87e0/murmurhash-1.0.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0861cb11039409eaf46878456b7d985ef17b6b484103a6fc367b2ecec846891d", size = 27855, upload-time = "2025-11-14T09:50:14.859Z" }, + { url = "https://files.pythonhosted.org/packages/ff/30/ea8f601a9bf44db99468696efd59eb9cff1157cd55cb586d67116697583f/murmurhash-1.0.15-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5a301decfaccfec70fe55cb01dde2a012c3014a874542eaa7cc73477bb749616", size = 134088, upload-time = "2025-11-14T09:50:15.958Z" }, + { url = "https://files.pythonhosted.org/packages/c9/de/c40ce8c0877d406691e735b8d6e9c815f36a82b499d358313db5dbe219d7/murmurhash-1.0.15-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:32c6fde7bd7e9407003370a07b5f4addacabe1556ad3dc2cac246b7a2bba3400", size = 133978, upload-time = "2025-11-14T09:50:17.572Z" }, + { url = "https://files.pythonhosted.org/packages/47/84/bd49963ecd84ebab2fe66595e2d1ed41d5e8b5153af5dc930f0bd827007c/murmurhash-1.0.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d8b43a7011540dc3c7ce66f2134df9732e2bc3bbb4a35f6458bc755e48bde26", size = 132956, upload-time = "2025-11-14T09:50:18.742Z" }, + { url = "https://files.pythonhosted.org/packages/4f/7c/2530769c545074417c862583f05f4245644599f1e9ff619b3dfe2969aafc/murmurhash-1.0.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:43bf4541892ecd95963fcd307bf1c575fc0fee1682f41c93007adee71ca2bb40", size = 134184, upload-time = "2025-11-14T09:50:19.941Z" }, + { url = "https://files.pythonhosted.org/packages/84/a4/b249b042f5afe34d14ada2dc4afc777e883c15863296756179652e081c44/murmurhash-1.0.15-cp312-cp312-win_amd64.whl", hash = "sha256:f4ac15a2089dc42e6eb0966622d42d2521590a12c92480aafecf34c085302cca", size = 25647, upload-time = "2025-11-14T09:50:21.049Z" }, + { url = "https://files.pythonhosted.org/packages/13/bf/028179259aebc18fd4ba5cae2601d1d47517427a537ab44336446431a215/murmurhash-1.0.15-cp312-cp312-win_arm64.whl", hash = "sha256:4a70ca4ae19e600d9be3da64d00710e79dde388a4d162f22078d64844d0ebdda", size = 23338, upload-time = "2025-11-14T09:50:22.359Z" }, + { url = "https://files.pythonhosted.org/packages/29/2f/ba300b5f04dae0409202d6285668b8a9d3ade43a846abee3ef611cb388d5/murmurhash-1.0.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fe50dc70e52786759358fd1471e309b94dddfffb9320d9dfea233c7684c894ba", size = 27861, upload-time = "2025-11-14T09:50:23.804Z" }, + { url = "https://files.pythonhosted.org/packages/34/02/29c19d268e6f4ea1ed2a462c901eed1ed35b454e2cbc57da592fad663ac6/murmurhash-1.0.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1349a7c23f6092e7998ddc5bd28546cc31a595afc61e9fdb3afc423feec3d7ad", size = 27840, upload-time = "2025-11-14T09:50:25.146Z" }, + { url = "https://files.pythonhosted.org/packages/e2/63/58e2de2b5232cd294c64092688c422196e74f9fa8b3958bdf02d33df24b9/murmurhash-1.0.15-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3ba6d05de2613535b5a9227d4ad8ef40a540465f64660d4a8800634ae10e04f", size = 133080, upload-time = "2025-11-14T09:50:26.566Z" }, + { url = "https://files.pythonhosted.org/packages/aa/9a/d13e2e9f8ba1ced06840921a50f7cece0a475453284158a3018b72679761/murmurhash-1.0.15-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fa1b70b3cc2801ab44179c65827bbd12009c68b34e9d9ce7125b6a0bd35af63c", size = 132648, upload-time = "2025-11-14T09:50:27.788Z" }, + { url = "https://files.pythonhosted.org/packages/b2/e1/47994f1813fa205c84977b0ff51ae6709f8539af052c7491a5f863d82bdc/murmurhash-1.0.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:213d710fb6f4ef3bc11abbfad0fa94a75ffb675b7dc158c123471e5de869f9af", size = 131502, upload-time = "2025-11-14T09:50:29.339Z" }, + { url = "https://files.pythonhosted.org/packages/b9/ea/90c1fd00b4aeb704fb5e84cd666b33ffd7f245155048071ffbb51d2bb57d/murmurhash-1.0.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b65a5c4e7f5d71f7ccac2d2b60bdf7092d7976270878cfec59d5a66a533db823", size = 132736, upload-time = "2025-11-14T09:50:30.545Z" }, + { url = "https://files.pythonhosted.org/packages/00/db/da73462dbfa77f6433b128d2120ba7ba300f8c06dc4f4e022c38d240a5f5/murmurhash-1.0.15-cp313-cp313-win_amd64.whl", hash = "sha256:9aba94c5d841e1904cd110e94ceb7f49cfb60a874bbfb27e0373622998fb7c7c", size = 25682, upload-time = "2025-11-14T09:50:31.624Z" }, + { url = "https://files.pythonhosted.org/packages/bb/83/032729ef14971b938fbef41ee125fc8800020ee229bd35178b6ede8ee934/murmurhash-1.0.15-cp313-cp313-win_arm64.whl", hash = "sha256:263807eca40d08c7b702413e45cca75ecb5883aa337237dc5addb660f1483378", size = 23370, upload-time = "2025-11-14T09:50:33.264Z" }, + { url = "https://files.pythonhosted.org/packages/10/83/7547d9205e9bd2f8e5dfd0b682cc9277594f98909f228eb359489baec1df/murmurhash-1.0.15-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:694fd42a74b7ce257169d14c24aa616aa6cd4ccf8abe50eca0557e08da99d055", size = 29955, upload-time = "2025-11-14T09:50:34.488Z" }, + { url = "https://files.pythonhosted.org/packages/b7/c7/3afd5de7a5b3ae07fe2d3a3271b327ee1489c58ba2b2f2159bd31a25edb9/murmurhash-1.0.15-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a2ea4546ba426390beff3cd10db8f0152fdc9072c4f2583ec7d8aa9f3e4ac070", size = 30108, upload-time = "2025-11-14T09:50:35.53Z" }, + { url = "https://files.pythonhosted.org/packages/02/69/d6637ee67d78ebb2538c00411f28ea5c154886bbe1db16c49435a8a4ab16/murmurhash-1.0.15-cp313-cp313t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:34e5a91139c40b10f98d0b297907f5d5267b4b1b2e5dd2eb74a021824f751b98", size = 164054, upload-time = "2025-11-14T09:50:36.591Z" }, + { url = "https://files.pythonhosted.org/packages/ab/4c/89e590165b4c7da6bf941441212a721a270195332d3aacfdfdf527d466ca/murmurhash-1.0.15-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:dc35606868a5961cf42e79314ca0bddf5a400ce377b14d83192057928d6252ec", size = 168153, upload-time = "2025-11-14T09:50:37.856Z" }, + { url = "https://files.pythonhosted.org/packages/07/7a/95c42df0c21d2e413b9fcd17317a7587351daeb264dc29c6aec1fdbd26f8/murmurhash-1.0.15-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:43cc6ac3b91ca0f7a5ae9c063ba4d6c26972c97fd7c25280ecc666413e4c5535", size = 164345, upload-time = "2025-11-14T09:50:39.346Z" }, + { url = "https://files.pythonhosted.org/packages/d0/22/9d02c880a88b83bb3ce7d6a38fb727373ab78d82e5f3d8d9fc5612219f90/murmurhash-1.0.15-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:847d712136cb462f0e4bd6229ee2d9eb996d8854eb8312dff3d20c8f5181fda5", size = 161990, upload-time = "2025-11-14T09:50:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e3/750232524e0dc262e8dcede6536dafc766faadd9a52f1d23746b02948ad8/murmurhash-1.0.15-cp313-cp313t-win_amd64.whl", hash = "sha256:2680851af6901dbe66cc4aa7ef8e263de47e6e1b425ae324caa571bdf18f8d58", size = 28812, upload-time = "2025-11-14T09:50:41.971Z" }, + { url = "https://files.pythonhosted.org/packages/ff/89/4ad9d215ef6ade89f27a72dc4e86b98ef1a43534cc3e6a6900a362a0bf0a/murmurhash-1.0.15-cp313-cp313t-win_arm64.whl", hash = "sha256:189a8de4d657b5da9efd66601b0636330b08262b3a55431f2379097c986995d0", size = 25398, upload-time = "2025-11-14T09:50:43.023Z" }, + { url = "https://files.pythonhosted.org/packages/1c/69/726df275edf07688146966e15eaaa23168100b933a2e1a29b37eb56c6db8/murmurhash-1.0.15-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7c4280136b738e85ff76b4bdc4341d0b867ee753e73fd8b6994288080c040d0b", size = 28029, upload-time = "2025-11-14T09:50:44.124Z" }, + { url = "https://files.pythonhosted.org/packages/59/8f/24ecf9061bc2b20933df8aba47c73e904274ea8811c8300cab92f6f82372/murmurhash-1.0.15-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d4d681f474830489e2ec1d912095cfff027fbaf2baa5414c7e9d25b89f0fab68", size = 27912, upload-time = "2025-11-14T09:50:45.266Z" }, + { url = "https://files.pythonhosted.org/packages/ba/26/fff3caba25aa3c0622114e03c69fb66c839b22335b04d7cce91a3a126d44/murmurhash-1.0.15-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d7e47c5746785db6a43b65fac47b9e63dd71dfbd89a8c92693425b9715e68c6e", size = 131847, upload-time = "2025-11-14T09:50:46.819Z" }, + { url = "https://files.pythonhosted.org/packages/df/e4/0f2b9fc533467a27afb4e906c33f32d5f637477de87dd94690e0c44335a6/murmurhash-1.0.15-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e8e674f02a99828c8a671ba99cd03299381b2f0744e6f25c29cadfc6151dc724", size = 132267, upload-time = "2025-11-14T09:50:48.298Z" }, + { url = "https://files.pythonhosted.org/packages/da/bf/9d1c107989728ec46e25773d503aa54070b32822a18cfa7f9d5f41bc17a5/murmurhash-1.0.15-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:26fd7c7855ac4850ad8737991d7b0e3e501df93ebaf0cf45aa5954303085fdba", size = 131894, upload-time = "2025-11-14T09:50:49.485Z" }, + { url = "https://files.pythonhosted.org/packages/0d/81/dcf27c71445c0e993b10e33169a098ca60ee702c5c58fcbde205fa6332a6/murmurhash-1.0.15-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cb8ebafae60d5f892acff533cc599a359954d8c016a829514cb3f6e9ee10f322", size = 132054, upload-time = "2025-11-14T09:50:50.747Z" }, + { url = "https://files.pythonhosted.org/packages/bc/32/e874a14b2d2246bd2d16f80f49fad393a3865d4ee7d66d2cae939a67a29a/murmurhash-1.0.15-cp314-cp314-win_amd64.whl", hash = "sha256:898a629bf111f1aeba4437e533b5b836c0a9d2dd12d6880a9c75f6ca13e30e22", size = 26579, upload-time = "2025-11-14T09:50:52.278Z" }, + { url = "https://files.pythonhosted.org/packages/af/8e/4fca051ed8ae4d23a15aaf0a82b18cb368e8cf84f1e3b474d5749ec46069/murmurhash-1.0.15-cp314-cp314-win_arm64.whl", hash = "sha256:88dc1dd53b7b37c0df1b8b6bce190c12763014492f0269ff7620dc6027f470f4", size = 24341, upload-time = "2025-11-14T09:50:53.295Z" }, + { url = "https://files.pythonhosted.org/packages/38/9c/c72c2a4edd86aac829337ab9f83cf04cdb15e5d503e4c9a3a243f30a261c/murmurhash-1.0.15-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:6cb4e962ec4f928b30c271b2d84e6707eff6d942552765b663743cfa618b294b", size = 30146, upload-time = "2025-11-14T09:50:54.705Z" }, + { url = "https://files.pythonhosted.org/packages/ac/d7/72b47ebc86436cd0aa1fd4c6e8779521ec389397ac11389990278d0f7a47/murmurhash-1.0.15-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5678a3ea4fbf0cbaaca2bed9b445f556f294d5f799c67185d05ffcb221a77faf", size = 30141, upload-time = "2025-11-14T09:50:55.829Z" }, + { url = "https://files.pythonhosted.org/packages/64/bb/6d2f09135079c34dc2d26e961c52742d558b320c61503f273eab6ba743d9/murmurhash-1.0.15-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ef19f38c6b858eef83caf710773db98c8f7eb2193b4c324650c74f3d8ba299e0", size = 163898, upload-time = "2025-11-14T09:50:56.946Z" }, + { url = "https://files.pythonhosted.org/packages/b9/e2/9c1b462e33f9cb2d632056f07c90b502fc20bd7da50a15d0557343bd2fed/murmurhash-1.0.15-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22aa3ceaedd2e57078b491ed08852d512b84ff4ff9bb2ff3f9bf0eec7f214c9e", size = 168040, upload-time = "2025-11-14T09:50:58.234Z" }, + { url = "https://files.pythonhosted.org/packages/e8/73/8694db1408fcdfa73589f7df6c445437ea146986fa1e393ec60d26d6e30c/murmurhash-1.0.15-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bba0e0262c0d08682b028cb963ac477bd9839029486fa1333fc5c01fb6072749", size = 164239, upload-time = "2025-11-14T09:50:59.95Z" }, + { url = "https://files.pythonhosted.org/packages/2d/f9/8e360bdfc3c44e267e7e046f0e0b9922766da92da26959a6963f597e6bb5/murmurhash-1.0.15-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4fd8189ee293a09f30f4931408f40c28ccd42d9de4f66595f8814879339378bc", size = 161811, upload-time = "2025-11-14T09:51:01.289Z" }, + { url = "https://files.pythonhosted.org/packages/f9/31/97649680595b1096803d877ababb9a67c07f4378f177ec885eea28b9db6d/murmurhash-1.0.15-cp314-cp314t-win_amd64.whl", hash = "sha256:66395b1388f7daa5103db92debe06842ae3be4c0749ef6db68b444518666cdcc", size = 29817, upload-time = "2025-11-14T09:51:02.493Z" }, + { url = "https://files.pythonhosted.org/packages/76/66/4fce8755f25d77324401886c00017c556be7ca3039575b94037aff905385/murmurhash-1.0.15-cp314-cp314t-win_arm64.whl", hash = "sha256:c22e56c6a0b70598a66e456de5272f76088bc623688da84ef403148a6d41851d", size = 26219, upload-time = "2025-11-14T09:51:03.563Z" }, +] + +[[package]] +name = "mypy" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ast-serialize" }, + { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/15/cca9d88503549ed6fedeaa1d448cdddd542ee8a490232d732e278036fbf2/mypy-2.1.0.tar.gz", hash = "sha256:81e76ad12c2d804512e9b13240d1588316531bfba07558286078bfbce9613633", size = 3898359, upload-time = "2026-05-11T18:37:36.237Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/b1/55861beb5c339b44f9a2ba92df9e2cb1eeb4ae1eee674cdf7772c797778b/mypy-2.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:244358bf1c0da7722230bce60683d52e8e9fd030554926f15b747a84efb5b3af", size = 14874381, upload-time = "2026-05-11T18:37:31.784Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b3/b7f770114b7d0ac92d0f76e8d93c2780844a70488a90e91821927850da86/mypy-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4ec7c57657493c7a75534df2751c8ae2cda383c16ecc55d2106c54476b1b16f6", size = 13665501, upload-time = "2026-05-11T18:34:23.063Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f3/8ae2037967e2126689a0c11d99e2b707134a565191e92c60ca2572aec60a/mypy-2.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8161b6ff4392410023224f0969d17db93e1e154bc3e4ba62598e720723ae211", size = 14045750, upload-time = "2026-05-11T18:31:48.151Z" }, + { url = "https://files.pythonhosted.org/packages/a0/32/615eb5911859e43d054941b0d0a7d06cfa2870eba86529cf385b052b111c/mypy-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf03e12003084a67395184d3eb8cbd6a489dc3655b5664b28c210a9e2403ab0b", size = 15061630, upload-time = "2026-05-11T18:37:06.898Z" }, + { url = "https://files.pythonhosted.org/packages/d4/03/4eafbfff8bfab1b87082741eae6e6a624028c984e6708b73bce2a8570c9d/mypy-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:20509760fd791c51579d573153407d226385ec1f8bcce55d730b354f3336bc22", size = 15288831, upload-time = "2026-05-11T18:31:18.07Z" }, + { url = "https://files.pythonhosted.org/packages/99/ee/919661478e5891a3c96e549c036e467e64563ab85995b10c53c8358e16a3/mypy-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:6753d0c1fdd6b1a23b9e4f283ce80b2153b724adcb2653b20b85a8a28ac6436b", size = 11135228, upload-time = "2026-05-11T18:34:31.23Z" }, + { url = "https://files.pythonhosted.org/packages/24/0a/6a12b9782ca0831a553192f351679f4548abc9d19a7cc93bb7feb02084c7/mypy-2.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:98ebb6589bb3b6d0c6f0c459d53ca55b8091fbc13d277c4041c885392e8195e8", size = 10040684, upload-time = "2026-05-11T18:36:48.199Z" }, + { url = "https://files.pythonhosted.org/packages/6e/dd/c7191469c777f07689c032a8f7326e393ea34c92d6d76eb7ce5ba57ea66d/mypy-2.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35aac3bb114e03888f535d5eb51b8bafbb3266586b599da1940f9b1be3ec5bd5", size = 14852174, upload-time = "2026-05-11T18:31:38.929Z" }, + { url = "https://files.pythonhosted.org/packages/55/8c/aed55408879043d72bb9135f4d0d19a02b886dd569631e113e3d2706cb8d/mypy-2.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8de55a8c861f2a49331f807be98d90caeceeef520bde13d43a160207f8af613e", size = 13651542, upload-time = "2026-05-11T18:36:04.636Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8e/f371a824b1f1fa8ea6e3dbb8703d232977d572be2329554a3bc4d960302f/mypy-2.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5fdf2941a07434af755837d9880f7d7d25f1dacb1af9dcd4b9b66f2220a3024e", size = 14033929, upload-time = "2026-05-11T18:35:55.742Z" }, + { url = "https://files.pythonhosted.org/packages/94/21/f54be870d6dd53a82c674407e0f8eed7174b05ec78d42e5abd7b42e84fd5/mypy-2.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e195b817c13f02352a9c124301f9f30f078405444679b6753c1b96b6eed37285", size = 15039200, upload-time = "2026-05-11T18:33:10.281Z" }, + { url = "https://files.pythonhosted.org/packages/17/99/bf21748626a40ce59fd29a39386ab46afec88b7bd2f0fa6c3a97c995523f/mypy-2.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5431d42af987ebd92ba2f71d45c85ed41d8e6ca9f5fd209a69f68f707d2469e5", size = 15272690, upload-time = "2026-05-11T18:32:07.205Z" }, + { url = "https://files.pythonhosted.org/packages/d6/d7/9e90d2cf47100bea550ed2bc7b0d4de3a62181d84d5e37da0003e8462637/mypy-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:767fe8c66dc3e01e19e1737d4c38ebefead16125e1b8e58ad421903b376f5c65", size = 11147435, upload-time = "2026-05-11T18:33:56.477Z" }, + { url = "https://files.pythonhosted.org/packages/ec/46/e5c449e858798e35ffc90946282a27c62a77be743fe17480e4977374eb91/mypy-2.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:ecfe70d43775ab99562ab128ce49854a362044c9f894961f68f898c23cb7429d", size = 10035052, upload-time = "2026-05-11T18:32:30.049Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ca/b279a672e874aedd5498ae25f722dacc8aa86bbffb939b3f97cbb1cf6686/mypy-2.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7354c5a7f69d9345c3d6e69921d57088eea3ddeeb6b20d34c1b3855b02c36ec2", size = 14848422, upload-time = "2026-05-11T18:35:45.984Z" }, + { url = "https://files.pythonhosted.org/packages/27/e6/3efe56c631d959b9b4454e208b0ac4b7f4f58b404c89f8bec7b49efdfc21/mypy-2.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:49890d4f76ac9e06ec117f9e09f3174da70a620a0c300953d8595c926e80947f", size = 13677374, upload-time = "2026-05-11T18:36:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/84/7f/8107ea87a44fd1f1b59882442f033c9c3488c127201b1d1d15f1cbd6022e/mypy-2.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:761be68e023ef5d94678772396a8af1220030f80837a3afd8d0aef3b419666f4", size = 14055743, upload-time = "2026-05-11T18:35:18.361Z" }, + { url = "https://files.pythonhosted.org/packages/51/4d/b6d34db183133b83761b9199a82d31557cdbb70a380d8c3b3438e11882a3/mypy-2.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c90345fc182dc363b891350457ec69c35140858538f38b4540845afcc32b1aef", size = 15020937, upload-time = "2026-05-11T18:34:59.618Z" }, + { url = "https://files.pythonhosted.org/packages/ff/d7/f08360c691d758acb02f45022c34d98b92892f4ea756644e1000d4b9f3d8/mypy-2.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b84802e7b5a6daf1f5e15bc9fcd7ddae77be13981ffab037f1c67bb84d67d135", size = 15253371, upload-time = "2026-05-11T18:36:41.081Z" }, + { url = "https://files.pythonhosted.org/packages/67/1b/09460a13719530a19bce27bd3bc8449e83569dd2ba7faf51c9c3c30c0b61/mypy-2.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:022c771234936ceac541ebaf836fe9e2abeb3f5e09aff21588fe543ff006fe21", size = 11326429, upload-time = "2026-05-11T18:34:13.526Z" }, + { url = "https://files.pythonhosted.org/packages/40/62/75dbf0f82f7b6680340efc614af29dd0b3c17b8a4f1cd09b8bd2fd6bc814/mypy-2.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:498207db725cec88829a6a5c2fc771205fd043719ef98bc49aba8fb9fc4e6d57", size = 10218799, upload-time = "2026-05-11T18:32:23.491Z" }, + { url = "https://files.pythonhosted.org/packages/b2/66/caca04ed7d972fb6eb6dd1ccd6df1de5c38fae8c5b3dc1c4e8e0d85ee6b9/mypy-2.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7d5e5cad0efeba72b93cd17490cc0d69c5ac9ca132994fe3fb0314808aeeb83e", size = 15923458, upload-time = "2026-05-11T18:35:28.64Z" }, + { url = "https://files.pythonhosted.org/packages/ed/52/2d90cbe49d014b13ed7ff337930c30bad35893fe38a1e4641e756bb62191/mypy-2.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ff715050c127d724fd260a2e666e7747fdd83511c0c47d449d98238970aef780", size = 14757697, upload-time = "2026-05-11T18:36:14.208Z" }, + { url = "https://files.pythonhosted.org/packages/ac/37/d98f4a14e081b238992d0ed96b6d39c7cc0148c9699eb71eaa68629665ea/mypy-2.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:82208da9e09414d520e912d3e462d454854bed0810b71540bb016dcbca7308fd", size = 15405638, upload-time = "2026-05-11T18:33:48.249Z" }, + { url = "https://files.pythonhosted.org/packages/a3/c2/15c46613b24a84fad2aea1248bf9619b99c2767ae9071fe224c179a0b7d4/mypy-2.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e79ebc1b904b84f0310dff7469655a9c36c7a68bddb37bdd42b67a332df61d08", size = 16215852, upload-time = "2026-05-11T18:32:50.296Z" }, + { url = "https://files.pythonhosted.org/packages/5c/90/9c16a57f482c76d25f6379762b56bbf65c711d8158cf271fb2802cfb0640/mypy-2.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e583edc957cfb0deb142079162ae826f58449b116c1d442f2d91c69d9fced081", size = 16452695, upload-time = "2026-05-11T18:33:38.182Z" }, + { url = "https://files.pythonhosted.org/packages/0f/4c/215a4eeb63cacc5f17f516691ea7285d11e249802b942476bff15922a314/mypy-2.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b33b6cd332695bba180d55e717a79d3038e479a2c49cc5eb3d53603409b9a5d7", size = 12866622, upload-time = "2026-05-11T18:34:39.945Z" }, + { url = "https://files.pythonhosted.org/packages/4b/50/1043e1db5f455ffe4c9ab22747cd8ca2bc492b1e4f4e21b130a44ee2b217/mypy-2.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:4f910fe825376a7b66ef7ca8c98e5a149e8cd64c19ae71d84047a74ee060d4e6", size = 10610798, upload-time = "2026-05-11T18:36:31.444Z" }, + { url = "https://files.pythonhosted.org/packages/0d/2a/13ca1f292f6db1b98ff495ef3467736b331621c5917cad984b7043e7348d/mypy-2.1.0-py3-none-any.whl", hash = "sha256:a663814603a5c563fb87a4f96fb473eeb30d1f5a4885afcf44f9db000a366289", size = 2693302, upload-time = "2026-05-11T18:31:29.246Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "nest-asyncio" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418, upload-time = "2024-01-21T14:25:19.227Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" }, +] + +[[package]] +name = "networkx" +version = "3.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025, upload-time = "2025-12-08T17:02:39.908Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504, upload-time = "2025-12-08T17:02:38.159Z" }, +] + +[[package]] +name = "numpy" +version = "2.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda", size = 20735807, upload-time = "2026-05-18T23:37:14.07Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/2a/3d7b5ac8aac24feaf9ad7ed58f45b0bbc06d37e4338ae84c9f2298b570f9/numpy-2.4.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:001fbb8e08d942dd57599e781f2472269ee7f2755fae407b4f67b2f0b17da3f1", size = 16689119, upload-time = "2026-05-18T23:33:54.065Z" }, + { url = "https://files.pythonhosted.org/packages/ea/12/92c4c131527599e8288d6918e888d88726f84d805d784b771f32408aeaef/numpy-2.4.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ebfb099f8dcf083deef3ac1ca4c1503f387cf76296fcb3816b66f5ecb5f54fdb", size = 14699246, upload-time = "2026-05-18T23:33:57.621Z" }, + { url = "https://files.pythonhosted.org/packages/ad/fe/c0a6b7b2ca128a8fb228575147073b660656734b8ebe4d76c8fd748dcc79/numpy-2.4.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3213d622a0283a39a93d188f3cf72b26862df52fbb4ca3697f51705016523d41", size = 5204410, upload-time = "2026-05-18T23:34:00.302Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d4/9770d14ba719432bb90a421bfd443872ed0f70f7264b64bec12ea363d5fd/numpy-2.4.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:357cc07a6d7b0b182ff02249616a03742827ebb1277546b5c7cd7f7620a45698", size = 6551240, upload-time = "2026-05-18T23:34:02.852Z" }, + { url = "https://files.pythonhosted.org/packages/c9/c6/50a46a6205feba2343f1d6d17438107c5dc491ed1c736e6ea68689fd906b/numpy-2.4.6-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f9fb9157b4ce2971008323afe46053787b526ef624fea915b261468a8421a0f", size = 15671012, upload-time = "2026-05-18T23:34:05.485Z" }, + { url = "https://files.pythonhosted.org/packages/99/60/14115e6364fa676c5397c2ad3004e527e9aa487abf5d0706ec81bbd08529/numpy-2.4.6-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90f9849678c75fe7afa2d348ac842c168b0a4d3d61919687216dfc547976d853", size = 16645538, upload-time = "2026-05-18T23:34:09.265Z" }, + { url = "https://files.pythonhosted.org/packages/ae/c5/693cbe59e57db94d2231fa519ca3978dc9e19da5a8f088588f5c6e947ff2/numpy-2.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c1a2af6c6ef86344a6b0db6b97834208bf598db514f2b155042439b62605601a", size = 17020706, upload-time = "2026-05-18T23:34:13.053Z" }, + { url = "https://files.pythonhosted.org/packages/ef/fc/85b7c4eff9b4966ade25c2273cf7e7012e92366c032058653934b37de044/numpy-2.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e5805d5a22fd19c8ccff10a9561f9df94436b0545619ea579db2d3c35294bce2", size = 18368541, upload-time = "2026-05-18T23:34:17.024Z" }, + { url = "https://files.pythonhosted.org/packages/f6/81/e1b27545deedce7f4a0b348618c6b62d74e36a4dc9ccd42f3eb2f85eee32/numpy-2.4.6-cp312-cp312-win32.whl", hash = "sha256:e3eeb0aabd6bd5ce64faae67e9935203a6991b4bc2a485a767fbafb2c5125f45", size = 5962825, upload-time = "2026-05-18T23:34:20.3Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ca/feab00bd44aa5fe1ad2c18f08b4d3bb92e26484b0b1d1443897809ed528c/numpy-2.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:d8e8286dd7cea7895157318d1b91cdacac64c479f3cbc8dce548331728484751", size = 12321687, upload-time = "2026-05-18T23:34:23.095Z" }, + { url = "https://files.pythonhosted.org/packages/63/cf/5a6d34850a39d1093558564f77ee8e8e0bee5061151b8f05a55711001ec7/numpy-2.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:4081eb135ac24158bd51cdfbef16f1c64df7063b1143f24731387137c092bec8", size = 10221482, upload-time = "2026-05-18T23:34:25.876Z" }, + { url = "https://files.pythonhosted.org/packages/fb/82/bdab26d7438c6791ca31b7c024ca37c1eab8b726ba236129005cd4a06e45/numpy-2.4.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:511dbaf848decaaaf4b4ca48032619fb3138710c4bf7da7617765edad1ef96b0", size = 16684648, upload-time = "2026-05-18T23:34:29.41Z" }, + { url = "https://files.pythonhosted.org/packages/1b/30/a80189bcc7f5e4258b3fbc3968d909d1756f54d023299ecc39ad6fdb9ef8/numpy-2.4.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bf162abab1c1a736333192707cef898e735a5ca00f38f27eeedf44b39d9e85eb", size = 14693902, upload-time = "2026-05-18T23:34:33.013Z" }, + { url = "https://files.pythonhosted.org/packages/97/12/70b5d0d7c15e1ebb8a6a84a8caa1d19e181d84fb58bb6d70aca29099dec1/numpy-2.4.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:043191bfa8eab18c776647b62723ac9dddece59743b13f49b2016094129c2b3f", size = 5198992, upload-time = "2026-05-18T23:34:36.132Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8c/ebd2a8f8a83541f8d38cc5667e8c2b69cecfd30da6e45693e8158857d44b/numpy-2.4.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:6180d8b35af935aed8ece3a85e0a43f87393ae0ac87c8d2c8bd2c993f7270ef3", size = 6546944, upload-time = "2026-05-18T23:34:38.484Z" }, + { url = "https://files.pythonhosted.org/packages/bb/c5/7b863a97a91671a0338f4253bd3b5a3d3852f0692dae91711c9f4a10e787/numpy-2.4.6-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72fbe16c6fac95aedf5937fa873445cec2110be35d8a4e9433d7501fd98dae6b", size = 15669392, upload-time = "2026-05-18T23:34:41.257Z" }, + { url = "https://files.pythonhosted.org/packages/a5/9d/3584b9984ca4c047aea75214ce1a4c4c73d849bd71b604264b7f5653f8a8/numpy-2.4.6-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7830bab239b79cda9c08c2da014761cafb48da6150e1da17ac06283f43b6089", size = 16633220, upload-time = "2026-05-18T23:34:45.075Z" }, + { url = "https://files.pythonhosted.org/packages/05/ae/7c67fba23bd98caec7c99261f3a16072ade14813486b0282cb29846de832/numpy-2.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ef4aea96ce4d3b074422cb4f2f64e216bf9e213004bb58ecfdf50ea02ea8eb9a", size = 17020800, upload-time = "2026-05-18T23:34:49.065Z" }, + { url = "https://files.pythonhosted.org/packages/d9/5d/3b6725cb31d983c5e66916f5d36f6d7e5521129e4c4404d64f918292a5b6/numpy-2.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dfa20cc6ca228e6b155b11da03825975ce66aea520985dbbddf0f2a5a495c605", size = 18357600, upload-time = "2026-05-18T23:34:52.709Z" }, + { url = "https://files.pythonhosted.org/packages/f7/da/2ccc6c2fe8898dee01d90c75c5f5f914a23daf99e3e0f59516a08760c8b5/numpy-2.4.6-cp313-cp313-win32.whl", hash = "sha256:56b39e5e0622a09a25bf5baf62f4bcf0cb8a41ae6e2819cf49bbc5a74c083f91", size = 5961134, upload-time = "2026-05-18T23:34:55.618Z" }, + { url = "https://files.pythonhosted.org/packages/b5/cd/9cc4dc876fb065d5c220aae4d5e14826b2715331bb7618ce1fb07a679d99/numpy-2.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:c4fc99836233ea196540b17ab0983aff60ed07941751930f5f4d05bc3b3b7359", size = 12318598, upload-time = "2026-05-18T23:34:58.928Z" }, + { url = "https://files.pythonhosted.org/packages/39/1e/c0bcba1f8694116485fe28fd1be698c278fcda4141c5b0e53a2aed8b12a8/numpy-2.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a7c711e21628b52034bb5ab8d1bce291f752fcc5e92accc615778acee1ff4778", size = 10222272, upload-time = "2026-05-18T23:35:02.167Z" }, + { url = "https://files.pythonhosted.org/packages/63/6d/cc5619247c8f4204e507f5883528372e4ac4bb189e579fb859a12e480b1f/numpy-2.4.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:112b06a867b235ef466ed3508ddf0238050df9c727cafb5301ac385b899189a1", size = 14821197, upload-time = "2026-05-18T23:35:05.468Z" }, + { url = "https://files.pythonhosted.org/packages/00/58/f1c39161c87d9e9bed660f1ed4bafc0e403d5ec9650b6dd77aead07d489b/numpy-2.4.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:eaf7fa2de5c0be8ae6ff8e9bea2ccd725e980541244521d8d4b5f3354a27babe", size = 5326287, upload-time = "2026-05-18T23:35:08.693Z" }, + { url = "https://files.pythonhosted.org/packages/af/57/3917ab0fd97f271a8694513581b8a36c655f111c446852c302f04ccdb6fc/numpy-2.4.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7265a2f3d436e54ef9f2b52b5c937e6be778781bd97a590319d7348f1c1ca997", size = 6646763, upload-time = "2026-05-18T23:35:11.459Z" }, + { url = "https://files.pythonhosted.org/packages/eb/0f/037e64c494b67581ae18193d770adef354c41f3f2c8ebf865602d949bf8f/numpy-2.4.6-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f74a575920ab21fe304421a3fc28793d82e299cae9eccb37084e9fc7f3617c20", size = 15728070, upload-time = "2026-05-18T23:35:14.79Z" }, + { url = "https://files.pythonhosted.org/packages/21/a6/5d2bae9c9542eb4df16dc9c46dc79c186e9bad53805dfa5399a6023c6db0/numpy-2.4.6-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ede83e07a75dd06bc501566c1eca2afc0d61677c1472ac9ad93fdee6e638a48d", size = 16681752, upload-time = "2026-05-18T23:35:18.836Z" }, + { url = "https://files.pythonhosted.org/packages/92/14/23d1dfb410ae362cd59ce53e936b1513d545eb40db3949ced632e19a459e/numpy-2.4.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:68bb27509ac1b9a3443094260f6326150663b06abe40b73a2f81160623da5b67", size = 17086024, upload-time = "2026-05-18T23:35:22.52Z" }, + { url = "https://files.pythonhosted.org/packages/4b/6e/23595a2c642cdf3bc567877064bdd7f91c8b0038a4453cf2daf7248eafe9/numpy-2.4.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a0df0043bdb289bde1f62da130d20df23d58b45429f752bc7a8fc5325a225ecd", size = 18403398, upload-time = "2026-05-18T23:35:26.398Z" }, + { url = "https://files.pythonhosted.org/packages/8a/90/0ac3bc947217e66dec77e7cbc6a1979d1af70b6461b82f620d3bccd5e4c8/numpy-2.4.6-cp313-cp313t-win32.whl", hash = "sha256:29a287e0cf63ff528da061de6b9f64a4618da591ca1046aafc54062e40ca7eab", size = 6084971, upload-time = "2026-05-18T23:35:29.387Z" }, + { url = "https://files.pythonhosted.org/packages/77/71/5673e351671a1d2bd6063b91b44f70c0affea7d1516fa7a6572941ba4aa1/numpy-2.4.6-cp313-cp313t-win_amd64.whl", hash = "sha256:25c692919ac5a01f170a3bfcd62d745b24fd095c353d50812637d6fcab442e75", size = 12458532, upload-time = "2026-05-18T23:35:32.175Z" }, + { url = "https://files.pythonhosted.org/packages/3f/88/19d3503c5046e688f049274b27a3ef3d771152fa80d3ba3d01a3dff61abe/numpy-2.4.6-cp313-cp313t-win_arm64.whl", hash = "sha256:1e978ec1e8bd0e0e4de6bb75de9d30cbb74db6b6a2bb727618613703ca0167dd", size = 10291881, upload-time = "2026-05-18T23:35:35.465Z" }, + { url = "https://files.pythonhosted.org/packages/f8/91/3ab2044d05fd16d343c5ac2e69b127f1b2854040dd20b193257c78028bd3/numpy-2.4.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06ca2f61ec4385a07a6977c55ba998a4466c123642b4a32694d3128fce18c079", size = 16683458, upload-time = "2026-05-18T23:35:38.353Z" }, + { url = "https://files.pythonhosted.org/packages/8e/62/764ce66fa4147ae6d73071a3abf804ffe606f174618697c571acdf26a7c9/numpy-2.4.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:38efbc8de75c7a0fc1ac190162d892787f3f47b57cc291231aafee36b80982b7", size = 14704559, upload-time = "2026-05-18T23:35:42.14Z" }, + { url = "https://files.pythonhosted.org/packages/60/61/23f27c172f022e04025b7dc2367f4d63c1a398120607ec896228649a6f48/numpy-2.4.6-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d581b735e177fdcdce6fed8e7e8880a3fb6ee4e3653a3ac6af01c6f4c03effc5", size = 5209716, upload-time = "2026-05-18T23:35:45.377Z" }, + { url = "https://files.pythonhosted.org/packages/03/71/21cf70dc6ea3e3acb95fc53a265b2fc248b981f0194ceb5b475271b8809d/numpy-2.4.6-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:0a041d3d761dc3c35cc56ce0351506a02bcbc25f7b169f652435141a17db9096", size = 6543947, upload-time = "2026-05-18T23:35:47.926Z" }, + { url = "https://files.pythonhosted.org/packages/d5/91/64288395ee1799bd2e0b04a305dce9666da90c961e1f3fe982a05ee1c036/numpy-2.4.6-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40fdc1ae7125e518ea98e53e69a4ebc27e1fd50510c47b7ea130cf21e5e1d42b", size = 15685197, upload-time = "2026-05-18T23:35:50.863Z" }, + { url = "https://files.pythonhosted.org/packages/f3/eb/ebffaa97dc55502df69584a8f0dcf07f69a3e0b3e2323670a2722db9aa39/numpy-2.4.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2c306dea656c12c68f51f4cea133cbe78ca7435eb28c735eac1d3ebe73be6e8", size = 16638245, upload-time = "2026-05-18T23:35:54.752Z" }, + { url = "https://files.pythonhosted.org/packages/b8/0b/54f9da33128d7e350fab89c7455902eeae70349ee52bddb448dc4a576f45/numpy-2.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:33111801a01c12a8a1e3721f0a9232f8cfc8ae2c6b7098167e6f623c6073f402", size = 17036587, upload-time = "2026-05-18T23:35:58.355Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f0/fdebc1052db1cc37c64beb22072d67cd6d1c71adca1299f53dec2b5e20d3/numpy-2.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae506e6902902557576a26ff33eda8695e7ecb3cb36c3b573a0765dee114ebdb", size = 18363226, upload-time = "2026-05-18T23:36:02.845Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b4/298628d98c72b57e57f7165ae6a481a1deaf6f3c28262a6e4c739c275930/numpy-2.4.6-cp314-cp314-win32.whl", hash = "sha256:aaf159caa35993cb1f56fb9b8e4610d35758e7ca005412eb1daa856a78c9c4b1", size = 6010196, upload-time = "2026-05-18T23:36:05.92Z" }, + { url = "https://files.pythonhosted.org/packages/df/ac/46de6dda46478f7942f839e094970be2d4a861e005c4b3bf07c92e291a09/numpy-2.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:b507f5c4c1d508876d1819b6bf9a49d365b96320b5d4993426b33a23ca4b8261", size = 12450334, upload-time = "2026-05-18T23:36:09.107Z" }, + { url = "https://files.pythonhosted.org/packages/78/92/b8b798ac784102c0da830d2257d59358e3d3d90d1e2b3f2575dad976c5cf/numpy-2.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:6f41ae150c4e32db4f3310cdaf64b1593a03dbabe29eec77fc9b50fe64061df6", size = 10495678, upload-time = "2026-05-18T23:36:12.766Z" }, + { url = "https://files.pythonhosted.org/packages/30/34/ec28d1aa8115971537c01469ab2011ee96827930f0a124de1000cc2a7ed7/numpy-2.4.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ece3d2cfe132e7d51f44a832b303895e6f2d499c5e74dfbdb06ee246147a304a", size = 14823672, upload-time = "2026-05-18T23:36:16.473Z" }, + { url = "https://files.pythonhosted.org/packages/16/bd/f6d1fede4e54e8042a7ff97bb495510f3c220f94bcd9e8b228e87c92cc0d/numpy-2.4.6-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:e3e5193ef5a3dc73bceee50f7fdc2c90dbb76c42df8d8fae3d1067a583df579e", size = 5328731, upload-time = "2026-05-18T23:36:19.767Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f0/e105b9e2fd728a9910103884decd6951d9dd73896b914a98d9a231de02ee/numpy-2.4.6-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:17f9ade344e7d9b464a084d69bcf18fc691cb1db67c62ed80820bf4926d78f0e", size = 6649805, upload-time = "2026-05-18T23:36:22.266Z" }, + { url = "https://files.pythonhosted.org/packages/82/dd/1206a7ca6ab15e3f02069707ca96222e202af681bb73756da7527f3cb837/numpy-2.4.6-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cd5ffd25db4e7ba6a375693b3fc0fc1791ec636c17db3720da19bde7180ec43", size = 15730496, upload-time = "2026-05-18T23:36:25.713Z" }, + { url = "https://files.pythonhosted.org/packages/51/e7/38d3ea825dcab85a591734decb2f6c67caa7c8367d374df1a1c3842f9b07/numpy-2.4.6-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d92c3819208a60205a12a245c91ad70cb0a85336659b19b834205573ac8456e", size = 16679616, upload-time = "2026-05-18T23:36:29.652Z" }, + { url = "https://files.pythonhosted.org/packages/93/b7/caabfdf53edf663e0b4eb74d7d405d83baef09eb5e83bcd32d601d72b93e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e85b752a1e912b70eaad4fafbd4d1238007ab221de2009b9a2f5ae7461239895", size = 17085145, upload-time = "2026-05-18T23:36:33.449Z" }, + { url = "https://files.pythonhosted.org/packages/f9/45/68d7c33a6bcf3e5aa3bdbd57a367e6f615286dfd6482f97e8ffeb734306e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:29cb7f67d10b479ff07c17d33e39f78c07f71c40ef30d63c153d340e96cd3fb4", size = 18403813, upload-time = "2026-05-18T23:36:37.369Z" }, + { url = "https://files.pythonhosted.org/packages/9c/50/0753655aa844c99cd9e018aacf76f130f1bd81d881bb74bc0aef5d73a8ba/numpy-2.4.6-cp314-cp314t-win32.whl", hash = "sha256:260a5d70215b61ab4fadf5c7baacd64821842975eea312125ed3c39a6391b063", size = 6156982, upload-time = "2026-05-18T23:36:40.817Z" }, + { url = "https://files.pythonhosted.org/packages/b2/d4/7c67becf668f973cb490cec3e98dfd799d866f9c989a54d355672cfa0db6/numpy-2.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:81a1cca95ed5bb92aa8b10dd2cdc9a0d3853a50fad926c28b5d7e8ea54389627", size = 12638908, upload-time = "2026-05-18T23:36:43.996Z" }, + { url = "https://files.pythonhosted.org/packages/43/bb/e1c71a4295b1b1d1393d50dbb4f2a36283c6859d9d3892e84f00ec5a91d5/numpy-2.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:0c9136e14ed34a9e343a31c533d78a9813a69a3148332bce5e9821cb2f996e66", size = 10565867, upload-time = "2026-05-18T23:36:47.114Z" }, +] + +[[package]] +name = "openai" +version = "2.41.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3c/a6/5815fe2e2aca74b36c650d1bd43b69827cee568073d0d2d9b6fc5aaac80c/openai-2.41.0.tar.gz", hash = "sha256:db5c362acd6604b84f076abbefa66826ea4b46ecba2954ed866e6a149a1352c0", size = 783525, upload-time = "2026-06-03T22:39:40.719Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/51/d82bb424e8aa372190c5233253a2ceb399a778747d18b42cff487411e663/openai-2.41.0-py3-none-any.whl", hash = "sha256:20cc7952e8501c7e5773dd2ef7be437bae9cb549044902e1041a83a54516e375", size = 1353378, upload-time = "2026-06-03T22:39:38.964Z" }, +] + +[[package]] +name = "openai-agents" +version = "0.17.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "griffelib" }, + { name = "mcp" }, + { name = "openai" }, + { name = "pydantic" }, + { name = "requests" }, + { name = "types-requests" }, + { name = "typing-extensions" }, + { name = "websockets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e3/48/ba0ad614c66d88100d61b9ddaf0793022a425b5b958c0139c2f1c7473764/openai_agents-0.17.4.tar.gz", hash = "sha256:6af9afd4b40de23493c9ab285c28cd4e8fd088240af6e96e2dee45826ad568fd", size = 5409840, upload-time = "2026-05-26T08:55:10.459Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/89/adf09ec269d4de1c7be37c747d8488aac51b58d5589a9c1dd55f3c1e8e05/openai_agents-0.17.4-py3-none-any.whl", hash = "sha256:feea8264c9812bba7c526a01f6efd4f8c0efdb348c2233c36ff9c292a9d465af", size = 842963, upload-time = "2026-05-26T08:55:08.767Z" }, +] + +[[package]] +name = "openai-guardrails" +version = "0.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "openai" }, + { name = "openai-agents" }, + { name = "pip" }, + { name = "presidio-analyzer" }, + { name = "pydantic" }, + { name = "thinc" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bb/e4/163e05d8643c9abaaf90ea2ff2447bd0bf87ddb70da631f3fc4f048dae99/openai_guardrails-0.2.1.tar.gz", hash = "sha256:71e4d76744c60deb0de5640b9f65e8437e826d4dbc7bc34dd3aac4f57f0df168", size = 2521064, upload-time = "2025-12-15T23:54:29.419Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/de/b02f27f8a022804e64b09dfa1f0ca477546dbb3b83257a5961fa2013d1ac/openai_guardrails-0.2.1-py3-none-any.whl", hash = "sha256:d2d27735ec0d8a801c2398b600ecb484ae063f93502ead146be2ad36436eba36", size = 160382, upload-time = "2025-12-15T23:54:27.684Z" }, +] + +[[package]] +name = "openinference-instrumentation" +version = "0.1.53" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "openinference-semantic-conventions" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-sdk" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f9/b6/c0e7e047ae4962f2755a3bc9141fdd6272c75c74e47dfc6aa71978a9b78f/openinference_instrumentation-0.1.53.tar.gz", hash = "sha256:3c0c145cf6e13cfa630b29d0e3ca806f3821470ffca7922f1590e3970fadd4da", size = 33712, upload-time = "2026-06-02T16:37:21.771Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/bb/01262d9945c476e15aa21bb9ca05b18604e525d73d9761cadb677f485198/openinference_instrumentation-0.1.53-py3-none-any.whl", hash = "sha256:f43695080eded47b1e03ff1b19cb5c23ea4409459cfe16c5b5748d5656832eb1", size = 40958, upload-time = "2026-06-02T16:37:20.69Z" }, +] + +[[package]] +name = "openinference-instrumentation-openai-agents" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "openinference-instrumentation" }, + { name = "openinference-semantic-conventions" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "typing-extensions" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8f/d5/4981d08e7e334a874a932b29df9a1299c4c3f68021a5a3db223681ad10b1/openinference_instrumentation_openai_agents-1.6.0.tar.gz", hash = "sha256:aaeb5db16c19f33c0a2203fcb44d252134281dc123c16b130c82704b3d65fef5", size = 26065, upload-time = "2026-06-03T18:14:59.857Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/8a/e5e9a34f657911268f69222cc8733c047bfb2efdfc7fc4652e2bfd1ada55/openinference_instrumentation_openai_agents-1.6.0-py3-none-any.whl", hash = "sha256:cfa4cf7a1089f94bdf229871f71ea2408c6409e6326ddb8e7c606d771197499a", size = 28446, upload-time = "2026-06-03T18:14:58.492Z" }, +] + +[[package]] +name = "openinference-semantic-conventions" +version = "0.1.30" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/51/8ba1182ee86fc79793d5ff2d11e7fdcda10ded2d01f3e46ca6fcf0568213/openinference_semantic_conventions-0.1.30.tar.gz", hash = "sha256:81fece76e09c83789e35c393b8b30523481eeabf1008745b955631a53e3221d9", size = 13391, upload-time = "2026-05-22T21:10:44.065Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/76/5b7e78cf0de38589b821bbe8e9c29c59a6e76edfb980488d0854cbb90f7c/openinference_semantic_conventions-0.1.30-py3-none-any.whl", hash = "sha256:36d946d3f95f699b7c4b12324ae9c1f02d6c7750df11eece56aa159cff430b3d", size = 10911, upload-time = "2026-05-22T21:10:43.04Z" }, +] + +[[package]] +name = "opentelemetry-api" +version = "1.42.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b4/1c/125e1c936c0873796771b7f04f6c93b9f1bf5d424cea90fda94a99f61da8/opentelemetry_api-1.42.1.tar.gz", hash = "sha256:56c63bea9f77b62856be8c47600474acad853b2924b99b1687c4cb6297166716", size = 72296, upload-time = "2026-05-21T16:32:49.335Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/ca/9520cc1f3dfbbd03ac5903bbf55833e257bc64b1cf30fa8b0d6df374d821/opentelemetry_api-1.42.1-py3-none-any.whl", hash = "sha256:51a69edacadbc03a8950ace1c4c21099cacc538820ac2c9e36277e78cebba714", size = 61311, upload-time = "2026-05-21T16:32:28.822Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation" +version = "0.63b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "packaging" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/6d/4de72d97ff54db1ed270c7a59c9b904b917c0ac7af429c086c388b824ddb/opentelemetry_instrumentation-0.63b1.tar.gz", hash = "sha256:32368d6ae52c8de20aa790a6ad86b10a76f09956092337ae37d675773990e541", size = 41081, upload-time = "2026-05-21T16:36:14.206Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/a1/9314e621c143e4d82a5bf7a43c2ff7a745d31023506336857607c8c543cc/opentelemetry_instrumentation-0.63b1-py3-none-any.whl", hash = "sha256:f1986716d52cc316ea5f60189098726a9071d8ecc0eee96c9ed110be08bade9c", size = 35577, upload-time = "2026-05-21T16:34:56.818Z" }, +] + +[[package]] +name = "opentelemetry-sdk" +version = "1.42.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/40/f7/b390bd9bfd703bf98a68fea1f27786c6872331fd617164a54b8a59bdc008/opentelemetry_sdk-1.42.1.tar.gz", hash = "sha256:8c834e8f8c9ba4171d4ec843d0cb8a67e4c7394d3f9e9297e582cbd9456ddbf7", size = 239262, upload-time = "2026-05-21T16:33:04.641Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/6b/4287766cfbde577ae2272e8884abac325aeaac0d64f41c61d5b8cc595105/opentelemetry_sdk-1.42.1-py3-none-any.whl", hash = "sha256:083cd4bbfaa5aa7b5a9e552430d9951219967cfb27aa61feb13a77aba1fc839d", size = 170907, upload-time = "2026-05-21T16:32:45.894Z" }, +] + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.63b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/93/99/4d7dd6df64795951413ce6e815f8cf1eb191daf7196ae86574589643d5f3/opentelemetry_semantic_conventions-0.63b1.tar.gz", hash = "sha256:3daf963611334b365e98a57438183eb012d3bfb40b2d931a9af613476b8701a9", size = 148340, upload-time = "2026-05-21T16:33:05.455Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/7a/7fe66f5f3682b1dd47d88cc4e11f1c6c0966b737de2d16671146e23c39a5/opentelemetry_semantic_conventions-0.63b1-py3-none-any.whl", hash = "sha256:dfe5ef4dee82586b746f522b818ceb298d00b3d59f660042bd79404bff8d0682", size = 203713, upload-time = "2026-05-21T16:32:47.016Z" }, +] + +[[package]] +name = "orjson" +version = "3.11.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/0c/964746fcafbd16f8ff53219ad9f6b412b34f345c75f384ad434ceaadb538/orjson-3.11.9.tar.gz", hash = "sha256:4fef17e1f8722c11587a6ef18e35902450221da0028e65dbaaa543619e68e48f", size = 5599163, upload-time = "2026-05-06T15:11:08.309Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/6d/11867a3ffa3a3608d84a4de51ef4dd0896d6b5cc9132fbe1daf593e677bc/orjson-3.11.9-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9ef6fe90aadef185c7b128859f40beb24720b4ecea95379fc9000931179c3a49", size = 228515, upload-time = "2026-05-06T15:09:57.265Z" }, + { url = "https://files.pythonhosted.org/packages/24/75/05912954c8b288f34fcf5cd4b9b071cb4f6e77b9961e175e56ebb258089f/orjson-3.11.9-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:e5c9b8f28e726e97d97696c826bc7bea5d71cecd63576dba92924a32c1961291", size = 128409, upload-time = "2026-05-06T15:09:59.063Z" }, + { url = "https://files.pythonhosted.org/packages/ab/86/1c3a47df3bc8191ea9ac51603bbb872a95167a364320c269f2557911f406/orjson-3.11.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26a473dbb4162108b27901492546f83c76fdcea3d0eadff00ae7a07e18dcce09", size = 132106, upload-time = "2026-05-06T15:10:00.798Z" }, + { url = "https://files.pythonhosted.org/packages/d7/cf/b33b5f3e695ae7d63feef9d915c37cc3b8f465493dcd4f8e0b4c697a2366/orjson-3.11.9-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:011382e2a60fda9d46f1cdee31068cfc52ffe952b587d683ec0463002802a0f4", size = 127864, upload-time = "2026-05-06T15:10:02.15Z" }, + { url = "https://files.pythonhosted.org/packages/31/6a/6cf69385a58208024fcb8c014e2141b8ce838aba6492b589f8acfff97fab/orjson-3.11.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c2d3dc759490128c5c1711a53eeaa8ee1d437fd0038ffd2b6008abf46db3f882", size = 135213, upload-time = "2026-05-06T15:10:03.515Z" }, + { url = "https://files.pythonhosted.org/packages/e8/f8/0b1bd3e8f2efcdd376af5c8cfd79eaf13f018080c0089c80ebd724e3c7fb/orjson-3.11.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d8ea516b3726d190e1b4297e6f4e7a8650347ae053868a18163b4dd3641d1fff", size = 145994, upload-time = "2026-05-06T15:10:05.083Z" }, + { url = "https://files.pythonhosted.org/packages/f3/59/dab79f61044c529d2c81aecdc589b1f833a1c8dec11ba3b1c2498a02ca7e/orjson-3.11.9-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:380cdce7ba24989af81d0a7013d0aaec5d0e2a21734c0e2681b1bc4f141957fe", size = 132744, upload-time = "2026-05-06T15:10:06.853Z" }, + { url = "https://files.pythonhosted.org/packages/0e/a4/82b7a2fe5d8a67a59ed831b24d59a3d46ea7d207b66e1602d376541d94a6/orjson-3.11.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be4fa4f0af7fa18951f7ab3fc2148e223af211bf03f59e1c6034ec3f97f21d61", size = 134014, upload-time = "2026-05-06T15:10:08.213Z" }, + { url = "https://files.pythonhosted.org/packages/50/c7/375e83a76851b73b2e39f3bcf0e5a19e2b89bad13e5bca97d0b293d27f24/orjson-3.11.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a8f5f8bc7ce7d59f08d9f99fa510c06496164a24cb5f3d34537dbd9ca30132e2", size = 141509, upload-time = "2026-05-06T15:10:09.595Z" }, + { url = "https://files.pythonhosted.org/packages/7f/7c/49d5d82a3d3097f641f094f552131f1e2723b0b8cb0fa2874ab65ecfffa6/orjson-3.11.9-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:4d7fde5501b944f83b3e665e1b31343ff6e154b15560a16b7130ea1e594a4206", size = 415127, upload-time = "2026-05-06T15:10:11.049Z" }, + { url = "https://files.pythonhosted.org/packages/3a/dc/7446c538590d55f455647e5f3c61fc33f7108714e7afcffa6a2a033f8350/orjson-3.11.9-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cde1a448023ba7d5bb4c01c5afb48894380b5e4956e0627266526587ef4e535f", size = 148025, upload-time = "2026-05-06T15:10:12.842Z" }, + { url = "https://files.pythonhosted.org/packages/df/e5/4d2d8af06f788329b4f78f8cc3679bb395392fcaa1e4d8d3c33e85308fa4/orjson-3.11.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:71e63adb0e1f1ed5d9e168f50a91ceb93ae6420731d222dc7da5c69409aa47aa", size = 136943, upload-time = "2026-05-06T15:10:14.405Z" }, + { url = "https://files.pythonhosted.org/packages/06/69/850264ccf6d80f6b174620d30a87f65c9b1490aba33fe6b62798e618cad3/orjson-3.11.9-cp312-cp312-win32.whl", hash = "sha256:2d057a602cdd19a0ad680417527c45b6961a095081c0f46fe0e03e304aac6470", size = 131606, upload-time = "2026-05-06T15:10:15.791Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d5/973a43fc9c55e20f2051e9830997649f669be0cb3ca52192087c0143f118/orjson-3.11.9-cp312-cp312-win_amd64.whl", hash = "sha256:59e403b1cc5a676da8eaf31f6254801b7341b3e29efa85f92b48d272637e77be", size = 127101, upload-time = "2026-05-06T15:10:17.129Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ae/495470f0e4a18f73fa10b7f6b84b464ec4cc5291c4e0c7c2a6c400bef006/orjson-3.11.9-cp312-cp312-win_arm64.whl", hash = "sha256:9af678d6488357948f1f84c6cd1c1d397c014e1ae2f98ae082a44eb48f602624", size = 126736, upload-time = "2026-05-06T15:10:18.645Z" }, + { url = "https://files.pythonhosted.org/packages/32/33/93fcc25907235c344ae73122f8a4e01d2d393ef062b4af7d2e2487a32c37/orjson-3.11.9-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:4bab1b2d6141fe7b32ae71dac905666ece4f94936efbfb13d55bb7739a3a6021", size = 228458, upload-time = "2026-05-06T15:10:20.079Z" }, + { url = "https://files.pythonhosted.org/packages/8f/27/b1e6dadb3c080313c03fdd8067b85e6a0460c7d8d6a1c3984ef77b904e4d/orjson-3.11.9-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:844417969855fc7a41be124aafe83dc424592a7f77cd4501900c67307122b92c", size = 128368, upload-time = "2026-05-06T15:10:21.549Z" }, + { url = "https://files.pythonhosted.org/packages/21/0f/c9ede0bf052f6b4051e64a7d4fa91b725cccf8321a6a786e86eb03519f00/orjson-3.11.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffe02797b5e9f3a9d8292ddcd289b474ad13e81ad83cd1891a240811f1d2cb81", size = 132070, upload-time = "2026-05-06T15:10:23.371Z" }, + { url = "https://files.pythonhosted.org/packages/fd/26/d398e28048dc18205bbe812f2c88cb9b40313db2470778e25964796458fe/orjson-3.11.9-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e4eed3b200023042814d2fc8a5d2e880f13b52e1ed2485e83da4f3962f7dc1a", size = 127892, upload-time = "2026-05-06T15:10:24.714Z" }, + { url = "https://files.pythonhosted.org/packages/66/60/52b0054c4c700d5aa7fc5b7ca96917400d8f061307778578e67a10e25852/orjson-3.11.9-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8aff7da9952a5ad1cef8e68017724d96c7b9a66e99e91d6252e1b133d67a7b10", size = 135217, upload-time = "2026-05-06T15:10:26.084Z" }, + { url = "https://files.pythonhosted.org/packages/d5/97/1e3dc2b2a28b7b2528f403d2fc1d79ec5f39af3bc143ab65d3ec26426385/orjson-3.11.9-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d4e98d6f3b8afed8bc8cd9718ec0cdf46661826beefb53fe8eafb37f2bf0362", size = 145980, upload-time = "2026-05-06T15:10:28.062Z" }, + { url = "https://files.pythonhosted.org/packages/fc/39/31fbfe7850f2de32dee7e7e5c09f26d403ab01e440ac96001c6b01ad3c99/orjson-3.11.9-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a81d52442a7c99b3662333235b3adf96a1715864658b35bb797212be7bddb97", size = 132738, upload-time = "2026-05-06T15:10:29.727Z" }, + { url = "https://files.pythonhosted.org/packages/a1/08/dca0082dd2a194acb93e5457e73455388e2e2ca464a2672449a9ddbb679d/orjson-3.11.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e39364e726a8fff737309aff059ff67d8a8c8d5b677be7bb49a8b3e84b7e218", size = 134033, upload-time = "2026-05-06T15:10:31.152Z" }, + { url = "https://files.pythonhosted.org/packages/11/d4/5bdb0626801230139987385554c5d4c42255218ac906525bf4347f22cd95/orjson-3.11.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4fd66214623f1b17501df9f0543bef0b833979ab5b6ded1e1d123222866aa8c9", size = 141492, upload-time = "2026-05-06T15:10:32.641Z" }, + { url = "https://files.pythonhosted.org/packages/fa/88/a21fb53b3ede6703aede6dce4710ed4111e5b201cfa6bbff5e544f9d47d7/orjson-3.11.9-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:8ecc30f10465fa1e0ce13fd01d9e22c316e5053a719a8d915d4545a09a5ff677", size = 415087, upload-time = "2026-05-06T15:10:34.438Z" }, + { url = "https://files.pythonhosted.org/packages/3d/57/1b30daf70f0d8180e9a73cefbfbdd99e4bf19eb020466502b01fba7e0e50/orjson-3.11.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:97db4c94a7db398a5bd636273324f0b3fd58b350bbbac8bb380ceb825a9b40f4", size = 148031, upload-time = "2026-05-06T15:10:36.358Z" }, + { url = "https://files.pythonhosted.org/packages/04/83/45fbb6d962e260807f99441db9613cee868ceda4baceda59b3720a563f97/orjson-3.11.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9f78cf8fec5bd627f4082b8dfeac7871b43d7f3274904492a43dab39f18a19a0", size = 136915, upload-time = "2026-05-06T15:10:38.013Z" }, + { url = "https://files.pythonhosted.org/packages/5f/cc/2d10025f9056d376e4127ec05a5808b218d46f035fdc08178a5411b34250/orjson-3.11.9-cp313-cp313-win32.whl", hash = "sha256:d4087e5c0209a0a8efe4de3303c234b9c44d1174161dcd851e8eea07c7560b32", size = 131613, upload-time = "2026-05-06T15:10:39.569Z" }, + { url = "https://files.pythonhosted.org/packages/67/bd/2775ff28bfe883b9aa1ff348300542eb2ef1ee18d8ae0e3a49846817a865/orjson-3.11.9-cp313-cp313-win_amd64.whl", hash = "sha256:051b102c93b4f634e89f3866b07b9a9a98915ada541f4ec30f177067b2694979", size = 127086, upload-time = "2026-05-06T15:10:41.262Z" }, + { url = "https://files.pythonhosted.org/packages/91/2b/d26799e580939e32a7da9a39531bc9e58e15ca32ffaa6a8cb3e9bb0d22cd/orjson-3.11.9-cp313-cp313-win_arm64.whl", hash = "sha256:cce9127885941bd28f080cecf1f1d288336b7e0d812c345b08be88b572796254", size = 126696, upload-time = "2026-05-06T15:10:42.651Z" }, + { url = "https://files.pythonhosted.org/packages/8e/eb/5da01e356015aee6ecfa1187ced87aef51364e306f5e695dd52719bf0e78/orjson-3.11.9-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:b6ef1979adc4bc243523f1a2ba91418030a8e29b0a99cbe7e0e2d6807d4dce6e", size = 228465, upload-time = "2026-05-06T15:10:44.097Z" }, + { url = "https://files.pythonhosted.org/packages/64/62/3e0e0c14c957133bcd855395c62b55ed4e3b0af23ffea11b032cb1dcbdb1/orjson-3.11.9-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:f36b7f32c7c0db4a719f1fc5824db4a9c6f8bd1a354debb91faf26ebf3a4c71e", size = 128364, upload-time = "2026-05-06T15:10:45.839Z" }, + { url = "https://files.pythonhosted.org/packages/5a/5a/07d8aa117211a8ed7630bda80c8c0b14d04e0f8dcf99bcf49656e4a710eb/orjson-3.11.9-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08f4d8ebb44925c794e535b2bebc507cebf32209df81de22ae285fb0d8d66de0", size = 132063, upload-time = "2026-05-06T15:10:47.267Z" }, + { url = "https://files.pythonhosted.org/packages/d6/ec/4acaf21483e18aa945be74a474c74b434f284b549f275a0a39b9f98956e9/orjson-3.11.9-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6cc7923789694fd58f001cbcac7e47abc13af4d560ebbfcf3b41a8b1a0748124", size = 122356, upload-time = "2026-05-06T15:10:48.765Z" }, + { url = "https://files.pythonhosted.org/packages/13/d8/5f0555e7638801323b7a75850f92e7dfa891bc84fe27a1ba4449170d1200/orjson-3.11.9-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea5c46eb2d3af39e806b986f4b09d5c2706a1f5afde3cbf7544ce6616127173c", size = 129592, upload-time = "2026-05-06T15:10:50.13Z" }, + { url = "https://files.pythonhosted.org/packages/b6/30/ed9860412a3603ceb3c5955bfd72d28b9d0e7ba6ed81add14f83d7114236/orjson-3.11.9-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f5d89a2ed90731df3be64bab0aa44f78bff39fdc9d71c291f4a8023aa46425b7", size = 140491, upload-time = "2026-05-06T15:10:51.582Z" }, + { url = "https://files.pythonhosted.org/packages/d0/17/adc514dea7ac7c505527febf884934b815d34f0c7b8693c1a8b39c5c4a57/orjson-3.11.9-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:25e4aed0312d292c09f61af25bba34e0b2c88546041472b09088c39a4d828af1", size = 127309, upload-time = "2026-05-06T15:10:53.329Z" }, + { url = "https://files.pythonhosted.org/packages/76/3e/c0b690253f0b82d86e99949af13533363acfb5432ecb5d53dd5b3bce9c34/orjson-3.11.9-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaea64f3f467d22e70eeed68bdccb3bc4f83f650446c4a03c59f2cba28a108db", size = 134030, upload-time = "2026-05-06T15:10:54.988Z" }, + { url = "https://files.pythonhosted.org/packages/c1/7a/bc82a0bb25e9faaf92dc4d9ef002732efc09737706af83e346788641d4a7/orjson-3.11.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a028425d1b440c5d92a6be1e1a020739dfe67ea87d96c6dbe828c1b30041728b", size = 141482, upload-time = "2026-05-06T15:10:56.663Z" }, + { url = "https://files.pythonhosted.org/packages/01/55/e69188b939f77d5d32a9833745ace31ea5ccae3ab613a1ec185d3cd2c4fb/orjson-3.11.9-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:5b192c6cf397e4455b11523c5cf2b18ed084c1bbd61b6c0926344d2129481972", size = 415178, upload-time = "2026-05-06T15:10:58.446Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1a/b8a5a7ac527e80b9cb11d51e3f6689b709279183264b9ec5c7bc680bb8b5/orjson-3.11.9-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ea407d4ccf5891d667d045fecae97a7a1e5e87b3b97f97ae1803c2e741130be0", size = 148089, upload-time = "2026-05-06T15:11:00.441Z" }, + { url = "https://files.pythonhosted.org/packages/97/4e/00503f64204bf859b37213a63927028f30fb6268cd8677fb0a5ad48155e1/orjson-3.11.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5f63aaf97afd9f6dec5b1a68e1b8da12bfccb4cb9a9a65c3e0b6c847849e7586", size = 136921, upload-time = "2026-05-06T15:11:02.176Z" }, + { url = "https://files.pythonhosted.org/packages/0d/ba/a23b82a0a8d0ed7bed4e5f5035aae751cad4ff6a1e8d2ecd14d8860f5929/orjson-3.11.9-cp314-cp314-win32.whl", hash = "sha256:e30ab17845bb9fa54ccf67fa4f9f5282652d54faa6d17452f47d0f369d038673", size = 131638, upload-time = "2026-05-06T15:11:03.696Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c3/0c6798456bade745c75c452342dabacce5798196483e77e643be1f53877d/orjson-3.11.9-cp314-cp314-win_amd64.whl", hash = "sha256:32ef5f4283a3be81913947d19608eacb7c6608026851123790cd9cc8982af34b", size = 127078, upload-time = "2026-05-06T15:11:05.123Z" }, + { url = "https://files.pythonhosted.org/packages/16/21/5a3f1e8913103b703a436a5664238e5b965ec392b555fe68943ea3691e6b/orjson-3.11.9-cp314-cp314-win_arm64.whl", hash = "sha256:eebdbdeef0094e4f5aefa20dcd4eb2368ab5e7a3b4edea27f1e7b2892e009cf9", size = 126687, upload-time = "2026-05-06T15:11:06.602Z" }, +] + +[[package]] +name = "ormsgpack" +version = "1.12.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/12/0c/f1761e21486942ab9bb6feaebc610fa074f7c5e496e6962dea5873348077/ormsgpack-1.12.2.tar.gz", hash = "sha256:944a2233640273bee67521795a73cf1e959538e0dfb7ac635505010455e53b33", size = 39031, upload-time = "2026-01-18T20:55:28.023Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/36/16c4b1921c308a92cef3bf6663226ae283395aa0ff6e154f925c32e91ff5/ormsgpack-1.12.2-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7a29d09b64b9694b588ff2f80e9826bdceb3a2b91523c5beae1fab27d5c940e7", size = 378618, upload-time = "2026-01-18T20:55:50.835Z" }, + { url = "https://files.pythonhosted.org/packages/c0/68/468de634079615abf66ed13bb5c34ff71da237213f29294363beeeca5306/ormsgpack-1.12.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b39e629fd2e1c5b2f46f99778450b59454d1f901bc507963168985e79f09c5d", size = 203186, upload-time = "2026-01-18T20:56:11.163Z" }, + { url = "https://files.pythonhosted.org/packages/73/a9/d756e01961442688b7939bacd87ce13bfad7d26ce24f910f6028178b2cc8/ormsgpack-1.12.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:958dcb270d30a7cb633a45ee62b9444433fa571a752d2ca484efdac07480876e", size = 210738, upload-time = "2026-01-18T20:56:09.181Z" }, + { url = "https://files.pythonhosted.org/packages/7b/ba/795b1036888542c9113269a3f5690ab53dd2258c6fb17676ac4bd44fcf94/ormsgpack-1.12.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58d379d72b6c5e964851c77cfedfb386e474adee4fd39791c2c5d9efb53505cc", size = 212569, upload-time = "2026-01-18T20:56:06.135Z" }, + { url = "https://files.pythonhosted.org/packages/6c/aa/bff73c57497b9e0cba8837c7e4bcab584b1a6dbc91a5dd5526784a5030c8/ormsgpack-1.12.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8463a3fc5f09832e67bdb0e2fda6d518dc4281b133166146a67f54c08496442e", size = 387166, upload-time = "2026-01-18T20:55:36.738Z" }, + { url = "https://files.pythonhosted.org/packages/d3/cf/f8283cba44bcb7b14f97b6274d449db276b3a86589bdb363169b51bc12de/ormsgpack-1.12.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:eddffb77eff0bad4e67547d67a130604e7e2dfbb7b0cde0796045be4090f35c6", size = 482498, upload-time = "2026-01-18T20:55:29.626Z" }, + { url = "https://files.pythonhosted.org/packages/05/be/71e37b852d723dfcbe952ad04178c030df60d6b78eba26bfd14c9a40575e/ormsgpack-1.12.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fcd55e5f6ba0dbce624942adf9f152062135f991a0126064889f68eb850de0dd", size = 425518, upload-time = "2026-01-18T20:55:49.556Z" }, + { url = "https://files.pythonhosted.org/packages/7a/0c/9803aa883d18c7ef197213cd2cbf73ba76472a11fe100fb7dab2884edf48/ormsgpack-1.12.2-cp312-cp312-win_amd64.whl", hash = "sha256:d024b40828f1dde5654faebd0d824f9cc29ad46891f626272dd5bfd7af2333a4", size = 117462, upload-time = "2026-01-18T20:55:47.726Z" }, + { url = "https://files.pythonhosted.org/packages/c8/9e/029e898298b2cc662f10d7a15652a53e3b525b1e7f07e21fef8536a09bb8/ormsgpack-1.12.2-cp312-cp312-win_arm64.whl", hash = "sha256:da538c542bac7d1c8f3f2a937863dba36f013108ce63e55745941dda4b75dbb6", size = 111559, upload-time = "2026-01-18T20:55:54.273Z" }, + { url = "https://files.pythonhosted.org/packages/eb/29/bb0eba3288c0449efbb013e9c6f58aea79cf5cb9ee1921f8865f04c1a9d7/ormsgpack-1.12.2-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:5ea60cb5f210b1cfbad8c002948d73447508e629ec375acb82910e3efa8ff355", size = 378661, upload-time = "2026-01-18T20:55:57.765Z" }, + { url = "https://files.pythonhosted.org/packages/6e/31/5efa31346affdac489acade2926989e019e8ca98129658a183e3add7af5e/ormsgpack-1.12.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3601f19afdbea273ed70b06495e5794606a8b690a568d6c996a90d7255e51c1", size = 203194, upload-time = "2026-01-18T20:56:08.252Z" }, + { url = "https://files.pythonhosted.org/packages/eb/56/d0087278beef833187e0167f8527235ebe6f6ffc2a143e9de12a98b1ce87/ormsgpack-1.12.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:29a9f17a3dac6054c0dce7925e0f4995c727f7c41859adf9b5572180f640d172", size = 210778, upload-time = "2026-01-18T20:55:17.694Z" }, + { url = "https://files.pythonhosted.org/packages/1c/a2/072343e1413d9443e5a252a8eb591c2d5b1bffbe5e7bfc78c069361b92eb/ormsgpack-1.12.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39c1bd2092880e413902910388be8715f70b9f15f20779d44e673033a6146f2d", size = 212592, upload-time = "2026-01-18T20:55:32.747Z" }, + { url = "https://files.pythonhosted.org/packages/a2/8b/a0da3b98a91d41187a63b02dda14267eefc2a74fcb43cc2701066cf1510e/ormsgpack-1.12.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:50b7249244382209877deedeee838aef1542f3d0fc28b8fe71ca9d7e1896a0d7", size = 387164, upload-time = "2026-01-18T20:55:40.853Z" }, + { url = "https://files.pythonhosted.org/packages/19/bb/6d226bc4cf9fc20d8eb1d976d027a3f7c3491e8f08289a2e76abe96a65f3/ormsgpack-1.12.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:5af04800d844451cf102a59c74a841324868d3f1625c296a06cc655c542a6685", size = 482516, upload-time = "2026-01-18T20:55:42.033Z" }, + { url = "https://files.pythonhosted.org/packages/fb/f1/bb2c7223398543dedb3dbf8bb93aaa737b387de61c5feaad6f908841b782/ormsgpack-1.12.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cec70477d4371cd524534cd16472d8b9cc187e0e3043a8790545a9a9b296c258", size = 425539, upload-time = "2026-01-18T20:55:24.727Z" }, + { url = "https://files.pythonhosted.org/packages/7b/e8/0fb45f57a2ada1fed374f7494c8cd55e2f88ccd0ab0a669aa3468716bf5f/ormsgpack-1.12.2-cp313-cp313-win_amd64.whl", hash = "sha256:21f4276caca5c03a818041d637e4019bc84f9d6ca8baa5ea03e5cc8bf56140e9", size = 117459, upload-time = "2026-01-18T20:55:56.876Z" }, + { url = "https://files.pythonhosted.org/packages/7a/d4/0cfeea1e960d550a131001a7f38a5132c7ae3ebde4c82af1f364ccc5d904/ormsgpack-1.12.2-cp313-cp313-win_arm64.whl", hash = "sha256:baca4b6773d20a82e36d6fd25f341064244f9f86a13dead95dd7d7f996f51709", size = 111577, upload-time = "2026-01-18T20:55:43.605Z" }, + { url = "https://files.pythonhosted.org/packages/94/16/24d18851334be09c25e87f74307c84950f18c324a4d3c0b41dabdbf19c29/ormsgpack-1.12.2-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:bc68dd5915f4acf66ff2010ee47c8906dc1cf07399b16f4089f8c71733f6e36c", size = 378717, upload-time = "2026-01-18T20:55:26.164Z" }, + { url = "https://files.pythonhosted.org/packages/b5/a2/88b9b56f83adae8032ac6a6fa7f080c65b3baf9b6b64fd3d37bd202991d4/ormsgpack-1.12.2-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46d084427b4132553940070ad95107266656cb646ea9da4975f85cb1a6676553", size = 203183, upload-time = "2026-01-18T20:55:18.815Z" }, + { url = "https://files.pythonhosted.org/packages/a9/80/43e4555963bf602e5bdc79cbc8debd8b6d5456c00d2504df9775e74b450b/ormsgpack-1.12.2-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c010da16235806cf1d7bc4c96bf286bfa91c686853395a299b3ddb49499a3e13", size = 210814, upload-time = "2026-01-18T20:55:33.973Z" }, + { url = "https://files.pythonhosted.org/packages/78/e1/7cfbf28de8bca6efe7e525b329c31277d1b64ce08dcba723971c241a9d60/ormsgpack-1.12.2-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18867233df592c997154ff942a6503df274b5ac1765215bceba7a231bea2745d", size = 212634, upload-time = "2026-01-18T20:55:28.634Z" }, + { url = "https://files.pythonhosted.org/packages/95/f8/30ae5716e88d792a4e879debee195653c26ddd3964c968594ddef0a3cc7e/ormsgpack-1.12.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b009049086ddc6b8f80c76b3955df1aa22a5fbd7673c525cd63bf91f23122ede", size = 387139, upload-time = "2026-01-18T20:56:02.013Z" }, + { url = "https://files.pythonhosted.org/packages/dc/81/aee5b18a3e3a0e52f718b37ab4b8af6fae0d9d6a65103036a90c2a8ffb5d/ormsgpack-1.12.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:1dcc17d92b6390d4f18f937cf0b99054824a7815818012ddca925d6e01c2e49e", size = 482578, upload-time = "2026-01-18T20:55:35.117Z" }, + { url = "https://files.pythonhosted.org/packages/bd/17/71c9ba472d5d45f7546317f467a5fc941929cd68fb32796ca3d13dcbaec2/ormsgpack-1.12.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f04b5e896d510b07c0ad733d7fce2d44b260c5e6c402d272128f8941984e4285", size = 425539, upload-time = "2026-01-18T20:56:04.009Z" }, + { url = "https://files.pythonhosted.org/packages/2e/a6/ac99cd7fe77e822fed5250ff4b86fa66dd4238937dd178d2299f10b69816/ormsgpack-1.12.2-cp314-cp314-win_amd64.whl", hash = "sha256:ae3aba7eed4ca7cb79fd3436eddd29140f17ea254b91604aa1eb19bfcedb990f", size = 117493, upload-time = "2026-01-18T20:56:07.343Z" }, + { url = "https://files.pythonhosted.org/packages/3a/67/339872846a1ae4592535385a1c1f93614138566d7af094200c9c3b45d1e5/ormsgpack-1.12.2-cp314-cp314-win_arm64.whl", hash = "sha256:118576ea6006893aea811b17429bfc561b4778fad393f5f538c84af70b01260c", size = 111579, upload-time = "2026-01-18T20:55:21.161Z" }, + { url = "https://files.pythonhosted.org/packages/49/c2/6feb972dc87285ad381749d3882d8aecbde9f6ecf908dd717d33d66df095/ormsgpack-1.12.2-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7121b3d355d3858781dc40dafe25a32ff8a8242b9d80c692fd548a4b1f7fd3c8", size = 378721, upload-time = "2026-01-18T20:55:52.12Z" }, + { url = "https://files.pythonhosted.org/packages/a3/9a/900a6b9b413e0f8a471cf07830f9cf65939af039a362204b36bd5b581d8b/ormsgpack-1.12.2-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ee766d2e78251b7a63daf1cddfac36a73562d3ddef68cacfb41b2af64698033", size = 203170, upload-time = "2026-01-18T20:55:44.469Z" }, + { url = "https://files.pythonhosted.org/packages/87/4c/27a95466354606b256f24fad464d7c97ab62bce6cc529dd4673e1179b8fb/ormsgpack-1.12.2-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:292410a7d23de9b40444636b9b8f1e4e4b814af7f1ef476e44887e52a123f09d", size = 212816, upload-time = "2026-01-18T20:55:23.501Z" }, + { url = "https://files.pythonhosted.org/packages/73/cd/29cee6007bddf7a834e6cd6f536754c0535fcb939d384f0f37a38b1cddb8/ormsgpack-1.12.2-cp314-cp314t-win_amd64.whl", hash = "sha256:837dd316584485b72ef451d08dd3e96c4a11d12e4963aedb40e08f89685d8ec2", size = 117232, upload-time = "2026-01-18T20:55:45.448Z" }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "pandas" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "tzdata", marker = "sys_platform == 'emscripten' or sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/87/4341c6252d1c47b08768c3d25ac487362bf403f0313ddae4a2a26c9b1b4c/pandas-3.0.3.tar.gz", hash = "sha256:696a4a00a2a2a35d4e5deb3fc946641b96c944f02230e4f76137fe35d806c4fc", size = 4651414, upload-time = "2026-05-11T18:54:29.21Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/f1/392f8c5bfc16f66a0d2d41561c01627c228fe7ed2a0d056ef11315042570/pandas-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fed2ff7fd9779120e388e285fc029bd5cf9490cdd2e4166a9ee22c0e49a9ab09", size = 10357846, upload-time = "2026-05-11T18:52:36.143Z" }, + { url = "https://files.pythonhosted.org/packages/cf/3d/b16412745651e855f357e5e66930248688378853a6e2698a214e331fba1f/pandas-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b168fc218fd80a6cbdbdbc1a97ddc7889ed057d7eb45f50d866ceab5f39904c4", size = 9899550, upload-time = "2026-05-11T18:52:38.976Z" }, + { url = "https://files.pythonhosted.org/packages/31/a8/fa2535168fffcedf67f4f6de28d2dd903a747ca7c8ea6989451aaeb3a92f/pandas-3.0.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0383c72c75cdcca61a9e116e611143902dbfd08bff356829c2f6d1cf40a9ca8c", size = 10412965, upload-time = "2026-05-11T18:52:41.915Z" }, + { url = "https://files.pythonhosted.org/packages/65/b6/09b01cdbc15224e2850365192d17b7bdebb8bdbd8780ed221fcdf0d9a515/pandas-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6dc0b3fd2169c9157deed50b4d519553a3655c8c6a96027136d654592be973a9", size = 10894600, upload-time = "2026-05-11T18:52:45.02Z" }, + { url = "https://files.pythonhosted.org/packages/c9/a4/2eb28f2fccb4ced4a2c79ab2a5dee9ade1ebf44922ebad6fea158c9f95d4/pandas-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7e65d5407dc0b394f509699650e4a2ec01c0514f21850f453fa60f3be79a5dbf", size = 11422824, upload-time = "2026-05-11T18:52:48.058Z" }, + { url = "https://files.pythonhosted.org/packages/f8/45/830bb57f533a4604b355e07edcb8ea18cf88b5f94e5fca92f27052d7c597/pandas-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f8894dc474d648fe7b6ff0ca9b0bd73950d19952bc1a6534540762c5d79d305c", size = 11950889, upload-time = "2026-05-11T18:52:50.905Z" }, + { url = "https://files.pythonhosted.org/packages/b9/c5/fc1b368f303087d20e8c9bf3d6ceb186263cfac0ade735cd938538bea839/pandas-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:c7be265b62cef88e253a941e4698604973736dcfe242fdb5198f0f7bc473cdcc", size = 9755463, upload-time = "2026-05-11T18:52:53.386Z" }, + { url = "https://files.pythonhosted.org/packages/86/bd/fda8f9705b1b09c6ebe14bfc0fa0e4ec8584d54ea673628f157ff55131af/pandas-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:557409bc4178e70ee8d9ddb494798e51ebf6ea59330f6be22c51bab2a7db6c49", size = 9066158, upload-time = "2026-05-11T18:52:56.038Z" }, + { url = "https://files.pythonhosted.org/packages/c5/90/62d8302883c44308c477e222c3daf7c813a34c8e96985882fbd53d964352/pandas-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:67b3b64c11910cfa29f4e94a14d3bff9ee693b6fc76055e7cad549cee0aec5fa", size = 10331071, upload-time = "2026-05-11T18:52:58.838Z" }, + { url = "https://files.pythonhosted.org/packages/7f/ae/6a6493c783a101f165e4356953ba3c74d6f77f0042fa7d753da9dfbb640c/pandas-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:39436b377d56d2a2e52d0395bdbee171f01068e99af5250509aceeb929f765c7", size = 9875690, upload-time = "2026-05-11T18:53:01.431Z" }, + { url = "https://files.pythonhosted.org/packages/62/7c/5df8e9f56c69a2769fbe9382a5ef8f2658c007e376434e1e2cbb57ad895f/pandas-3.0.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4be06d68f9ddcfc645b87534911da79a8fbffc7573c80e0edcf42a5020624d8", size = 10381634, upload-time = "2026-05-11T18:53:04.393Z" }, + { url = "https://files.pythonhosted.org/packages/99/68/1237369725aa617bb358263d535803e3053fdbc593513ec5ed9c9896b5b6/pandas-3.0.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a4eeb6830daf35a71cc09649bd823e2b542dac246cdee9614c6e4bd65028cd6a", size = 10891243, upload-time = "2026-05-11T18:53:07.643Z" }, + { url = "https://files.pythonhosted.org/packages/25/93/77d108e8af7222b4a503ebde0e30215b1c2e4f8e53a526431890f22d5586/pandas-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1928e07221f82db493cd4af1e23c1bfca524a19a4699887975bff68f49a72bfb", size = 11388659, upload-time = "2026-05-11T18:53:10.634Z" }, + { url = "https://files.pythonhosted.org/packages/d0/bd/eff5b4399f332ac386c853f6cd2bd3fa2ca0061b9f36ecd9c4d7c4265649/pandas-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51b1fe551acb77dac643c6fda86084d8d446c10fe64b06a9cc29c4cc8540e7f2", size = 11942880, upload-time = "2026-05-11T18:53:13.536Z" }, + { url = "https://files.pythonhosted.org/packages/2c/20/559ace4200982c3887d0b86bfd0d856a2143ef8ddab63cc07934951a964c/pandas-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:a82d532a3351d435432cd913edbccaf8b8e01d4dd0e5ced5a8d2e8ecd94c7e44", size = 9757091, upload-time = "2026-05-11T18:53:16.306Z" }, + { url = "https://files.pythonhosted.org/packages/3a/66/69055a09fe200f29f922a3eeec4804611900b95f52d932ece3393c3c0c19/pandas-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:275c14e0fce14a2ec20eee474aecd305478ea3c1e6f6a9d8fe219a165542717e", size = 9057282, upload-time = "2026-05-11T18:53:18.768Z" }, + { url = "https://files.pythonhosted.org/packages/57/0e/efe801b0e6811e8e650cd21b7f2608e30f08a7067e2bf6e8752b0d56ee3c/pandas-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:46997386d528eb40376ecd6b033cf4a8a1e5282580f68f43de875b78cba2199d", size = 10767016, upload-time = "2026-05-11T18:53:21.227Z" }, + { url = "https://files.pythonhosted.org/packages/ea/dc/eb55135a1d5f0f0519f28da1f609a206d2cad1f9c35c32d51e38dd7261ae/pandas-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:261e308dfb22448384b7580cf719d2f998fe2966c92893c3e77d14008af1f066", size = 10420210, upload-time = "2026-05-11T18:53:23.982Z" }, + { url = "https://files.pythonhosted.org/packages/c6/3e/b1d5d955ce33ffecb407465a60bc32769d74fcf68224b7ae67ae11d4dea4/pandas-3.0.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dd1a5d1def6a46002e964510bdc67c368aa0951df5d1d9f8365336f5a1f490cd", size = 10336126, upload-time = "2026-05-11T18:53:26.731Z" }, + { url = "https://files.pythonhosted.org/packages/f5/76/a01261711ab60a22d71b862f0de20e4c504bf80457270ad8cb42110f6abc/pandas-3.0.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d72828c20c6d6e83e1e22a6a3b47b326b71664112fa9705dcbccfd7a39b62085", size = 10728051, upload-time = "2026-05-11T18:53:29.125Z" }, + { url = "https://files.pythonhosted.org/packages/e9/21/ea191195e587b18cf682e97f433f81b2d0fbe341380e80a3e0d6e4403c8e/pandas-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d26cbe1fcfc12e8fd900e2454163e466b2d3af84f7c75481df7683ffc073d870", size = 11350796, upload-time = "2026-05-11T18:53:32.056Z" }, + { url = "https://files.pythonhosted.org/packages/64/69/f0eaaf54939f0e8c6768fd06be9af2cef9b36048b96dfb9e1b2c685a807e/pandas-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3e91cec1879ada0624fc3dc9953c5cbd60208e59c0db28f540c5d6d47502422f", size = 11799741, upload-time = "2026-05-11T18:53:34.985Z" }, + { url = "https://files.pythonhosted.org/packages/45/a4/865e0e510cae5fc2194de4db28be638952de942571ba9125934fd9c01d47/pandas-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:08d789b41f87e0905880e293cedf6197ce71fe67cc081358b1e148a491b9bd13", size = 10499958, upload-time = "2026-05-11T18:53:37.857Z" }, + { url = "https://files.pythonhosted.org/packages/86/54/effdcc3c0ff7a08037889200e148ebe94c16c4f653be078c7b3675955df1/pandas-3.0.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:3650109c0f22879df8bd6179ab9ee3d7f1d1d4e7e0094a3f0032d9f51e2e64ac", size = 10336065, upload-time = "2026-05-11T18:53:41.099Z" }, + { url = "https://files.pythonhosted.org/packages/68/10/bf2d6738d72748b961a3751ab89522d58c54efc36a8e1a12161216cd45cf/pandas-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:bab900348131a7db1f69a7309ef141fd5680f1487094193bcbbb61791573bf8f", size = 9926101, upload-time = "2026-05-11T18:53:43.515Z" }, + { url = "https://files.pythonhosted.org/packages/ae/e9/e35cf11c8a136e757b956f5f0efdcaa50aecde85ea055f1898dfc68262f3/pandas-3.0.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba7e08b9ac1d54569cd1e256e3668975ed624d6826f7b68df0342b012007bddb", size = 10457553, upload-time = "2026-05-11T18:53:46.394Z" }, + { url = "https://files.pythonhosted.org/packages/58/3b/1cdec6772bdbaf7b25dab360c59f03cadf05492dd724c6540af905389b07/pandas-3.0.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d71c63ae4ebdbf70209742096f1fc46a83a0613c99d4b23766cced9ff8cd62a", size = 10914065, upload-time = "2026-05-11T18:53:49.134Z" }, + { url = "https://files.pythonhosted.org/packages/c4/c2/1ef644445fcd72e3627bceec77e3560636f87ddce4ed841afe76b83b5bf9/pandas-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e3a2ec42c98ffa2565a67e08e218d06d72576d758d90facb7c00805194d8f360", size = 11459188, upload-time = "2026-05-11T18:53:52.527Z" }, + { url = "https://files.pythonhosted.org/packages/7e/49/4d8d4f42cbc9c4adc7a1870f269c02cbd6cd40d059622c06fb298addcbad/pandas-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:335f62418ed562cfc3c49e9e196375c28b729dcef8543abf4f9438e381bf3c76", size = 11982966, upload-time = "2026-05-11T18:53:55.043Z" }, + { url = "https://files.pythonhosted.org/packages/38/55/792619469bab9882d8bbd5865d45a72f6478762d04a9af4bf0d08c503e95/pandas-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:3c20a521bbb85902f79f7270c80a59e1b5452d96d170c034f207181870f97ac5", size = 9876755, upload-time = "2026-05-11T18:53:58.067Z" }, + { url = "https://files.pythonhosted.org/packages/2a/af/33c469653b0ba03b50c3a98192d4c07f0c75c66b263ceb097fce0ee97d31/pandas-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:a2d2dff8a04f3917b55ab3910c32990f8ddf7eceba114947838cefa976a68977", size = 9198658, upload-time = "2026-05-11T18:54:00.733Z" }, + { url = "https://files.pythonhosted.org/packages/a2/fa/b8c257bd76b8bd060c3a9151c1fca05e9b9c5e3af5d0f549c0356f6d143d/pandas-3.0.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:0d589105b3c14645af1738ff279b2995102d8f7a03b0a66dc8d95550eb513e04", size = 10787242, upload-time = "2026-05-11T18:54:03.564Z" }, + { url = "https://files.pythonhosted.org/packages/54/eb/f19206ffb0bf1919002969aa448b4702c6594845156a6f8050674855aac3/pandas-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:13fc1e853d9e04743d11ba75a985ccbc2a317fe07d8af61e445a6fd24dacd6a6", size = 10436369, upload-time = "2026-05-11T18:54:06.311Z" }, + { url = "https://files.pythonhosted.org/packages/fd/24/c7c39fb4fe22b71a0c2d78bf0c585c600092d85f94f086d2b3b2f6ca27e2/pandas-3.0.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:819959dab7bbd0049c15623fbac4e29a191b9528160a61fb1032242d8ced2d9c", size = 10358306, upload-time = "2026-05-11T18:54:09.085Z" }, + { url = "https://files.pythonhosted.org/packages/16/ec/dd2a9eb7fa1204df88c0864164e35b228ac581062ac612ba0a67fd812e4c/pandas-3.0.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:60ae316d3fd75d1858d450d0db0103ea2be3e7d4a95ec2f064f7e2ae63f7b028", size = 10758394, upload-time = "2026-05-11T18:54:11.956Z" }, + { url = "https://files.pythonhosted.org/packages/95/6e/00c61ea8e85b4f6d8d35e11852a1a4998fc7fafc91c6a602d1cc9c972d64/pandas-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bd3a518890b400d32f9023722dc9a9a5c969f00b415419a3c06c043f09bb5d7d", size = 11375717, upload-time = "2026-05-11T18:54:14.539Z" }, + { url = "https://files.pythonhosted.org/packages/31/89/8fc1c268969fac43688d65fd92e67df24bd128d53cb4d2eee534cd307399/pandas-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9c39be2d709d01fa972a0cabc522389fceca4f3969332ba25a7d6c5802cf976a", size = 11828897, upload-time = "2026-05-11T18:54:17.146Z" }, + { url = "https://files.pythonhosted.org/packages/56/3b/e7d20dea247a3e6dc0bd8a6953854afbedc03951def4e7371e05e7263e25/pandas-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4db8c527972a821cf5286b40ccc57642a39bc62e62022b42f99f8a67fca8c3a1", size = 10900855, upload-time = "2026-05-11T18:54:19.72Z" }, + { url = "https://files.pythonhosted.org/packages/0f/54/68a0978d1ef8502b8492099beaa6e7a0c1b32e3b5d4f677f5810cb08711c/pandas-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b2c95f8bfc1ee412bf482605d7bfd30c12d1d26bd59fdd91efeef1d4718decb1", size = 9466464, upload-time = "2026-05-11T18:54:22.754Z" }, +] + +[[package]] +name = "pathspec" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" }, +] + +[[package]] +name = "phonenumbers" +version = "9.0.31" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/20/51f0eafd5819e923d09c6f508a764b84d00e7e7d96c4087627a1adab8d30/phonenumbers-9.0.31.tar.gz", hash = "sha256:287dbd012b12cf9bcc4803b55a29e0d204db5a0df747d38c9130fee1b1b6b0aa", size = 2306679, upload-time = "2026-05-23T06:09:06.338Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/d3/17268202ea3fa5a597c98ca5dd178ca013f96b42373c54604e59b3bb8d02/phonenumbers-9.0.31-py2.py3-none-any.whl", hash = "sha256:f308c1f425e2637b92ff34a174f0888ddf47aacfa6199531f7097e00ad90f57c", size = 2595455, upload-time = "2026-05-23T06:09:03.35Z" }, +] + +[[package]] +name = "pillow" +version = "12.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/21/c2bcdd5906101a30244eaffc1b6e6ce71a31bd0742a01eb89e660ebfac2d/pillow-12.2.0.tar.gz", hash = "sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5", size = 46987819, upload-time = "2026-04-01T14:46:17.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/be/7482c8a5ebebbc6470b3eb791812fff7d5e0216c2be3827b30b8bb6603ed/pillow-12.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d192a155bbcec180f8564f693e6fd9bccff5a7af9b32e2e4bf8c9c69dbad6b5", size = 5308279, upload-time = "2026-04-01T14:43:13.246Z" }, + { url = "https://files.pythonhosted.org/packages/d8/95/0a351b9289c2b5cbde0bacd4a83ebc44023e835490a727b2a3bd60ddc0f4/pillow-12.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3f40b3c5a968281fd507d519e444c35f0ff171237f4fdde090dd60699458421", size = 4695490, upload-time = "2026-04-01T14:43:15.584Z" }, + { url = "https://files.pythonhosted.org/packages/de/af/4e8e6869cbed569d43c416fad3dc4ecb944cb5d9492defaed89ddd6fe871/pillow-12.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:03e7e372d5240cc23e9f07deca4d775c0817bffc641b01e9c3af208dbd300987", size = 6284462, upload-time = "2026-04-01T14:43:18.268Z" }, + { url = "https://files.pythonhosted.org/packages/e9/9e/c05e19657fd57841e476be1ab46c4d501bffbadbafdc31a6d665f8b737b6/pillow-12.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b86024e52a1b269467a802258c25521e6d742349d760728092e1bc2d135b4d76", size = 8094744, upload-time = "2026-04-01T14:43:20.716Z" }, + { url = "https://files.pythonhosted.org/packages/2b/54/1789c455ed10176066b6e7e6da1b01e50e36f94ba584dc68d9eebfe9156d/pillow-12.2.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7371b48c4fa448d20d2714c9a1f775a81155050d383333e0a6c15b1123dda005", size = 6398371, upload-time = "2026-04-01T14:43:23.443Z" }, + { url = "https://files.pythonhosted.org/packages/43/e3/fdc657359e919462369869f1c9f0e973f353f9a9ee295a39b1fea8ee1a77/pillow-12.2.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62f5409336adb0663b7caa0da5c7d9e7bdbaae9ce761d34669420c2a801b2780", size = 7087215, upload-time = "2026-04-01T14:43:26.758Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f8/2f6825e441d5b1959d2ca5adec984210f1ec086435b0ed5f52c19b3b8a6e/pillow-12.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:01afa7cf67f74f09523699b4e88c73fb55c13346d212a59a2db1f86b0a63e8c5", size = 6509783, upload-time = "2026-04-01T14:43:29.56Z" }, + { url = "https://files.pythonhosted.org/packages/67/f9/029a27095ad20f854f9dba026b3ea6428548316e057e6fc3545409e86651/pillow-12.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc3d34d4a8fbec3e88a79b92e5465e0f9b842b628675850d860b8bd300b159f5", size = 7212112, upload-time = "2026-04-01T14:43:32.091Z" }, + { url = "https://files.pythonhosted.org/packages/be/42/025cfe05d1be22dbfdb4f264fe9de1ccda83f66e4fc3aac94748e784af04/pillow-12.2.0-cp312-cp312-win32.whl", hash = "sha256:58f62cc0f00fd29e64b29f4fd923ffdb3859c9f9e6105bfc37ba1d08994e8940", size = 6378489, upload-time = "2026-04-01T14:43:34.601Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7b/25a221d2c761c6a8ae21bfa3874988ff2583e19cf8a27bf2fee358df7942/pillow-12.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f84204dee22a783350679a0333981df803dac21a0190d706a50475e361c93f5", size = 7084129, upload-time = "2026-04-01T14:43:37.213Z" }, + { url = "https://files.pythonhosted.org/packages/10/e1/542a474affab20fd4a0f1836cb234e8493519da6b76899e30bcc5d990b8b/pillow-12.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:af73337013e0b3b46f175e79492d96845b16126ddf79c438d7ea7ff27783a414", size = 2463612, upload-time = "2026-04-01T14:43:39.421Z" }, + { url = "https://files.pythonhosted.org/packages/4a/01/53d10cf0dbad820a8db274d259a37ba50b88b24768ddccec07355382d5ad/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8297651f5b5679c19968abefd6bb84d95fe30ef712eb1b2d9b2d31ca61267f4c", size = 4100837, upload-time = "2026-04-01T14:43:41.506Z" }, + { url = "https://files.pythonhosted.org/packages/0f/98/f3a6657ecb698c937f6c76ee564882945f29b79bad496abcba0e84659ec5/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:50d8520da2a6ce0af445fa6d648c4273c3eeefbc32d7ce049f22e8b5c3daecc2", size = 4176528, upload-time = "2026-04-01T14:43:43.773Z" }, + { url = "https://files.pythonhosted.org/packages/69/bc/8986948f05e3ea490b8442ea1c1d4d990b24a7e43d8a51b2c7d8b1dced36/pillow-12.2.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:766cef22385fa1091258ad7e6216792b156dc16d8d3fa607e7545b2b72061f1c", size = 3640401, upload-time = "2026-04-01T14:43:45.87Z" }, + { url = "https://files.pythonhosted.org/packages/34/46/6c717baadcd62bc8ed51d238d521ab651eaa74838291bda1f86fe1f864c9/pillow-12.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5d2fd0fa6b5d9d1de415060363433f28da8b1526c1c129020435e186794b3795", size = 5308094, upload-time = "2026-04-01T14:43:48.438Z" }, + { url = "https://files.pythonhosted.org/packages/71/43/905a14a8b17fdb1ccb58d282454490662d2cb89a6bfec26af6d3520da5ec/pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56b25336f502b6ed02e889f4ece894a72612fe885889a6e8c4c80239ff6e5f5f", size = 4695402, upload-time = "2026-04-01T14:43:51.292Z" }, + { url = "https://files.pythonhosted.org/packages/73/dd/42107efcb777b16fa0393317eac58f5b5cf30e8392e266e76e51cff28c3d/pillow-12.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f1c943e96e85df3d3478f7b691f229887e143f81fedab9b20205349ab04d73ed", size = 6280005, upload-time = "2026-04-01T14:43:54.242Z" }, + { url = "https://files.pythonhosted.org/packages/a8/68/b93e09e5e8549019e61acf49f65b1a8530765a7f812c77a7461bca7e4494/pillow-12.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:03f6fab9219220f041c74aeaa2939ff0062bd5c364ba9ce037197f4c6d498cd9", size = 8090669, upload-time = "2026-04-01T14:43:57.335Z" }, + { url = "https://files.pythonhosted.org/packages/4b/6e/3ccb54ce8ec4ddd1accd2d89004308b7b0b21c4ac3d20fa70af4760a4330/pillow-12.2.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cdfebd752ec52bf5bb4e35d9c64b40826bc5b40a13df7c3cda20a2c03a0f5ed", size = 6395194, upload-time = "2026-04-01T14:43:59.864Z" }, + { url = "https://files.pythonhosted.org/packages/67/ee/21d4e8536afd1a328f01b359b4d3997b291ffd35a237c877b331c1c3b71c/pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eedf4b74eda2b5a4b2b2fb4c006d6295df3bf29e459e198c90ea48e130dc75c3", size = 7082423, upload-time = "2026-04-01T14:44:02.74Z" }, + { url = "https://files.pythonhosted.org/packages/78/5f/e9f86ab0146464e8c133fe85df987ed9e77e08b29d8d35f9f9f4d6f917ba/pillow-12.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:00a2865911330191c0b818c59103b58a5e697cae67042366970a6b6f1b20b7f9", size = 6505667, upload-time = "2026-04-01T14:44:05.381Z" }, + { url = "https://files.pythonhosted.org/packages/ed/1e/409007f56a2fdce61584fd3acbc2bbc259857d555196cedcadc68c015c82/pillow-12.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e1757442ed87f4912397c6d35a0db6a7b52592156014706f17658ff58bbf795", size = 7208580, upload-time = "2026-04-01T14:44:08.39Z" }, + { url = "https://files.pythonhosted.org/packages/23/c4/7349421080b12fb35414607b8871e9534546c128a11965fd4a7002ccfbee/pillow-12.2.0-cp313-cp313-win32.whl", hash = "sha256:144748b3af2d1b358d41286056d0003f47cb339b8c43a9ea42f5fea4d8c66b6e", size = 6375896, upload-time = "2026-04-01T14:44:11.197Z" }, + { url = "https://files.pythonhosted.org/packages/3f/82/8a3739a5e470b3c6cbb1d21d315800d8e16bff503d1f16b03a4ec3212786/pillow-12.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:390ede346628ccc626e5730107cde16c42d3836b89662a115a921f28440e6a3b", size = 7081266, upload-time = "2026-04-01T14:44:13.947Z" }, + { url = "https://files.pythonhosted.org/packages/c3/25/f968f618a062574294592f668218f8af564830ccebdd1fa6200f598e65c5/pillow-12.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:8023abc91fba39036dbce14a7d6535632f99c0b857807cbbbf21ecc9f4717f06", size = 2463508, upload-time = "2026-04-01T14:44:16.312Z" }, + { url = "https://files.pythonhosted.org/packages/4d/a4/b342930964e3cb4dce5038ae34b0eab4653334995336cd486c5a8c25a00c/pillow-12.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:042db20a421b9bafecc4b84a8b6e444686bd9d836c7fd24542db3e7df7baad9b", size = 5309927, upload-time = "2026-04-01T14:44:18.89Z" }, + { url = "https://files.pythonhosted.org/packages/9f/de/23198e0a65a9cf06123f5435a5d95cea62a635697f8f03d134d3f3a96151/pillow-12.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd025009355c926a84a612fecf58bb315a3f6814b17ead51a8e48d3823d9087f", size = 4698624, upload-time = "2026-04-01T14:44:21.115Z" }, + { url = "https://files.pythonhosted.org/packages/01/a6/1265e977f17d93ea37aa28aa81bad4fa597933879fac2520d24e021c8da3/pillow-12.2.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88ddbc66737e277852913bd1e07c150cc7bb124539f94c4e2df5344494e0a612", size = 6321252, upload-time = "2026-04-01T14:44:23.663Z" }, + { url = "https://files.pythonhosted.org/packages/3c/83/5982eb4a285967baa70340320be9f88e57665a387e3a53a7f0db8231a0cd/pillow-12.2.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d362d1878f00c142b7e1a16e6e5e780f02be8195123f164edf7eddd911eefe7c", size = 8126550, upload-time = "2026-04-01T14:44:26.772Z" }, + { url = "https://files.pythonhosted.org/packages/4e/48/6ffc514adce69f6050d0753b1a18fd920fce8cac87620d5a31231b04bfc5/pillow-12.2.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c727a6d53cb0018aadd8018c2b938376af27914a68a492f59dfcaca650d5eea", size = 6433114, upload-time = "2026-04-01T14:44:29.615Z" }, + { url = "https://files.pythonhosted.org/packages/36/a3/f9a77144231fb8d40ee27107b4463e205fa4677e2ca2548e14da5cf18dce/pillow-12.2.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:efd8c21c98c5cc60653bcb311bef2ce0401642b7ce9d09e03a7da87c878289d4", size = 7115667, upload-time = "2026-04-01T14:44:32.773Z" }, + { url = "https://files.pythonhosted.org/packages/c1/fc/ac4ee3041e7d5a565e1c4fd72a113f03b6394cc72ab7089d27608f8aaccb/pillow-12.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f08483a632889536b8139663db60f6724bfcb443c96f1b18855860d7d5c0fd4", size = 6538966, upload-time = "2026-04-01T14:44:35.252Z" }, + { url = "https://files.pythonhosted.org/packages/c0/a8/27fb307055087f3668f6d0a8ccb636e7431d56ed0750e07a60547b1e083e/pillow-12.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dac8d77255a37e81a2efcbd1fc05f1c15ee82200e6c240d7e127e25e365c39ea", size = 7238241, upload-time = "2026-04-01T14:44:37.875Z" }, + { url = "https://files.pythonhosted.org/packages/ad/4b/926ab182c07fccae9fcb120043464e1ff1564775ec8864f21a0ebce6ac25/pillow-12.2.0-cp313-cp313t-win32.whl", hash = "sha256:ee3120ae9dff32f121610bb08e4313be87e03efeadfc6c0d18f89127e24d0c24", size = 6379592, upload-time = "2026-04-01T14:44:40.336Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c4/f9e476451a098181b30050cc4c9a3556b64c02cf6497ea421ac047e89e4b/pillow-12.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:325ca0528c6788d2a6c3d40e3568639398137346c3d6e66bb61db96b96511c98", size = 7085542, upload-time = "2026-04-01T14:44:43.251Z" }, + { url = "https://files.pythonhosted.org/packages/00/a4/285f12aeacbe2d6dc36c407dfbbe9e96d4a80b0fb710a337f6d2ad978c75/pillow-12.2.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2e5a76d03a6c6dcef67edabda7a52494afa4035021a79c8558e14af25313d453", size = 2465765, upload-time = "2026-04-01T14:44:45.996Z" }, + { url = "https://files.pythonhosted.org/packages/bf/98/4595daa2365416a86cb0d495248a393dfc84e96d62ad080c8546256cb9c0/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:3adc9215e8be0448ed6e814966ecf3d9952f0ea40eb14e89a102b87f450660d8", size = 4100848, upload-time = "2026-04-01T14:44:48.48Z" }, + { url = "https://files.pythonhosted.org/packages/0b/79/40184d464cf89f6663e18dfcf7ca21aae2491fff1a16127681bf1fa9b8cf/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:6a9adfc6d24b10f89588096364cc726174118c62130c817c2837c60cf08a392b", size = 4176515, upload-time = "2026-04-01T14:44:51.353Z" }, + { url = "https://files.pythonhosted.org/packages/b0/63/703f86fd4c422a9cf722833670f4f71418fb116b2853ff7da722ea43f184/pillow-12.2.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:6a6e67ea2e6feda684ed370f9a1c52e7a243631c025ba42149a2cc5934dec295", size = 3640159, upload-time = "2026-04-01T14:44:53.588Z" }, + { url = "https://files.pythonhosted.org/packages/71/e0/fb22f797187d0be2270f83500aab851536101b254bfa1eae10795709d283/pillow-12.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2bb4a8d594eacdfc59d9e5ad972aa8afdd48d584ffd5f13a937a664c3e7db0ed", size = 5312185, upload-time = "2026-04-01T14:44:56.039Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8c/1a9e46228571de18f8e28f16fabdfc20212a5d019f3e3303452b3f0a580d/pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:80b2da48193b2f33ed0c32c38140f9d3186583ce7d516526d462645fd98660ae", size = 4695386, upload-time = "2026-04-01T14:44:58.663Z" }, + { url = "https://files.pythonhosted.org/packages/70/62/98f6b7f0c88b9addd0e87c217ded307b36be024d4ff8869a812b241d1345/pillow-12.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22db17c68434de69d8ecfc2fe821569195c0c373b25cccb9cbdacf2c6e53c601", size = 6280384, upload-time = "2026-04-01T14:45:01.5Z" }, + { url = "https://files.pythonhosted.org/packages/5e/03/688747d2e91cfbe0e64f316cd2e8005698f76ada3130d0194664174fa5de/pillow-12.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7b14cc0106cd9aecda615dd6903840a058b4700fcb817687d0ee4fc8b6e389be", size = 8091599, upload-time = "2026-04-01T14:45:04.5Z" }, + { url = "https://files.pythonhosted.org/packages/f6/35/577e22b936fcdd66537329b33af0b4ccfefaeabd8aec04b266528cddb33c/pillow-12.2.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cbeb542b2ebc6fcdacabf8aca8c1a97c9b3ad3927d46b8723f9d4f033288a0f", size = 6396021, upload-time = "2026-04-01T14:45:07.117Z" }, + { url = "https://files.pythonhosted.org/packages/11/8d/d2532ad2a603ca2b93ad9f5135732124e57811d0168155852f37fbce2458/pillow-12.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bfd07bc812fbd20395212969e41931001fd59eb55a60658b0e5710872e95286", size = 7083360, upload-time = "2026-04-01T14:45:09.763Z" }, + { url = "https://files.pythonhosted.org/packages/5e/26/d325f9f56c7e039034897e7380e9cc202b1e368bfd04d4cbe6a441f02885/pillow-12.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9aba9a17b623ef750a4d11b742cbafffeb48a869821252b30ee21b5e91392c50", size = 6507628, upload-time = "2026-04-01T14:45:12.378Z" }, + { url = "https://files.pythonhosted.org/packages/5f/f7/769d5632ffb0988f1c5e7660b3e731e30f7f8ec4318e94d0a5d674eb65a4/pillow-12.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:deede7c263feb25dba4e82ea23058a235dcc2fe1f6021025dc71f2b618e26104", size = 7209321, upload-time = "2026-04-01T14:45:15.122Z" }, + { url = "https://files.pythonhosted.org/packages/6a/7a/c253e3c645cd47f1aceea6a8bacdba9991bf45bb7dfe927f7c893e89c93c/pillow-12.2.0-cp314-cp314-win32.whl", hash = "sha256:632ff19b2778e43162304d50da0181ce24ac5bb8180122cbe1bf4673428328c7", size = 6479723, upload-time = "2026-04-01T14:45:17.797Z" }, + { url = "https://files.pythonhosted.org/packages/cd/8b/601e6566b957ca50e28725cb6c355c59c2c8609751efbecd980db44e0349/pillow-12.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:4e6c62e9d237e9b65fac06857d511e90d8461a32adcc1b9065ea0c0fa3a28150", size = 7217400, upload-time = "2026-04-01T14:45:20.529Z" }, + { url = "https://files.pythonhosted.org/packages/d6/94/220e46c73065c3e2951bb91c11a1fb636c8c9ad427ac3ce7d7f3359b9b2f/pillow-12.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:b1c1fbd8a5a1af3412a0810d060a78b5136ec0836c8a4ef9aa11807f2a22f4e1", size = 2554835, upload-time = "2026-04-01T14:45:23.162Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ab/1b426a3974cb0e7da5c29ccff4807871d48110933a57207b5a676cccc155/pillow-12.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:57850958fe9c751670e49b2cecf6294acc99e562531f4bd317fa5ddee2068463", size = 5314225, upload-time = "2026-04-01T14:45:25.637Z" }, + { url = "https://files.pythonhosted.org/packages/19/1e/dce46f371be2438eecfee2a1960ee2a243bbe5e961890146d2dee1ff0f12/pillow-12.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d5d38f1411c0ed9f97bcb49b7bd59b6b7c314e0e27420e34d99d844b9ce3b6f3", size = 4698541, upload-time = "2026-04-01T14:45:28.355Z" }, + { url = "https://files.pythonhosted.org/packages/55/c3/7fbecf70adb3a0c33b77a300dc52e424dc22ad8cdc06557a2e49523b703d/pillow-12.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c0a9f29ca8e79f09de89293f82fc9b0270bb4af1d58bc98f540cc4aedf03166", size = 6322251, upload-time = "2026-04-01T14:45:30.924Z" }, + { url = "https://files.pythonhosted.org/packages/1c/3c/7fbc17cfb7e4fe0ef1642e0abc17fc6c94c9f7a16be41498e12e2ba60408/pillow-12.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1610dd6c61621ae1cf811bef44d77e149ce3f7b95afe66a4512f8c59f25d9ebe", size = 8127807, upload-time = "2026-04-01T14:45:33.908Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c3/a8ae14d6defd2e448493ff512fae903b1e9bd40b72efb6ec55ce0048c8ce/pillow-12.2.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a34329707af4f73cf1782a36cd2289c0368880654a2c11f027bcee9052d35dd", size = 6433935, upload-time = "2026-04-01T14:45:36.623Z" }, + { url = "https://files.pythonhosted.org/packages/6e/32/2880fb3a074847ac159d8f902cb43278a61e85f681661e7419e6596803ed/pillow-12.2.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e9c4f5b3c546fa3458a29ab22646c1c6c787ea8f5ef51300e5a60300736905e", size = 7116720, upload-time = "2026-04-01T14:45:39.258Z" }, + { url = "https://files.pythonhosted.org/packages/46/87/495cc9c30e0129501643f24d320076f4cc54f718341df18cc70ec94c44e1/pillow-12.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb043ee2f06b41473269765c2feae53fc2e2fbf96e5e22ca94fb5ad677856f06", size = 6540498, upload-time = "2026-04-01T14:45:41.879Z" }, + { url = "https://files.pythonhosted.org/packages/18/53/773f5edca692009d883a72211b60fdaf8871cbef075eaa9d577f0a2f989e/pillow-12.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f278f034eb75b4e8a13a54a876cc4a5ab39173d2cdd93a638e1b467fc545ac43", size = 7239413, upload-time = "2026-04-01T14:45:44.705Z" }, + { url = "https://files.pythonhosted.org/packages/c9/e4/4b64a97d71b2a83158134abbb2f5bd3f8a2ea691361282f010998f339ec7/pillow-12.2.0-cp314-cp314t-win32.whl", hash = "sha256:6bb77b2dcb06b20f9f4b4a8454caa581cd4dd0643a08bacf821216a16d9c8354", size = 6482084, upload-time = "2026-04-01T14:45:47.568Z" }, + { url = "https://files.pythonhosted.org/packages/ba/13/306d275efd3a3453f72114b7431c877d10b1154014c1ebbedd067770d629/pillow-12.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6562ace0d3fb5f20ed7290f1f929cae41b25ae29528f2af1722966a0a02e2aa1", size = 7225152, upload-time = "2026-04-01T14:45:50.032Z" }, + { url = "https://files.pythonhosted.org/packages/ff/6e/cf826fae916b8658848d7b9f38d88da6396895c676e8086fc0988073aaf8/pillow-12.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aa88ccfe4e32d362816319ed727a004423aab09c5cea43c01a4b435643fa34eb", size = 2556579, upload-time = "2026-04-01T14:45:52.529Z" }, +] + +[[package]] +name = "pip" +version = "26.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/91/47e7d486260f618783899587af63ccf7980fb60245c3e63dd4571c6b57ad/pip-26.1.2.tar.gz", hash = "sha256:f49cd134c61cf2fd75e0ce2676db03e4054504a5a4986d00f8299ae632dc4605", size = 1840799, upload-time = "2026-05-31T17:33:58.56Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/95/6b5cb3461ea5673ba0995989746db58eb18b91b54dbf331e72f569540946/pip-26.1.2-py3-none-any.whl", hash = "sha256:382ff9f685ee3bc25864f820aa50505825f10f5458ffff07e30a6d96e5715cab", size = 1813144, upload-time = "2026-05-31T17:33:56.772Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/47/e4501f49c178ae1d9f4a75073fda4204f52647993f075a9db4d14930e0c5/platformdirs-4.10.0.tar.gz", hash = "sha256:31e761a6a0ca04faf7353ea759bdba55652be214725111e5aac52dfa29d4bef7", size = 31224, upload-time = "2026-05-28T03:32:53.587Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/e6/cd9575ac904136b3cbf7aa7ee819ef86eedb7274e46f230e94ea4342e729/platformdirs-4.10.0-py3-none-any.whl", hash = "sha256:fb516cdb12eb0d857d0cd85a7c57cea4d060bee4578d6cf5a14dfdf8cbf8784a", size = 22743, upload-time = "2026-05-28T03:32:52.175Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "preshed" +version = "3.0.13" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cymem" }, + { name = "murmurhash" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/75/fe6b7bbd0dea530a001b0e24c331b21a0be2786e402abf3c57f5dce43d4b/preshed-3.0.13.tar.gz", hash = "sha256:d75f718bbfd97e992f7827e0fa7faf6a91bdd9c922d5baa4b50d62731396cb89", size = 18338, upload-time = "2026-03-23T08:57:31.378Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/fb/ccff23c44c04088c248539005fcda78b9014512a34d170c5360f02ad908b/preshed-3.0.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5d14eea14bd01291388928991d7df7d60b9fd19ae970e55006eb4d29b0c1e8eb", size = 138497, upload-time = "2026-03-23T08:56:35.321Z" }, + { url = "https://files.pythonhosted.org/packages/8e/ce/cad5a8145881a771e6c0d002f2e585fc19b962f120860b54d32af5baa342/preshed-3.0.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f05b08ce92399c0655b5e0eb5a1cc1f9e295703ed3aabdfaf6538dfa8ae23d57", size = 138010, upload-time = "2026-03-23T08:56:36.399Z" }, + { url = "https://files.pythonhosted.org/packages/a7/a2/c5fed4fb3e946699259d11e4036a3cfdd8c89b3e542e3077d46781642425/preshed-3.0.13-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:62cf7f3113132891d6bba70ff547ad81c6fe50a31930bbbb8499f1d47cd122b7", size = 861498, upload-time = "2026-03-23T08:56:37.67Z" }, + { url = "https://files.pythonhosted.org/packages/51/94/8c9bc48a6ea4903f53a1a0031ce8e35687526949f25821762ef21493c007/preshed-3.0.13-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8b8de3f58043070a354477995acdd98626ce43e4193c708ebd0f694e467f5155", size = 868988, upload-time = "2026-03-23T08:56:39.324Z" }, + { url = "https://files.pythonhosted.org/packages/b6/df/ecd2f40055ff52527ca117ffbfafb888c1a3079b59fbabe03c5b8f9b7240/preshed-3.0.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:183b339956a9e1d7a4a00038a3b9587a734db9e8bd915939a49791bd1b372156", size = 1847382, upload-time = "2026-03-23T08:56:40.89Z" }, + { url = "https://files.pythonhosted.org/packages/e6/88/bdb244e40284ded3632a9f88c23bc80230bd7b2ae4a8b7f2cc91adead7a8/preshed-3.0.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2e77bed56aded7cbe5d28d6bd2178bc5b13eda0e0e464dab205fb578fa915000", size = 1919236, upload-time = "2026-03-23T08:56:42.616Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c9/c91ea56342e6c364fc69b444a1ac5432327857199c44032c9cc9dc4c3a23/preshed-3.0.13-cp312-cp312-win_amd64.whl", hash = "sha256:04d8f13f2986e5d11af5ac51f55ce3106c70c41b483d20ea392e6180bdd0f870", size = 122938, upload-time = "2026-03-23T08:56:44.271Z" }, + { url = "https://files.pythonhosted.org/packages/b2/0b/6a99d99619fd83b14c696e2489caed7070647488d4d3ac0b723d35db2de0/preshed-3.0.13-cp312-cp312-win_arm64.whl", hash = "sha256:19318dc1cd8cac6663c6c830bf7e0002d2de853769fb03e056774e97c21bedfd", size = 109194, upload-time = "2026-03-23T08:56:45.346Z" }, + { url = "https://files.pythonhosted.org/packages/0e/2a/401158195d6dc7f6aef0b354d74d0e95c9da124499448c2b3dbb95b71204/preshed-3.0.13-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c0d0c14187dc0078d8a63bf190ec045a4d13e7748b6caeb557a7d575e411410b", size = 137289, upload-time = "2026-03-23T08:56:46.516Z" }, + { url = "https://files.pythonhosted.org/packages/88/8f/e20e64573988528785447a6893b2e7ab287ecfd85b3888e978b28812fd20/preshed-3.0.13-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7770987c2e57497cd26124a9be5f652b5b3ccd0def89859ab0da8bca6144a3de", size = 136847, upload-time = "2026-03-23T08:56:47.572Z" }, + { url = "https://files.pythonhosted.org/packages/b9/72/18168f881359c4482d312f8dc196371bdd61c1583a52b34390da4c88bbea/preshed-3.0.13-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4a7bc48220de579be6bdb0a8715482cf36e2a625a6fd5ad26c9f43485a4a23b5", size = 831478, upload-time = "2026-03-23T08:56:48.769Z" }, + { url = "https://files.pythonhosted.org/packages/fd/3a/3543476091087102775568cea9885dde3453569e9aeee365809108de572f/preshed-3.0.13-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e5c8462472f790c16708306aef3a102a762bd19dfe3d2f8ee08bd5e12f51b835", size = 839913, upload-time = "2026-03-23T08:56:49.937Z" }, + { url = "https://files.pythonhosted.org/packages/cf/65/b13f01329decc44ef53cfb6b4601ba85382dcb2a4ec78d9250f03a418066/preshed-3.0.13-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c046736239cc8d72670749b79b526e4111839a2fc461a58545d212797649129c", size = 1816452, upload-time = "2026-03-23T08:56:51.233Z" }, + { url = "https://files.pythonhosted.org/packages/d1/c7/f1a996c6832234efd4d543041b582418d41ac480ee55c557ec9e65344637/preshed-3.0.13-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7c333f18e9a81c8a6de0603fd8781e17115324b117c445ca91abdf7bfb1abe49", size = 1888978, upload-time = "2026-03-23T08:56:52.591Z" }, + { url = "https://files.pythonhosted.org/packages/e3/b9/96fb71499049885ce19545903fdd38877bbc2be0da47e37c04d01f3e9f66/preshed-3.0.13-cp313-cp313-win_amd64.whl", hash = "sha256:461327f8dd36520dcf1fd55a671e0c3c2c97a2d95e22fc85faa31173f4785dda", size = 122134, upload-time = "2026-03-23T08:56:54.392Z" }, + { url = "https://files.pythonhosted.org/packages/ef/a7/32a4903019d936a2316fdd330bedddac287ac26326107d24fb76a1fbc60a/preshed-3.0.13-cp313-cp313-win_arm64.whl", hash = "sha256:35d6c5acb3ee3b12b87a551913063f0cec784055c2af16e028c19fe875f079d0", size = 108497, upload-time = "2026-03-23T08:56:55.816Z" }, + { url = "https://files.pythonhosted.org/packages/bb/b5/993886c98f5caaa6f07a648cac97a7c62a3093091cad65e1e43a1bd41cc4/preshed-3.0.13-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d2f1efae396cadab5f3890a2fd43d2ee65373ef9096ccbb805e51e8d8bcc563b", size = 137882, upload-time = "2026-03-23T08:56:56.878Z" }, + { url = "https://files.pythonhosted.org/packages/c6/86/b7fd137cbf140afd6c45e895946068a15f5b55642916de0075e6eb18581c/preshed-3.0.13-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8d6acc1f5031a535a55a6f7148e2f274554a8343a16309c700cebea0fe7aee8c", size = 138233, upload-time = "2026-03-23T08:56:58.318Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ca/21a7e79625614134273dfed32bca5bb4c2ec1313e33fbd12d41657536f1f/preshed-3.0.13-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7da9d931e7660dcdd757e5870269f0c159126d682ed73ed313971d199eb0f334", size = 834835, upload-time = "2026-03-23T08:56:59.48Z" }, + { url = "https://files.pythonhosted.org/packages/8f/3a/2dbd299516461831ae90e0d5b0637137bf28520c4e6dd0b01d6f1886659a/preshed-3.0.13-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d4ae5cfe075bb7a07982e382bca44f41ddf041f4d24cbd358e8cccfc049259b8", size = 834928, upload-time = "2026-03-23T08:57:01.075Z" }, + { url = "https://files.pythonhosted.org/packages/7c/d3/af654eba4f6587c4ee02c5043e62c194b0a1c4431ffef0c67b9518f6b61c/preshed-3.0.13-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7557963d0125a3a7bcdb2eb6948f3e45da31b5a7f066b55320de3dea22d7557f", size = 1820368, upload-time = "2026-03-23T08:57:02.351Z" }, + { url = "https://files.pythonhosted.org/packages/bf/9b/ebcb2b9e8cb881e40b55b0bf450f8a6b187e2ef3ae0c685cce81d2d85026/preshed-3.0.13-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c4bc60dc994864095d784b7e4d77dba3e64188d169ac88722b699d175561fddb", size = 1888251, upload-time = "2026-03-23T08:57:04.158Z" }, + { url = "https://files.pythonhosted.org/packages/97/f7/c6c012779edcaa6e2cd092c554e98dc53e77f41205b07208655ba77e2327/preshed-3.0.13-cp314-cp314-win_amd64.whl", hash = "sha256:208dcebbe294bf1881ce33fb015d56ab2a7587aece85a09147727174207892e4", size = 125211, upload-time = "2026-03-23T08:57:05.83Z" }, + { url = "https://files.pythonhosted.org/packages/f8/82/390ef87d732ef64e673ef6bf9e5d898453986e979efa50fb3a400e2c0766/preshed-3.0.13-cp314-cp314-win_arm64.whl", hash = "sha256:cf8e1a7a1823b2a7765121446c630140ac6e8650c07a6efbf375e168d1fef4f7", size = 111942, upload-time = "2026-03-23T08:57:06.996Z" }, + { url = "https://files.pythonhosted.org/packages/80/3a/a9dde3167bcecb27ae82ce4567b5ab1aa3989113ae6814c092ce223cc4ef/preshed-3.0.13-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9ca43ecbc3783eda4d6ab3416ae2ecd9ef23dca5f53995843f69f7457bcd0677", size = 144997, upload-time = "2026-03-23T08:57:08.064Z" }, + { url = "https://files.pythonhosted.org/packages/74/d4/22d9355b50b6a13b407dcad0a81df83fb1d5602092d1f05834674dde8fda/preshed-3.0.13-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c8596e41a258ff213553a441e0bb3eb388fd8158e84a7bf3aae6d8ede2c166d3", size = 147294, upload-time = "2026-03-23T08:57:09.411Z" }, + { url = "https://files.pythonhosted.org/packages/70/42/a225ee83fdb306d2a503f21a627953b820f4e079c90c8a84338957cb8ff5/preshed-3.0.13-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4f8856ca3d88e9b250630d70abb4f260d8933151ddfb413024784b25b009868e", size = 952110, upload-time = "2026-03-23T08:57:10.592Z" }, + { url = "https://files.pythonhosted.org/packages/40/ba/09a9dfe3d22d7e745483fd5d7f2a82cd4d39c161f7d2daa0faa4bd6402be/preshed-3.0.13-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0e5b2865aecbd2e1e10e5d19bb8bfad765863c1307c6c3e51f2a08bd64122409", size = 932217, upload-time = "2026-03-23T08:57:12.124Z" }, + { url = "https://files.pythonhosted.org/packages/6c/5c/e10e2e05133e7fcbd7c40536af1148c82dd24357b8f5726e2c7bc51cfd53/preshed-3.0.13-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:09f96b477c987755b3c945df214ea1c1c80bfb350e9f34e78da89585535b77e8", size = 1896542, upload-time = "2026-03-23T08:57:13.525Z" }, + { url = "https://files.pythonhosted.org/packages/37/aa/51e5b4109a4cdfae28c3613eeeb10764a3794ebef8de93ffbb109465bea3/preshed-3.0.13-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:670db59a52e1823b5f088c764df474e65b686592d4093adbeef14581c95ee2cb", size = 1959473, upload-time = "2026-03-23T08:57:15.706Z" }, + { url = "https://files.pythonhosted.org/packages/0e/6a/1d966f367a14c703dde629d150d996c1b727d442f620300b21c9ec1a24d1/preshed-3.0.13-cp314-cp314t-win_amd64.whl", hash = "sha256:b03e21b0bf95eb56e23973f32cabb930e94f352228652f81c0955dbd6967d904", size = 146229, upload-time = "2026-03-23T08:57:17.457Z" }, + { url = "https://files.pythonhosted.org/packages/22/80/368139067603e590a000122355f9c8576c8ebed4fb0b8849feaa2698489d/preshed-3.0.13-cp314-cp314t-win_arm64.whl", hash = "sha256:b980f3ea9bb74b7f94464bc3d6eb3c9162b6b79b531febd14c6465c24344d2cc", size = 119339, upload-time = "2026-03-23T08:57:18.882Z" }, +] + +[[package]] +name = "presidio-analyzer" +version = "2.2.362" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "phonenumbers" }, + { name = "pydantic" }, + { name = "pyyaml" }, + { name = "regex" }, + { name = "spacy" }, + { name = "tldextract" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/8a/d6cf4bddbb11d9c78f5c9342bbbcde4f90fe4e3c7de114a836ec4ed8a6cf/presidio_analyzer-2.2.362-py3-none-any.whl", hash = "sha256:4c36438924b1fcb4df92ea5cf2d8dc57508808e116b10923c983b8732aa07d90", size = 201084, upload-time = "2026-03-15T12:40:43.801Z" }, +] + +[[package]] +name = "propcache" +version = "0.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/44/c87281c333769159c50594f22610f77398a47ccbfbbf23074e744e86f87c/propcache-0.5.2.tar.gz", hash = "sha256:01c4fc7480cd0598bb4b57022df55b9ca296da7fc5a8760bd8451a7e63a7d427", size = 50208, upload-time = "2026-05-08T21:02:12.199Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/cb/e27bc2b2737a0bb49962b275efa051e8f1c35a936df7d5139b6b658b7dc9/propcache-0.5.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:806719138ecd720339a12410fb9614ac9b2b2d3a5fdf8235d56981c36f4039ba", size = 95887, upload-time = "2026-05-08T21:00:11.277Z" }, + { url = "https://files.pythonhosted.org/packages/e6/13/b8ae04c59392f8d11c6cd9fb4011d1dc7c86b81225c770280300e259ffe1/propcache-0.5.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:db2b80ea58eab4f86b2beec3cc8b39e8ff9276ac20e96b7cce43c8ae84cd6b5a", size = 54654, upload-time = "2026-05-08T21:00:12.604Z" }, + { url = "https://files.pythonhosted.org/packages/2c/7d/49777a3e20b55863d4794384a38acd460c04157b0a00f8602b0d508b8431/propcache-0.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e5cbfac9f61484f7e9f3597775500cd3ebe8274e9b050c38f9525c77c97520bf", size = 55190, upload-time = "2026-05-08T21:00:13.935Z" }, + { url = "https://files.pythonhosted.org/packages/44/c7/085d0cd63062e84044e3f05797749c3f8e3938ff3aeb0eb2f69d43fafc91/propcache-0.5.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbc581d2814337da56222fab8dc5f161cd798a434e49bac27930aaef798e144", size = 59995, upload-time = "2026-05-08T21:00:15.526Z" }, + { url = "https://files.pythonhosted.org/packages/9c/42/32cf8e3009e92b2645cf1e944f701e8ea4e924dffde1ee26db860bcbf7e4/propcache-0.5.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:857187f381f88c8e2fa2fe56ab94879d011b883d5a2ee5a1b60a8cd2a06846d9", size = 63422, upload-time = "2026-05-08T21:00:16.824Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1b/f112433f99fc979431b87a39ef169e3f8df070d99a72792c56d6937ac48b/propcache-0.5.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:178b4a2cdaac1818e2bf1c5a99b94383fa73ea5382e032a48dec07dc5668dc42", size = 64342, upload-time = "2026-05-08T21:00:18.362Z" }, + { url = "https://files.pythonhosted.org/packages/14/15/5574111ae50dd6e879456888c0eadd4c5a869959775854e18e18a6b345f3/propcache-0.5.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f328175a2cde1f0ff2c4ed8ce968b9dcfb55f3a7153f39e2957ed994da13476", size = 61639, upload-time = "2026-05-08T21:00:19.692Z" }, + { url = "https://files.pythonhosted.org/packages/cc/da/4d775080b1490c0ae604acda868bd71aabe3a89ed16f2aa4339eb8a283e7/propcache-0.5.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5671d09a36b06d0fd4a3da0fccbcae360e9b1570924171a15e9e0997f0249fba", size = 61588, upload-time = "2026-05-08T21:00:21.155Z" }, + { url = "https://files.pythonhosted.org/packages/04/ac/f076982cbe2195ee9cf32de5a1e46951d9fb399fc207f390562dd0fd8fb2/propcache-0.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:80168e2ebe4d3ec6599d10ad8f520304ae1cad9b6c5a95372aef1b66b7bfb53a", size = 60029, upload-time = "2026-05-08T21:00:22.713Z" }, + { url = "https://files.pythonhosted.org/packages/70/60/189be62e0dd898dce3b331e1b8c7a543cd3a405ac0c81fe8ee8a9d5d77e1/propcache-0.5.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:45f11346f884bc47444f6e6647131055844134c3175b629f84952e2b5cd62b64", size = 56774, upload-time = "2026-05-08T21:00:24.001Z" }, + { url = "https://files.pythonhosted.org/packages/ea/9e/93377b9c7939c1ffae98f878dee955efadfd638078bc86dbc21f9d52f651/propcache-0.5.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e778ebd44ef4f66ed60a0416b06b489687db264a9c0b3620362f26489492913", size = 63532, upload-time = "2026-05-08T21:00:25.545Z" }, + { url = "https://files.pythonhosted.org/packages/14/f9/590ef6cfb9b8028d516d287812ece32bb0bc5f11fbb9c8bf6b2e6313fec8/propcache-0.5.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c0cb9ed24c8964e172768d455a38254c2dd8a552905729ce006cad3d3dda59b1", size = 61592, upload-time = "2026-05-08T21:00:27.186Z" }, + { url = "https://files.pythonhosted.org/packages/b4/5e/70958b3034c297a630bba2f17ca7abc2d5f39a803ad7e370ab79d1ecd022/propcache-0.5.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1d1ad32d9d4355e2be65574fd0bfd3677e7066b009cd5b9b2dee8aa6a6393b33", size = 64788, upload-time = "2026-05-08T21:00:28.8Z" }, + { url = "https://files.pythonhosted.org/packages/12/fd/77fe5936d8c3086ca9048f7f415f122ed82e53884a9ec193646b42deef06/propcache-0.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c80f4ba3e8f00189165999a742ee526ebeccedf6c3f7beb0c7df821e9772435a", size = 62514, upload-time = "2026-05-08T21:00:30.098Z" }, + { url = "https://files.pythonhosted.org/packages/cf/74/66bd798b5b3be70aa1b391f5cc9d6a0a5532d7fd3b19ec0b213e72e6ad9d/propcache-0.5.2-cp312-cp312-win32.whl", hash = "sha256:8c7972d8f193740d9175f0998ab38717e6cd322d5935c5b0fef8c0d323fd9031", size = 39018, upload-time = "2026-05-08T21:00:31.622Z" }, + { url = "https://files.pythonhosted.org/packages/61/7c/5c0d34aa3024694d6dcb9271cdbdd08c4e47c1c0ad95ec7e7bc74cdea145/propcache-0.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:d9ee8826a7d47863a08ac44e1a5f611a462eefc3a194b492da242128bec75b42", size = 42322, upload-time = "2026-05-08T21:00:32.918Z" }, + { url = "https://files.pythonhosted.org/packages/4d/91/875812f1a3feb20ceba818ef39fbe4d92f1081e04ac815c822496d0d038b/propcache-0.5.2-cp312-cp312-win_arm64.whl", hash = "sha256:2800a4a8ead6b28cccd1ec54b59346f0def7922ee1c7598e8499c733cfbb7c84", size = 38172, upload-time = "2026-05-08T21:00:35.124Z" }, + { url = "https://files.pythonhosted.org/packages/c5/09/f049e45385503fe67db75a6b6186a7b9f0c3930366dc960522c312a825b1/propcache-0.5.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:099aaf4b4d1a02265b92a977edf00b5c4f63b3b17ac6de39b0d637c9cac0188a", size = 94457, upload-time = "2026-05-08T21:00:36.355Z" }, + { url = "https://files.pythonhosted.org/packages/6b/65/83d1d05655baf63113731bd5a1008435e14f8d1e5a06cbe4ec5b23ad7a31/propcache-0.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:68ce1c44c7a813a7f71ea04315a8c7b330b63db99d059a797a4651bb6f69f117", size = 53835, upload-time = "2026-05-08T21:00:38.072Z" }, + { url = "https://files.pythonhosted.org/packages/a9/12/a6ba6482bb5ea3260c000c9b20881c95fa11c6b30173715668259f844ed7/propcache-0.5.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fc299c129490f55f254cd90be0deca4764e36e9a7c08b4aa588479a3bbed3098", size = 54545, upload-time = "2026-05-08T21:00:39.319Z" }, + { url = "https://files.pythonhosted.org/packages/a9/19/7fa086f5764c59ec8a8e157cd93aa8497acc00aba9dcdec56bfffb32602d/propcache-0.5.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6ae2198be502c10f09b2516e7b5d019816924bc3183a43ce792a7bd6625e6f4", size = 59886, upload-time = "2026-05-08T21:00:40.621Z" }, + { url = "https://files.pythonhosted.org/packages/a1/e4/5d7663dc8235956c8f5281698a3af1d351d8820341ddd890f59d9a9127f2/propcache-0.5.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6041d31504dc1779d700e1edcfb08eea334b357620b06681a4eabb57a74e574e", size = 63261, upload-time = "2026-05-08T21:00:41.775Z" }, + { url = "https://files.pythonhosted.org/packages/4a/4a/15a03adee24d6350da4292caeac44c34c033d2afe5e87eb370f38854560f/propcache-0.5.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7eabc04151c78a9f4d5bbb5f1faf571e4defeb4b585e0fe95b60ff2dbe4d3d7", size = 64184, upload-time = "2026-05-08T21:00:43.018Z" }, + { url = "https://files.pythonhosted.org/packages/8b/c6/979176efdaa3d239e36d503d5af63a0a773b36662ed8f52e5b6a6d9fd40e/propcache-0.5.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4db0ba63d693afd40d249bd93f842b5f144f8fcbb83de05660373bcf30517b1d", size = 61534, upload-time = "2026-05-08T21:00:44.507Z" }, + { url = "https://files.pythonhosted.org/packages/c8/22/63e8cd1bae4c2d2be6493b6b7d10566ddafad88137cfbc99964a1119853c/propcache-0.5.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1dbcf7675229b35d31abb6547d8ebc8c27a830ac3f9a794edff6254873ec7c0a", size = 61500, upload-time = "2026-05-08T21:00:45.796Z" }, + { url = "https://files.pythonhosted.org/packages/60/5a/28e5d9acbac1cc9ccb67045e8c1b943aa8d79fdf39c93bd73cacd68008ea/propcache-0.5.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d310c013aad2c72f1c3f2f8dd3279d460a858c551f97aeb8c63e4693cca7b4d2", size = 59994, upload-time = "2026-05-08T21:00:47.093Z" }, + { url = "https://files.pythonhosted.org/packages/f3/40/db650677f554a95b9c01a7c9d93d629e93a15562f5deb4573c9ee136fed2/propcache-0.5.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:06187263ddad280d05b4d8a8b3bb7d164cbebd469236544a42e6d9b28ac6a4fa", size = 56884, upload-time = "2026-05-08T21:00:48.376Z" }, + { url = "https://files.pythonhosted.org/packages/80/45/70b39b89516ff8b96bf732fa6fded8cef20f293cb1508690101c3c07ec51/propcache-0.5.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3115559b8effafd63b142ea5ed53d63a16ea6469cbc63dce4ee194b42db5d853", size = 63464, upload-time = "2026-05-08T21:00:49.954Z" }, + { url = "https://files.pythonhosted.org/packages/f9/e2/fa59d3a89eac5534293124af4f1d0d0ada091ce4a0ab4610ce03fd2bdd8d/propcache-0.5.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c60462af8e6dc30c35407c7237ea908d777b22862bbee27bc4699c0d8bcdc45a", size = 61588, upload-time = "2026-05-08T21:00:51.281Z" }, + { url = "https://files.pythonhosted.org/packages/0b/97/efb547a55c4bc7381cfb202d6a2239ac621045277bc1ea5dfd3a7f0516c0/propcache-0.5.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:40314bca9ac559716fe374094fc81c11dcc34b64fd6c585360f5775690505704", size = 64667, upload-time = "2026-05-08T21:00:52.602Z" }, + { url = "https://files.pythonhosted.org/packages/92/56/f5c7d9b4b7595d5127da38974d791b2153f3d1eae6c674af3583ace92ad3/propcache-0.5.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cfa21e036ce1e1db2be04ba3b85d2df1bb1702fa01932d984c5464c665228ff4", size = 62463, upload-time = "2026-05-08T21:00:54.303Z" }, + { url = "https://files.pythonhosted.org/packages/bd/3b/484a3a65fc9f9f60c41dcd17b428bace5389544e2c680994534a20755066/propcache-0.5.2-cp313-cp313-win32.whl", hash = "sha256:f156a3529f38063b6dbaf356e15602a7f95f8055b1295a438433a6386f10463d", size = 38621, upload-time = "2026-05-08T21:00:55.808Z" }, + { url = "https://files.pythonhosted.org/packages/1c/fd/3f0f10dba4dabad3bf53102be007abf55481067952bde0fdddff439e7c61/propcache-0.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:dfed59d0a5aeb01e242e66ff0300bc4a265a7c05f612d30016f0b60b1017d757", size = 41649, upload-time = "2026-05-08T21:00:57.061Z" }, + { url = "https://files.pythonhosted.org/packages/90/ec/6ce619cc32bb500a482f811f9cd509368b4e58e638d13f2c68f370d6b475/propcache-0.5.2-cp313-cp313-win_arm64.whl", hash = "sha256:ba338430e87ceb9c8f0cf754de38a9860560261e56c00376debd628698a7364f", size = 37636, upload-time = "2026-05-08T21:00:58.646Z" }, + { url = "https://files.pythonhosted.org/packages/1b/82/c1d268bbbf2ef981c5bf0fbbe746db617c66e3bcefe431a1aa8943fbe23a/propcache-0.5.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a592f5f3da71c8691c788c13cb6734b6d17663d2e1cb8caddf0673d01ef8847d", size = 98872, upload-time = "2026-05-08T21:00:59.889Z" }, + { url = "https://files.pythonhosted.org/packages/f4/d4/52c871e73e864e6b34c0e2d58ac1ec5ccd149497ddc7ad2137ae98323a35/propcache-0.5.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6a997d0489e9668a384fcfd5061b857aa5361de73191cac204d04b889cfbbafa", size = 56257, upload-time = "2026-05-08T21:01:01.195Z" }, + { url = "https://files.pythonhosted.org/packages/67/f0/9b90ca2a210b3d09bcfcd96ecd0f55545c091535abce2a45de2775cfd357/propcache-0.5.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:10734b5484ea113152ee25a91dccedf81631791805d2c9ccb054958e51842c94", size = 56696, upload-time = "2026-05-08T21:01:02.941Z" }, + { url = "https://files.pythonhosted.org/packages/9d/0e/6e9d4ba07c8e56e21ddec1e75f12148142b21ca83a51871babce095334f4/propcache-0.5.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cafca7e56c12bb02ae16d283742bef25a61122e9dab2b5b3f2ccbe589ce32164", size = 62378, upload-time = "2026-05-08T21:01:04.475Z" }, + { url = "https://files.pythonhosted.org/packages/65/19/c10badaa463dde8a27ce884f8ee2ec37e6035b7c9f5ff0c8f74f06f08dac/propcache-0.5.2-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f064f8d2b59177878b7615df1735cd8fe3462ed6be8c7b217d17a276489c2b7f", size = 65283, upload-time = "2026-05-08T21:01:05.959Z" }, + { url = "https://files.pythonhosted.org/packages/b0/b6/93bea99ca80e19cef6512a8580e5b7857bbe09422d9daa7fd4ef5723306c/propcache-0.5.2-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f78abfa8dfc32376fd1aacf597b2f2fbbe0ea751419aee718af5d4f82537ef8c", size = 66616, upload-time = "2026-05-08T21:01:07.228Z" }, + { url = "https://files.pythonhosted.org/packages/83/e4/5c7462e50625f051f37fb38b8224f7639f667184bbd34424ec83819bb1b7/propcache-0.5.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7467da8a9822bf1a55336f877340c5bcbd3c482afc43a99771169f74a26dedc", size = 63773, upload-time = "2026-05-08T21:01:08.514Z" }, + { url = "https://files.pythonhosted.org/packages/ca/b6/99238894047b13c823be25027e736626cd414a52a5e30d2c3347c2733529/propcache-0.5.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a6ddc6ac9e25de626c1f129c1b467d7ecd33ce2237d3fd0c4e429feef0a7ee1f", size = 63664, upload-time = "2026-05-08T21:01:09.874Z" }, + { url = "https://files.pythonhosted.org/packages/85/1e/a3a1a63116a2b8edb415a8bb9a6f0c34bd03830b1e18e8ce2904e1dc1cf4/propcache-0.5.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2f22cbbac9e26a8e864c0985ff1268d5d939d53d9d9411a9824279097e03a2cb", size = 62643, upload-time = "2026-05-08T21:01:11.132Z" }, + { url = "https://files.pythonhosted.org/packages/e4/03/893cf147de2fc6543c5eaa07ad833170e7e2a2385725bbebe8c0503723bb/propcache-0.5.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:fc76378c62a0f04d0cd82fbb1a2cd2d7e28fcb40d5873f28a6c44e388aaa2751", size = 59595, upload-time = "2026-05-08T21:01:12.387Z" }, + { url = "https://files.pythonhosted.org/packages/86/3b/04c1a2e12c57766568ba75ba72b3bf2042818d4c1425fab6fc07155c7cff/propcache-0.5.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:acd2c8edba48e31e58a363b8cf4e5c7db3b04b3f9e371f601df30d9b0d244836", size = 65711, upload-time = "2026-05-08T21:01:13.676Z" }, + { url = "https://files.pythonhosted.org/packages/1c/34/80f8d0099f8d6bacc4de1624c85672681c8cd1149ca2da0e38fd120b817f/propcache-0.5.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:452b5065457eb9991ec5eb38ff41d6cd4c991c9ac7c531c4d5849ae473a9a13f", size = 64247, upload-time = "2026-05-08T21:01:14.936Z" }, + { url = "https://files.pythonhosted.org/packages/f3/1a/8b08f3a5f1037e9e370c55883ceeeee0f6dd0416fb2d2d67b8bfc91f2a79/propcache-0.5.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:3430bb2bfe1331885c427745a751e774ee679fd4344f80b97bf879815fe8fa55", size = 67102, upload-time = "2026-05-08T21:01:16.281Z" }, + { url = "https://files.pythonhosted.org/packages/34/68/8bdb7bb7756d76e005490649d10e4a8369e610c74d619f71e1aedf889e9c/propcache-0.5.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cef6cea3922890dd6c9654971001fa797b526c16ab5e1e46c05fd6f877be7568", size = 64964, upload-time = "2026-05-08T21:01:17.57Z" }, + { url = "https://files.pythonhosted.org/packages/0a/aa/50fb0b5d3968b61a510926ff8b8465f1d6e976b3ab74496d7a4b9fc42515/propcache-0.5.2-cp313-cp313t-win32.whl", hash = "sha256:72d61e16dd78228b58c5d47be830ff3da7e5f139abdf0aef9d86cde1c5cf2191", size = 42546, upload-time = "2026-05-08T21:01:18.946Z" }, + { url = "https://files.pythonhosted.org/packages/ae/4c/0ddbae64321bd4a95bcbfc19307238016b5b1fee645c84626c8d539e5b74/propcache-0.5.2-cp313-cp313t-win_amd64.whl", hash = "sha256:0958834041a0166d343b8d2cedcd8bcbaeb4fdbe0cf08320c5379f143c3be6e7", size = 46330, upload-time = "2026-05-08T21:01:20.162Z" }, + { url = "https://files.pythonhosted.org/packages/00/d9/9cddc8efb78d8af264c5ec9f6d10b62f57c515feda8d321595f56010fb23/propcache-0.5.2-cp313-cp313t-win_arm64.whl", hash = "sha256:6de8bd93ddde9b992cf2b2e0d796d501a19026b5b9fd87356d7d0779531a8d96", size = 40521, upload-time = "2026-05-08T21:01:21.399Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ea/23ee535d90ce8bcc465a3028eb3cc0ce3bd1005f4bb27710b30587de798d/propcache-0.5.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:46088abff4cba581dea21ae0467a480526cb25aa5f3c269e909f800328bc3999", size = 94662, upload-time = "2026-05-08T21:01:22.683Z" }, + { url = "https://files.pythonhosted.org/packages/b5/06/c5a52f419b5d8972f8d46a7577476090d8e3263ff589ce40b5ca4968d5be/propcache-0.5.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fc88b26f08d634f7bc819a7852e5214f5802641ab8d9fd5326892292eee1993e", size = 53928, upload-time = "2026-05-08T21:01:23.986Z" }, + { url = "https://files.pythonhosted.org/packages/63/b1/4260d67d6bd85e58a66b72d54ce15d5de789b6f3870cc6bedf8ff9667401/propcache-0.5.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:97797ebb098e670a2f92dd66f32897e30d7615b14e7f59711de23e30a9072539", size = 54650, upload-time = "2026-05-08T21:01:25.305Z" }, + { url = "https://files.pythonhosted.org/packages/70/06/2f46c318e3307cd7a6a7481def374ce838c0fe20084b39dd54b0879d0e99/propcache-0.5.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba57fffe4ac99c5d30076161b5866336d97600769bad35cc68f7774b15298a4e", size = 59912, upload-time = "2026-05-08T21:01:26.545Z" }, + { url = "https://files.pythonhosted.org/packages/4c/29/fe1aebec2ce57ab985a9c382bded1124431f85078113aa222c5d278430d4/propcache-0.5.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:583c19759d9eec1e5b69e2fbef36a7d9c326041be9746cb822d335c8cedc2979", size = 63300, upload-time = "2026-05-08T21:01:27.937Z" }, + { url = "https://files.pythonhosted.org/packages/b4/18/2334b26768b6c82be8c69e83671b767d5ef426aa09b0cba6c2ea47816774/propcache-0.5.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d0326e2e5e1f3163fa306c834e48e8d490e5fae607a097a40c0648109b47ba80", size = 64208, upload-time = "2026-05-08T21:01:29.484Z" }, + { url = "https://files.pythonhosted.org/packages/2b/76/7f1bfd6afff4c5e38e36a3c6d68eb5f4b7311ea80baf693db78d95b603c4/propcache-0.5.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e00820e192c8dbebcafb383ebbf99030895f09905e7a0eb2e0340a0bcc2bc825", size = 61633, upload-time = "2026-05-08T21:01:31.068Z" }, + { url = "https://files.pythonhosted.org/packages/c4/46/b3ff8aba2b4953a3e50de2cf72f1b5748b8eca93b15f3dc2c84339084c09/propcache-0.5.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c66afea89b1e43725731d2004732a046fe6fe955d51f952c3e95a7314a284a39", size = 61724, upload-time = "2026-05-08T21:01:32.374Z" }, + { url = "https://files.pythonhosted.org/packages/c5/01/814cfcafbcff954f94c01cf30e097ddc88a076b5440fbcf4570753437d40/propcache-0.5.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d4dc37dec6c6cdad0b57881a5658fd14fbf53e333b1a86cf86559f190e1d9ec4", size = 60069, upload-time = "2026-05-08T21:01:33.67Z" }, + { url = "https://files.pythonhosted.org/packages/da/68/5c6f7622d510cc666a300687e06fd060c1a43361c0c9b20d284f06d8096a/propcache-0.5.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:5570dbcc97571c15f68068e529c92715a12f8d54030e272d264b377e22bd17a5", size = 57099, upload-time = "2026-05-08T21:01:34.915Z" }, + { url = "https://files.pythonhosted.org/packages/55/27/9cb0b4c679124085327957d42521c99dba04c88c90c3e55a6f0b633ebccc/propcache-0.5.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f814362777a9f841adddb200ecdf8f5cb1e5a3c4b7a86378edbd6ccb26edd702", size = 63391, upload-time = "2026-05-08T21:01:36.231Z" }, + { url = "https://files.pythonhosted.org/packages/f0/9d/7258aaa5bdf60fc6f27591eef6fe52768cb0beda7140be477c8b12c9794a/propcache-0.5.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:196913dea116aeb5a2ba95af4ddcb7ea85559ae07d8eee8751688310d09168c3", size = 61626, upload-time = "2026-05-08T21:01:37.545Z" }, + { url = "https://files.pythonhosted.org/packages/8e/0d/41c602003e8a9b16fe1e7eadf62c7bfba9d5474370b24200bf48b315f45f/propcache-0.5.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:6e7b8719005dd1175be4ab1cd25e9b98659a5e0347331506ec6760d2773a7fb5", size = 64781, upload-time = "2026-05-08T21:01:38.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f3/38e66b1856e9bd079deea015bc4a55f7767c0e4db2f7dcf69e7e680ba4ce/propcache-0.5.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:51f96d685ab16e88cab128cd37a52c5da540809c8b879fa047731bfcb4ad35a4", size = 62570, upload-time = "2026-05-08T21:01:40.415Z" }, + { url = "https://files.pythonhosted.org/packages/95/ca/bbfe9b910ce57dde8bb4876b4520fc02a4e89497c10de26be936758a3aaa/propcache-0.5.2-cp314-cp314-win32.whl", hash = "sha256:cc6fc3cc62e8501d3ed62894425040d2728ecddb1ed072737a5c70bd537aa9f0", size = 39436, upload-time = "2026-05-08T21:01:41.654Z" }, + { url = "https://files.pythonhosted.org/packages/61/d2/45c9defbaa1ea297035d9d4cce9e8f80daafbf19319c6007f157c6256ea9/propcache-0.5.2-cp314-cp314-win_amd64.whl", hash = "sha256:81e3a30b0bb60caa22033dd0f8a3618d1d67356212514f62c57db75cb0ef410c", size = 42373, upload-time = "2026-05-08T21:01:43.041Z" }, + { url = "https://files.pythonhosted.org/packages/44/68/9ea5103f41d5217d7d6ec24db90018e23aebec070c3f9a6e54d12b841fd8/propcache-0.5.2-cp314-cp314-win_arm64.whl", hash = "sha256:0d2c9bf8528f135dbb805ce027567e09164f7efa51a2be07458a2c0420f292d0", size = 38554, upload-time = "2026-05-08T21:01:44.336Z" }, + { url = "https://files.pythonhosted.org/packages/8a/81/fadf555f42d3b762eea8a53950b0489fdc0aa9da5f8ed9e10ce0a4e01b48/propcache-0.5.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:4bc8ff1feffc6a61c7002ffe84634c41b822e104990ae009f44a0834430070bb", size = 99395, upload-time = "2026-05-08T21:01:45.883Z" }, + { url = "https://files.pythonhosted.org/packages/f5/c9/c61e134a686949cf7971af3a390148b1156f7be81c73bc0cd12c873e2d48/propcache-0.5.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:79aa3ff0a9b566633b642fa9caf7e21ed1c13d6feca718187873f199e1514078", size = 56653, upload-time = "2026-05-08T21:01:47.307Z" }, + { url = "https://files.pythonhosted.org/packages/cb/73/daf935ea7048ddd7ec8eec5345b4a40b619d2d178b3c0a0900796bc3c794/propcache-0.5.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1b31822f4474c4036bae62de9402710051d431a606d6a0f907fec79935a071aa", size = 56914, upload-time = "2026-05-08T21:01:48.573Z" }, + { url = "https://files.pythonhosted.org/packages/79/9f/aba959b435ea18617edd7cf0a7ad0b9c574b8fc7e3d2cd55fb59cb255d33/propcache-0.5.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13fef48778b5a2a756523fdb781326b028ca75e32858b04f2cdd19f394564917", size = 62567, upload-time = "2026-05-08T21:01:49.903Z" }, + { url = "https://files.pythonhosted.org/packages/6c/a1/859942de9a791ff42f6141736f5b37749b8f53e65edfa49638c67dd67e6a/propcache-0.5.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8b73ab70f1a3351fbc71f663b3e645af6dd0329100c353081cf69c37433fc6fe", size = 65542, upload-time = "2026-05-08T21:01:51.204Z" }, + { url = "https://files.pythonhosted.org/packages/b5/61/315bc0fd6c0fc7f80a528b8afd209e5fc4a875ea79571b91b8f50f442907/propcache-0.5.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5538d2c13d93e4698af7e092b57bc7298fd35d1d58e656ae18f23ee0d0378e03", size = 66845, upload-time = "2026-05-08T21:01:52.539Z" }, + { url = "https://files.pythonhosted.org/packages/47/f7/9f8122e3132e8e354ac41975ef8f1099be7d5a16bc7ae562734e993665c0/propcache-0.5.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd645f03898405cabe694fb8bc35241e3a9c332ec85627584fe3de201452b335", size = 63985, upload-time = "2026-05-08T21:01:53.847Z" }, + { url = "https://files.pythonhosted.org/packages/c8/54/c317819ec157cbf6f35df9df9657a6f82daf34d5faf15948b2f639c2192e/propcache-0.5.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a473b3440261e0c60706e732b2ed2f517857344fc21bf48fdfe211e2d98eb285", size = 63999, upload-time = "2026-05-08T21:01:55.179Z" }, + { url = "https://files.pythonhosted.org/packages/5a/56/387e3f7dfce0a9233df41fb888aa1c30222cb4bbbf09537c02dd9bd85fe2/propcache-0.5.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7afa37062e6650640e932e4cc9297d81f9f42d9944029cc386b8247dea4da837", size = 62779, upload-time = "2026-05-08T21:01:57.489Z" }, + { url = "https://files.pythonhosted.org/packages/a1/9c/596784cb5824ed61ee960d3f8655a3f0993e107c6e98ab6c818b7fb92ccb/propcache-0.5.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:8a90efd5777e996e42d568db9ac740b944d691e565cbfd31b2f7832f9184b2b8", size = 59796, upload-time = "2026-05-08T21:01:58.736Z" }, + { url = "https://files.pythonhosted.org/packages/c2/3d/1a6cfa1726a48542c1e8784a0761421476a5b68e09b7f36bf95eb954aaba/propcache-0.5.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:f19bb891234d72535764d703bfed1153cc34f4214d5bd7150aee1eec9e8f4366", size = 66023, upload-time = "2026-05-08T21:02:00.228Z" }, + { url = "https://files.pythonhosted.org/packages/e4/0e/05fd6990369477076e4e280bcb970de760fddf0161a46e988bc95f7940ec/propcache-0.5.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:32775082acd2d807ee3db715c7770d38767b817870acfa08c29e057f3c4d5b56", size = 64448, upload-time = "2026-05-08T21:02:01.888Z" }, + { url = "https://files.pythonhosted.org/packages/cd/86/5f8da315a4309c62c10c0b2516b17492d5d3bbe1bb862b96604db67e2a37/propcache-0.5.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9282fb1a3bccd038da9f768b927b24a0c753e466c086b7c4f3c6982851eefb2d", size = 67329, upload-time = "2026-05-08T21:02:03.484Z" }, + { url = "https://files.pythonhosted.org/packages/da/d3/3368efe79ab21f0cdf86ef49895811c9cc933131d4cde1f28a624e22e712/propcache-0.5.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cc49723e2f60d6b32a0f0b08a3fd6d13203c07f1cd9566cfce0f12a917c967a2", size = 65172, upload-time = "2026-05-08T21:02:04.745Z" }, + { url = "https://files.pythonhosted.org/packages/d5/07/127e8b0bacfb325396196f9d976a22453049b89b9b2b08477cc3145faa44/propcache-0.5.2-cp314-cp314t-win32.whl", hash = "sha256:2d7aa89ebca5acc98cba9d1472d976e394782f587bad6661003602a619fd1821", size = 43813, upload-time = "2026-05-08T21:02:06.025Z" }, + { url = "https://files.pythonhosted.org/packages/88/fb/46dad6c0ae49ed230ab1b16c890c2b6314e2403e6c412976f4a72d64a527/propcache-0.5.2-cp314-cp314t-win_amd64.whl", hash = "sha256:d447bb0b3054be5818458fbb171208b1d9ff11eba14e18ca18b90cbb45767370", size = 47764, upload-time = "2026-05-08T21:02:07.353Z" }, + { url = "https://files.pythonhosted.org/packages/e7/c4/a47d0a63aa309d10d59ede6e9d4cff03a344a79d1f0f4cd0cd74997b53e0/propcache-0.5.2-cp314-cp314t-win_arm64.whl", hash = "sha256:fe67a3d11cd9b4efabfa45c3d00ffba2b26811442a73a581a94b67c2b5faccf6", size = 41140, upload-time = "2026-05-08T21:02:09.065Z" }, + { url = "https://files.pythonhosted.org/packages/3a/ed/1cdcab6ba3d6ab7feca11fc14f0eeea80755bb53ef4e892079f31b10a25f/propcache-0.5.2-py3-none-any.whl", hash = "sha256:be1ddfcbb376e3de5d2e2db1d58d6d67463e6b4f9f040c000de8e300295465fe", size = 14036, upload-time = "2026-05-08T21:02:10.673Z" }, +] + +[[package]] +name = "pyarrow" +version = "24.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/91/13/13e1069b351bdc3881266e11147ffccf687505dbb0ea74036237f5d454a5/pyarrow-24.0.0.tar.gz", hash = "sha256:85fe721a14dd823aca09127acbb06c3ca723efbd436c004f16bca601b04dcc83", size = 1180261, upload-time = "2026-04-21T10:51:25.837Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/a9/9686d9f07837f91f775e8932659192e02c74f9d8920524b480b85212cc68/pyarrow-24.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:6233c9ed9ab9d1db47de57d9753256d9dcffbf42db341576099f0fd9f6bf4810", size = 34981559, upload-time = "2026-04-21T10:47:22.17Z" }, + { url = "https://files.pythonhosted.org/packages/80/b6/0ddf0e9b6ead3474ab087ae598c76b031fc45532bf6a63f3a553440fb258/pyarrow-24.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:f7616236ec1bc2b15bfdec22a71ab38851c86f8f05ff64f379e1278cf20c634a", size = 36663654, upload-time = "2026-04-21T10:47:28.315Z" }, + { url = "https://files.pythonhosted.org/packages/7c/3b/926382efe8ce27ba729071d3566ade6dfb86bdf112f366000196b2f5780a/pyarrow-24.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:1617043b99bd33e5318ae18eb2919af09c71322ef1ca46566cdafc6e6712fb66", size = 45679394, upload-time = "2026-04-21T10:47:34.821Z" }, + { url = "https://files.pythonhosted.org/packages/b3/7a/829f7d9dfd37c207206081d6dad474d81dde29952401f07f2ba507814818/pyarrow-24.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:6165461f55ef6314f026de6638d661188e3455d3ec49834556a0ebbdbace18bb", size = 48863122, upload-time = "2026-04-21T10:47:42.056Z" }, + { url = "https://files.pythonhosted.org/packages/5f/e8/f88ce625fe8babaae64e8db2d417c7653adb3019b08aae85c5ed787dc816/pyarrow-24.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3b13dedfe76a0ad2d1d859b0811b53827a4e9d93a0bcb05cf59333ab4980cc7e", size = 49376032, upload-time = "2026-04-21T10:47:48.967Z" }, + { url = "https://files.pythonhosted.org/packages/36/7a/82c363caa145fff88fb475da50d3bf52bb024f61917be5424c3392eaf878/pyarrow-24.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:25ea65d868eb04015cd18e6df2fbe98f07e5bda2abefabcb88fce39a947716f6", size = 51929490, upload-time = "2026-04-21T10:47:55.981Z" }, + { url = "https://files.pythonhosted.org/packages/66/1c/e3e72c8014ad2743ca64a701652c733cc5cbcee15c0463a32a8c55518d9e/pyarrow-24.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:295f0a7f2e242dabd513737cf076007dc5b2d59237e3eca37b05c0c6446f3826", size = 27355660, upload-time = "2026-04-21T10:48:01.718Z" }, + { url = "https://files.pythonhosted.org/packages/6f/d3/a1abf004482026ddc17f4503db227787fa3cfe41ec5091ff20e4fea55e57/pyarrow-24.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:02b001b3ed4723caa44f6cd1af2d5c86aa2cf9971dacc2ffa55b21237713dfba", size = 34976759, upload-time = "2026-04-21T10:48:07.258Z" }, + { url = "https://files.pythonhosted.org/packages/4f/4a/34f0a36d28a2dd32225301b79daad44e243dc1a2bb77d43b60749be255c4/pyarrow-24.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:04920d6a71aabd08a0417709efce97d45ea8e6fb733d9ca9ecffb13c67839f68", size = 36658471, upload-time = "2026-04-21T10:48:13.347Z" }, + { url = "https://files.pythonhosted.org/packages/1f/78/543b94712ae8bb1a6023bcc1acf1a740fbff8286747c289cd9468fced2a5/pyarrow-24.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:a964266397740257f16f7bb2e4f08a0c81454004beab8ff59dd531b73610e9f2", size = 45675981, upload-time = "2026-04-21T10:48:20.201Z" }, + { url = "https://files.pythonhosted.org/packages/84/9f/8fb7c222b100d314137fa40ec050de56cd8c6d957d1cfff685ce72f15b17/pyarrow-24.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:6f066b179d68c413374294bc1735f68475457c933258df594443bb9d88ddc2a0", size = 48859172, upload-time = "2026-04-21T10:48:27.541Z" }, + { url = "https://files.pythonhosted.org/packages/a7/d3/1ea72538e6c8b3b475ed78d1049a2c518e655761ea50fe1171fc855fcab7/pyarrow-24.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1183baeb14c5f587b1ec52831e665718ce632caab84b7cd6b85fd44f96114495", size = 49385733, upload-time = "2026-04-21T10:48:34.7Z" }, + { url = "https://files.pythonhosted.org/packages/c3/be/c3d8b06a1ba35f2260f8e1f771abbee7d5e345c0937aab90675706b1690a/pyarrow-24.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:806f24b4085453c197a5078218d1ee08783ebbba271badd153d1ae22a3ee804f", size = 51934335, upload-time = "2026-04-21T10:48:42.099Z" }, + { url = "https://files.pythonhosted.org/packages/9c/62/89e07a1e7329d2cde3e3c6994ba0839a24977a2beda8be6005ea3d860b99/pyarrow-24.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:e4505fc6583f7b05ab854934896bcac8253b04ac1171a77dfb73efef92076d91", size = 27271748, upload-time = "2026-04-21T10:49:42.532Z" }, + { url = "https://files.pythonhosted.org/packages/17/1a/cff3a59f80b5b1658549d46611b67163f65e0664431c076ad728bf9d5af4/pyarrow-24.0.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:1a4e45017efbf115032e4475ee876d525e0e36c742214fbe405332480ecd6275", size = 35238554, upload-time = "2026-04-21T10:48:48.526Z" }, + { url = "https://files.pythonhosted.org/packages/a8/99/cce0f42a327bfef2c420fb6078a3eb834826e5d6697bf3009fe11d2ad051/pyarrow-24.0.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:7986f1fa71cee060ad00758bcc79d3a93bab8559bf978fab9e53472a2e25a17b", size = 36782301, upload-time = "2026-04-21T10:48:55.181Z" }, + { url = "https://files.pythonhosted.org/packages/2a/66/8e560d5ff6793ca29aca213c53eec0dd482dd46cb93b2819e5aab52e4252/pyarrow-24.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:d3e0b61e8efb24ed38898e5cdc5fffa9124be480008d401a1f8071500494ae42", size = 45721929, upload-time = "2026-04-21T10:49:03.676Z" }, + { url = "https://files.pythonhosted.org/packages/27/0c/a26e25505d030716e078d9f16eb74973cbf0b33b672884e9f9da1c83b871/pyarrow-24.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:55a3bc1e3df3b5567b7d27ef551b2283f0c68a5e86f1cd56abc569da4f31335b", size = 48825365, upload-time = "2026-04-21T10:49:11.714Z" }, + { url = "https://files.pythonhosted.org/packages/5f/eb/771f9ecb0c65e73fe9dccdd1717901b9594f08c4515d000c7c62df573811/pyarrow-24.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:641f795b361874ac9da5294f8f443dfdbee355cf2bd9e3b8d97aaac2306b9b37", size = 49451819, upload-time = "2026-04-21T10:49:21.474Z" }, + { url = "https://files.pythonhosted.org/packages/48/da/61ae89a88732f5a785646f3ec6125dbb640fa98a540eb2b9889caa561403/pyarrow-24.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8adc8e6ce5fccf5dc707046ae4914fd537def529709cc0d285d37a7f9cd442ca", size = 51909252, upload-time = "2026-04-21T10:49:31.164Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1a/8dd5cafab7b66573fa91c03d06d213356ad4edd71813aa75e08ce2b3a844/pyarrow-24.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:9b18371ad2f44044b81a8d23bc2d8a9b6a6226dca775e8e16cfee640473d6c5d", size = 27388127, upload-time = "2026-04-21T10:49:37.334Z" }, + { url = "https://files.pythonhosted.org/packages/ad/80/d022a34ff05d2cbedd8ccf841fc1f532ecfa9eb5ed1711b56d0e0ea71fc9/pyarrow-24.0.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:1cc9057f0319e26333b357e17f3c2c022f1a83739b48a88b25bfd5fa2dc18838", size = 35007997, upload-time = "2026-04-21T10:49:48.796Z" }, + { url = "https://files.pythonhosted.org/packages/1a/ff/f01485fda6f4e5d441afb8dd5e7681e4db18826c1e271852f5d3957d6a80/pyarrow-24.0.0-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:e6f1278ee4785b6db21229374a1c9e54ec7c549de5d1efc9630b6207de7e170b", size = 36678720, upload-time = "2026-04-21T10:49:55.858Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c2/2d2d5fea814237923f71b36495211f20b43a1576f9a4d6da7e751a64ec6f/pyarrow-24.0.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:adbbedc55506cbdabb830890444fb856bfb0060c46c6f8026c6c2f2cf86ae795", size = 45741852, upload-time = "2026-04-21T10:50:04.624Z" }, + { url = "https://files.pythonhosted.org/packages/8e/3a/28ba9c1c1ebdbb5f1b94dfebb46f207e52e6a554b7fe4132540fde29a3a0/pyarrow-24.0.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:ae8a1145af31d903fa9bb166824d7abe9b4681a000b0159c9fb99c11bc11ad26", size = 48889852, upload-time = "2026-04-21T10:50:12.293Z" }, + { url = "https://files.pythonhosted.org/packages/df/51/4a389acfd31dca009f8fb82d7f510bb4130f2b3a8e18cf00194d0687d8ac/pyarrow-24.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d7027eba1df3b2069e2e8d80f644fa0918b68c46432af3d088ddd390d063ecde", size = 49445207, upload-time = "2026-04-21T10:50:20.677Z" }, + { url = "https://files.pythonhosted.org/packages/19/4b/0bab2b23d2ae901b1b9a03c0efd4b2d070256f8ce3fc43f6e58c167b2081/pyarrow-24.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e56a1ffe9bf7b727432b89104cc0849c21582949dd7bdcb34f17b2001a351a76", size = 51954117, upload-time = "2026-04-21T10:50:29.14Z" }, + { url = "https://files.pythonhosted.org/packages/29/88/f4e9145da0417b3d2c12035a8492b35ff4a3dbc653e614fcfb51d9dedb38/pyarrow-24.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:38be1808cdd068605b787e6ca9119b27eb275a0234e50212c3492331680c3b1e", size = 28001155, upload-time = "2026-04-21T10:51:22.337Z" }, + { url = "https://files.pythonhosted.org/packages/79/4f/46a49a63f43526da895b1a45bbb51d5baf8e4d77159f8528fc3e5490007f/pyarrow-24.0.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:418e48ce50a45a6a6c73c454677203a9c75c966cb1e92ca3370959185f197a05", size = 35250387, upload-time = "2026-04-21T10:50:35.552Z" }, + { url = "https://files.pythonhosted.org/packages/a0/da/d5e0cd5ef00796922404806d5f00325cdadc3441ce2c13fe7115f2df9a64/pyarrow-24.0.0-cp314-cp314t-macosx_12_0_x86_64.whl", hash = "sha256:2f16197705a230a78270cdd4ea8a1d57e86b2fdcbc34a1f6aebc72e65c986f9a", size = 36797102, upload-time = "2026-04-21T10:50:42.417Z" }, + { url = "https://files.pythonhosted.org/packages/34/c7/5904145b0a593a05236c882933d439b5720f0a145381179063722fbfc123/pyarrow-24.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:fb24ac194bfc5e86839d7dcd52092ee31e5fe6733fe11f5e3b06ef0812b20072", size = 45745118, upload-time = "2026-04-21T10:50:49.324Z" }, + { url = "https://files.pythonhosted.org/packages/13/d3/cca42fe166d1c6e4d5b80e530b7949104d10e17508a90ae202dac205ce2a/pyarrow-24.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:9700ebd9a51f5895ce75ff4ac4b3c47a7d4b42bc618be8e713e5d56bacf5f931", size = 48844765, upload-time = "2026-04-21T10:50:55.579Z" }, + { url = "https://files.pythonhosted.org/packages/b0/49/942c3b79878ba928324d1e17c274ed84581db8c0a749b24bcf4cbdf15bd3/pyarrow-24.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d8ddd2768da81d3ee08cfea9b597f4abb4e8e1dc8ae7e204b608d23a0d3ab699", size = 49471890, upload-time = "2026-04-21T10:51:02.439Z" }, + { url = "https://files.pythonhosted.org/packages/76/97/ff71431000a75d84135a1ace5ca4ba11726a231a8007bbb320a4c54075d5/pyarrow-24.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:61a3d7eaa97a14768b542f3d284dc6400dd2470d9f080708b13cd46b6ae18136", size = 51932250, upload-time = "2026-04-21T10:51:10.576Z" }, + { url = "https://files.pythonhosted.org/packages/51/be/6f79d55816d5c22557cf27533543d5d70dfe692adfbee4b99f2760674f38/pyarrow-24.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:c91d00057f23b8d353039520dc3a6c09d8608164c692e9f59a175a42b2ae0c19", size = 28131282, upload-time = "2026-04-21T10:51:16.815Z" }, +] + +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, +] + +[[package]] +name = "pydantic" +version = "2.13.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.46.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" }, + { url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" }, + { url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" }, + { url = "https://files.pythonhosted.org/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418, upload-time = "2026-05-06T13:37:38.234Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274, upload-time = "2026-05-06T13:38:27.753Z" }, + { url = "https://files.pythonhosted.org/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940, upload-time = "2026-05-06T13:38:05.353Z" }, + { url = "https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516, upload-time = "2026-05-06T13:39:10.577Z" }, + { url = "https://files.pythonhosted.org/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854, upload-time = "2026-05-06T13:40:22.59Z" }, + { url = "https://files.pythonhosted.org/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306, upload-time = "2026-05-06T13:40:10.666Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044, upload-time = "2026-05-06T13:40:43.231Z" }, + { url = "https://files.pythonhosted.org/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133, upload-time = "2026-05-06T13:39:57.365Z" }, + { url = "https://files.pythonhosted.org/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464, upload-time = "2026-05-06T13:38:06.976Z" }, + { url = "https://files.pythonhosted.org/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823, upload-time = "2026-05-06T13:40:47.985Z" }, + { url = "https://files.pythonhosted.org/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919, upload-time = "2026-05-06T13:39:21.153Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604, upload-time = "2026-05-06T13:39:03.753Z" }, + { url = "https://files.pythonhosted.org/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" }, + { url = "https://files.pythonhosted.org/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" }, + { url = "https://files.pythonhosted.org/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" }, + { url = "https://files.pythonhosted.org/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" }, + { url = "https://files.pythonhosted.org/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" }, + { url = "https://files.pythonhosted.org/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" }, + { url = "https://files.pythonhosted.org/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" }, + { url = "https://files.pythonhosted.org/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" }, + { url = "https://files.pythonhosted.org/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" }, + { url = "https://files.pythonhosted.org/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" }, + { url = "https://files.pythonhosted.org/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" }, + { url = "https://files.pythonhosted.org/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" }, + { url = "https://files.pythonhosted.org/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" }, + { url = "https://files.pythonhosted.org/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" }, + { url = "https://files.pythonhosted.org/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" }, + { url = "https://files.pythonhosted.org/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" }, + { url = "https://files.pythonhosted.org/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" }, + { url = "https://files.pythonhosted.org/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" }, + { url = "https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" }, + { url = "https://files.pythonhosted.org/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" }, + { url = "https://files.pythonhosted.org/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" }, + { url = "https://files.pythonhosted.org/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" }, + { url = "https://files.pythonhosted.org/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" }, + { url = "https://files.pythonhosted.org/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" }, + { url = "https://files.pythonhosted.org/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" }, + { url = "https://files.pythonhosted.org/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" }, + { url = "https://files.pythonhosted.org/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" }, + { url = "https://files.pythonhosted.org/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" }, + { url = "https://files.pythonhosted.org/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" }, + { url = "https://files.pythonhosted.org/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" }, + { url = "https://files.pythonhosted.org/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" }, + { url = "https://files.pythonhosted.org/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" }, + { url = "https://files.pythonhosted.org/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" }, + { url = "https://files.pythonhosted.org/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" }, + { url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" }, + { url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" }, + { url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" }, + { url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" }, + { url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" }, +] + +[[package]] +name = "pydantic-settings" +version = "2.14.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/07/60/1d1e59c9c90d54591469ada7d268251f71c24bdb765f1a8a832cee8c6653/pydantic_settings-2.14.1.tar.gz", hash = "sha256:e874d3bec7e787b0c9958277956ed9b4dd5de6a80e162188fdaff7c5e26fd5fa", size = 235551, upload-time = "2026-05-08T13:40:06.542Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ae/8d/f1af3832f5e6eb13ba94ee809e72b8ecb5eef226d27ee0bef7d963d943c7/pydantic_settings-2.14.1-py3-none-any.whl", hash = "sha256:6e3c7edfd8277687cdc598f56e5cff0e9bfff0910a3749deaa8d4401c3a2b9de", size = 60964, upload-time = "2026-05-08T13:40:04.958Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pyjwt" +version = "2.13.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/81/58d0ac84e1ef3a3843791d6954d94c0b33d526c75eeb1efbce9d0a4c4077/pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423", size = 107515, upload-time = "2026-05-21T19:54:36.618Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/5e/ecf12fdb62546d64385c158514e9b2b671f7832108ef2ecd2020ce0af2d1/pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728", size = 31274, upload-time = "2026-05-21T19:54:35.362Z" }, +] + +[package.optional-dependencies] +crypto = [ + { name = "cryptography" }, +] + +[[package]] +name = "pytest" +version = "9.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, +] + +[[package]] +name = "pytest-asyncio" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/7c/d36d04db312ecf4298932ef77e6e4a9e8ad017906e24e34f0b0c361a2473/pytest_asyncio-1.4.0.tar.gz", hash = "sha256:c6c0d2259945122819f171a32ecea2c349ead889ee28176caaf492143424be42", size = 58514, upload-time = "2026-05-26T09:56:04.083Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/e2/08a497ef684b88559c9cc5f4ad53a37e7b99e727094a86d6ea32536d5d3c/pytest_asyncio-1.4.0-py3-none-any.whl", hash = "sha256:933ca923a23075a87fb7070c0ec272a6848489824d887c85c812670932835aa1", size = 16930, upload-time = "2026-05-26T09:56:02.576Z" }, +] + +[[package]] +name = "pytest-cov" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage" }, + { name = "pluggy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" }, +] + +[[package]] +name = "pytest-html" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jinja2" }, + { name = "pytest" }, + { name = "pytest-metadata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c4/08/2076aa09507e51c1119d16a84c6307354d16270558f1a44fc9a2c99fdf1d/pytest_html-4.2.0.tar.gz", hash = "sha256:b6a88cba507500d8709959201e2e757d3941e859fd17cfd4ed87b16fc0c67912", size = 108634, upload-time = "2026-01-19T11:25:26.471Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/47/07046e0acedc12fe2bae79cf6c73ad67f51ae9d67df64d06b0f3eac73d36/pytest_html-4.2.0-py3-none-any.whl", hash = "sha256:ff5caf3e17a974008e5816edda61168e6c3da442b078a44f8744865862a85636", size = 23801, upload-time = "2026-01-19T11:25:25.008Z" }, +] + +[[package]] +name = "pytest-metadata" +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/85/8c969f8bec4e559f8f2b958a15229a35495f5b4ce499f6b865eac54b878d/pytest_metadata-3.1.1.tar.gz", hash = "sha256:d2a29b0355fbc03f168aa96d41ff88b1a3b44a3b02acbe491801c98a048017c8", size = 9952, upload-time = "2024-02-12T19:38:44.887Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/43/7e7b2ec865caa92f67b8f0e9231a798d102724ca4c0e1f414316be1c1ef2/pytest_metadata-3.1.1-py3-none-any.whl", hash = "sha256:c8e0844db684ee1c798cfa38908d20d67d0463ecb6137c72e91f418558dd5f4b", size = 11428, upload-time = "2024-02-12T19:38:42.531Z" }, +] + +[[package]] +name = "pytest-order" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fe/76/c7b4a2ad3758a3c2757f532c6e931aa5e387781512a71b67b6ddc19d9ef8/pytest_order-1.4.0.tar.gz", hash = "sha256:327fb6eee1ae771051da13d2a0d9306d947e87f9ab8f4d6302e5d122c7472691", size = 49891, upload-time = "2026-04-26T12:37:43.056Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/d0/c62c07141151f259faddff6bd591f28235c37dd0c486160d0d2a0d4e6e5a/pytest_order-1.4.0-py3-none-any.whl", hash = "sha256:05b1710cf16bb2123294eec5bdfaee513322ff1926c0dfa86eb8be632eb264a1", size = 14918, upload-time = "2026-04-26T12:37:41.123Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, +] + +[[package]] +name = "python-multipart" +version = "0.0.31" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/64/7e/9b35ad8f3d9ca680f7c87a88f19612fdd8da9796c4d3b46e560ac79dcc4a/python_multipart-0.0.31.tar.gz", hash = "sha256:fc631183bb13e56db3158a4909908dfb2e23565286744e798241e63750e5d680", size = 46689, upload-time = "2026-06-04T08:27:49.014Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/1e/7f7f299527a5a8ad90acd5f2f78dfa6c8495c6301a3205106ea68a84de96/python_multipart-0.0.31-py3-none-any.whl", hash = "sha256:8408153d68a9773291fc1da39a8b85a50044bddbabd2dd72e9229776b7b15e28", size = 29996, upload-time = "2026-06-04T08:27:47.804Z" }, +] + +[[package]] +name = "pytokens" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/34/b4e015b99031667a7b960f888889c5bd34ef585c85e1cb56a594b92836ac/pytokens-0.4.1.tar.gz", hash = "sha256:292052fe80923aae2260c073f822ceba21f3872ced9a68bb7953b348e561179a", size = 23015, upload-time = "2026-01-30T01:03:45.924Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/5d/e44573011401fb82e9d51e97f1290ceb377800fb4eed650b96f4753b499c/pytokens-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:140709331e846b728475786df8aeb27d24f48cbcf7bcd449f8de75cae7a45083", size = 160663, upload-time = "2026-01-30T01:03:06.473Z" }, + { url = "https://files.pythonhosted.org/packages/f0/e6/5bbc3019f8e6f21d09c41f8b8654536117e5e211a85d89212d59cbdab381/pytokens-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d6c4268598f762bc8e91f5dbf2ab2f61f7b95bdc07953b602db879b3c8c18e1", size = 255626, upload-time = "2026-01-30T01:03:08.177Z" }, + { url = "https://files.pythonhosted.org/packages/bf/3c/2d5297d82286f6f3d92770289fd439956b201c0a4fc7e72efb9b2293758e/pytokens-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24afde1f53d95348b5a0eb19488661147285ca4dd7ed752bbc3e1c6242a304d1", size = 269779, upload-time = "2026-01-30T01:03:09.756Z" }, + { url = "https://files.pythonhosted.org/packages/20/01/7436e9ad693cebda0551203e0bf28f7669976c60ad07d6402098208476de/pytokens-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5ad948d085ed6c16413eb5fec6b3e02fa00dc29a2534f088d3302c47eb59adf9", size = 268076, upload-time = "2026-01-30T01:03:10.957Z" }, + { url = "https://files.pythonhosted.org/packages/2e/df/533c82a3c752ba13ae7ef238b7f8cdd272cf1475f03c63ac6cf3fcfb00b6/pytokens-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:3f901fe783e06e48e8cbdc82d631fca8f118333798193e026a50ce1b3757ea68", size = 103552, upload-time = "2026-01-30T01:03:12.066Z" }, + { url = "https://files.pythonhosted.org/packages/cb/dc/08b1a080372afda3cceb4f3c0a7ba2bde9d6a5241f1edb02a22a019ee147/pytokens-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8bdb9d0ce90cbf99c525e75a2fa415144fd570a1ba987380190e8b786bc6ef9b", size = 160720, upload-time = "2026-01-30T01:03:13.843Z" }, + { url = "https://files.pythonhosted.org/packages/64/0c/41ea22205da480837a700e395507e6a24425151dfb7ead73343d6e2d7ffe/pytokens-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5502408cab1cb18e128570f8d598981c68a50d0cbd7c61312a90507cd3a1276f", size = 254204, upload-time = "2026-01-30T01:03:14.886Z" }, + { url = "https://files.pythonhosted.org/packages/e0/d2/afe5c7f8607018beb99971489dbb846508f1b8f351fcefc225fcf4b2adc0/pytokens-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29d1d8fb1030af4d231789959f21821ab6325e463f0503a61d204343c9b355d1", size = 268423, upload-time = "2026-01-30T01:03:15.936Z" }, + { url = "https://files.pythonhosted.org/packages/68/d4/00ffdbd370410c04e9591da9220a68dc1693ef7499173eb3e30d06e05ed1/pytokens-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b08dd6b86058b6dc07efe9e98414f5102974716232d10f32ff39701e841c4", size = 266859, upload-time = "2026-01-30T01:03:17.458Z" }, + { url = "https://files.pythonhosted.org/packages/a7/c9/c3161313b4ca0c601eeefabd3d3b576edaa9afdefd32da97210700e47652/pytokens-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:9bd7d7f544d362576be74f9d5901a22f317efc20046efe2034dced238cbbfe78", size = 103520, upload-time = "2026-01-30T01:03:18.652Z" }, + { url = "https://files.pythonhosted.org/packages/8f/a7/b470f672e6fc5fee0a01d9e75005a0e617e162381974213a945fcd274843/pytokens-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4a14d5f5fc78ce85e426aa159489e2d5961acf0e47575e08f35584009178e321", size = 160821, upload-time = "2026-01-30T01:03:19.684Z" }, + { url = "https://files.pythonhosted.org/packages/80/98/e83a36fe8d170c911f864bfded690d2542bfcfacb9c649d11a9e6eb9dc41/pytokens-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f50fd18543be72da51dd505e2ed20d2228c74e0464e4262e4899797803d7fa", size = 254263, upload-time = "2026-01-30T01:03:20.834Z" }, + { url = "https://files.pythonhosted.org/packages/0f/95/70d7041273890f9f97a24234c00b746e8da86df462620194cef1d411ddeb/pytokens-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc74c035f9bfca0255c1af77ddd2d6ae8419012805453e4b0e7513e17904545d", size = 268071, upload-time = "2026-01-30T01:03:21.888Z" }, + { url = "https://files.pythonhosted.org/packages/da/79/76e6d09ae19c99404656d7db9c35dfd20f2086f3eb6ecb496b5b31163bad/pytokens-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f66a6bbe741bd431f6d741e617e0f39ec7257ca1f89089593479347cc4d13324", size = 271716, upload-time = "2026-01-30T01:03:23.633Z" }, + { url = "https://files.pythonhosted.org/packages/79/37/482e55fa1602e0a7ff012661d8c946bafdc05e480ea5a32f4f7e336d4aa9/pytokens-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:b35d7e5ad269804f6697727702da3c517bb8a5228afa450ab0fa787732055fc9", size = 104539, upload-time = "2026-01-30T01:03:24.788Z" }, + { url = "https://files.pythonhosted.org/packages/30/e8/20e7db907c23f3d63b0be3b8a4fd1927f6da2395f5bcc7f72242bb963dfe/pytokens-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8fcb9ba3709ff77e77f1c7022ff11d13553f3c30299a9fe246a166903e9091eb", size = 168474, upload-time = "2026-01-30T01:03:26.428Z" }, + { url = "https://files.pythonhosted.org/packages/d6/81/88a95ee9fafdd8f5f3452107748fd04c24930d500b9aba9738f3ade642cc/pytokens-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79fc6b8699564e1f9b521582c35435f1bd32dd06822322ec44afdeba666d8cb3", size = 290473, upload-time = "2026-01-30T01:03:27.415Z" }, + { url = "https://files.pythonhosted.org/packages/cf/35/3aa899645e29b6375b4aed9f8d21df219e7c958c4c186b465e42ee0a06bf/pytokens-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d31b97b3de0f61571a124a00ffe9a81fb9939146c122c11060725bd5aea79975", size = 303485, upload-time = "2026-01-30T01:03:28.558Z" }, + { url = "https://files.pythonhosted.org/packages/52/a0/07907b6ff512674d9b201859f7d212298c44933633c946703a20c25e9d81/pytokens-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:967cf6e3fd4adf7de8fc73cd3043754ae79c36475c1c11d514fc72cf5490094a", size = 306698, upload-time = "2026-01-30T01:03:29.653Z" }, + { url = "https://files.pythonhosted.org/packages/39/2a/cbbf9250020a4a8dd53ba83a46c097b69e5eb49dd14e708f496f548c6612/pytokens-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:584c80c24b078eec1e227079d56dc22ff755e0ba8654d8383b2c549107528918", size = 116287, upload-time = "2026-01-30T01:03:30.912Z" }, + { url = "https://files.pythonhosted.org/packages/c6/78/397db326746f0a342855b81216ae1f0a32965deccfd7c830a2dbc66d2483/pytokens-0.4.1-py3-none-any.whl", hash = "sha256:26cef14744a8385f35d0e095dc8b3a7583f6c953c2e3d269c7f82484bf5ad2de", size = 13729, upload-time = "2026-01-30T01:03:45.029Z" }, +] + +[[package]] +name = "pywin32" +version = "312" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/ff/32aa7d2ed0ab12b323aaa64f9b75e6ad4f8fd09f9ccfc28c79414d46838d/pywin32-312-cp312-cp312-win32.whl", hash = "sha256:dab4f65ac9c4e48400a2a0530c46c3c579cd5905ecd11b80692373915269208b", size = 6371877, upload-time = "2026-06-04T07:49:28.836Z" }, + { url = "https://files.pythonhosted.org/packages/03/d9/77040d3b43df3f3be32ea289433d660d2727f5ba327bc73be835127d9d60/pywin32-312-cp312-cp312-win_amd64.whl", hash = "sha256:b457f6d628a47e8a7346ce22acb7e1a46a4a78b52e1d17e1af56871bd19a93bc", size = 6914841, upload-time = "2026-06-04T07:49:31.85Z" }, + { url = "https://files.pythonhosted.org/packages/e3/cc/7b1ec671775756020a0ee7f4feeaf3c568f0ab86bd3900088cf986937a92/pywin32-312-cp312-cp312-win_arm64.whl", hash = "sha256:6017c58e12f6809fbb0555b75df144c2922a9ffd18e4b9b5afa863b6c1a9d950", size = 6727901, upload-time = "2026-06-04T07:49:34.244Z" }, + { url = "https://files.pythonhosted.org/packages/2d/41/12fbfd7f36ed2146d8bc9de96c2741296bf0d490b98508496cff322e274c/pywin32-312-cp313-cp313-win32.whl", hash = "sha256:7a27df850933d16a8eabfbaeb73d52b273e2da667f80d70b01a89d1f6828d02c", size = 6370184, upload-time = "2026-06-04T07:49:36.253Z" }, + { url = "https://files.pythonhosted.org/packages/ba/db/36a78e3403099d31d9746d13fdcde5accc43c1155f375a34d15983a479a7/pywin32-312-cp313-cp313-win_amd64.whl", hash = "sha256:c53e878d15a1c44788082bfe712a905433473aa38f86375b7cf8b45e3acbaaf9", size = 6914298, upload-time = "2026-06-04T07:49:38.876Z" }, + { url = "https://files.pythonhosted.org/packages/84/37/c1697194092b76de9ed47ca124323f02c57ffc8a45c06f88a3d5acaf01eb/pywin32-312-cp313-cp313-win_arm64.whl", hash = "sha256:59aba5d5940842075343a5ddc6b11f1cdf0d1567fe745290359dfbcc7c2eb831", size = 6727640, upload-time = "2026-06-04T07:49:41.083Z" }, + { url = "https://files.pythonhosted.org/packages/fc/2b/1f3cded5822fd49c02f40544cbb5f58c7cfd6b1694869fd476cb6170ee97/pywin32-312-cp314-cp314-win32.whl", hash = "sha256:a77a90fbb6881238d2ca9c6fd797b25817f3768fe78d214a90137ff055a75f5b", size = 6468928, upload-time = "2026-06-04T07:49:43.188Z" }, + { url = "https://files.pythonhosted.org/packages/21/82/3bf86d2e2808902013132e1ce905a7da0da53790f3836c64bf44d55e24f3/pywin32-312-cp314-cp314-win_amd64.whl", hash = "sha256:a4dd3a848290ef724347b19f301045831d8e802fa4464f491b98b1e0a081432e", size = 7024157, upload-time = "2026-06-04T07:49:45.34Z" }, + { url = "https://files.pythonhosted.org/packages/a4/0e/73f6d6800b4f27655abd9e9f6aaeaefcddb2b946e4674efa2bab184a7f7b/pywin32-312-cp314-cp314-win_arm64.whl", hash = "sha256:9fce94568364e0155e6dfb781ac5d95903be8baf28670632beab1b523f300daa", size = 6839598, upload-time = "2026-06-04T07:49:47.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/61/caa39686032d2ebdd04ff0ab5cbe163126c0066d98e00c9018646e42393b/pywin32-312-cp315-cp315-win32.whl", hash = "sha256:5c1fbe4a937a73ae9297384a3da38518cbc694c68ad8a809b2e19acd350f03ed", size = 6471159, upload-time = "2026-06-04T07:49:50.035Z" }, + { url = "https://files.pythonhosted.org/packages/0f/cd/7e1de64a4a6f69c04214169657ccab0d93a670ea50e35eb8f489d7378249/pywin32-312-cp315-cp315-win_amd64.whl", hash = "sha256:c2f03a0f73f804a13c2735b99392b0cd426bb4f2c4d0178e5ac966a0f21618d5", size = 7025293, upload-time = "2026-06-04T07:49:54.857Z" }, + { url = "https://files.pythonhosted.org/packages/23/ed/4532e9388e65fa16b46776ef47ad631a64eda1631884488af707666350ed/pywin32-312-cp315-cp315-win_arm64.whl", hash = "sha256:a8597d28f267b39074aef51fa593530082b39cbe5a074226096857b1fed2dfb9", size = 6840337, upload-time = "2026-06-04T07:49:57.531Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "ragas" +version = "0.4.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "appdirs" }, + { name = "datasets" }, + { name = "diskcache" }, + { name = "instructor" }, + { name = "langchain" }, + { name = "langchain-community" }, + { name = "langchain-core" }, + { name = "langchain-openai" }, + { name = "nest-asyncio" }, + { name = "networkx" }, + { name = "numpy" }, + { name = "openai" }, + { name = "pillow" }, + { name = "pydantic" }, + { name = "rich" }, + { name = "scikit-network" }, + { name = "tiktoken" }, + { name = "tqdm" }, + { name = "typer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d2/bc/3234517692ac0ffae1ec2ec940992e4057844c49ee6c51c07ce385bb98f1/ragas-0.4.3.tar.gz", hash = "sha256:1eb1f61dbc8613ad014fdb8d630cbe9a1caec1ea01664a106993cb756128c001", size = 44029626, upload-time = "2026-01-13T17:48:01.043Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/e0/1fecd22c93d3ed66453cbbdefd05528331af4d33b2b76a370d751231912c/ragas-0.4.3-py3-none-any.whl", hash = "sha256:ef1d75f674c294e9a6e7d8e9ad261b6bf4697dad1c9cbd1a756ba7a6b4849a38", size = 466452, upload-time = "2026-01-13T17:47:59.2Z" }, +] + +[[package]] +name = "rapidfuzz" +version = "3.14.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/21/ef6157213316e85790041254259907eb722e00b03480256c0545d98acd33/rapidfuzz-3.14.5.tar.gz", hash = "sha256:ba10ac57884ce82112f7ed910b67e7fb6072d8ef2c06e30dc63c0f604a112e0e", size = 57901753, upload-time = "2026-04-07T11:16:31.931Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/e3/574435c6aafb80254c191ef40d7aca2cb2bb97a095ec9395e9fa59ac307a/rapidfuzz-3.14.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0d3378f471ef440473a396ce2f8e97ee12f89a78b495540e0a5617bbfe895638", size = 1944601, upload-time = "2026-04-07T11:14:18.771Z" }, + { url = "https://files.pythonhosted.org/packages/d0/1f/fbad3102a255ecc112ce9a7e779bacab7fd14398217be8868dc9082ba363/rapidfuzz-3.14.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e910eebca9fd0eba245c0555e764597e8a0cccb673a92da2dc2397050725f48", size = 1164293, upload-time = "2026-04-07T11:14:20.534Z" }, + { url = "https://files.pythonhosted.org/packages/88/37/a3eb7ff6121ed3a5f199a8c38cc86c8e481816f879cb0e0b738b078c9a7e/rapidfuzz-3.14.5-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:01550fe5f60fd176aa66b7611289d46dc4aa4b1b904874c7b6d1d54e581c5ec1", size = 1371999, upload-time = "2026-04-07T11:14:22.63Z" }, + { url = "https://files.pythonhosted.org/packages/79/72/97a9728c711c7c1b06e107d3f0623880fb4ef90e147ed13c551a1730e7cc/rapidfuzz-3.14.5-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:48bee0b91bebfaec41e1081e351000659ab7570cc4598d617aa04d5bf827f9e6", size = 3145715, upload-time = "2026-04-07T11:14:24.508Z" }, + { url = "https://files.pythonhosted.org/packages/ed/54/d5caabbea233ac90c286c87c260e49d7641467e87438a18d858e41c82e91/rapidfuzz-3.14.5-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:7e580cb04ad849ae9b786fa21383c6b994b6e6c1444ad1cb9f22392759d72741", size = 1456304, upload-time = "2026-04-07T11:14:26.515Z" }, + { url = "https://files.pythonhosted.org/packages/fc/a7/2d1a81250ac8c01a0100c026018e76f0e7a097ff63e4c553e02a6938c6fb/rapidfuzz-3.14.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:09d6c9ba091854f07817055d795d604179c12a8f308ba4c7d56f3719dfea1646", size = 2389089, upload-time = "2026-04-07T11:14:28.635Z" }, + { url = "https://files.pythonhosted.org/packages/65/0d/c47c3872203ae88e6506997c0b576ad731f5261daa25d559be09c9756658/rapidfuzz-3.14.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:1e989f86113be66574113b9c7bdf4793f3f863d248e47d911b355e05ca6b6b10", size = 2493404, upload-time = "2026-04-07T11:14:30.577Z" }, + { url = "https://files.pythonhosted.org/packages/8f/2f/71e0a5a3130792146c8a200a2dd1e52aa16f7c1074012e17f2601eea9a90/rapidfuzz-3.14.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ebd1a18e2e47bc0b292a07e6ed9c3642f8aaa672d12253885f599b50807a4f9", size = 4251709, upload-time = "2026-04-07T11:14:32.451Z" }, + { url = "https://files.pythonhosted.org/packages/86/45/d39874901abacef325adb5b34ae416817c8486dfb4fb87c7a9b74ec5b072/rapidfuzz-3.14.5-cp312-cp312-win32.whl", hash = "sha256:9981d38a703b86f0e315a3cd229fd1906fe1d91c989ed121fb975b3c849f89f5", size = 1710069, upload-time = "2026-04-07T11:14:34.37Z" }, + { url = "https://files.pythonhosted.org/packages/85/0b/f65572c53de8a1c704bda707f63a447b67bdbe95d7cdc70d18885e191df5/rapidfuzz-3.14.5-cp312-cp312-win_amd64.whl", hash = "sha256:d8375e3da319593389727c3187ccaf3e0e84199accc530866b8e0f2b79af05e9", size = 1540630, upload-time = "2026-04-07T11:14:36.287Z" }, + { url = "https://files.pythonhosted.org/packages/5e/c3/143be3a578f989758cae516f3270d5cbb49783a7bfdf57cc27a670e00456/rapidfuzz-3.14.5-cp312-cp312-win_arm64.whl", hash = "sha256:478b59bb018a6780d73f33e38d0b3ec5e968a6c1ed42876b993dd456b7aa20e8", size = 813137, upload-time = "2026-04-07T11:14:38.289Z" }, + { url = "https://files.pythonhosted.org/packages/11/66/252803f2010ba699618cdc048b6e1f7cc1f433c08b4a9a17579b92ab0142/rapidfuzz-3.14.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ebd8fd343bf8492a1e60bcb6dc99f90f74f65d98d8241a6b3e1fed225b76ecd6", size = 1940205, upload-time = "2026-04-07T11:14:40.319Z" }, + { url = "https://files.pythonhosted.org/packages/ea/59/b2afd98e41af9cd54554a4c1c423d84cdd60e6b1c0a09496f033b55f60ec/rapidfuzz-3.14.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6737b35d5af7479c5bf9710f7b17edd9d2c43128d974d25fb4ea653e42c64609", size = 1159639, upload-time = "2026-04-07T11:14:42.52Z" }, + { url = "https://files.pythonhosted.org/packages/a3/31/7aa7e62c4c516a7af322ed0c4f0774208b72d457d0cfec808bad0df12f4a/rapidfuzz-3.14.5-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b002c7994cc9f2bc9d9856f0fbaee6e8072c983873846c92f25cefba5b2a925f", size = 1367194, upload-time = "2026-04-07T11:14:44.25Z" }, + { url = "https://files.pythonhosted.org/packages/90/79/2fc252a63bc91d3c3b234d0a3a6ad4ebc460037a23cdcdaf9285f986e6c9/rapidfuzz-3.14.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:17a34330cd2a538c1ce5d400b61ba358c5b72c654b928ff87b362e88f8b864c7", size = 3151805, upload-time = "2026-04-07T11:14:46.21Z" }, + { url = "https://files.pythonhosted.org/packages/17/54/0c83508f2683ea70e2d05f8527eb07328acf7bb1e9d97a3bece5702378e7/rapidfuzz-3.14.5-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:95d937e74c1a7a1287dfb03b62a827be08ede10a155cf1af73bbf47f2b73ee6e", size = 1455667, upload-time = "2026-04-07T11:14:47.991Z" }, + { url = "https://files.pythonhosted.org/packages/71/1b/070175e873177814d58850a01ebe80e20ae11e93eb4da894d563988660fa/rapidfuzz-3.14.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:46b92a9970dcc34f0096901c792644094cab49554ac3547f35e3aebbdf0a3610", size = 2388246, upload-time = "2026-04-07T11:14:50.098Z" }, + { url = "https://files.pythonhosted.org/packages/c9/dd/77caf7aaf9c2be050ad1f128d7c24ff0f59079aa62c5f62f9df41c0af45e/rapidfuzz-3.14.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e012177c8e8a8a0754ae0d6027d63042aa5ff036d9f40f07cb3466a6082e21b8", size = 2494333, upload-time = "2026-04-07T11:14:52.303Z" }, + { url = "https://files.pythonhosted.org/packages/2c/e2/dd7e1f2aa31a8fbbfc16b0610af1d770ffaf1287490f3c8c5b1c52da264f/rapidfuzz-3.14.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a2ae6f53f99c9a0eca7a0afc5b4e45fc73bc1dd4ac74c00509031d76df80ed98", size = 4258579, upload-time = "2026-04-07T11:14:54.538Z" }, + { url = "https://files.pythonhosted.org/packages/9c/0a/ac99e1ba347ba0e85e0bb60b74231d55fb93c0eff43f2920ccb413d0be08/rapidfuzz-3.14.5-cp313-cp313-win32.whl", hash = "sha256:4a60f0057231188e3bd30216f7b4e0f279b11fa4ec818bb6c1d9f014d1562fbc", size = 1709231, upload-time = "2026-04-07T11:14:56.524Z" }, + { url = "https://files.pythonhosted.org/packages/cf/cb/0e251d731b3166378644238e8f0cf9e89858c024e19f75ca9f7e3ae83fd5/rapidfuzz-3.14.5-cp313-cp313-win_amd64.whl", hash = "sha256:11bfc2ed8fbe4ab86bd516fadefab126f90e6dcadffa761739fcb304707dfd35", size = 1538519, upload-time = "2026-04-07T11:14:58.635Z" }, + { url = "https://files.pythonhosted.org/packages/30/6f/4548132acc947db6d5346a248e44a8b3a22d608ef30e770fb578caaf2d00/rapidfuzz-3.14.5-cp313-cp313-win_arm64.whl", hash = "sha256:b486b5218808f6f4dc471b114b1054e63553db69705c97da0271f47bd706aedd", size = 812628, upload-time = "2026-04-07T11:15:00.552Z" }, + { url = "https://files.pythonhosted.org/packages/00/60/69b177577290c5eab892c6f75fe89c3aff3f9ae80298a78d9372b1cecb9a/rapidfuzz-3.14.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:39ef8658aaf67d51667e7bdaf7096f432333377d8302ac43c70b5df8a4cf89b8", size = 1970231, upload-time = "2026-04-07T11:15:02.603Z" }, + { url = "https://files.pythonhosted.org/packages/48/38/2fd790052659cc4e2907b63c25433f0987864b445c1aeec1a302ef5ad948/rapidfuzz-3.14.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9ad37a0be705b544af6296da8edddc260d10a8ae5462530fc9991f66498bb1f9", size = 1194394, upload-time = "2026-04-07T11:15:04.572Z" }, + { url = "https://files.pythonhosted.org/packages/80/f4/28430ad8472fc3536e8ebd51a864a226e979cfe924c6e3f83d111373aa74/rapidfuzz-3.14.5-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d45e06f60729e07d9b20c205f7e5cff90b6ef2584e852eecf46e045aea69627d", size = 1377051, upload-time = "2026-04-07T11:15:06.728Z" }, + { url = "https://files.pythonhosted.org/packages/77/7e/9aeacabcfd1e77397968362e5b98fe14248b8307011136b17daf99752a8e/rapidfuzz-3.14.5-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e52da10236aa6212de71b9e170bace65b64b129c0dea7fc243d6c9ce976f5074", size = 3160565, upload-time = "2026-04-07T11:15:08.667Z" }, + { url = "https://files.pythonhosted.org/packages/56/f4/db4dd7be0cd2f2022117ac5407d905f435d60e48baaea313a567ad27e865/rapidfuzz-3.14.5-cp313-cp313t-manylinux_2_39_riscv64.whl", hash = "sha256:440d30faaf682ca496170a7f0cc5453ec942e3e079f0fd802c9a7f938dfb50a3", size = 1442113, upload-time = "2026-04-07T11:15:11.138Z" }, + { url = "https://files.pythonhosted.org/packages/a4/99/0e9f6aa57f3e32a767216f797e56dc96b720fcecfb9d8ee907ecc82f8d66/rapidfuzz-3.14.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:56227a61fd3d17b0cd9793132431f3a3d07c8654be96794ba9f89fe0fc8b2d09", size = 2396618, upload-time = "2026-04-07T11:15:13.154Z" }, + { url = "https://files.pythonhosted.org/packages/60/94/44a78e39ffce17cbdd3e2b53b696acc751d5d153be0f499d052b07a4d904/rapidfuzz-3.14.5-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:2e83cd2e25bb4edd97b689d9979d9c3acccdaaf26ceac08212ceece202febcfa", size = 2478220, upload-time = "2026-04-07T11:15:15.193Z" }, + { url = "https://files.pythonhosted.org/packages/dd/df/454311469a09a507e9d784a35796742bec22e4cebe75551e2da4e0e290fd/rapidfuzz-3.14.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:af3b859726cd3374287e405e14b9634563c078c5531a4f62375508addebddad1", size = 4265027, upload-time = "2026-04-07T11:15:17.28Z" }, + { url = "https://files.pythonhosted.org/packages/fc/01/175465a9ab3e3b70ba669058372f009d1d49c1746e2dcd56b69df188d3a5/rapidfuzz-3.14.5-cp313-cp313t-win32.whl", hash = "sha256:8ce1d850b3c0178440efde9e884d98421b5e87ff925f364d6d79e23910d7593f", size = 1766814, upload-time = "2026-04-07T11:15:19.687Z" }, + { url = "https://files.pythonhosted.org/packages/1b/a0/a9b84a47af06ebed94a1439eb2f02adebfb8628bcd30af1fe3e02f5ef56c/rapidfuzz-3.14.5-cp313-cp313t-win_amd64.whl", hash = "sha256:c84af70bcf34e99aee894e46a0f1ac77f17d0ef828179c387407642e2466d28a", size = 1582448, upload-time = "2026-04-07T11:15:21.98Z" }, + { url = "https://files.pythonhosted.org/packages/1e/f1/5937800238b3f8248e70860d79f69ba8f73e764fff47e36bc9e2f26dbcc6/rapidfuzz-3.14.5-cp313-cp313t-win_arm64.whl", hash = "sha256:aac0ad28c686a5e72b81668b906c030ee28050b244544b8af68e12fb32543895", size = 832932, upload-time = "2026-04-07T11:15:24.358Z" }, + { url = "https://files.pythonhosted.org/packages/81/41/aa3ffb3355e62e1bf91f6599b3092e866bc88487a07c524004943c7676df/rapidfuzz-3.14.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1a31cc6d7d03e7318a0974c038959c59e19c752b81115f2e9138b3331cd64d45", size = 1943327, upload-time = "2026-04-07T11:15:26.266Z" }, + { url = "https://files.pythonhosted.org/packages/2d/e1/c2141f1840a41e07ad2db6f724945f8f8ff3065463899a22939152dd6e09/rapidfuzz-3.14.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0298d357e2bc59d572da4db0bc631009b6f8f6c9bc8c11e99a12b833f16b6575", size = 1161755, upload-time = "2026-04-07T11:15:28.659Z" }, + { url = "https://files.pythonhosted.org/packages/ca/07/66e753eeaa353161d1d331b7dd517bb349b0bacfebe8496d7b26be26f81f/rapidfuzz-3.14.5-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:59b3dba758661a318995655435c6ab20a04ade79fa51e75bc8dc107cac8df280", size = 1376571, upload-time = "2026-04-07T11:15:31.225Z" }, + { url = "https://files.pythonhosted.org/packages/c8/85/9535df0b78ba51f478c9ce7eb6d1f85535cc31fe356773b48fd9d3e563ca/rapidfuzz-3.14.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4900143d82071bdda533b00300c40b14b963ff826b3642cc463b6dd0f036585e", size = 3156468, upload-time = "2026-04-07T11:15:33.428Z" }, + { url = "https://files.pythonhosted.org/packages/81/ee/b667eb93bba6dc4e0de658edd778e1619dc4d6aab68fa5e5c7f075152735/rapidfuzz-3.14.5-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:feedf219672eef83ea6be6f3bb093bba396a8560fc75be85ba225f082903df0a", size = 1458311, upload-time = "2026-04-07T11:15:35.557Z" }, + { url = "https://files.pythonhosted.org/packages/7d/ce/479074f5624364a48df3403c538797ef22d3ac49c19dc76c3f79fcdcc70c/rapidfuzz-3.14.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:419e4397a36e2665ec992d8d64c20ba4b2a42500c76ecadeca78a4f19cb9cc32", size = 2398228, upload-time = "2026-04-07T11:15:37.669Z" }, + { url = "https://files.pythonhosted.org/packages/0b/15/a8982f649150fffbdcd6f17565974501f6ab33b2795267bffbd4a7ba905b/rapidfuzz-3.14.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:97131ab2be39043054ee28d99e09efe316e6d53449b7e962dfcf3c2de8b2b246", size = 2497226, upload-time = "2026-04-07T11:15:39.857Z" }, + { url = "https://files.pythonhosted.org/packages/19/52/5267c03ef6759831b7d4625a0c9c06e87baa2fae084b61ac9c388858317b/rapidfuzz-3.14.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:593c00dac4e30231c35bf3b4f1da8ec0998762e9e94425586a5d636fcd57f9d0", size = 4262283, upload-time = "2026-04-07T11:15:42.279Z" }, + { url = "https://files.pythonhosted.org/packages/71/c0/2579f343a97f5254c43bb5853baccc01488357dcb64a27bcb869b7888a4a/rapidfuzz-3.14.5-cp314-cp314-win32.whl", hash = "sha256:0084b687b02b4e569b46d8d6d4ad25659528e6081cd6d067ca453a69035f07e4", size = 1744614, upload-time = "2026-04-07T11:15:44.498Z" }, + { url = "https://files.pythonhosted.org/packages/17/eb/8edfed1e80119dc9c35b11df4bc701eea85622ad681fff0263b6961d3224/rapidfuzz-3.14.5-cp314-cp314-win_amd64.whl", hash = "sha256:5dfa89d78f22cd773054caff44827b846161a29f2dcf7e78b8f90d086621e502", size = 1588971, upload-time = "2026-04-07T11:15:46.86Z" }, + { url = "https://files.pythonhosted.org/packages/f6/04/5676df93c85cfa57a3045d8047318df9f3cd58c7b8a99340dd95f874795e/rapidfuzz-3.14.5-cp314-cp314-win_arm64.whl", hash = "sha256:67f3f9d2b444268ab53e47d31bab89954888d23c04c6789f2c727e51fe4b1d13", size = 834985, upload-time = "2026-04-07T11:15:49.411Z" }, + { url = "https://files.pythonhosted.org/packages/f7/0d/4a8988cea658fe335048ddef8c876addff1b6daa3c9ca8ad65a5a2196e69/rapidfuzz-3.14.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:77eac0526899b3c3ad1454bb2b03cdb491d67358ec8ef0c9c48bd61b632b431d", size = 1972517, upload-time = "2026-04-07T11:15:51.819Z" }, + { url = "https://files.pythonhosted.org/packages/1c/a3/f5cfd9965a9d9a9e32249159797c47b5d6299ea6d1629f9126b25f1c10a3/rapidfuzz-3.14.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b9c6bd754d11f6e78ac54e3d86b4b11dc1ba2f13e5fc958899574532897f5a99", size = 1196056, upload-time = "2026-04-07T11:15:54.292Z" }, + { url = "https://files.pythonhosted.org/packages/64/07/561c2e40cfd10e6630a7b0ac5a2a813aef50d944bcd1f3d260319d659d5b/rapidfuzz-3.14.5-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:738c96944d076deeaff70e92b65696ab4f7ecb8081d7791c5403a3257dfaf8ff", size = 1374732, upload-time = "2026-04-07T11:15:56.584Z" }, + { url = "https://files.pythonhosted.org/packages/c2/39/123bb94fee40e2fb3b7c49b80827c7ef42d838e18def3fc2fef5a3cf817a/rapidfuzz-3.14.5-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4c1bca487a17fe4226b4ffb2d30e799d2b274d692cffa76bd0746f56235fca3", size = 3166902, upload-time = "2026-04-07T11:15:58.768Z" }, + { url = "https://files.pythonhosted.org/packages/75/0a/45716fafc9fd2e028cf20b5ac5bc704887081cd312f84edb0e325599414b/rapidfuzz-3.14.5-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:af6a90a4ed2a48fa1a2d17e9d824e6c7c950bea5bad0b707c77fd55751e6bfef", size = 1452130, upload-time = "2026-04-07T11:16:01.453Z" }, + { url = "https://files.pythonhosted.org/packages/ca/49/4e96c413114398481c0a5b0086af32c364a18613c9a2ea578d17c4bea4ee/rapidfuzz-3.14.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bf5018938208d4597b2e679a4f8cff9fd252f1df53583130ae56281a21801b64", size = 2396308, upload-time = "2026-04-07T11:16:03.588Z" }, + { url = "https://files.pythonhosted.org/packages/89/b7/49fea9fc6878d59bd259d01dd1972d9b86117992b1c66d9b16f0a65273c3/rapidfuzz-3.14.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:c0919d1f89ddf91129906705723118ea09754171e4116f5a5dbc667c7bc9b261", size = 2488210, upload-time = "2026-04-07T11:16:05.871Z" }, + { url = "https://files.pythonhosted.org/packages/0c/44/a1f732b93ffacbdad077b7c801149549b2938e1bece6addb5ad85ed74df8/rapidfuzz-3.14.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:93d8da883a35116d6813432177f35e570db5b0a5e30ecb0cbd7cb39c815735df", size = 4270621, upload-time = "2026-04-07T11:16:08.483Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ce/ff942d19fce5385054650bb71a58495ddda299d94661ccc4e6e7fa44868b/rapidfuzz-3.14.5-cp314-cp314t-win32.whl", hash = "sha256:0f23e37019ec07712d58976b1ab2b889f8649a7f7c2f626a2f34ea9139e79279", size = 1803950, upload-time = "2026-04-07T11:16:10.873Z" }, + { url = "https://files.pythonhosted.org/packages/5c/0f/9aafc63f9661222b819b391c187eed29fc90ad5935f9690e5ecc2d2047a4/rapidfuzz-3.14.5-cp314-cp314t-win_amd64.whl", hash = "sha256:7d5ca9c7832e6879a707296d1463685f7c243a27846227044504741640caec66", size = 1632357, upload-time = "2026-04-07T11:16:13.1Z" }, + { url = "https://files.pythonhosted.org/packages/70/a6/51fc1b0e61e3326e1c68a61cfd0c6b3c34c843681c4b1eefbf0596f59162/rapidfuzz-3.14.5-cp314-cp314t-win_arm64.whl", hash = "sha256:3e91dcd2549b8f8d843f98ba03a17e01f3d8b72ce942adbbb6761bc58ffce813", size = 855409, upload-time = "2026-04-07T11:16:15.787Z" }, +] + +[[package]] +name = "referencing" +version = "0.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, +] + +[[package]] +name = "regex" +version = "2026.5.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/0e/49aee608ad09480e7fd276898c99ec6192985fa331abe4eb3a986094490b/regex-2026.5.9.tar.gz", hash = "sha256:a8234aa23ec39894bfe4a3f1b85616a7032481964a13ac6fc9f10de4f6fca270", size = 416074, upload-time = "2026-05-09T23:15:19.37Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/9b/6550044bc44e17c84d312c031c2ec42fbdb6a4ec4e29093be3a172d08772/regex-2026.5.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:57eeeb05db7979413dec5438f2db21d7ecbba787cde7a711df1a6f6df672aa06", size = 490451, upload-time = "2026-05-09T23:12:34.72Z" }, + { url = "https://files.pythonhosted.org/packages/1e/95/fc7ba4303b5a0f92446a12ee6778ef2c6c799233f5060042a31bf390cfe9/regex-2026.5.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:398c521292f4c7fb807001dcd54694d3a1fcafc179a36ad9cc56f98df85930b6", size = 292112, upload-time = "2026-05-09T23:12:36.285Z" }, + { url = "https://files.pythonhosted.org/packages/54/4b/ee27938d1b2c443e89a9a10e00d2d19aa5ee300cd3d61140644e93bb083e/regex-2026.5.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f7a7c26137296beba7784de6eba69c6a93a63ccebc385e4962fe67e267a91225", size = 289599, upload-time = "2026-05-09T23:12:38.089Z" }, + { url = "https://files.pythonhosted.org/packages/d8/dd/ba103dc19614e25f3880800ca67ce093d6e21b325d72b8383c7bf906e9fa/regex-2026.5.9-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6441cc660d76107934a09c22167200839a0e89604a6297f78a974e66e931d2c0", size = 796732, upload-time = "2026-05-09T23:12:40.062Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e7/f035b4fd858b050b0080bf302968dc0f59ba34e391872d54936758e6844e/regex-2026.5.9-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:91328f1c23d47595ca3ef0a7557fa129c5a23404b775c770697d2f35b33e0107", size = 865440, upload-time = "2026-05-09T23:12:42.059Z" }, + { url = "https://files.pythonhosted.org/packages/0a/51/8cd301ecc899aea28124357f729f4272f44de7806fc7ca02490bfbe253e8/regex-2026.5.9-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:93a7860539414dddaefba2b40f8771765ae17949d4c7182b876ce429e11a8309", size = 912329, upload-time = "2026-05-09T23:12:44.373Z" }, + { url = "https://files.pythonhosted.org/packages/cc/1e/3fbe2fa1e8cebd62f3bb7d3321cff1640aca2e240b51d9bd624aad949260/regex-2026.5.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd2810d22146b6d838acc5ec15602cb6b47920aa4e33015df3868eedfd20bab8", size = 801239, upload-time = "2026-05-09T23:12:46.268Z" }, + { url = "https://files.pythonhosted.org/packages/17/2f/6f6008682bf2cf98040a0d3153a8e557b6ab728d7713d045cee4ce544ab8/regex-2026.5.9-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:daff2bdbaf1d23e52fdff7c0b7bc2048b68f978df6a4d107ac981f94caef2e66", size = 777054, upload-time = "2026-05-09T23:12:48.051Z" }, + { url = "https://files.pythonhosted.org/packages/19/2b/eee0d20a6842ba04df4b8847a920b57ef56853f14ef85405473e586b605a/regex-2026.5.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4eeb011098fcb77af513dcef521a3dbecbf8849b1e38940759d293b7a93f5026", size = 785098, upload-time = "2026-05-09T23:12:49.851Z" }, + { url = "https://files.pythonhosted.org/packages/4a/98/6fc1e6410feefb92159edaed5041992bfe390e8d26c721865434acbca558/regex-2026.5.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ea9c8ecfa1b73c73b626534d6626e5340d429630943672b8480724f44e84b962", size = 860095, upload-time = "2026-05-09T23:12:51.666Z" }, + { url = "https://files.pythonhosted.org/packages/18/a3/bd855e0f2cb1a978ecf6fa6bb69632dd9c3f6ea3b81cde62fde14c9daec7/regex-2026.5.9-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:cd2846168eb9ee3c513902bc8225409cb1caab31d04728b145171fa1625d9621", size = 765762, upload-time = "2026-05-09T23:12:53.413Z" }, + { url = "https://files.pythonhosted.org/packages/dc/66/0ae8c092e60b14c79d24f8e0b7f0aea5bfbffdcab00b5483d13404d3c3a5/regex-2026.5.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:39617fb0cde9c0e6306dc70e3bfc096f3da793219879f7ae7aa341a69fbdcf6d", size = 852100, upload-time = "2026-05-09T23:12:55.256Z" }, + { url = "https://files.pythonhosted.org/packages/21/de/8dfde60fc1b21c946a893ba273403b72617edb261370cb1087099a83f088/regex-2026.5.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fd03c4f0e33280d15cae17159b899245d6b7c53d21def19b263b39655061f5ce", size = 789479, upload-time = "2026-05-09T23:12:57.573Z" }, + { url = "https://files.pythonhosted.org/packages/c3/1c/bdcc98f9a4af4fdd166c74941174619ccff4726d3ce32faa8e9a2ecd38dd/regex-2026.5.9-cp312-cp312-win32.whl", hash = "sha256:164eba9b755ea6f244b0d881196fbc1fac09714e9782c9e2732b813142033c8e", size = 266699, upload-time = "2026-05-09T23:12:59.14Z" }, + { url = "https://files.pythonhosted.org/packages/78/87/240d36864f9e48ace85f72e79ced97ceb7f27ce87739a947dcb834b4e6bc/regex-2026.5.9-cp312-cp312-win_amd64.whl", hash = "sha256:86f40a5d6444db30a125c9c9177e6b25dad981cbc37451fd838f145e6edac92e", size = 277783, upload-time = "2026-05-09T23:13:00.789Z" }, + { url = "https://files.pythonhosted.org/packages/4f/b5/7b30f312b0669dff5beebe5b0989dc2d1a312b1a44fab852199c387a5b96/regex-2026.5.9-cp312-cp312-win_arm64.whl", hash = "sha256:96f5f58b54a063d7ea9dca08e1cf57bfe10499c4d579ee672da284f57f5f0070", size = 270513, upload-time = "2026-05-09T23:13:02.426Z" }, + { url = "https://files.pythonhosted.org/packages/aa/da/797e91ecec6f84135da778ddce78c20e0af5d2a15c26f87a81bc3eadb6db/regex-2026.5.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d626b84406444b165fc0ba981604edea39f0588ff1f92baa23fe50799ea9afdb", size = 490303, upload-time = "2026-05-09T23:13:04.382Z" }, + { url = "https://files.pythonhosted.org/packages/44/da/bf30abaaa737b58f4a4b8c4a03659e02fd92092c822e0197ed9e0daab917/regex-2026.5.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d7bdc0ab8f3dd7e1b4f9ab88634e13374669db86bb3c72e8292f07ae313f539f", size = 292019, upload-time = "2026-05-09T23:13:06.022Z" }, + { url = "https://files.pythonhosted.org/packages/2d/e7/d0eaf5713828417b9e5648cf81fa9bacd4961f6ab98c380c2034f8716e35/regex-2026.5.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a8820737949116ffff55fe18f9fc644530063ba6ebfcb8314239416e78f1347c", size = 289468, upload-time = "2026-05-09T23:13:08.214Z" }, + { url = "https://files.pythonhosted.org/packages/d3/9b/b3fdd62b003baa1a9b593cd8c8699c9651c2e80cc21a5c715707983c42d7/regex-2026.5.9-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa0fbdbac82cb3e4450d0ccde7d7a35607f4cb2dd9fba4b8b69bfaf8c9fa6aed", size = 796749, upload-time = "2026-05-09T23:13:10.573Z" }, + { url = "https://files.pythonhosted.org/packages/d4/30/66ab84588765f5b4b271a9ca09ef7ce2b87caa95176ec3d2ad65d7bc4902/regex-2026.5.9-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:57e8915c7986aa33d25e4d3629cef711cd2863f2961b10409f0c04cb8b7d9020", size = 865445, upload-time = "2026-05-09T23:13:12.523Z" }, + { url = "https://files.pythonhosted.org/packages/1a/89/f05169e8588aac365f35ffc7f3bc3184f095ef4cfded7cfaa3c7fd5dbd89/regex-2026.5.9-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:508f56a89ba9cb26e4168cbc37dbd60a28d82430a9e18ad1d25fe0883c314ca2", size = 912322, upload-time = "2026-05-09T23:13:14.281Z" }, + { url = "https://files.pythonhosted.org/packages/30/e1/c93444052cf41581f3c884ab3fb5823daf0992f11cd4388d4275ca610558/regex-2026.5.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6d189041f15691cfa2b6c4290448ec221244d225b3f5fe9e7771b34ffcdf6e2", size = 801269, upload-time = "2026-05-09T23:13:16.569Z" }, + { url = "https://files.pythonhosted.org/packages/50/fe/0cf96b882f540e62e8b9956599798203d599c44cf4c77917ca27400ff69b/regex-2026.5.9-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e82db382b44d0111b22601c509c89f64434816c9e0eef9d1989cda8cc6ff1c04", size = 777085, upload-time = "2026-05-09T23:13:18.675Z" }, + { url = "https://files.pythonhosted.org/packages/23/5c/d78d4924e7fc875557b9e9b768423925fdfaac5549d06da7810019a9bd26/regex-2026.5.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2acfb48634f64996b57f90f39afa692ff362162722581921fe92239a59960f3c", size = 785153, upload-time = "2026-05-09T23:13:20.525Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e0/5214774090e7b4524dcea3e3c4aa74141d43043f8beb49c1599db1c8b53a/regex-2026.5.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d29eebfc9525db68cad3c97eedd7f754fa265aa5cd0cf4f863b2421e1b48fc9f", size = 860164, upload-time = "2026-05-09T23:13:22.263Z" }, + { url = "https://files.pythonhosted.org/packages/6e/e1/4a57a83350319b1271f0d7a249b8672513ed928b237a741631270de6caea/regex-2026.5.9-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:debb893095e944091c16e641a6e33c1b0f4cb61ab945ec5afbf53ce7068834d8", size = 765731, upload-time = "2026-05-09T23:13:24.277Z" }, + { url = "https://files.pythonhosted.org/packages/12/f4/499e74a20c156fc75836ee04a72a38d1a063978f600937f9760467beb1b0/regex-2026.5.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d659eee77986549c9ea45b861c7567e44d6287c3dc9a4565478853f7b9fe2ff6", size = 852062, upload-time = "2026-05-09T23:13:26.125Z" }, + { url = "https://files.pythonhosted.org/packages/5b/92/7eebc0d0a01e78629695f342ba17e0deaff8fb45e79cc0d7b98287da6e3e/regex-2026.5.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2efa205e6d98b24d1f3ab395c11aa15cdf10935bca283d0285e0499c284fba21", size = 789577, upload-time = "2026-05-09T23:13:27.814Z" }, + { url = "https://files.pythonhosted.org/packages/05/a4/018e71f7d2ad48c1ebe6d3ae0026f9b7cb4802fd15c7cc02fdf724355102/regex-2026.5.9-cp313-cp313-win32.whl", hash = "sha256:f3844f134e834076677dd369976e9f5068679fcb8e50102fdf6b7ac96a3ec127", size = 266691, upload-time = "2026-05-09T23:13:29.549Z" }, + { url = "https://files.pythonhosted.org/packages/e6/1d/861a93719fb9ee7dbfc3761b3797b7a3e112a5d42c6129459d2d741be9b5/regex-2026.5.9-cp313-cp313-win_amd64.whl", hash = "sha256:3527bb4942d2c14552155406cdedd906567456821848aed1cb4933a391bf5eca", size = 277747, upload-time = "2026-05-09T23:13:31.859Z" }, + { url = "https://files.pythonhosted.org/packages/d9/c6/0a2436ae4da1ba76e51cb98943c6838a9a721faa40ebe2dce07694ae34e3/regex-2026.5.9-cp313-cp313-win_arm64.whl", hash = "sha256:56a33f191f17d8c417f99945ebdc1e691d3af9605d86ec68c7e54a57e3e17af6", size = 270500, upload-time = "2026-05-09T23:13:33.525Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e9/d21346f7b60ed58789371358ed66b09d00f832e1bd7c06e55d9da5679882/regex-2026.5.9-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:01f28d868834624c934b8d2e0aa1c8341337e37831f4a012f18a5afcba4cbaf3", size = 494172, upload-time = "2026-05-09T23:13:35.935Z" }, + { url = "https://files.pythonhosted.org/packages/c4/43/fd1177a2032037c681baecdb3422ee4e1424aec4e4f470ef47793d325274/regex-2026.5.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:48036f6374aaa79eb3b754ec29c61d1c6b1606749d705a13f8854fa2539671f6", size = 293952, upload-time = "2026-05-09T23:13:38.307Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7d/9fbf919768368d3f8a4f6c692cf2aa61e482b2b81ec6a298ace4cbf02480/regex-2026.5.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b96350aa424e79d4fd6b567b344dcbe2b2d6bfc48dfe7717587e1fa6d43da6ff", size = 292314, upload-time = "2026-05-09T23:13:40.353Z" }, + { url = "https://files.pythonhosted.org/packages/e2/6c/e41bfeecb589716843e7c4df09ba46ff2a42961457afece19059d85caeef/regex-2026.5.9-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f3af7a4903c5c04a11a196a5aa75cdd7dd3f8508132f9fb3259d9f5908e3b88", size = 811681, upload-time = "2026-05-09T23:13:42.543Z" }, + { url = "https://files.pythonhosted.org/packages/87/83/a5c1c525fba0aa656e88ad0face0b1829788ef4c2fb6b26df58aa1151b84/regex-2026.5.9-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7e87577720152d2caae19fe2baaf1f8d5ca12091e9e229f03915c37d1e4b9178", size = 871135, upload-time = "2026-05-09T23:13:44.326Z" }, + { url = "https://files.pythonhosted.org/packages/18/d4/80882e799e440dd878b0979cbebf8fa4d54624a332c83037c7a701649e3f/regex-2026.5.9-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c8b9b9d294cfea3cd19c718ade7cc93492b2c4991abd9a68d0b3477ae6d8e100", size = 917265, upload-time = "2026-05-09T23:13:47.295Z" }, + { url = "https://files.pythonhosted.org/packages/ae/ff/8db60211e2286e396aad7dc7725356c502bff0901ea05bd6cdc2e1a042b9/regex-2026.5.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:728d8bfd28a8845c8b6bc5dc7ce010453d206396786c0765c2740cb65f37791e", size = 816311, upload-time = "2026-05-09T23:13:49.885Z" }, + { url = "https://files.pythonhosted.org/packages/4c/47/742ef579c61730f8d268e5cf1f9ce0e37e2ea041ad0f5644724f2378e463/regex-2026.5.9-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7e30b874d341fac767d7df5a0870540541c2c054b80cfaac116e8d367a8a7ff2", size = 785498, upload-time = "2026-05-09T23:13:52.25Z" }, + { url = "https://files.pythonhosted.org/packages/7f/ab/cb0999802dcb0fb95b1ab005e8d4163d8afdd67efc2cb6b6630ac13f8cb1/regex-2026.5.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fd190e88a895a8901325fad284a3f74ea52b1da8525b76cc811fa9b1edf0ce2b", size = 801348, upload-time = "2026-05-09T23:13:54.127Z" }, + { url = "https://files.pythonhosted.org/packages/7d/62/8ca59a24c55bc34d166eefaf3717bd77772f329fdbf984d86581e0a3571c/regex-2026.5.9-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:8e76e8161ad00694cfce6767d5dea860c6391ac5b83e5c3a39661e696f11fc7e", size = 866493, upload-time = "2026-05-09T23:13:56.067Z" }, + { url = "https://files.pythonhosted.org/packages/8d/3d/30f2ae62cef3278bb5bb821f467277a55fb73f01032cf85997e15e8289a8/regex-2026.5.9-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ddda5340e6c01a293027dd46232fa79eaff1b48058ce7a98f572b6445b088041", size = 772811, upload-time = "2026-05-09T23:13:57.867Z" }, + { url = "https://files.pythonhosted.org/packages/d8/ae/7d2089bcd78ad0c0161bc684339df50032acb438a7bd3305e7ddb1193cec/regex-2026.5.9-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:205109e96b3cf5adf8f4cd62bedde9487feb282b9497a3535451e5a24cd706a0", size = 856584, upload-time = "2026-05-09T23:13:59.679Z" }, + { url = "https://files.pythonhosted.org/packages/a9/29/92ff47f75990131ea4f24ba17819e5a9d141e10819807e09addd73409af6/regex-2026.5.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dfbe4579b9f08036aa7d101d1835437a20783574ac66327e6b29b4018a138081", size = 803453, upload-time = "2026-05-09T23:14:01.978Z" }, + { url = "https://files.pythonhosted.org/packages/04/99/eff29f1037dcab36702c9ee5d6858cf1ce2336ea8ea2987f64245b99ea5e/regex-2026.5.9-cp313-cp313t-win32.whl", hash = "sha256:ed2c9e8068b614c574d8d30e543d617cf5379b0535d46f97ef00e904745a08b5", size = 269951, upload-time = "2026-05-09T23:14:03.661Z" }, + { url = "https://files.pythonhosted.org/packages/0e/9d/8870b8981d27b22cda77bb26a5ac7ebfa9c7d9e0dea195a834a82380e748/regex-2026.5.9-cp313-cp313t-win_amd64.whl", hash = "sha256:b46b0f094dc1d3b90356c85a0bd2c9bafc4a6a190b9d6f8ddd5a033b6e088ed4", size = 281240, upload-time = "2026-05-09T23:14:05.56Z" }, + { url = "https://files.pythonhosted.org/packages/72/b1/3379415e8f135c13ac551353397cc4fe97b4978f3cac73c5fcbcded548b8/regex-2026.5.9-cp313-cp313t-win_arm64.whl", hash = "sha256:872acc074bd29ffc9913ecdfedf6ea77502312ca44a4aa0d3779089c6069d8de", size = 272383, upload-time = "2026-05-09T23:14:07.843Z" }, + { url = "https://files.pythonhosted.org/packages/13/3e/9c3cd292d8808b3645a2ce517e200179b6d0e903f176300bd8b542e14de5/regex-2026.5.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:1bd7587a2948b4085195d5a3374eaf4a425dc3e55784c038175355ecf3bbbf8a", size = 490376, upload-time = "2026-05-09T23:14:09.64Z" }, + { url = "https://files.pythonhosted.org/packages/60/70/d43ee8a2ca0a8b68d167f21658b85520ac0574617c7f320367c5047f7556/regex-2026.5.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:dea2e88e1cce4522496cce630e11e67b98b7076620bc4336c3f674bc21a375f4", size = 291964, upload-time = "2026-05-09T23:14:11.424Z" }, + { url = "https://files.pythonhosted.org/packages/21/91/9d50b433828d8e74196904e168a43abf1e6e88b2a15d47ed742456720c37/regex-2026.5.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2099f7e7ff7b6aa3192312650a56e91cc091e49d50b04e4f6f8b6e28b3b27f1c", size = 289682, upload-time = "2026-05-09T23:14:13.123Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d2/b835e3cafbb9d977736912436259ff551d60919f7d7b3d37d46659c63564/regex-2026.5.9-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecd353045824e4477562a2ac718c25799cdaaa41f7aa925a806a8a3e6848a5b9", size = 796996, upload-time = "2026-05-09T23:14:14.923Z" }, + { url = "https://files.pythonhosted.org/packages/2c/a6/9f992d00019166b9de01c546dd4549bc679f2a68df11b877740b0760b7c2/regex-2026.5.9-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:65c8c8c37377794bd5b2f3ebe51919042bf17aec802e23c833d89782ed0c78af", size = 866089, upload-time = "2026-05-09T23:14:17.757Z" }, + { url = "https://files.pythonhosted.org/packages/e0/08/4d32af657e049b19cb62b02e46e38fe1518797bfb2203ee93a510b21b0dc/regex-2026.5.9-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5b73ab8afcf66c622db143d1c6fda4e58e4d537ee4f125229ad47b1ab80f34c0", size = 911530, upload-time = "2026-05-09T23:14:20.353Z" }, + { url = "https://files.pythonhosted.org/packages/d9/27/2af43dd1dc201d1fecefda64a45f4ad0995855b92724f795a777b402ee69/regex-2026.5.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0de5cf193997384ed2ca6f1cd4f78055b255d93d82d5a8cd6ba0d11c10b167e4", size = 800643, upload-time = "2026-05-09T23:14:22.265Z" }, + { url = "https://files.pythonhosted.org/packages/a4/dd/23a249047013b5321d4a60c4d2437462086f601b061776a525e5fba2a59f/regex-2026.5.9-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d641a8c9a61618047796d572a39a79b26167b0411d2c3031937b2fe2d081e2cf", size = 777223, upload-time = "2026-05-09T23:14:24.179Z" }, + { url = "https://files.pythonhosted.org/packages/94/6a/e85ed9538cd19586d0465076a4578a12e093ce776d15f3f8ce92733a8dd6/regex-2026.5.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:24b2355ef5cc9aa5b8f07d17704face1c166fdcc2290fa7bd6e6c925655a8346", size = 785760, upload-time = "2026-05-09T23:14:26.065Z" }, + { url = "https://files.pythonhosted.org/packages/2a/c4/f25473209438638e947c55f9156fd8f236f74169229028cc99116380868e/regex-2026.5.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:a24852d3c29ad9e47593593d8a247c44ccc3d0548ef12c822d6ed0810affe676", size = 860891, upload-time = "2026-05-09T23:14:28.17Z" }, + { url = "https://files.pythonhosted.org/packages/f9/f7/f4f86e3c74419c37370e91f150ae0c2ef7d34b2e0e4cdd5da046a02e4022/regex-2026.5.9-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:916714069da19329ef7de197dcbc77bb3104145c7c2c864dbfbe318f46b88b14", size = 765891, upload-time = "2026-05-09T23:14:30.06Z" }, + { url = "https://files.pythonhosted.org/packages/26/70/704d8e13765939146b1cd0ef4e2feb71d7929727d2290f026eed10095955/regex-2026.5.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:fa411799ca8da32a8d38d020a88faa5b6f91657d284761352940ecf9f7c3bbdd", size = 851380, upload-time = "2026-05-09T23:14:32.123Z" }, + { url = "https://files.pythonhosted.org/packages/26/29/1a13582a8460038edc38e49f64ceb0dd7c60f5caba77571f4bf6601965d9/regex-2026.5.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1e6da47d679b7010ef27556b6e0f99771b744936db1792a10ceac6547ae1503e", size = 789350, upload-time = "2026-05-09T23:14:34.799Z" }, + { url = "https://files.pythonhosted.org/packages/73/56/3dcafe34fc72e271d62ad9a291801e88a1457bb251c132f15fcc2e5aad1a/regex-2026.5.9-cp314-cp314-win32.whl", hash = "sha256:98bd73080e8756255137e1bd3f3f00295bbc5aa383c0e0f973920e9134d7c4ad", size = 272130, upload-time = "2026-05-09T23:14:36.729Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9c/02eebf0be95efe416c664db7fb8b6b05b7a0b06a7544f2884f2558b0526f/regex-2026.5.9-cp314-cp314-win_amd64.whl", hash = "sha256:ff8d372ac2acdc048d1c19916f27ee61bc5722728458ba6ca5052f2c72d51763", size = 280999, upload-time = "2026-05-09T23:14:39.126Z" }, + { url = "https://files.pythonhosted.org/packages/70/5a/1dd1abee76cb7a846a0bcf42fdc87e5720c3c33c24f3e37814310a513d9f/regex-2026.5.9-cp314-cp314-win_arm64.whl", hash = "sha256:e1d93bf647916292e8edcec150c07ddf3dc50179ccaf770c04a7f9e452155372", size = 273500, upload-time = "2026-05-09T23:14:41.059Z" }, + { url = "https://files.pythonhosted.org/packages/86/c1/c5f619b0057a7965cb78ec559c1d7a45ce8c99a35bea95483d64959a93d9/regex-2026.5.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:83d0ee4a57d1c87cb549e195ec300b8f0ec3a82eba66d835e4e2ed8634fe4499", size = 494269, upload-time = "2026-05-09T23:14:42.869Z" }, + { url = "https://files.pythonhosted.org/packages/05/2c/5d01f1aee33de4bbe60c8452945bfc8477ca7c5ae4450f6bfe711036cb36/regex-2026.5.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d3d7eb5c9a7f6df82ed3cfac9beb93882a5cbcb5b8b157b56cb2b3b276574ac1", size = 293954, upload-time = "2026-05-09T23:14:44.822Z" }, + { url = "https://files.pythonhosted.org/packages/7a/fe/e8988b2ae2108c6ef71bd4aa8d87fbe257976dd0810e826cd75f701c68b6/regex-2026.5.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:075160bf16658e16d35233300b8453aac25de4cbea808d22348b6979668e924d", size = 292405, upload-time = "2026-05-09T23:14:47.211Z" }, + { url = "https://files.pythonhosted.org/packages/79/34/d2b0937faa7859263f7f0a3c6b103a1296306be6952dc173d0154e9a2f49/regex-2026.5.9-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45375819235558a4ff1c4971dc32881f022613abdb180128f5cb4768c1765a1c", size = 811855, upload-time = "2026-05-09T23:14:49.21Z" }, + { url = "https://files.pythonhosted.org/packages/80/fe/daf53a47457a8486db66c66c01ceb9c2303eecee3f87197f1e77eb1a736d/regex-2026.5.9-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ead4b163ac30a29574510cd4b3e2e985ac5290c05fc7095557d6a5f403fc31b5", size = 871189, upload-time = "2026-05-09T23:14:51.555Z" }, + { url = "https://files.pythonhosted.org/packages/1c/75/058fc4470cbfbf57d800aff1a0022b929a3f9fa553ee10a0cdf2070eb31f/regex-2026.5.9-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8c6e4218fbdfbcd4f6c19efca40930d24a621bf4b48cb76bc6640543bd28ef20", size = 917485, upload-time = "2026-05-09T23:14:53.633Z" }, + { url = "https://files.pythonhosted.org/packages/88/e7/179cfda3a28bc843b5c6cfe7f79f23489c791ed95f151083803660878432/regex-2026.5.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6351571c8a42b505eb555c0dc47d740d0fb66977dc142919eea6f4325b7c56a0", size = 816369, upload-time = "2026-05-09T23:14:56.198Z" }, + { url = "https://files.pythonhosted.org/packages/41/90/6f0cc422071688266d344fca8462d787cba0a2c144acb25721f9a61ec265/regex-2026.5.9-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:002205cafd2a9e78c6290c7d1df277bf3277b3b7a30e0b4bb0dac2e2e3f7cb2d", size = 785869, upload-time = "2026-05-09T23:14:58.602Z" }, + { url = "https://files.pythonhosted.org/packages/02/67/a31f1760f09c27b251ef39e9beb541f462cf977381d067faa764c2c0e393/regex-2026.5.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8abd33fef90b2a9efac5557d6033ca82d1195ed3a15fea5af15ba7b463c6a63b", size = 801427, upload-time = "2026-05-09T23:15:00.642Z" }, + { url = "https://files.pythonhosted.org/packages/e3/c4/1a80654597b6bc1e1ea0494824c31200e8a956abe290afae9b19a166a148/regex-2026.5.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:31037c82eccb44b7ea2e9e221d7c01429430e989a1f4b91ea5a855f6017b509a", size = 866482, upload-time = "2026-05-09T23:15:03.384Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/960724e06482c08466ff5611e242e86f80062949cdf6b4b9cc317b9dd93d/regex-2026.5.9-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:5604dfd046dc37eca90250fc3be938b076c8059fa772ac0ed6f499b0f0fb0415", size = 773022, upload-time = "2026-05-09T23:15:05.625Z" }, + { url = "https://files.pythonhosted.org/packages/50/a8/a9979c3e7918280e93159ebcab5ef1a65116dd4f3bd6091be0eae4a126e8/regex-2026.5.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0e1b1b4e496afbb24f4a62aba855ee4f88f25578927697b340702e48c9ee6bc2", size = 856642, upload-time = "2026-05-09T23:15:07.966Z" }, + { url = "https://files.pythonhosted.org/packages/fe/d4/a9b732f2f0072c0ab12227483abb24fffcb9f73f8a2b203df0a6d0434735/regex-2026.5.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:be3372b9df6ddecff6486d37e19095a7b4973137caf5512407a89f4455361f41", size = 803552, upload-time = "2026-05-09T23:15:10.215Z" }, + { url = "https://files.pythonhosted.org/packages/d5/fe/1b3113817447a1d4155e4ac76d2e072f42c0bcba2f43fa8a0e756ea2cd91/regex-2026.5.9-cp314-cp314t-win32.whl", hash = "sha256:3ddd90103f9e5c471c49c7852ecc1fe27c7e45eb99e977aefe7caa4e779f4f58", size = 275746, upload-time = "2026-05-09T23:15:12.609Z" }, + { url = "https://files.pythonhosted.org/packages/92/73/93d42045302636c91f2e5ef588b65b84b01428f28ec77de256b1dfdfbe5c/regex-2026.5.9-cp314-cp314t-win_amd64.whl", hash = "sha256:ca518ed29c46eecba6010b15f1b9a479314d2de409536e71b6a13aa04e3b8a77", size = 285685, upload-time = "2026-05-09T23:15:15.086Z" }, + { url = "https://files.pythonhosted.org/packages/da/80/35b4c33c804a165a7f55289afda3ea9e3eb6d15800341a2d66455c0f1f30/regex-2026.5.9-cp314-cp314t-win_arm64.whl", hash = "sha256:5e41809d2683fcde7d5a8c87a6567ba1fb1ce0de9f31bff578de00a4b2d76daa", size = 275713, upload-time = "2026-05-09T23:15:16.98Z" }, +] + +[[package]] +name = "requests" +version = "2.34.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, +] + +[[package]] +name = "requests-file" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3c/f8/5dc70102e4d337063452c82e1f0d95e39abfe67aa222ed8a5ddeb9df8de8/requests_file-3.0.1.tar.gz", hash = "sha256:f14243d7796c588f3521bd423c5dea2ee4cc730e54a3cac9574d78aca1272576", size = 6967, upload-time = "2025-10-20T18:56:42.279Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/d5/de8f089119205a09da657ed4784c584ede8381a0ce6821212a6d4ca47054/requests_file-3.0.1-py2.py3-none-any.whl", hash = "sha256:d0f5eb94353986d998f80ac63c7f146a307728be051d4d1cd390dbdb59c10fa2", size = 4514, upload-time = "2025-10-20T18:56:41.184Z" }, +] + +[[package]] +name = "requests-toolbelt" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888, upload-time = "2023-05-01T04:11:33.229Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" }, +] + +[[package]] +name = "rich" +version = "14.3.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/67/cae617f1351490c25a4b8ac3b8b63a4dda609295d8222bad12242dfdc629/rich-14.3.4.tar.gz", hash = "sha256:817e02727f2b25b40ef56f5aa2217f400c8489f79ca8f46ea2b70dd5e14558a9", size = 230524, upload-time = "2026-04-11T02:57:45.419Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/76/6d163cfac87b632216f71879e6b2cf17163f773ff59c00b5ff4900a80fa3/rich-14.3.4-py3-none-any.whl", hash = "sha256:07e7adb4690f68864777b1450859253bed81a99a31ac321ac1817b2313558952", size = 310480, upload-time = "2026-04-11T02:57:47.484Z" }, +] + +[[package]] +name = "rpds-py" +version = "2026.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/43/25a8dcd3feedd735039a8f0b5b7e3b118232b5eae288c4fd9ab200d41094/rpds_py-2026.5.1.tar.gz", hash = "sha256:07b24fea40541e28570e5b795a4a38fbdcd12550c06bd0748005ecc8116ca256", size = 64459, upload-time = "2026-05-28T12:02:13.232Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/e7/a78582dc57caa592dcc7d4fb69b61390561e908eb3d2f5df5928a8e354c0/rpds_py-2026.5.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3abe24a66e57adcfa645d718063a5fa5103ecc71ddbf26d78af8f9368018ff1d", size = 353040, upload-time = "2026-05-28T11:59:12.531Z" }, + { url = "https://files.pythonhosted.org/packages/a3/43/35e3f136343aef451e545ce8c38d36c2f93c0ed88703db8b64ba2b205c68/rpds_py-2026.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58b1d94308ddf0b1982f61f2eb54bf92997c9ece8a8093ef014250f4a517906c", size = 345775, upload-time = "2026-05-28T11:59:13.827Z" }, + { url = "https://files.pythonhosted.org/packages/20/e1/0f2160c5982d3157734d5cb3ed63d8b2d583a73c9864f77b666449f32cf8/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fa92420128dadce7f54bd73ba1825a273e9268fe9e35dbf7e6362890efa4e08", size = 376329, upload-time = "2026-05-28T11:59:15.271Z" }, + { url = "https://files.pythonhosted.org/packages/d0/11/ee0ba42aff83bf4effdbc576673c6be64c5e173978c3f6d537e94482f77d/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ca653c6546386227cd9800d1bef6a348099acf8db4250341da6d90f663d6dfcb", size = 383539, upload-time = "2026-05-28T11:59:16.665Z" }, + { url = "https://files.pythonhosted.org/packages/11/df/d94aa6a499d4ac40afe2d7620f2c597fd3c0f182e854ad7cf3f596a81cb6/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66c93681c4729e4e3ecba31b8179fae083ff3118841672835140338b4b9867c1", size = 494674, upload-time = "2026-05-28T11:59:17.991Z" }, + { url = "https://files.pythonhosted.org/packages/1f/75/33d30f43bb2f458de11979486a591b1bf6e5651765ed1704c6197c2dc773/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40ff257542e04796880e011e15cd4dc21c2599975df2aaa8f2c8495ca574e1a5", size = 389268, upload-time = "2026-05-28T11:59:19.434Z" }, + { url = "https://files.pythonhosted.org/packages/f4/1e/2c9096fc19d5fd084b0184ca2b651e659aa0a37e6fdbecf6ece47f147fe1/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6825cc329b290e93c5f6a9be2393118a763f6ccf6abd83704e0c102ca583644", size = 376280, upload-time = "2026-05-28T11:59:21Z" }, + { url = "https://files.pythonhosted.org/packages/b9/e5/61ec9f8be8211ea7f48448195549e4aaf02004083475493b0e137702ecb2/rpds_py-2026.5.1-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:de42116e69cb53b911cc34aee5ab98f36c597b822545045d49e938818b99e5e4", size = 387233, upload-time = "2026-05-28T11:59:22.454Z" }, + { url = "https://files.pythonhosted.org/packages/0d/ca/bcec1005c4f4a234f92a29078631fee49206c7265ccae966f18fd332e80e/rpds_py-2026.5.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c0f920015df2a504bebaba6d4c31ccf3fcf942f92655c086da30b671aad19aa6", size = 405009, upload-time = "2026-05-28T11:59:23.845Z" }, + { url = "https://files.pythonhosted.org/packages/72/e6/4d5718c5cf26c522dc7c9999e238da1e77380b81d0c5d1df11e271ddfeb1/rpds_py-2026.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0408a24e44feb919423dc6d9da677cb5cddb894d2ca9e763967d156d9c60fab4", size = 553113, upload-time = "2026-05-28T11:59:25.184Z" }, + { url = "https://files.pythonhosted.org/packages/d4/25/2ee807bdb3e1f0b7eddf7782acd5665a8b5205a331a7d7244a52c4812fd9/rpds_py-2026.5.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cea68bcd53467561ae2f96a6bdad1544299ba97b5b0ddcd5ac3d376e5c781c24", size = 618838, upload-time = "2026-05-28T11:59:26.749Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c1/7d4c26f167f8c41501cc073d30ee22082b16ce358cf5b00ec97cbc7804ea/rpds_py-2026.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4be8b1d2a705cc37d08256004e1d07de143fa0075c8e85a3df020b776f62b732", size = 582436, upload-time = "2026-05-28T11:59:28.11Z" }, + { url = "https://files.pythonhosted.org/packages/04/1d/9d12b0a337bab46f4769f8857f4007e3b2d639e14f9a44a0efe157696e64/rpds_py-2026.5.1-cp312-cp312-win32.whl", hash = "sha256:6736718bd4fc49cbcb538ba30516fdbef161522acefb739657d48b97bd864fed", size = 212734, upload-time = "2026-05-28T11:59:29.689Z" }, + { url = "https://files.pythonhosted.org/packages/c5/93/e4116f2de7f56bc7406a76033dc501811ddeb22b7f056b92d632871ebb0c/rpds_py-2026.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:0a7d1eec967df0e9b22614a5e177622e0c89611d03727fa0cb48e45028907870", size = 229045, upload-time = "2026-05-28T11:59:31.033Z" }, + { url = "https://files.pythonhosted.org/packages/cb/53/6c3419d85eb2ec5938a37627c585b42d76a63bb731d6e42ed4b079ebf486/rpds_py-2026.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:1841d067089e117142d79b98aa0df2f08b52f2ecc1819dd2700636c0db74a473", size = 223967, upload-time = "2026-05-28T11:59:32.318Z" }, + { url = "https://files.pythonhosted.org/packages/6c/32/14c961ad295f490eb0849ada8b79683e93a59b9de3afdd983eaf55fa6867/rpds_py-2026.5.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:efef4ac29c6ff495531eb17ee705b62841ecaa291b7c7077e848ea03e237164d", size = 352787, upload-time = "2026-05-28T11:59:33.655Z" }, + { url = "https://files.pythonhosted.org/packages/ca/bb/d1b85117967c11191441a7274ae616c65d93901d082c588f89a50a8da5ae/rpds_py-2026.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c39f5b67a8a2e67179ada2a954227d670fe65fa9098457f698f56ddf248709b3", size = 345179, upload-time = "2026-05-28T11:59:35Z" }, + { url = "https://files.pythonhosted.org/packages/7c/46/d84105f062e626a1b233f863907288a4708c2d833b8b4c6fb2764bc080c0/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5c30f3f04eef4fbd362226a6f31d7c8895ca4fbb6e0b790f6890a98d8da8559", size = 376173, upload-time = "2026-05-28T11:59:36.43Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ae/469d7959ce5b1201e1de135dc735b86db3b35dd0d1734f6a44246d5f061c/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:277f6c82f0580848796c7ecc8a7173aa3bfb928e4ff831261c2f60a81dc270db", size = 383162, upload-time = "2026-05-28T11:59:37.995Z" }, + { url = "https://files.pythonhosted.org/packages/dc/a2/57853d31a1116a561aa072794602ad3f6341e18d70a8523f1bd5b9fc1e5a/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:63c2c4c213f1a4e3f3de28ecab029dbdee976324e729c0d7a55211be72576b02", size = 495093, upload-time = "2026-05-28T11:59:39.453Z" }, + { url = "https://files.pythonhosted.org/packages/99/63/3a8eabcad9314b7daf5c65f451d2c33d989235cd8a5762186cf2c3f5a4f8/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3350ec808fb538fe71a1f94dfaa0e29c598dfad805ce49f0caec5ae3183c652b", size = 389829, upload-time = "2026-05-28T11:59:40.896Z" }, + { url = "https://files.pythonhosted.org/packages/4b/25/05678d97fc25e2622df14dc530fb82023174ecfff6733991ed0d78f167bd/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1b964e3ab599e718dc46c018d104b1ebc007cbc6567d827c94a687fca56d77e", size = 374786, upload-time = "2026-05-28T11:59:42.626Z" }, + { url = "https://files.pythonhosted.org/packages/88/d1/8c90b6431e80a3b91b284a5c7c8c0c4f9c006444d90477a740d6e0f9c694/rpds_py-2026.5.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:19cb09fab7b7fc96b2a6e28f2e34b72a3705ff27b37edb77455316e5d3f3dc9b", size = 386920, upload-time = "2026-05-28T11:59:44.124Z" }, + { url = "https://files.pythonhosted.org/packages/ff/99/4638f672ab356682d633ee0da9255f5b67ce6efd0b85eb94ad3e255e65a5/rpds_py-2026.5.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:abe76bcdba31e576cb83eeb8797aa0d882b738fef6dc65d0601fc753806a5b46", size = 405059, upload-time = "2026-05-28T11:59:47.177Z" }, + { url = "https://files.pythonhosted.org/packages/66/3f/3546524b6eb4cc2e1f363a3d638fa52f6c24faae3500c25fb488b02f1740/rpds_py-2026.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8bff7073db3899158fff55ebf57b113a67030af26f80a18978f9f0aa60250ddf", size = 553030, upload-time = "2026-05-28T11:59:48.603Z" }, + { url = "https://files.pythonhosted.org/packages/c6/c3/7b3388c796fcf471bd17194242d4dc1a7608567c0fa422bcc1c5e79f9c1e/rpds_py-2026.5.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8ba264fa49be666cd9cc56bf34ec7002fb3d27a4aee5bcb4d43d0d18feb1bb6f", size = 618975, upload-time = "2026-05-28T11:59:50.314Z" }, + { url = "https://files.pythonhosted.org/packages/61/1e/a3cb07f2795075d1d88efddae2f541359fde5f08c81ee114c29c2949c90a/rpds_py-2026.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4860b603ddda0475a8885499b3729e90229d480105b42651962a5397d995fa89", size = 581178, upload-time = "2026-05-28T11:59:51.673Z" }, + { url = "https://files.pythonhosted.org/packages/a1/74/e758c03a5ef46f04c37f2651a2893db846d569ba8a7bca469d4b58939bcd/rpds_py-2026.5.1-cp313-cp313-win32.whl", hash = "sha256:7944270ae71383f6e2657dd7d5ce4eeb4ac2d0059a6738f0510583d462ab4842", size = 212481, upload-time = "2026-05-28T11:59:53.148Z" }, + { url = "https://files.pythonhosted.org/packages/70/ec/a2aca432db9c7359b40fa393eeeaa0d166c2f70175be956e75fa24197c44/rpds_py-2026.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:88647f43a73c4e01be19b04ceef0c8d3a1958153604d13c773becd8016f2a0cf", size = 228519, upload-time = "2026-05-28T11:59:54.505Z" }, + { url = "https://files.pythonhosted.org/packages/29/60/a73bfdd45b096574556acf303bbd9fa9eed36ca8a818b514e2a5d5fe2b9d/rpds_py-2026.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:453895624ecf7db7063b1004e44037522bbaef9ff6a945e59bc71662d7a03abd", size = 223446, upload-time = "2026-05-28T11:59:56.081Z" }, + { url = "https://files.pythonhosted.org/packages/18/e2/408105fd611823f00882aea810f3989a30d26b1bab8b6beb20f98c724e0e/rpds_py-2026.5.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:b4e4bc98639ec915f512fde3aa7a95e0041d95d9c3cc86eea841fa63cb1e8600", size = 355287, upload-time = "2026-05-28T11:59:57.448Z" }, + { url = "https://files.pythonhosted.org/packages/8d/58/5c4a43436843c90d0f6d19f82c200c80e3843ca9fa07b237623327f6d384/rpds_py-2026.5.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cacedb7a6e167680acba45ad5716e89067d225dc80da0d7040cae8c81d4572fa", size = 347033, upload-time = "2026-05-28T11:59:58.881Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c2/1a71acdacaf4e259b10278fb87b039ded3cf80041bcd89dd8a3ea702ded6/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68700371c5d7ae1412862ddfa719090925c93ecf351c566d66f09d04b136ea00", size = 376891, upload-time = "2026-05-28T12:00:00.516Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c8/535f3d9b65addd8e28aa87b83c6e526799c3717a88273db8ea795beeef7a/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:296c799becfa849c779c8725494fe9ed94959ed886787df4364b058465bad7f0", size = 385646, upload-time = "2026-05-28T12:00:02.394Z" }, + { url = "https://files.pythonhosted.org/packages/1c/91/dc033f313345c354ade914dbe73cdb90b615a4409ea02430d5356794f3d8/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3858b908218ee108d0bbfb2095ccc237648053c9bf98affad7cb079acaf1d97", size = 498830, upload-time = "2026-05-28T12:00:04.189Z" }, + { url = "https://files.pythonhosted.org/packages/27/fc/90fcbea459dbb8ddc18a2e0fd1de9412b48bc84ffff2db771cf714bacfd6/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4fb8d2e7cb2f850b169806d61d1b991738acec96500a75c30f49caf064ce7cef", size = 392830, upload-time = "2026-05-28T12:00:05.797Z" }, + { url = "https://files.pythonhosted.org/packages/b2/1d/46cd11a228c9750684a798d98f878be6f614aa762438da7378f035e79e35/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27b74c10ed6a8f190f4287f53bcfea348b92a84a9c9f70d30183d1e6172d580d", size = 379613, upload-time = "2026-05-28T12:00:07.433Z" }, + { url = "https://files.pythonhosted.org/packages/24/4a/d9b0c6af3a1de03eb93741bbe8be2bdce84d8fda8224f3005451d86df389/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:b9a6528956191c48c52294a592dbd4a8386d7048bdb25c0efcb6b966466c6d83", size = 388183, upload-time = "2026-05-28T12:00:09.227Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b4/db7aaabdda6d020afc87d981bcc2f57a434c7dec60ecfc2ab3dd50b20351/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:af03e34e860047bc7a352b842856fcf78798fbb81132cc98bd2f907ab4eb9cd2", size = 408578, upload-time = "2026-05-28T12:00:10.779Z" }, + { url = "https://files.pythonhosted.org/packages/08/d6/070f6a41cbb343e2ac4171859bf3f3623e0ab002f72619d6d505313ec2de/rpds_py-2026.5.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fea6e836d10abbe191d557d33bd58bd5987725fe63aa1eefe557d230209855bd", size = 553573, upload-time = "2026-05-28T12:00:12.443Z" }, + { url = "https://files.pythonhosted.org/packages/75/ab/1a71ea3589c4345dac0a0518f0e6a031cb42689277851b683c46d27463a5/rpds_py-2026.5.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:fc0c0f878ea770a0a8a462456c5ad36fc9fe6358e6b76fdadc7f17575e0b8bf1", size = 620861, upload-time = "2026-05-28T12:00:14.09Z" }, + { url = "https://files.pythonhosted.org/packages/8a/22/9bf80a56069c0c443fcfefac639a86a744550a2898817a6dfd3e26654924/rpds_py-2026.5.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e0b360f316d966b048b085857630b3cc51f3db2f07b06f440eac8f695374d1e3", size = 585633, upload-time = "2026-05-28T12:00:15.66Z" }, + { url = "https://files.pythonhosted.org/packages/da/68/3b2c0a75c9e04125696f84ebdbbf304acf5a40b58ba4481cdb98a922c3ba/rpds_py-2026.5.1-cp313-cp313t-win32.whl", hash = "sha256:a2999883eedf72fdfb7520b92c7d4ec2572a71ff40239377aa604cc529eecafc", size = 210074, upload-time = "2026-05-28T12:00:17.291Z" }, + { url = "https://files.pythonhosted.org/packages/e7/8b/609157d5a25d37d4f29f92840ba531f416907c34ae5c5739dd21fc2bef98/rpds_py-2026.5.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e07be2a9d7122bd6e82dea89814ef8dc893feb1aae97fec1630f3263bbb30e55", size = 228635, upload-time = "2026-05-28T12:00:18.73Z" }, + { url = "https://files.pythonhosted.org/packages/d4/6f/19c1918a4b590d8de87e712e4abe4b3875771eff60216fb6153cf6665c68/rpds_py-2026.5.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:1f2c391c3059798093b65df23aca2cac150460ae9c630d99dec83d703d9485b9", size = 349756, upload-time = "2026-05-28T12:00:20.217Z" }, + { url = "https://files.pythonhosted.org/packages/e5/60/a06fe7da34eca79dacbf958a2ba0c6eea85bc2b29de20080bf40f72f66fa/rpds_py-2026.5.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:413b424f7c4ee65ab5e5be91f5731be0f8b41a1ee2b12dfe810d716312e95a78", size = 343831, upload-time = "2026-05-28T12:00:21.711Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ec/b2333b97b90e2a6ef6ca8ad386ee284968e74bcfe113b3f1a8d9036429a9/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c595a1d9255dce0599e13130d1440ab2506654f2b50294226ee06402f8fef63", size = 375127, upload-time = "2026-05-28T12:00:23.326Z" }, + { url = "https://files.pythonhosted.org/packages/14/7f/e00aae54067f2b488c4637961d5f58204d470795fc791085fa3f15060d2e/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1c27c5f6102eac8c03e7595a00827a53b271ba40a53b59ff8709170e0855ea4a", size = 379034, upload-time = "2026-05-28T12:00:24.89Z" }, + { url = "https://files.pythonhosted.org/packages/be/cc/423999bbb8ae8dc93c77fc1d5e984ade5eb89d237d3bb884ccfa72ae2890/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c7fcf61d44cacecaf3aea542b0e053db77972a4573e7ceda16fb2b399161195", size = 490823, upload-time = "2026-05-28T12:00:26.676Z" }, + { url = "https://files.pythonhosted.org/packages/0f/aa/c671bf660f12e68d3c52ff86c7066ed1372df5a0f4f2ff584e419b8207e7/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c817a189d4ee14290420e5ff051e4dd6baa13f3edf84685071dee07a6d538ee", size = 388144, upload-time = "2026-05-28T12:00:28.577Z" }, + { url = "https://files.pythonhosted.org/packages/19/c8/d63bb75b68afe77b229e3021c6031bcaf01da5db5b0e69d0d10f9ba679a7/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21846aac0ed2e0589f38c12dc44e77bb64e494b771eadbcf169cba00566ba7ba", size = 371959, upload-time = "2026-05-28T12:00:30.304Z" }, + { url = "https://files.pythonhosted.org/packages/82/35/c51122014d8274ff37dc606d60049c3db7d83da02b5b282511e5a906a9a6/rpds_py-2026.5.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b317c87a13f769a4e787819bd508aaa5d69aa09b0880de9af6d3a8a54571cdec", size = 383558, upload-time = "2026-05-28T12:00:31.764Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f9/2790cb99c136a5363acdeacf5c27c56f3de0d4118a1f48fca83404c99c89/rpds_py-2026.5.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ce87129d9f2c14fa6c4a8601fb80eb4488c80d38a20cd13758ef11123e14995d", size = 402789, upload-time = "2026-05-28T12:00:33.247Z" }, + { url = "https://files.pythonhosted.org/packages/e5/1b/e4fb584f8c75d35c38150ff6a332cda949e6f97acba1f4fd123b14ab56fe/rpds_py-2026.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9cdddb6c1207d284d94fd1530adf57fbd797fe7c4b8704ba85f49414f2557e7d", size = 551405, upload-time = "2026-05-28T12:00:34.819Z" }, + { url = "https://files.pythonhosted.org/packages/d8/f7/a6731b4216cb3793ea1af5391da240f5683dacc0d13e034fe5fc3503f240/rpds_py-2026.5.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:4e237e139f94d3c036fd28eb9f564c99055476ff4ff05cd42be55ce349b5aa02", size = 616975, upload-time = "2026-05-28T12:00:36.268Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/2e051a81d95d8e63f4b35a1c463a87e8766bc3d083c067c5dfb6bf220747/rpds_py-2026.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ed0954b524873214369184a9c82b0eaa45a3fbb9a798cd95b17e0d98499e7ea0", size = 578701, upload-time = "2026-05-28T12:00:37.82Z" }, + { url = "https://files.pythonhosted.org/packages/65/56/b5f6fdb2083e32bca8a8993d89e70db114b4756c9e2c38421328126689d2/rpds_py-2026.5.1-cp314-cp314-win32.whl", hash = "sha256:2d88621d6a7d4dfa633d21abe90f280bb205274e16b1d1e61c6ad4640b2453b7", size = 209806, upload-time = "2026-05-28T12:00:39.492Z" }, + { url = "https://files.pythonhosted.org/packages/fb/80/65a5aa96c155e611d1ed844e4e1f57f3e36b021f396d9f8585d756e6b90d/rpds_py-2026.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:cef8ac28d26f4dda3533060c20fbf80a325458fa9fd23ea72a73cdfa8e978838", size = 225985, upload-time = "2026-05-28T12:00:40.94Z" }, + { url = "https://files.pythonhosted.org/packages/27/7c/ad185212e87b05f196daef92bc5f3caf07298eb47c295b5585c3dd3093ac/rpds_py-2026.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:eaaea962c68cdc68d4a533ba985ab8e9484277910bbfaa2ab3ef7732667bfed8", size = 221219, upload-time = "2026-05-28T12:00:43.15Z" }, + { url = "https://files.pythonhosted.org/packages/23/58/e14ae18759020334646b031e708ab4158d653a938822bfb7b95ef2e93aa3/rpds_py-2026.5.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:21942f52dbbd5f8758bf021213d28bd45c39e873e65e2407faf5f1846f5761ad", size = 352148, upload-time = "2026-05-28T12:00:44.638Z" }, + { url = "https://files.pythonhosted.org/packages/31/9b/5f4a1e2f960bca3ac5d052b139dd31eed97b259f9d909173821760d542e8/rpds_py-2026.5.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f414556f6e3958300ff941e40c9f97e3dc9774ddd1b3434c475d73dd354bbed3", size = 345196, upload-time = "2026-05-28T12:00:46.14Z" }, + { url = "https://files.pythonhosted.org/packages/1a/71/1d9574d6a2fa20ab60eaa55c7467f5aa20cbc770f341a05f09c0876f59e2/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef1013a8625c74043210190b246f5b1551e09757c1f356c6e4160ef96c5bc081", size = 374981, upload-time = "2026-05-28T12:00:47.531Z" }, + { url = "https://files.pythonhosted.org/packages/0c/9a/37e99f4915a80aa71670263c1267f7ae0af95f53a3f61e6c3bdc016d4515/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cc68e231a77a5f0d774ae278a1f8e55c0456501820847c1e4efb3829f3441df6", size = 379961, upload-time = "2026-05-28T12:00:49.216Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ff/6e73f74b89d2e0715e0fc86b7dde893f9a61ae2f9b256ff3bdfe41ac4e94/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9baffb505aff33acc69b422a19f77806680f3c8632227d79f48de8a810d1c2c5", size = 495965, upload-time = "2026-05-28T12:00:51.111Z" }, + { url = "https://files.pythonhosted.org/packages/ea/e0/425faba25f59d74d4638b267f7c7a80e8649d2ef4db10a19b0c4a71e6e6f/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8d2f912928d426e8cfa396f7f3f8d29a59e6689c86dcca3c420730c1096322b", size = 389526, upload-time = "2026-05-28T12:00:52.77Z" }, + { url = "https://files.pythonhosted.org/packages/c6/76/7a41960e3fddae47fab43a28684d5da981401dffd88253de0944148654cb/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90f628283be835db980c941767d41c9a27b5239e54ba0a9c1335247e82406964", size = 376190, upload-time = "2026-05-28T12:00:54.215Z" }, + { url = "https://files.pythonhosted.org/packages/27/60/5f38dc70824fc6951b51d35377e577a3a3a4c81a6769cc5a2de25ebe0ad1/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:1ebb2f0ab7e16132995a72de805170e0203df0c3dd22e1ef1cd1fdd90bd7a131", size = 383921, upload-time = "2026-05-28T12:00:55.673Z" }, + { url = "https://files.pythonhosted.org/packages/60/1a/d60a38caa1505f4b9483c3fbbde12c94e1079154f4f401a6da96f7e77621/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f3df3d16ded76f1f8c9cdebd0e1ea55fdf4c23b812de189814da7cf229c22a81", size = 404766, upload-time = "2026-05-28T12:00:57.518Z" }, + { url = "https://files.pythonhosted.org/packages/87/ff/602fd3f174d6425f0bce05ad0dfbec0e96b38d0f7d08a79af5aa20083885/rpds_py-2026.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9af8905b8f854990e40d5206aa5ac58d9b0fe0b7f351ff2bb086c20f6c8c6a47", size = 551343, upload-time = "2026-05-28T12:00:58.978Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c1/1be13327acdbead3eca1fde03b6a34dbb011f1e864e217f0d32cc1779a7f/rpds_py-2026.5.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:036a36a87fb1cd3b214d11c4b3c4f7d2ddad933625dca1c900b56a057c07740a", size = 618502, upload-time = "2026-05-28T12:01:00.656Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d7/afb49b49d7f2be8b7ba1a9f0977fa5168003437b93086726f066544e8351/rpds_py-2026.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:62ae3853454fe9ef283a03c96c2d835d39e84b14643a9d62c82ef0fb87d702ca", size = 581916, upload-time = "2026-05-28T12:01:02.22Z" }, + { url = "https://files.pythonhosted.org/packages/25/d1/dbef8c1f8a10f07beb62b5f054e20099fd9924b3ec001b8f0b6ac7813a85/rpds_py-2026.5.1-cp314-cp314t-win32.whl", hash = "sha256:6c3d771a46ec18b12af06ce36243a9a80b07a5d0515236332d90863ca8bb326a", size = 207855, upload-time = "2026-05-28T12:01:03.821Z" }, + { url = "https://files.pythonhosted.org/packages/2a/72/bfa4e61ab8e7dc1c8adf397e05e6cbdd4239357bd72b248d3de662f23915/rpds_py-2026.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c93c629be4636cf54337bd5f06c104d55e42ced54d681f6fe21ae510a65116f6", size = 225422, upload-time = "2026-05-28T12:01:05.194Z" }, + { url = "https://files.pythonhosted.org/packages/27/3a/7b5da92b640f67b6717ccafc83cdd06bfa7ff2395c3685c68922bb54d703/rpds_py-2026.5.1-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:3574b55c604b8f75dacb007136508bbc0db406e626301778096a133327e7f2fb", size = 349576, upload-time = "2026-05-28T12:01:06.722Z" }, + { url = "https://files.pythonhosted.org/packages/d7/8a/2aafd7ad355a1bd48ca76e2262b74b15e6432b5a1efe150efd4d779cd55d/rpds_py-2026.5.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:94068eb3ae6d43f5a786b7db96a406a34e6d5c24489feef32fd6e8946ea7b291", size = 343640, upload-time = "2026-05-28T12:01:08.441Z" }, + { url = "https://files.pythonhosted.org/packages/f7/7d/6c9523c1abbe840a1b7fba3c516d48e1d3487cc80fea4366c4071cf56784/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a5b10e8ce894825f380a8f1b6444cf73c294dfea62afbb2d13e3a9e630cec1", size = 375322, upload-time = "2026-05-28T12:01:09.934Z" }, + { url = "https://files.pythonhosted.org/packages/5a/5d/0b7b03fb1dc509321f01de3149784ab773e34c8573022029af8076afcb9c/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fc09f82e63d4bcd58149572f857a431bae851dc747e313c3b5bdf7abb907fda8", size = 379066, upload-time = "2026-05-28T12:01:11.48Z" }, + { url = "https://files.pythonhosted.org/packages/d7/e2/8ef6012999ebf1cb1c22f876d9ce5e63d960fd4631d2af3202d3f480aa25/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e10464d17df3b582745c25cec695cb9558bca2cb6ddb631aee1787fc72c767b2", size = 494586, upload-time = "2026-05-28T12:01:13.051Z" }, + { url = "https://files.pythonhosted.org/packages/80/af/1eeb029bec67582c226b7809172207cd005073af4ebd906e65ff494f4983/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ba05adbf15d994c38ec0b7ab32e858e5110c21e9009a00a86545fd220f84e038", size = 388415, upload-time = "2026-05-28T12:01:14.631Z" }, + { url = "https://files.pythonhosted.org/packages/18/23/ffbe10711c4d766c1cab0557d6906c074f795814863c67b351355d29354a/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77c004fdc7b891967106f78ddfd7b076bfe6813c6139c6fff6aed3bcaa960b26", size = 372427, upload-time = "2026-05-28T12:01:16.153Z" }, + { url = "https://files.pythonhosted.org/packages/bd/3a/30ba4a6ad457e5b070c18d742a33fb77d8d922b565cc881f8a5313d63bfe/rpds_py-2026.5.1-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:83bcf894486c9d78dd290d3c0124ff6dd8875d3025e2090a8ec49fcc37c55fdd", size = 383615, upload-time = "2026-05-28T12:01:17.809Z" }, + { url = "https://files.pythonhosted.org/packages/d3/69/62e242b53ce39c0814bd24e1a6e6eba6c92be716277745f317f9540a2e7b/rpds_py-2026.5.1-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c3df104083952a0e0c6f10de33e440eabe98fb6317d23e1a58c68f6df08d01b9", size = 402786, upload-time = "2026-05-28T12:01:19.419Z" }, + { url = "https://files.pythonhosted.org/packages/38/c1/a770b9c186928a1ed0f7e6d7ae50e7f3950ed23e3f9e366dbc8e38cb55de/rpds_py-2026.5.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:980450826cf22e133c57e0835070bdd0dd3f73b9b708c3ce223def2cb9469e14", size = 551583, upload-time = "2026-05-28T12:01:21.013Z" }, + { url = "https://files.pythonhosted.org/packages/21/7c/68e8579b95375b70d2a963103c42e705856cdb98569258bd807f4423891c/rpds_py-2026.5.1-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:205dde846f24332ab0c1188699a043b8d165b79bb84529ce272c45048ff6be01", size = 616941, upload-time = "2026-05-28T12:01:22.548Z" }, + { url = "https://files.pythonhosted.org/packages/70/a1/a6135aed5730ff03ab957182259987ac11e55fb392a28dc6f0592048a280/rpds_py-2026.5.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:3966b82dd563176396df030f3dd52a6e54cb69b718e95e78bd555ed3d1e0185d", size = 578349, upload-time = "2026-05-28T12:01:24.118Z" }, + { url = "https://files.pythonhosted.org/packages/09/6e/f24201a76a84e6c49d0bdfdfcb735210e21701e9b21c5bfc0ba497dd62f6/rpds_py-2026.5.1-cp315-cp315-win32.whl", hash = "sha256:7818f8d0a415be74d2be3590b0a1c1f463a642f4d0217e7d10602dceef5b79aa", size = 209922, upload-time = "2026-05-28T12:01:25.522Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e4/966bc240bb0485fc265278f6de44d05834bf0b3618886e0b22e33d54c49a/rpds_py-2026.5.1-cp315-cp315-win_amd64.whl", hash = "sha256:b3cc20c0d800af78fd0fac68086e28c1856cec51ea528bb81ea851aa40d39325", size = 226003, upload-time = "2026-05-28T12:01:27.062Z" }, + { url = "https://files.pythonhosted.org/packages/5c/5c/a15a59269cd5e74472734516c73795c15eccfc841b3d4b0228c3f53f19d0/rpds_py-2026.5.1-cp315-cp315-win_arm64.whl", hash = "sha256:3609e9939a8a76cd904cf98a3f1f13b5dc7e150adeaee89e0ea09652ea213e16", size = 221245, upload-time = "2026-05-28T12:01:28.51Z" }, + { url = "https://files.pythonhosted.org/packages/e0/22/135ce03804e179a71ceb13be095deda4a279bc88f7a6b8fa161c5ad44e12/rpds_py-2026.5.1-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:5d333a7127d4b307601ac37792bee01bb95c867cbfacf21b6375b804d6bbd723", size = 352015, upload-time = "2026-05-28T12:01:30.214Z" }, + { url = "https://files.pythonhosted.org/packages/3b/5f/f1f6d2652eb9d848f6eb369d8db83a2da6249bb49ad2c2a48f45d54538d3/rpds_py-2026.5.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:b5f077b44a4f7808520f66dae234988d867deb9aed9be5da057ce9ba831b2a41", size = 345016, upload-time = "2026-05-28T12:01:31.656Z" }, + { url = "https://files.pythonhosted.org/packages/88/66/b74182775691ea2290c99e52ac8d5db844e56fbec90ce421f107658c8314/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55d8f9b7b78c9538fc9e04e82ec0e888ff0c3cffcfad152c77e57cd09351a98a", size = 374775, upload-time = "2026-05-28T12:01:33.136Z" }, + { url = "https://files.pythonhosted.org/packages/ff/8f/15e5a61d9f0a43902d36561d4f07cae6ae9f4716be825159fd72717f33af/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e3a8ae58895ac107ed934a6bf51e5846f95c53b9b940c2c6d310838fd5846358", size = 380270, upload-time = "2026-05-28T12:01:34.574Z" }, + { url = "https://files.pythonhosted.org/packages/02/c3/f859b12763a80540cdf2af0f15b19904cf756a71d7bdd3f82ff3e5b1bbf9/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0957cf3c2b8632ec7aaebffebea8005b353cc2a237b6e2ae3c2cac0820704cfb", size = 495285, upload-time = "2026-05-28T12:01:36.127Z" }, + { url = "https://files.pythonhosted.org/packages/1c/c7/ff27c2ac8411d30b03b1829fd88cae8dad1a4d0da48dd25e57c4038042e6/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c396c1304de421050b3681ea70f371874b54d41b0151e96109758144c231e30b", size = 389581, upload-time = "2026-05-28T12:01:37.635Z" }, + { url = "https://files.pythonhosted.org/packages/6e/67/fe92ee32a6cc05c77228a2f8b1762e7124f386ec20ff83d0757b762d58d0/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aad1bff7f666b9598e573815affd666aac6a13a585dde336f843e33350c7fadc", size = 376041, upload-time = "2026-05-28T12:01:39.307Z" }, + { url = "https://files.pythonhosted.org/packages/f8/91/b4d6685c27aba55bd82f25b278be8237038117d05f9659a6213ad3408130/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:656a042550878f12d45752452d47094b7cfe5ad1e9d7b87b5a22ad3ae5ff8015", size = 383946, upload-time = "2026-05-28T12:01:41.043Z" }, + { url = "https://files.pythonhosted.org/packages/bd/79/2c1d832a53c8e0f8e98fc970ec257b950fecd4f62be2ab7182b500a0cbc8/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:73c4bd4f70294737b5206a3e8e30ccadbf8a60301831c8ea23eec5dbeea1ecfa", size = 405526, upload-time = "2026-05-28T12:01:43.032Z" }, + { url = "https://files.pythonhosted.org/packages/78/c4/c98117b03c6a8581ab2c2dfccfe9a5ad82bd8128a3c28b46a6ad2d97c393/rpds_py-2026.5.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:43bca78665423cabae77146f2fe7ce55272b6c8d55d82cca83effd42c7e13972", size = 551165, upload-time = "2026-05-28T12:01:44.648Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c1/bc479ca069200af730881b1bd525e3114b2b391a351509fcb1b772f28086/rpds_py-2026.5.1-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:42d0f20e85e549c870749d0e247f0c10d318a45b7e9676d575d2dcb04a1b2e66", size = 618778, upload-time = "2026-05-28T12:01:46.337Z" }, + { url = "https://files.pythonhosted.org/packages/77/65/38ab2f90df44c2febfb63cc10ced40763d9b4bc94d173e734528663fe7f5/rpds_py-2026.5.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:b1be5c35683684d5331b93600c210e8367c254683d8a6df6bd21bd2da3a334fb", size = 581839, upload-time = "2026-05-28T12:01:48.109Z" }, + { url = "https://files.pythonhosted.org/packages/15/2d/ce1f605fe036aadd460e5822e578c6c7ec3a860936cca37d6e0f299daa77/rpds_py-2026.5.1-cp315-cp315t-win32.whl", hash = "sha256:75808f6c38ce7749bb68cc2770161aae5045e6c6f6781a9782e74b93304399df", size = 207866, upload-time = "2026-05-28T12:01:49.648Z" }, + { url = "https://files.pythonhosted.org/packages/79/cb/966040123eb102371559746908ef2c9471f4d43e17ec9a645a2258dab64b/rpds_py-2026.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:90bd6630002a1c7f09e7843dd79f0d24f3d2897cc25a753480917865d14f15b3", size = 225441, upload-time = "2026-05-28T12:01:51.408Z" }, +] + +[[package]] +name = "s3transfer" +version = "0.18.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e0/1f/12417f7f493fc45e1f9fd5d4a9b6c125cf8d2cf3f8ddbdfab3e76406e9d6/s3transfer-0.18.0.tar.gz", hash = "sha256:3760b8b7ec1315da54048b2d626276732bee4300d054d492d4e1d43e20d4ecbd", size = 160560, upload-time = "2026-05-28T19:39:09.124Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/58/a58fc997655386daa2e25784e30c288aa3e3819e401f77029ee4899fb55a/s3transfer-0.18.0-py3-none-any.whl", hash = "sha256:239c13b09e65ad0346e1be7348b8a202dcad44ac7ea7c6eb858fc881dce739b6", size = 88572, upload-time = "2026-05-28T19:39:07.999Z" }, +] + +[[package]] +name = "scikit-network" +version = "0.33.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "scipy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/6d/28b00fbef9ff7d8ba31861bf16705a1a74a1696fb65aab2a7c584f966bec/scikit_network-0.33.5.tar.gz", hash = "sha256:ae2149d9a280fdc4bbadd5f8a7b17c8af61c054bc3f834792bc61483e6783c12", size = 1784205, upload-time = "2025-11-19T09:45:14.402Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/32/f092fca9a3ae256e0608ec6a4d8830023ea4ae478c2e27e94cf5802824f0/scikit_network-0.33.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ced625228be1632595d11adaf22d61d1d4c788909cd4d8c364e720b2814aac7f", size = 2874698, upload-time = "2025-11-19T09:44:52.765Z" }, + { url = "https://files.pythonhosted.org/packages/4b/c7/5b2ce72f93b422af48a2b755fbcbab271bf980a6b46484f754d63978f1ff/scikit_network-0.33.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:808b625d28005c24b47cbdf65f780a3a355aa4080992e6b78f03434e873d06e6", size = 2854224, upload-time = "2025-11-19T09:44:55.574Z" }, + { url = "https://files.pythonhosted.org/packages/86/02/974ae67f493ccf988108894e8a9dedfd00ca5113d2848e0b9fc2a4d18824/scikit_network-0.33.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9f2d98059cd79bdb935ff6a638f1c3a12b0b1cb7ace9e1d3fb35476eeeaabaa", size = 7924650, upload-time = "2025-11-19T09:44:57.704Z" }, + { url = "https://files.pythonhosted.org/packages/8d/34/b67e48e111916a6f09fe29a971a9716c14f78525e0ab7c46e6a6538cf2f6/scikit_network-0.33.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aac2d3bc14214f02dac300624f5ec2650af9a98b52f304d300f0ec2813a0e544", size = 8012322, upload-time = "2025-11-19T09:45:00.079Z" }, + { url = "https://files.pythonhosted.org/packages/c3/a2/49293b53b837b3248d19fffd41a9fd73dcb2eeabbab890cc4c8aa237b545/scikit_network-0.33.5-cp312-cp312-win_amd64.whl", hash = "sha256:2866b16aed9ef25ba42cb2f2e44ef2ad079337f336ce48d0604b55fa4af87688", size = 2746491, upload-time = "2025-11-19T09:45:01.997Z" }, + { url = "https://files.pythonhosted.org/packages/d6/cd/0069244e970d27fa0ab0512394295a106605f00c271e85618182460d2c92/scikit_network-0.33.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:aa8b490a777e081dd6f69627a26b50cc4012f27fff683a1e4828819a88a5dcf2", size = 2862564, upload-time = "2025-11-19T09:45:03.93Z" }, + { url = "https://files.pythonhosted.org/packages/51/37/85454864e50a65e528fe3b15eb3b41eb68b0c7f6d5c51c220b3198622ede/scikit_network-0.33.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3615d073ba9ae1ae30dda2de747474cd23c86cededa82b317471ee9f9bebd1b2", size = 2842521, upload-time = "2025-11-19T09:45:06.31Z" }, + { url = "https://files.pythonhosted.org/packages/c4/b9/4023f35e430b51020f17b0f1d8933768cf1ed7cd1623cc089f5543048983/scikit_network-0.33.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2408d3f4c81256a3193d536aad4a6ffcfbb05d096abe6a9cc0b6b5e275df876d", size = 7871695, upload-time = "2025-11-19T09:45:08.101Z" }, + { url = "https://files.pythonhosted.org/packages/8e/ec/e50755f7459130ba745c42c37665c5ae9a7c7e357f43400b5b8b966f902e/scikit_network-0.33.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:526490a1e0e8e49ad0f4cca193f581d60082a2fa8c9a825eb0b6936050b0d02b", size = 7968762, upload-time = "2025-11-19T09:45:10.347Z" }, + { url = "https://files.pythonhosted.org/packages/45/2a/616974a0adb9d04a791570e9371caaef14c54f8806b04dd59c80a7b60289/scikit_network-0.33.5-cp313-cp313-win_amd64.whl", hash = "sha256:722c15fcede5e07ac008354bbd6ef375e0f5bf1fd52bd40271775997be2fb715", size = 2742482, upload-time = "2025-11-19T09:45:12.843Z" }, +] + +[[package]] +name = "scipy" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:35c3a56d2ef83efc372eaec584314bd0ef2e2f0d2adb21c55e6ad5b344c0dcb8", size = 31610954, upload-time = "2026-02-23T00:17:49.855Z" }, + { url = "https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fcb310ddb270a06114bb64bbe53c94926b943f5b7f0842194d585c65eb4edd76", size = 28172662, upload-time = "2026-02-23T00:18:01.64Z" }, + { url = "https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cc90d2e9c7e5c7f1a482c9875007c095c3194b1cfedca3c2f3291cdc2bc7c086", size = 20344366, upload-time = "2026-02-23T00:18:12.015Z" }, + { url = "https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:c80be5ede8f3f8eded4eff73cc99a25c388ce98e555b17d31da05287015ffa5b", size = 22704017, upload-time = "2026-02-23T00:18:21.502Z" }, + { url = "https://files.pythonhosted.org/packages/da/34/16f10e3042d2f1d6b66e0428308ab52224b6a23049cb2f5c1756f713815f/scipy-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e19ebea31758fac5893a2ac360fedd00116cbb7628e650842a6691ba7ca28a21", size = 32927842, upload-time = "2026-02-23T00:18:35.367Z" }, + { url = "https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02ae3b274fde71c5e92ac4d54bc06c42d80e399fec704383dcd99b301df37458", size = 35235890, upload-time = "2026-02-23T00:18:49.188Z" }, + { url = "https://files.pythonhosted.org/packages/c5/5c/9d7f4c88bea6e0d5a4f1bc0506a53a00e9fcb198de372bfe4d3652cef482/scipy-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a604bae87c6195d8b1045eddece0514d041604b14f2727bbc2b3020172045eb", size = 35003557, upload-time = "2026-02-23T00:18:54.74Z" }, + { url = "https://files.pythonhosted.org/packages/65/94/7698add8f276dbab7a9de9fb6b0e02fc13ee61d51c7c3f85ac28b65e1239/scipy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f590cd684941912d10becc07325a3eeb77886fe981415660d9265c4c418d0bea", size = 37625856, upload-time = "2026-02-23T00:19:00.307Z" }, + { url = "https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87", size = 36549682, upload-time = "2026-02-23T00:19:07.67Z" }, + { url = "https://files.pythonhosted.org/packages/bc/98/fe9ae9ffb3b54b62559f52dedaebe204b408db8109a8c66fdd04869e6424/scipy-1.17.1-cp312-cp312-win_arm64.whl", hash = "sha256:f4115102802df98b2b0db3cce5cb9b92572633a1197c77b7553e5203f284a5b3", size = 24547340, upload-time = "2026-02-23T00:19:12.024Z" }, + { url = "https://files.pythonhosted.org/packages/76/27/07ee1b57b65e92645f219b37148a7e7928b82e2b5dbeccecb4dff7c64f0b/scipy-1.17.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5e3c5c011904115f88a39308379c17f91546f77c1667cea98739fe0fccea804c", size = 31590199, upload-time = "2026-02-23T00:19:17.192Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ae/db19f8ab842e9b724bf5dbb7db29302a91f1e55bc4d04b1025d6d605a2c5/scipy-1.17.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6fac755ca3d2c3edcb22f479fceaa241704111414831ddd3bc6056e18516892f", size = 28154001, upload-time = "2026-02-23T00:19:22.241Z" }, + { url = "https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:7ff200bf9d24f2e4d5dc6ee8c3ac64d739d3a89e2326ba68aaf6c4a2b838fd7d", size = 20325719, upload-time = "2026-02-23T00:19:26.329Z" }, + { url = "https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4b400bdc6f79fa02a4d86640310dde87a21fba0c979efff5248908c6f15fad1b", size = 22683595, upload-time = "2026-02-23T00:19:30.304Z" }, + { url = "https://files.pythonhosted.org/packages/b4/e0/e58fbde4a1a594c8be8114eb4aac1a55bcd6587047efc18a61eb1f5c0d30/scipy-1.17.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b64ca7d4aee0102a97f3ba22124052b4bd2152522355073580bf4845e2550b6", size = 32896429, upload-time = "2026-02-23T00:19:35.536Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:581b2264fc0aa555f3f435a5944da7504ea3a065d7029ad60e7c3d1ae09c5464", size = 35203952, upload-time = "2026-02-23T00:19:42.259Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a5/9afd17de24f657fdfe4df9a3f1ea049b39aef7c06000c13db1530d81ccca/scipy-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:beeda3d4ae615106d7094f7e7cef6218392e4465cc95d25f900bebabfded0950", size = 34979063, upload-time = "2026-02-23T00:19:47.547Z" }, + { url = "https://files.pythonhosted.org/packages/8b/13/88b1d2384b424bf7c924f2038c1c409f8d88bb2a8d49d097861dd64a57b2/scipy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6609bc224e9568f65064cfa72edc0f24ee6655b47575954ec6339534b2798369", size = 37598449, upload-time = "2026-02-23T00:19:53.238Z" }, + { url = "https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:37425bc9175607b0268f493d79a292c39f9d001a357bebb6b88fdfaff13f6448", size = 36510943, upload-time = "2026-02-23T00:20:50.89Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fd/3be73c564e2a01e690e19cc618811540ba5354c67c8680dce3281123fb79/scipy-1.17.1-cp313-cp313-win_arm64.whl", hash = "sha256:5cf36e801231b6a2059bf354720274b7558746f3b1a4efb43fcf557ccd484a87", size = 24545621, upload-time = "2026-02-23T00:20:55.871Z" }, + { url = "https://files.pythonhosted.org/packages/6f/6b/17787db8b8114933a66f9dcc479a8272e4b4da75fe03b0c282f7b0ade8cd/scipy-1.17.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:d59c30000a16d8edc7e64152e30220bfbd724c9bbb08368c054e24c651314f0a", size = 31936708, upload-time = "2026-02-23T00:19:58.694Z" }, + { url = "https://files.pythonhosted.org/packages/38/2e/524405c2b6392765ab1e2b722a41d5da33dc5c7b7278184a8ad29b6cb206/scipy-1.17.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:010f4333c96c9bb1a4516269e33cb5917b08ef2166d5556ca2fd9f082a9e6ea0", size = 28570135, upload-time = "2026-02-23T00:20:03.934Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c3/5bd7199f4ea8556c0c8e39f04ccb014ac37d1468e6cfa6a95c6b3562b76e/scipy-1.17.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2ceb2d3e01c5f1d83c4189737a42d9cb2fc38a6eeed225e7515eef71ad301dce", size = 20741977, upload-time = "2026-02-23T00:20:07.935Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b8/8ccd9b766ad14c78386599708eb745f6b44f08400a5fd0ade7cf89b6fc93/scipy-1.17.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:844e165636711ef41f80b4103ed234181646b98a53c8f05da12ca5ca289134f6", size = 23029601, upload-time = "2026-02-23T00:20:12.161Z" }, + { url = "https://files.pythonhosted.org/packages/6d/a0/3cb6f4d2fb3e17428ad2880333cac878909ad1a89f678527b5328b93c1d4/scipy-1.17.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:158dd96d2207e21c966063e1635b1063cd7787b627b6f07305315dd73d9c679e", size = 33019667, upload-time = "2026-02-23T00:20:17.208Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c3/2d834a5ac7bf3a0c806ad1508efc02dda3c8c61472a56132d7894c312dea/scipy-1.17.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74cbb80d93260fe2ffa334efa24cb8f2f0f622a9b9febf8b483c0b865bfb3475", size = 35264159, upload-time = "2026-02-23T00:20:23.087Z" }, + { url = "https://files.pythonhosted.org/packages/4d/77/d3ed4becfdbd217c52062fafe35a72388d1bd82c2d0ba5ca19d6fcc93e11/scipy-1.17.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dbc12c9f3d185f5c737d801da555fb74b3dcfa1a50b66a1a93e09190f41fab50", size = 35102771, upload-time = "2026-02-23T00:20:28.636Z" }, + { url = "https://files.pythonhosted.org/packages/bd/12/d19da97efde68ca1ee5538bb261d5d2c062f0c055575128f11a2730e3ac1/scipy-1.17.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94055a11dfebe37c656e70317e1996dc197e1a15bbcc351bcdd4610e128fe1ca", size = 37665910, upload-time = "2026-02-23T00:20:34.743Z" }, + { url = "https://files.pythonhosted.org/packages/06/1c/1172a88d507a4baaf72c5a09bb6c018fe2ae0ab622e5830b703a46cc9e44/scipy-1.17.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e30bdeaa5deed6bc27b4cc490823cd0347d7dae09119b8803ae576ea0ce52e4c", size = 36562980, upload-time = "2026-02-23T00:20:40.575Z" }, + { url = "https://files.pythonhosted.org/packages/70/b0/eb757336e5a76dfa7911f63252e3b7d1de00935d7705cf772db5b45ec238/scipy-1.17.1-cp313-cp313t-win_arm64.whl", hash = "sha256:a720477885a9d2411f94a93d16f9d89bad0f28ca23c3f8daa521e2dcc3f44d49", size = 24856543, upload-time = "2026-02-23T00:20:45.313Z" }, + { url = "https://files.pythonhosted.org/packages/cf/83/333afb452af6f0fd70414dc04f898647ee1423979ce02efa75c3b0f2c28e/scipy-1.17.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:a48a72c77a310327f6a3a920092fa2b8fd03d7deaa60f093038f22d98e096717", size = 31584510, upload-time = "2026-02-23T00:21:01.015Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a6/d05a85fd51daeb2e4ea71d102f15b34fedca8e931af02594193ae4fd25f7/scipy-1.17.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:45abad819184f07240d8a696117a7aacd39787af9e0b719d00285549ed19a1e9", size = 28170131, upload-time = "2026-02-23T00:21:05.888Z" }, + { url = "https://files.pythonhosted.org/packages/db/7b/8624a203326675d7746a254083a187398090a179335b2e4a20e2ddc46e83/scipy-1.17.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3fd1fcdab3ea951b610dc4cef356d416d5802991e7e32b5254828d342f7b7e0b", size = 20342032, upload-time = "2026-02-23T00:21:09.904Z" }, + { url = "https://files.pythonhosted.org/packages/c9/35/2c342897c00775d688d8ff3987aced3426858fd89d5a0e26e020b660b301/scipy-1.17.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7bdf2da170b67fdf10bca777614b1c7d96ae3ca5794fd9587dce41eb2966e866", size = 22678766, upload-time = "2026-02-23T00:21:14.313Z" }, + { url = "https://files.pythonhosted.org/packages/ef/f2/7cdb8eb308a1a6ae1e19f945913c82c23c0c442a462a46480ce487fdc0ac/scipy-1.17.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:adb2642e060a6549c343603a3851ba76ef0b74cc8c079a9a58121c7ec9fe2350", size = 32957007, upload-time = "2026-02-23T00:21:19.663Z" }, + { url = "https://files.pythonhosted.org/packages/0b/2e/7eea398450457ecb54e18e9d10110993fa65561c4f3add5e8eccd2b9cd41/scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eee2cfda04c00a857206a4330f0c5e3e56535494e30ca445eb19ec624ae75118", size = 35221333, upload-time = "2026-02-23T00:21:25.278Z" }, + { url = "https://files.pythonhosted.org/packages/d9/77/5b8509d03b77f093a0d52e606d3c4f79e8b06d1d38c441dacb1e26cacf46/scipy-1.17.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d2650c1fb97e184d12d8ba010493ee7b322864f7d3d00d3f9bb97d9c21de4068", size = 35042066, upload-time = "2026-02-23T00:21:31.358Z" }, + { url = "https://files.pythonhosted.org/packages/f9/df/18f80fb99df40b4070328d5ae5c596f2f00fffb50167e31439e932f29e7d/scipy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:08b900519463543aa604a06bec02461558a6e1cef8fdbb8098f77a48a83c8118", size = 37612763, upload-time = "2026-02-23T00:21:37.247Z" }, + { url = "https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:3877ac408e14da24a6196de0ddcace62092bfc12a83823e92e49e40747e52c19", size = 37290984, upload-time = "2026-02-23T00:22:35.023Z" }, + { url = "https://files.pythonhosted.org/packages/7c/56/fe201e3b0f93d1a8bcf75d3379affd228a63d7e2d80ab45467a74b494947/scipy-1.17.1-cp314-cp314-win_arm64.whl", hash = "sha256:f8885db0bc2bffa59d5c1b72fad7a6a92d3e80e7257f967dd81abb553a90d293", size = 25192877, upload-time = "2026-02-23T00:22:39.798Z" }, + { url = "https://files.pythonhosted.org/packages/96/ad/f8c414e121f82e02d76f310f16db9899c4fcde36710329502a6b2a3c0392/scipy-1.17.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:1cc682cea2ae55524432f3cdff9e9a3be743d52a7443d0cba9017c23c87ae2f6", size = 31949750, upload-time = "2026-02-23T00:21:42.289Z" }, + { url = "https://files.pythonhosted.org/packages/7c/b0/c741e8865d61b67c81e255f4f0a832846c064e426636cd7de84e74d209be/scipy-1.17.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:2040ad4d1795a0ae89bfc7e8429677f365d45aa9fd5e4587cf1ea737f927b4a1", size = 28585858, upload-time = "2026-02-23T00:21:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/ed/1b/3985219c6177866628fa7c2595bfd23f193ceebbe472c98a08824b9466ff/scipy-1.17.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:131f5aaea57602008f9822e2115029b55d4b5f7c070287699fe45c661d051e39", size = 20757723, upload-time = "2026-02-23T00:21:52.039Z" }, + { url = "https://files.pythonhosted.org/packages/c0/19/2a04aa25050d656d6f7b9e7b685cc83d6957fb101665bfd9369ca6534563/scipy-1.17.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9cdc1a2fcfd5c52cfb3045feb399f7b3ce822abdde3a193a6b9a60b3cb5854ca", size = 23043098, upload-time = "2026-02-23T00:21:56.185Z" }, + { url = "https://files.pythonhosted.org/packages/86/f1/3383beb9b5d0dbddd030335bf8a8b32d4317185efe495374f134d8be6cce/scipy-1.17.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e3dcd57ab780c741fde8dc68619de988b966db759a3c3152e8e9142c26295ad", size = 33030397, upload-time = "2026-02-23T00:22:01.404Z" }, + { url = "https://files.pythonhosted.org/packages/41/68/8f21e8a65a5a03f25a79165ec9d2b28c00e66dc80546cf5eb803aeeff35b/scipy-1.17.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9956e4d4f4a301ebf6cde39850333a6b6110799d470dbbb1e25326ac447f52a", size = 35281163, upload-time = "2026-02-23T00:22:07.024Z" }, + { url = "https://files.pythonhosted.org/packages/84/8d/c8a5e19479554007a5632ed7529e665c315ae7492b4f946b0deb39870e39/scipy-1.17.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a4328d245944d09fd639771de275701ccadf5f781ba0ff092ad141e017eccda4", size = 35116291, upload-time = "2026-02-23T00:22:12.585Z" }, + { url = "https://files.pythonhosted.org/packages/52/52/e57eceff0e342a1f50e274264ed47497b59e6a4e3118808ee58ddda7b74a/scipy-1.17.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a77cbd07b940d326d39a1d1b37817e2ee4d79cb30e7338f3d0cddffae70fcaa2", size = 37682317, upload-time = "2026-02-23T00:22:18.513Z" }, + { url = "https://files.pythonhosted.org/packages/11/2f/b29eafe4a3fbc3d6de9662b36e028d5f039e72d345e05c250e121a230dd4/scipy-1.17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:eb092099205ef62cd1782b006658db09e2fed75bffcae7cc0d44052d8aa0f484", size = 37345327, upload-time = "2026-02-23T00:22:24.442Z" }, + { url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165, upload-time = "2026-02-23T00:22:29.563Z" }, +] + +[[package]] +name = "setuptools" +version = "82.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4f/db/cfac1baf10650ab4d1c111714410d2fbb77ac5a616db26775db562c8fab2/setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9", size = 1152316, upload-time = "2026-03-09T12:47:17.221Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb", size = 1006223, upload-time = "2026-03-09T12:47:15.026Z" }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + +[[package]] +name = "singleton-type" +version = "0.0.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cb/9d/ad466bb2d879e951b2500639700e906c4d8d5e28ac39c1b0616a2087447a/singleton-type-0.0.5.tar.gz", hash = "sha256:c2c7a052f3fb151db5fc7aff547b754f8c4649c6d3ac86a49560d54466d935cb", size = 6966, upload-time = "2022-06-19T16:43:20.176Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/1d/f98e31e80e4322d3921a8e26d900733fda6a9db4b44564678693cb491994/singleton_type-0.0.5-py3-none-any.whl", hash = "sha256:f63617e856afdf23a2c67ac050454508b3d00739ecdbb69803627e5c2c8cc02c", size = 7715, upload-time = "2022-06-19T16:43:18.067Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "smart-open" +version = "7.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c5/65/3ada667d32675399001bf022ad3d9f3989b57101351ebc71d6fbe2384634/smart_open-7.6.1.tar.gz", hash = "sha256:4347996e7ba21db7cd1e059632e0b30395407e4f6c660d2ddffc8f2a9ae5f990", size = 54754, upload-time = "2026-05-09T06:23:37.06Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/78/0f68b93564b8c6b6987a0696c582ba2591a381ab2f733a501909e949f241/smart_open-7.6.1-py3-none-any.whl", hash = "sha256:b4de6aebef023aca91cc9fb372052e1343ba3f152de215bd22391a663e3ddd21", size = 64845, upload-time = "2026-05-09T06:23:35.386Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "spacy" +version = "3.8.14" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "catalogue" }, + { name = "confection" }, + { name = "cymem" }, + { name = "jinja2" }, + { name = "murmurhash" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "preshed" }, + { name = "pydantic" }, + { name = "requests" }, + { name = "setuptools" }, + { name = "spacy-legacy" }, + { name = "spacy-loggers" }, + { name = "srsly" }, + { name = "thinc" }, + { name = "tqdm" }, + { name = "typer" }, + { name = "wasabi" }, + { name = "weasel" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/78/e4f2ae19a791cae756cd0e801204953eaec4e9ab75a60ad39f671dbb8d5a/spacy-3.8.14-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:726f02c60a2c6b0029167370d22d51731172a053d29c7e2ea6190db6de3ab483", size = 6218335, upload-time = "2026-03-29T10:40:46.298Z" }, + { url = "https://files.pythonhosted.org/packages/06/df/178bbab47fa209c8baf2f1e609cbddc6b18a985200be1ceee22bd5b89beb/spacy-3.8.14-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e3ebe50b93f2d40e8ec3451255528bb622ccb12be39fd140bb87668ce8d1075b", size = 6033860, upload-time = "2026-03-29T10:40:47.861Z" }, + { url = "https://files.pythonhosted.org/packages/ff/e8/048d83b73b28686307bd9a60878a58de7b7b21b562ca4de8b5bd558031e9/spacy-3.8.14-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:daeb64b048f12c059997281aed53eb8776d26416dd313cf17ad6f63124b2b564", size = 32725099, upload-time = "2026-03-29T10:40:50.194Z" }, + { url = "https://files.pythonhosted.org/packages/8e/3f/1799af5f4ccc8eb7500e4a20ca301488134429dba08cda5be68ce6ab2992/spacy-3.8.14-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6d45715a24446f23b98ec3f09409a1d4111983d1d64613250ee38c3270e21853", size = 33205838, upload-time = "2026-03-29T10:40:53.029Z" }, + { url = "https://files.pythonhosted.org/packages/78/07/81ab9acd0ec64bfdd7339acfc4cf35f5fb74bbbb0b2be7e64d717c416bac/spacy-3.8.14-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1069a8be34940809f8462eb69f09a3f0ce59bf8b9cb82475f2a8e3580f50ece0", size = 32090380, upload-time = "2026-03-29T10:40:56.115Z" }, + { url = "https://files.pythonhosted.org/packages/74/a5/b081b5bd3cedb2634c23eb470b5e24c65c894c57646567f47627291c2b3f/spacy-3.8.14-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2dfa77aec7fdebac0455d8afd4ce1d92d6f868b03d507ed1976179a63db7b374", size = 32991946, upload-time = "2026-03-29T10:40:58.852Z" }, + { url = "https://files.pythonhosted.org/packages/5f/55/4371413a6dfc1fa837282a365498165f828c2f3fe018dfb35336acc869e0/spacy-3.8.14-cp312-cp312-win_amd64.whl", hash = "sha256:9def18c76a4472b326cb91a195623c9ca38a2b86999ad2df9e00b49ba8c63734", size = 14226946, upload-time = "2026-03-29T10:41:01.63Z" }, + { url = "https://files.pythonhosted.org/packages/f3/5e/12ac876017da6c1e6b72afcc3c8b309996227fd3aa15382cd3311aee21b8/spacy-3.8.14-cp312-cp312-win_arm64.whl", hash = "sha256:d6257133357e4801c9c5d011925af5439b0a015aacf3c16528aa0009982431c7", size = 13628765, upload-time = "2026-03-29T10:41:03.806Z" }, + { url = "https://files.pythonhosted.org/packages/1b/e5/822bbdfa459fee863ef2e9879a34b0ae5db7cd1e3eb76d32c766f19222e9/spacy-3.8.14-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2b4f60fa8b9641a5e93e7a96db0cdd106d05d61756bf1d0ddcd1705ad347909a", size = 6202114, upload-time = "2026-03-29T10:41:06.119Z" }, + { url = "https://files.pythonhosted.org/packages/7e/de/0e512154113e1f341567f2b9341835775e4180c180221e60faedaebb2f65/spacy-3.8.14-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0860c57220c633ccb20468bcd64bfb0d28908990c371a8857951d093a148dc8e", size = 6015458, upload-time = "2026-03-29T10:41:07.79Z" }, + { url = "https://files.pythonhosted.org/packages/0c/4f/29c7e56afc7db07348a9e0efe0243b5eef465d5dc3d56433f164378c3fa6/spacy-3.8.14-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c24620b7dba879c69cebc51ef3b1107d4d4e44a1e0d4baa439372887d00c3fd9", size = 32510659, upload-time = "2026-03-29T10:41:09.88Z" }, + { url = "https://files.pythonhosted.org/packages/1e/ce/cae678f664d5467016819253f5d6e52f8e68a12d8e799b651d73ec2a9a4b/spacy-3.8.14-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9699c1248d115d5825987c287a6f6acd66386ef3ebee7994ee67ba093e932c59", size = 32841057, upload-time = "2026-03-29T10:41:12.585Z" }, + { url = "https://files.pythonhosted.org/packages/04/d4/419868afd449bdd367df005932537eea66c71e97c899ba278f3124933f3c/spacy-3.8.14-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:042d799e342fdb6bb5b02a4213a95acc9116c40ed3c849bb0a8296fbe648ec22", size = 31763252, upload-time = "2026-03-29T10:41:15.569Z" }, + { url = "https://files.pythonhosted.org/packages/ec/53/df5c1fee45f200b749ba72eeb536fbb2c545fc56230324954263b2f3be00/spacy-3.8.14-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:69b2264294097336e86832e8663f1ab3a7215621184863c96c082ab17ee11937", size = 32717872, upload-time = "2026-03-29T10:41:18.193Z" }, + { url = "https://files.pythonhosted.org/packages/12/c2/f1882ec2f5cc9c4e73cf2132997a03c397d7ceeb5ee7f7bb878b51a16365/spacy-3.8.14-cp313-cp313-win_amd64.whl", hash = "sha256:4b6d4f20e291a7c70e37de2f246622b44a0ce82efaa710c9801c6bd599e75177", size = 14220335, upload-time = "2026-03-29T10:41:20.89Z" }, +] + +[[package]] +name = "spacy-legacy" +version = "3.0.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d9/79/91f9d7cc8db5642acad830dcc4b49ba65a7790152832c4eceb305e46d681/spacy-legacy-3.0.12.tar.gz", hash = "sha256:b37d6e0c9b6e1d7ca1cf5bc7152ab64a4c4671f59c85adaf7a3fcb870357a774", size = 23806, upload-time = "2023-01-23T09:04:15.104Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/55/12e842c70ff8828e34e543a2c7176dac4da006ca6901c9e8b43efab8bc6b/spacy_legacy-3.0.12-py2.py3-none-any.whl", hash = "sha256:476e3bd0d05f8c339ed60f40986c07387c0a71479245d6d0f4298dbd52cda55f", size = 29971, upload-time = "2023-01-23T09:04:13.45Z" }, +] + +[[package]] +name = "spacy-loggers" +version = "1.0.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/67/3d/926db774c9c98acf66cb4ed7faf6c377746f3e00b84b700d0868b95d0712/spacy-loggers-1.0.5.tar.gz", hash = "sha256:d60b0bdbf915a60e516cc2e653baeff946f0cfc461b452d11a4d5458c6fe5f24", size = 20811, upload-time = "2023-09-11T12:26:52.323Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/78/d1a1a026ef3af911159398c939b1509d5c36fe524c7b644f34a5146c4e16/spacy_loggers-1.0.5-py3-none-any.whl", hash = "sha256:196284c9c446cc0cdb944005384270d775fdeaf4f494d8e269466cfa497ef645", size = 22343, upload-time = "2023-09-11T12:26:50.586Z" }, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.50" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/57/da/6fbf010c8ebb347679d0d100b22fe9ba5e13fd04046c5df7280d2f0bf706/sqlalchemy-2.0.50.tar.gz", hash = "sha256:af5607d11ef90fd6a5c0549fe0045dce1663d427426bcfb506dcb5346a85a3b9", size = 9907424, upload-time = "2026-05-24T19:20:04.018Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/b0/a9d19b43f38f878b1278bca5b00b909f7540d41494396dd2561f9ad0956d/sqlalchemy-2.0.50-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23ae23d8b9d344d30d0a92f06d45825024a5790f1c1dd4cf452636a50d3e58cb", size = 2159807, upload-time = "2026-05-24T19:27:53.086Z" }, + { url = "https://files.pythonhosted.org/packages/f5/2c/191dd58a248fd2cfd4780fa82c375c505e4ad98c8b522fa69ec492130d77/sqlalchemy-2.0.50-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:47b71b933e7b4ebad407c8fdfd70d2c4f08b78b3238bb30eebdd6eb32ca51b89", size = 3343358, upload-time = "2026-05-24T20:09:29.279Z" }, + { url = "https://files.pythonhosted.org/packages/8a/2b/514fce8a7df81cf5bad7ff7865de7ac0c5776a38cc043475c4703eb7fe8b/sqlalchemy-2.0.50-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:110fdac56ace278949f00de805edacbd6141e382d992f9ba28238b3a0827a600", size = 3357994, upload-time = "2026-05-24T20:17:13.495Z" }, + { url = "https://files.pythonhosted.org/packages/35/a6/a0e283f5494f92b0d77e319ff77e437b1ffe4a051ba67c81d53234825475/sqlalchemy-2.0.50-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0f5e4ac70e9e757f6b3e87c0491ff034442ecd8dfd36d041a50564c322dafc0e", size = 3289399, upload-time = "2026-05-24T20:09:32.239Z" }, + { url = "https://files.pythonhosted.org/packages/b7/96/1b07325ba71752d6a028b77d07bed1483ad545f794e8b1dc89b3ba3b3c68/sqlalchemy-2.0.50-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:724f3dcbe53dd0151e3cb5e7ec4ba4c620bede579caacd16275dc35ce06e8615", size = 3321216, upload-time = "2026-05-24T20:17:15.581Z" }, + { url = "https://files.pythonhosted.org/packages/ed/8e/bad6ed253e8a99edfc99af02f7173ec48a1d3ed1b9b35a1b8bc1700900cc/sqlalchemy-2.0.50-cp312-cp312-win32.whl", hash = "sha256:1208050441471d003b7c8cb4054fb084f185cf35ac3f0ea270803865bca9939a", size = 2119194, upload-time = "2026-05-24T19:50:04.943Z" }, + { url = "https://files.pythonhosted.org/packages/b6/2d/314a6690dda4b9cfc571eab1a63cf6fe6e1470aa3759ccda6aa016ee0f5a/sqlalchemy-2.0.50-cp312-cp312-win_amd64.whl", hash = "sha256:9d1af51558029a156a70986b7df88f042b3d158d7c8d8fb5072912d4b32d89c7", size = 2146186, upload-time = "2026-05-24T19:50:06.74Z" }, + { url = "https://files.pythonhosted.org/packages/0b/c4/c42356b527296e9862f67990efce31ef78b4cf69cd3f80873a528a060320/sqlalchemy-2.0.50-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:06a9210bdc5f4298cff0781087e2ff45683922252dacc452846373a58761f093", size = 2156697, upload-time = "2026-05-24T19:27:54.764Z" }, + { url = "https://files.pythonhosted.org/packages/60/a1/b1a70e3c4365ac7fe9e347f3710f19b562c866fb96d45e3c891588789a7b/sqlalchemy-2.0.50-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b53784972ade4f8174b9aa661f31a06f8a936d2cfdd602913ff3c6dd40ae873", size = 3284260, upload-time = "2026-05-24T20:09:34.195Z" }, + { url = "https://files.pythonhosted.org/packages/3f/4a/f3ac3caa19f263d57b0a47f8c91bbf56583dc2d3fc63acfbf644abb24fe0/sqlalchemy-2.0.50-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:31648fa14460537e768a7303b078e4344d208e0d23e06867c1f376a227ed82db", size = 3302280, upload-time = "2026-05-24T20:17:17.825Z" }, + { url = "https://files.pythonhosted.org/packages/66/55/ccada3e3d62254587819749a0bc69f41173eb48a6e385d10e66d32a9c88e/sqlalchemy-2.0.50-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:03f4323c980ad0e918cc9e5369b015f759f4e534db5bbaf4dc36832c10d05064", size = 3231580, upload-time = "2026-05-24T20:09:36.406Z" }, + { url = "https://files.pythonhosted.org/packages/05/f6/6809349130a2de0e109e7f00fd7d431da9565b9b2868b32ee684754f672b/sqlalchemy-2.0.50-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2b9dcc43afef8ac157cd92fce96985d6b8b0cfbd3df4d666f66b4d55a75d202f", size = 3269375, upload-time = "2026-05-24T20:17:20.34Z" }, + { url = "https://files.pythonhosted.org/packages/48/84/278a811ef4e07be9c89dc5cdd7be833268509a66a68c4897cf585e67428f/sqlalchemy-2.0.50-cp313-cp313-win32.whl", hash = "sha256:60922d6599065ddca2c6f376b9aa2f41a6b85a271725e0909490bbc50b1998a5", size = 2117229, upload-time = "2026-05-24T19:50:08.215Z" }, + { url = "https://files.pythonhosted.org/packages/f6/1c/067cc6187ed32d2ec222fe6d2643acc1659a6d0659f8a7cbc5ad3ae83280/sqlalchemy-2.0.50-cp313-cp313-win_amd64.whl", hash = "sha256:287086e67275a212c4582d166a6fb03a65ccc5551d80866270ce0dd9f34eccd3", size = 2143126, upload-time = "2026-05-24T19:50:09.691Z" }, + { url = "https://files.pythonhosted.org/packages/df/32/10ac51b4be7cdecd7e93d069251c86dfbf70b7adbd7c67b48ccea6c49e1c/sqlalchemy-2.0.50-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c966932507a4d7d0a37314927dbfcd89720e3f37d2a1e3352e7ae7939fa8e8a0", size = 2158519, upload-time = "2026-05-24T19:27:56.472Z" }, + { url = "https://files.pythonhosted.org/packages/5a/76/e703d2f7681d7d66c4c891af3f07c7ccf4c76ad7f18351de035b5eda007a/sqlalchemy-2.0.50-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:faffef4bcc20a1892e65e155293d99d60855bbbc79250ab712819cfd56a8e6bb", size = 3282063, upload-time = "2026-05-24T20:09:38.57Z" }, + { url = "https://files.pythonhosted.org/packages/31/26/ef168b184a25701f9995e8fb7e503fafd7a99c1c77cda1bc1a26ea2ed486/sqlalchemy-2.0.50-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c206aec519a2e7bd08abbfb33436e325fd22c632d9c21a9047e376ce241646e", size = 3287069, upload-time = "2026-05-24T20:17:21.942Z" }, + { url = "https://files.pythonhosted.org/packages/c2/15/765acc2bc693bccc43ca4a95d5b69750da8aaf6db1b5c616536e087f8920/sqlalchemy-2.0.50-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bef4ac756363227ef6402a75fee025a4bc690f92328e825868939b3b3a446a6d", size = 3230453, upload-time = "2026-05-24T20:09:40.398Z" }, + { url = "https://files.pythonhosted.org/packages/63/61/08e03c3adbf5db0087a0b6816746fec8f3032fb2f7fc899a9bb9b2a48ce4/sqlalchemy-2.0.50-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:96fbee6b19c19cd1556c8bf9419447cf2ec149ffcab7ab64348c23e54ef8547f", size = 3252413, upload-time = "2026-05-24T20:17:24.067Z" }, + { url = "https://files.pythonhosted.org/packages/03/0c/370a1f2db38436c615e10134c8a37de3688e74084792380695f3f5083860/sqlalchemy-2.0.50-cp314-cp314-win32.whl", hash = "sha256:8f00e3eb43ba30eb1b238ee03a8a62309486d1321eda3328bb611e0340033ad8", size = 2120063, upload-time = "2026-05-24T19:50:11.08Z" }, + { url = "https://files.pythonhosted.org/packages/7f/a0/fe92bb9817863bc13ba093bda931979a26cc2ca69f8e8f26d07add3d7c6f/sqlalchemy-2.0.50-cp314-cp314-win_amd64.whl", hash = "sha256:15708c613cd5005b7dffe1f66ee6a63ee8f5e46799f71c70ebad74178c676a39", size = 2145830, upload-time = "2026-05-24T19:50:12.452Z" }, + { url = "https://files.pythonhosted.org/packages/cc/ff/e5640a98a0b2f491eb8fde10fb6c773621a2e44340de231fafcc9370f4a9/sqlalchemy-2.0.50-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3699dac4be410e97049a1658e9480da9cde956594aa0f3aebc60b88f21c5ba70", size = 2178435, upload-time = "2026-05-24T19:42:58.889Z" }, + { url = "https://files.pythonhosted.org/packages/b7/85/337116e186f1236375b5fb70c21cfac98e8e8ab0d3a47be838dc47a59e08/sqlalchemy-2.0.50-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f96233858e3df43932ac11589e22520da6e8aeb624b03fedfeebb0e8ea213086", size = 3566059, upload-time = "2026-05-24T20:01:20.848Z" }, + { url = "https://files.pythonhosted.org/packages/96/34/bb0e190e161c3c2c24314a65add57218be14a4a9486886b7f5047c1ff7c8/sqlalchemy-2.0.50-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c4e70c46fad30c3bcc6a4708bc0130a3173e11a5b25f0ea4a9d8911b450f1f52", size = 3535366, upload-time = "2026-05-24T20:03:56.768Z" }, + { url = "https://files.pythonhosted.org/packages/df/5a/a7f759f97e4fd499c5d4e4488c760d5a7fbecf3028b465a04274fcd52384/sqlalchemy-2.0.50-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1918a3cf564d16d95bca7301005f41ab2ad50b07cd3b9da50d3ed986db148d6a", size = 3474879, upload-time = "2026-05-24T20:01:23.058Z" }, + { url = "https://files.pythonhosted.org/packages/9d/d9/2907ea38eb60687d297bf9c39e5ee58053c87b57fe8a9cae97090cecbf10/sqlalchemy-2.0.50-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b00098cdbdbd38c7be3d568b0c9c3122b8c0ec62b911b57cd5e6e0254d60a76d", size = 3486117, upload-time = "2026-05-24T20:03:59.052Z" }, + { url = "https://files.pythonhosted.org/packages/f2/e3/5aa06f167559f8c0bdae487e297d23ba548150ab016a3418265d617a4985/sqlalchemy-2.0.50-cp314-cp314t-win32.whl", hash = "sha256:1fbd55a969d7ac44a98e3dec75016074f809fa08f871585ace58dde110d1bf3e", size = 2150823, upload-time = "2026-05-24T20:08:58.644Z" }, + { url = "https://files.pythonhosted.org/packages/65/9b/112fb8f977582d7489d036e409e3723948bcf5320b3ac465f3c481bbe8f9/sqlalchemy-2.0.50-cp314-cp314t-win_amd64.whl", hash = "sha256:c5c3cdb753a9004183e1ccb634b41611654c989e61bc68617ce878e46d6f1e51", size = 2185794, upload-time = "2026-05-24T20:09:00.319Z" }, + { url = "https://files.pythonhosted.org/packages/d0/10/f7220e9b784d295d241c86ed99aeb537f92afcd469a64861f2717e9bb077/sqlalchemy-2.0.50-py3-none-any.whl", hash = "sha256:92064363517a3ff8212b5a93b8c62876579d8dfd1ca5b561335f30152d884fa9", size = 1943861, upload-time = "2026-05-24T19:59:01.119Z" }, +] + +[[package]] +name = "srsly" +version = "2.5.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "catalogue" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2b/db/f794f219a6c788b881252d2536a8c4a97d2bdaadc690391e1cb53d123d71/srsly-2.5.3.tar.gz", hash = "sha256:08f98dbecbff3a31466c4ae7c833131f59d3655a0ad8ac749e6e2c149e2b0680", size = 490881, upload-time = "2026-03-23T11:56:59.865Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/cc/e9f7fcec4cc92ad8bad6316c4241638b8cf7380382d4489d94ec6c436452/srsly-2.5.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:71e51c046ccbeefb86524c6b1e17574f579c6ac4dc8ea4a09437d3e8f88342d3", size = 658379, upload-time = "2026-03-23T11:55:59.85Z" }, + { url = "https://files.pythonhosted.org/packages/21/e4/fea4512e9785f58509b2cf67d993323848e583161b5fcfdc7dd9d7c1f3df/srsly-2.5.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f73c0db911552e94fe2016e1759d261d2f47926f68826664cada3723c87006a", size = 658513, upload-time = "2026-03-23T11:56:01.239Z" }, + { url = "https://files.pythonhosted.org/packages/20/b1/53591681b6ff2699a4f97b2d5552ba196eaa6a979b0873605f4c04b5f7ee/srsly-2.5.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c1ac27ae5f4bb9163c7d2c45fc8ec173aac3d92e32086d9472b326c5c6e570e", size = 1172265, upload-time = "2026-03-23T11:56:02.589Z" }, + { url = "https://files.pythonhosted.org/packages/4e/c9/741e29f534919a944a16da4184924b1d3404c4bf60716ab2b91be771d1e3/srsly-2.5.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:99026bcd9cbd3211cc36517400b04ca0fc5d3e412b14daf84ee6e65f67d9a2d8", size = 1180873, upload-time = "2026-03-23T11:56:03.944Z" }, + { url = "https://files.pythonhosted.org/packages/89/57/5554f786eccf78b2750d6ac63be126e1b67badec2cb409dd611cf6f8c52b/srsly-2.5.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:07d682679e639eb46ff7e6da4a92714f4d5ffe351d088ee66f221e9b1f8865bb", size = 1120437, upload-time = "2026-03-23T11:56:05.283Z" }, + { url = "https://files.pythonhosted.org/packages/eb/95/9b4f73b1be3692f86d72ccc131c8e50f26f824d5c8830a59390bcc5b60ef/srsly-2.5.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8e0542d85d6b55cf2934050d6ffcb1cd76c768dcf9572e7467002cf087bb366d", size = 1137376, upload-time = "2026-03-23T11:56:06.613Z" }, + { url = "https://files.pythonhosted.org/packages/5a/de/89ca640ca1953c4612279ce515d0af35658df3c06cdb324329bc91b4a7e1/srsly-2.5.3-cp312-cp312-win_amd64.whl", hash = "sha256:598f1e494c18cacb978299d77125415a586417081959f8ec3f068b32d97f8933", size = 652459, upload-time = "2026-03-23T11:56:07.994Z" }, + { url = "https://files.pythonhosted.org/packages/6d/4f/7ab6d49e36d9cc72ee15746cabd116eb6f338be8a06c1882968ee9d6c7d7/srsly-2.5.3-cp312-cp312-win_arm64.whl", hash = "sha256:4b1b721cd3ad1a9b2343519aadc786a4d09d5c0666962d49852eb12d6ec3fe26", size = 638411, upload-time = "2026-03-23T11:56:09.31Z" }, + { url = "https://files.pythonhosted.org/packages/9d/5c/12901e3794f4158abc6da750725aad6c2afddb1e4227b300fe7c71f66957/srsly-2.5.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e67b6bbacbfadea5e100266d2797f2d4cec9883ea4dc84a5537673850036a8d8", size = 656750, upload-time = "2026-03-23T11:56:10.708Z" }, + { url = "https://files.pythonhosted.org/packages/04/61/181c26370995f96f56f1b64b801e3ca1e0d703fc36506ae28606d62369fb/srsly-2.5.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:348c231b4477d8fe86603131d0f166d2feac9c372704dfc4398be71cc5b6fb07", size = 656746, upload-time = "2026-03-23T11:56:12.28Z" }, + { url = "https://files.pythonhosted.org/packages/77/c6/35876c78889f8ffe11ed3521644e666c3aef20ea31527b70f47456cf35c2/srsly-2.5.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b0938c2978c91ae1ef9c1f2ba35abb86330e198fb23469e356eba311e02233ee", size = 1155762, upload-time = "2026-03-23T11:56:14.075Z" }, + { url = "https://files.pythonhosted.org/packages/3e/da/40b71ca9906c8eb8f8feb6ac11d33dad458c85a56e1de764b96d402168a0/srsly-2.5.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5f6a837954429ecbe6dcdd27390d2fb4c7d01a3f99c9ffcf9ce66b2a6dd1b738", size = 1161092, upload-time = "2026-03-23T11:56:15.778Z" }, + { url = "https://files.pythonhosted.org/packages/dc/14/c0dd30cc8b93ce8137ff4766f743c882440ce49195fffc5d50eaeef311a6/srsly-2.5.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3576c125c486ce2958c2047e8858fe3cfc9ea877adfa05203b0986f9badee355", size = 1109984, upload-time = "2026-03-23T11:56:17.056Z" }, + { url = "https://files.pythonhosted.org/packages/08/f3/34354f183d8faafc631585571224b54d1b4b67e796972c36519c074ca355/srsly-2.5.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5fb59c42922e095d1ea36085c55bc16e2adb06a7bfe57b24d381e0194ae699f2", size = 1128409, upload-time = "2026-03-23T11:56:18.761Z" }, + { url = "https://files.pythonhosted.org/packages/a4/d9/5531f8a19492060b4e76e4ab06aca6f096fb5128fe18cc813d1772daf653/srsly-2.5.3-cp313-cp313-win_amd64.whl", hash = "sha256:111805927f05f5db440aeeacb85ce43da0b19ce7b2a09567a9ef8d30f3cc4d83", size = 650820, upload-time = "2026-03-23T11:56:20.096Z" }, + { url = "https://files.pythonhosted.org/packages/8e/8a/62fb7a971eca29e12f03fb9ddacb058548c14d33e5b5675ff0f85839cc7b/srsly-2.5.3-cp313-cp313-win_arm64.whl", hash = "sha256:0f106b0a700ab56e4a7c431b0f1444009ab6cb332edc7bbf6811c2a43f4722cb", size = 637278, upload-time = "2026-03-23T11:56:21.439Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5b/e4ef43c2a381711230af98d4c94a5323df48d6a7899ee652e05bf889290e/srsly-2.5.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:39c13d552a9f9674a12cdcdc66b0c2f02f3430d0cd04c5f9cf598824c2bd3d65", size = 661294, upload-time = "2026-03-23T11:56:23.29Z" }, + { url = "https://files.pythonhosted.org/packages/92/2d/ebce7f3717e52cd0a01f4ec570f388f3b7098526794fcf1ad734e0b8f852/srsly-2.5.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:14c930767cc169611a2dc14e23bc7638cfb616d6f79029700ade033607343540", size = 660952, upload-time = "2026-03-23T11:56:24.908Z" }, + { url = "https://files.pythonhosted.org/packages/22/47/a8f3e9b214be2624c8e8a78d38ca7b1d4e26b92d57018412e4bfc4abe89a/srsly-2.5.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2f2d464f0d0237e32fb53f0ec6f05418652c550e772b50e9918e83a1577cba4d", size = 1154554, upload-time = "2026-03-23T11:56:26.608Z" }, + { url = "https://files.pythonhosted.org/packages/d6/71/2a89dc3180a51e633a87a079ca064225f4aaf46c7b2a5fc720e28f261d98/srsly-2.5.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d18933248a5bb0ad56a1bae6003a9a7f37daac2ecb0c5bcbfaaf081b317e1c84", size = 1155746, upload-time = "2026-03-23T11:56:28.102Z" }, + { url = "https://files.pythonhosted.org/packages/b8/36/72e5ce3153927ca404b6f5bf5280e6ff3399c11557df472b153945468e0a/srsly-2.5.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7ea5412ea229e571ac9738cbe14f845cc06c8e4e956afb5f42061ccd087ef31f", size = 1112374, upload-time = "2026-03-23T11:56:29.591Z" }, + { url = "https://files.pythonhosted.org/packages/04/b2/0895de109c28eca0d41a811ab7c076d4e4a505e8466f06bae22f5180a1dd/srsly-2.5.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8d3988970b4cf7d03bdd5b5169302ff84562dd2e1e0f84aeb34df3e5b5dc19bf", size = 1127732, upload-time = "2026-03-23T11:56:31.458Z" }, + { url = "https://files.pythonhosted.org/packages/c7/79/a37fa7759797fbdfe0a2e029ab13e78b1e81e191220d2bb8ff57d869aefb/srsly-2.5.3-cp314-cp314-win_amd64.whl", hash = "sha256:6a02d7dcc16126c8fae1c1c09b2072798a1dc482ab5f9c52b12c7114dac47325", size = 656467, upload-time = "2026-03-23T11:56:33.14Z" }, + { url = "https://files.pythonhosted.org/packages/d7/25/0dae019b3b90ad9037f91de4c390555cdaac9460a93ad62b02b03babdff5/srsly-2.5.3-cp314-cp314-win_arm64.whl", hash = "sha256:1c9129c4abe31903ff7996904a51afdd5428060de6c3d12af49a4da5e8df2821", size = 643040, upload-time = "2026-03-23T11:56:34.448Z" }, + { url = "https://files.pythonhosted.org/packages/3a/44/72dd5285b2e05435d98b0797f101d91d9b345d491ddc1fdb9bd09e27ccb8/srsly-2.5.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:29d5d01ba4c2e9c01f936e5e6d5babc4a47b38c9cbd6e1ec23f6d5a49df32605", size = 666200, upload-time = "2026-03-23T11:56:35.753Z" }, + { url = "https://files.pythonhosted.org/packages/d2/ad/002c71b87fc3f648c9bf0ec47de0c3822bf2c95c8896a589dd03e7fd3977/srsly-2.5.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5c8df4039426d99f0148b5743542842ab96b82daded0b342555e15a639927757", size = 667409, upload-time = "2026-03-23T11:56:37.172Z" }, + { url = "https://files.pythonhosted.org/packages/2a/35/2cea3d5e80aeecfc4ece9e7e1783e7792cc3bad7ab85ab585882e1db4e38/srsly-2.5.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:06a43d63bde2e8cccadb953d7fff70b18196ca286b65dd2ad16006d65f3f8166", size = 1265941, upload-time = "2026-03-23T11:56:38.825Z" }, + { url = "https://files.pythonhosted.org/packages/aa/38/8a4d7e86dd0370a2e5af251b646000197bb5b7e0f9aa360c71bbfb253d0d/srsly-2.5.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:808cfafc047f0dec507a34c8fa8e4cda5722737fd33577df73452f52f7aca644", size = 1250693, upload-time = "2026-03-23T11:56:40.449Z" }, + { url = "https://files.pythonhosted.org/packages/99/05/340129de5ea7b237271b12f8a6962cfa7eb0c5a3056794626d348c5ae7c7/srsly-2.5.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:71d4cbe2b2a1335c76ed0acae2dc862163787d8b01a705e1949796907ed94ccd", size = 1242408, upload-time = "2026-03-23T11:56:41.8Z" }, + { url = "https://files.pythonhosted.org/packages/01/cb/d7fee7ab27c6aa2e3f865fb7b50ba18c81a4c763bba12bdf53df246441bc/srsly-2.5.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:565f69083d33cb329cfc74317da937fb3270c0f40fabc1b4488702d8074b4a3e", size = 1242749, upload-time = "2026-03-23T11:56:43.246Z" }, + { url = "https://files.pythonhosted.org/packages/d8/d1/9bad3a0f2fa7b72f4e0cf1d267b00513092d20ef538c47f72823ae4f7656/srsly-2.5.3-cp314-cp314t-win_amd64.whl", hash = "sha256:8ac016ffaeac35bc010992b71bf8afdd39d458f201c8138d84cf78778a936e6c", size = 673783, upload-time = "2026-03-23T11:56:44.875Z" }, + { url = "https://files.pythonhosted.org/packages/2a/ae/57d1d7af907e20c077e113e0e4976f87b82c0a415403d99284a262229dd0/srsly-2.5.3-cp314-cp314t-win_arm64.whl", hash = "sha256:d822083fe26ec6728bd8c273ac121fc4ab3864a0fdf0cf0ff3efb188fcd209ed", size = 650229, upload-time = "2026-03-23T11:56:46.148Z" }, +] + +[[package]] +name = "sse-starlette" +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "starlette" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f7/2b/58abc2d1fd397e7dde08e947e05c884d8ef2f78d5e2588c17a12d42d6994/sse_starlette-3.4.4.tar.gz", hash = "sha256:07e0fa0460138baf25cdd5fb28683472c3995dc1642225191b3832d62526bcb0", size = 31819, upload-time = "2026-05-12T17:37:17.019Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/67/805710444ea8cc75fbf70b920ed431a560c4bf9c57f7d5a3117213189399/sse_starlette-3.4.4-py3-none-any.whl", hash = "sha256:3f4dd50d8aed2771a091f3a83000323fc3844541c16b4fe585ae2420cc6df973", size = 16514, upload-time = "2026-05-12T17:37:15.601Z" }, +] + +[[package]] +name = "starlette" +version = "1.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/25/44/ec35f1b6e83094b997da438a02c8c9b0ade2b1e84cfc48bd4656780760a6/starlette-1.2.1.tar.gz", hash = "sha256:9b9b5ebb992e67d6093741e63c2f59e4f6fff986f81163c087867bd7b924b3f6", size = 2701854, upload-time = "2026-05-31T01:07:51.847Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1c/54/196d0c1db10af76baa4f64894448505d60d3cdf70ef92cbb35f46a4e4c71/starlette-1.2.1-py3-none-any.whl", hash = "sha256:4de0082d08c8f6764a85a54cf1120d6939507a19905c7768acad2a9f875d2b89", size = 73350, upload-time = "2026-05-31T01:07:50.09Z" }, +] + +[[package]] +name = "tenacity" +version = "9.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/47/c6/ee486fd809e357697ee8a44d3d69222b344920433d3b6666ccd9b374630c/tenacity-9.1.4.tar.gz", hash = "sha256:adb31d4c263f2bd041081ab33b498309a57c77f9acf2db65aadf0898179cf93a", size = 49413, upload-time = "2026-02-07T10:45:33.841Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/c1/eb8f9debc45d3b7918a32ab756658a0904732f75e555402972246b0b8e71/tenacity-9.1.4-py3-none-any.whl", hash = "sha256:6095a360c919085f28c6527de529e76a06ad89b23659fa881ae0649b867a9d55", size = 28926, upload-time = "2026-02-07T10:45:32.24Z" }, +] + +[[package]] +name = "thinc" +version = "8.3.13" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "blis" }, + { name = "catalogue" }, + { name = "confection" }, + { name = "cymem" }, + { name = "murmurhash" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "preshed" }, + { name = "pydantic" }, + { name = "setuptools" }, + { name = "srsly" }, + { name = "wasabi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/13/46/76df95f2c327f9a9cef30c1523bf285627897097163584dcf5f77b2ebce2/thinc-8.3.13.tar.gz", hash = "sha256:68e658549fc1eb3ff92aed5147fcbb9c15d6e9cc0e623b4d0998d16522ffb4f9", size = 194640, upload-time = "2026-03-23T07:22:36.41Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/af/f7c1ebfe92eb5d27d7f2f3da67a11e2eb57bc30ab1553279af6dc65b65a8/thinc-8.3.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:77a41f66285321d20aaedaea1e87d7cd48dca6d2427bed1867ec7cba7109fc8d", size = 821097, upload-time = "2026-03-23T07:21:56.698Z" }, + { url = "https://files.pythonhosted.org/packages/45/8f/69d7338575d98df85d0b54c0f5fc277dba72587fe9ab846ecdd12a998bcb/thinc-8.3.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3710d318b4e5460cf366a6f7b5ddbefb5d39dbd4cfa408222750fdc6c27c4411", size = 791932, upload-time = "2026-03-23T07:21:58.38Z" }, + { url = "https://files.pythonhosted.org/packages/4b/a5/21d010c81e81e1589e5ccb4950e521804d13726e541e87f644c51815673b/thinc-8.3.13-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5a08c87143a6d20177652dca1ec0dc815d88216d8fc62594a57e8bc45bf5ed49", size = 3854219, upload-time = "2026-03-23T07:21:59.819Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ff/6914bf370bd1d604d89e6dfb46b97d10cd9b00d42ff8c036283e92314a8c/thinc-8.3.13-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4b5ec9ff313819e7d8667794a3559463fa89ff45aaa73e3fd8d6273b1e0d7a7f", size = 3903307, upload-time = "2026-03-23T07:22:01.652Z" }, + { url = "https://files.pythonhosted.org/packages/f3/3d/5572b47fa155fb3388c071515b74024fa17a6efd1df9406da378f0aa84ef/thinc-8.3.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5c9a48f2bc1e04f138240ed5f9b815a9141a5de26accd0f08fa0137fcefed258", size = 4836882, upload-time = "2026-03-23T07:22:03.565Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f0/a8d77c7bac089697c6df302cc3c936a1ab36a4720deae889e6f1dbcbd0eb/thinc-8.3.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:79a29a44d76bd02f5ac0624268c6e42b3576ae472c791a8ae9c2d813ae789b59", size = 5033398, upload-time = "2026-03-23T07:22:05.045Z" }, + { url = "https://files.pythonhosted.org/packages/21/82/5651bb1f904d04220fc7670035ada921bf0638e2cff6444d67c12887a968/thinc-8.3.13-cp312-cp312-win_amd64.whl", hash = "sha256:ed1dc709ac4f2f03b710457889e4e02f05de51bc8456980c241d0b28798bc7cb", size = 1721248, upload-time = "2026-03-23T07:22:06.749Z" }, + { url = "https://files.pythonhosted.org/packages/94/8d/683703de021ffbe46833d722b70f49ffbbca8e5bd6876256977555d92d7d/thinc-8.3.13-cp312-cp312-win_arm64.whl", hash = "sha256:c6a049703a6011c8fe26ee41af7e70272145594140d82f79bb23de619c6a6525", size = 1645777, upload-time = "2026-03-23T07:22:08.104Z" }, + { url = "https://files.pythonhosted.org/packages/af/b9/7b46942176df459d1804a9e77b0976f7c56f3abf3ec7485d0e5f836a0382/thinc-8.3.13-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c2811dfd8d46d8b5d3b39051b23e64006b2994a5143b1978b436938018792af8", size = 817337, upload-time = "2026-03-23T07:22:09.538Z" }, + { url = "https://files.pythonhosted.org/packages/a7/79/53085a72cd8f4fc4e6e313d05ea5aa98e870684f4a0fb318a9875fc0a964/thinc-8.3.13-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5593e6300cb1ebe0c0e546e9c9fb49e7c2627a0aa688795cd4f995a8b820d2ec", size = 788120, upload-time = "2026-03-23T07:22:11.215Z" }, + { url = "https://files.pythonhosted.org/packages/9e/3e/d61b462b16da95ac6885f95bb395e672040ee594833e571a6edcffd234f5/thinc-8.3.13-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f697174d3fb474966ce50b430bbafa101a6d2f7ffb559dac4b5c59389ef72d22", size = 3844666, upload-time = "2026-03-23T07:22:12.67Z" }, + { url = "https://files.pythonhosted.org/packages/78/4c/898cc654bb123734c71ec5a425c02ca34439517d01ce1c95a6563295580e/thinc-8.3.13-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e9c7c5c104737b414c8c4ec578e67d78b6c859afe25cbc0684402e721415bd7f", size = 3890658, upload-time = "2026-03-23T07:22:14.668Z" }, + { url = "https://files.pythonhosted.org/packages/cd/56/1abdbf0a4ad628e8a05d6516fe0745969649d805367a3dccad8ee872981b/thinc-8.3.13-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7a99d0e242d1ccd23f9ae6bea7cd502f8626efa65c156b91d84581d0356696c3", size = 4819933, upload-time = "2026-03-23T07:22:16.85Z" }, + { url = "https://files.pythonhosted.org/packages/f1/22/b84dbdc6be5055bbdb2a7352e2c393f67e8593c137f1b83c82bf1e062b6e/thinc-8.3.13-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e676edd21a747afbe3e6b9f3fca8b962e36d146ded03b070cb0c28e2dfbe9499", size = 5018099, upload-time = "2026-03-23T07:22:18.356Z" }, + { url = "https://files.pythonhosted.org/packages/0f/a8/763cd7ba949334c9d2cddc92dadb68b344cb9546dc01b8d4a733dcaa16c1/thinc-8.3.13-cp313-cp313-win_amd64.whl", hash = "sha256:8ad40307f20e83f77af28ff5c6be0b86af7a8b251d1231c545508d2763157d8f", size = 1720309, upload-time = "2026-03-23T07:22:19.81Z" }, + { url = "https://files.pythonhosted.org/packages/f5/15/a11f7bb3cbc97dfecf32a90552f5a8f8a5c99316a99c6c17bdabf5baf256/thinc-8.3.13-cp313-cp313-win_arm64.whl", hash = "sha256:723949cab11d1925c15447928513a718276316cec6e0de28337cca0a62be0521", size = 1644606, upload-time = "2026-03-23T07:22:21.339Z" }, + { url = "https://files.pythonhosted.org/packages/80/40/f4937d113912c6d669ffe982356ab29dcb6c7fe3be926a15981dbbb6a91c/thinc-8.3.13-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7badb0be4825535e6362c19e8a41872b65409e9da46d3453a391b843a0720865", size = 817024, upload-time = "2026-03-23T07:22:23.005Z" }, + { url = "https://files.pythonhosted.org/packages/d2/00/4d4ed1a11ba2920b85a03a0683b16d97dc5beb2e78078dbf0e13e43bcea7/thinc-8.3.13-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:565300b7e13de799e5abff00d445f537e9256cf7da4dcb0d0f005fc16748a29e", size = 792096, upload-time = "2026-03-23T07:22:24.349Z" }, + { url = "https://files.pythonhosted.org/packages/44/5d/dc33d6932be8721af2ef76b4a3a6e8020648630eabae61fb916d2a861d1d/thinc-8.3.13-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c17cef1900a1aba7e1487493d16b8aa0a8633116f1b2a51c6649a4000697f17b", size = 3842215, upload-time = "2026-03-23T07:22:25.836Z" }, + { url = "https://files.pythonhosted.org/packages/af/bc/a6d37d8dadc2c5b524f51192413481160c42c9dd6105e8d5551531623225/thinc-8.3.13-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f4f26d1eec9b2a6a8f2e0298a5515d13eb06d70730d0d9e1040bb329e12bf3fb", size = 3849253, upload-time = "2026-03-23T07:22:27.845Z" }, + { url = "https://files.pythonhosted.org/packages/7a/59/ce9c7067f1dfe5985875927de9cf7a79f9dae3e69487fd650dfba558029d/thinc-8.3.13-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a61a31fd0ce3c2771cf4901ba6df70e774ffe32febf1024c5b43d63575cd58fe", size = 4831163, upload-time = "2026-03-23T07:22:29.395Z" }, + { url = "https://files.pythonhosted.org/packages/4f/a8/f57819347fc4d8bef2204d15fcbb9d7dff2d6cdd5f83d5ed91456ddacc55/thinc-8.3.13-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ba8119daf84a12259ae4d251d36426417bafa0b34108890b4b7e2b50966bd990", size = 4986051, upload-time = "2026-03-23T07:22:30.933Z" }, + { url = "https://files.pythonhosted.org/packages/05/ef/a82214bb7c7c1e2d92b69e1a7654be90cfab180082c6108e45a98af2422c/thinc-8.3.13-cp314-cp314-win_amd64.whl", hash = "sha256:433e3826e018da489f1a8068e6de677f6eff3cc93991a599d90f12cd1bc26cdc", size = 1740382, upload-time = "2026-03-23T07:22:32.869Z" }, + { url = "https://files.pythonhosted.org/packages/9f/ef/1648fda54e9689058335ff54f650a7a314db2a42e21af1b83949b2dc748e/thinc-8.3.13-cp314-cp314-win_arm64.whl", hash = "sha256:11754fada9ad5ba2e02d5f3f234f940e24015b82333db58372f4a6aedad9b43f", size = 1667687, upload-time = "2026-03-23T07:22:34.967Z" }, +] + +[[package]] +name = "tiktoken" +version = "0.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "regex" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/e5/5f3cb2159769d0f4324c0e9e87f9de3c4b1cd45848a96b2eb3566ad5ca77/tiktoken-0.13.0.tar.gz", hash = "sha256:c9435714c3a84c2319499de9a300c0e604449dd0799ff246458b3bb6a7f433c1", size = 38986, upload-time = "2026-05-15T04:51:27.153Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/8e/144bde4e01df66b34bb865557c7cd754ed08b036217ebd79c9db5e9048a9/tiktoken-0.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:32ac870a806cfb260a02d0cb70426aef02e038297f8ad50df5040bb5af360791", size = 1034888, upload-time = "2026-05-15T04:50:31.579Z" }, + { url = "https://files.pythonhosted.org/packages/36/18/d4ac9d20956cdebca04841316660ed584c2fecdc2b81722a28bc7ad3b1e4/tiktoken-0.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4d9980f11429ed2d737c463bb1fb78cf330caa026adf002f714aced7849a687b", size = 982970, upload-time = "2026-05-15T04:50:32.961Z" }, + { url = "https://files.pythonhosted.org/packages/74/ed/6bb8d05b9f731f749fee5c6f5ca63e981143c826a5985877330507bd13b7/tiktoken-0.13.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:3f277ebea5edd7b8bf03c6f9431e1d67d517530115572b2dc1d465326e8f88c7", size = 1115741, upload-time = "2026-05-15T04:50:34.475Z" }, + { url = "https://files.pythonhosted.org/packages/34/de/2ca96b07a82d972b74fe4b46de055b79c904e45c7eab699354a0bfa697dc/tiktoken-0.13.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:a116178fa7e1b4065bff05214360373a65cac22f965be7b3f73d00a0dbfe7649", size = 1136523, upload-time = "2026-05-15T04:50:35.782Z" }, + { url = "https://files.pythonhosted.org/packages/ee/dc/9dafec002c2d4424378563cf4cf5c7fb93631d2a55013c8b87554ee4012c/tiktoken-0.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2c397ddda233208345b01bd30f2fca79ff730e55731d0108a603f9bc57f6af3b", size = 1181954, upload-time = "2026-05-15T04:50:36.99Z" }, + { url = "https://files.pythonhosted.org/packages/a1/d0/1f8578c45b2f24759b46f0b50d31878c63c73e6bf0f2227e10ec5c5408dc/tiktoken-0.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:95097e4f89b06403976e498abf61a0ee73a7497e73fb599cb211d8197a054d91", size = 1240069, upload-time = "2026-05-15T04:50:38.221Z" }, + { url = "https://files.pythonhosted.org/packages/aa/90/28d7f154888610aa9237e541986beb62b479df29d193a5a0617dbb1514d0/tiktoken-0.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:8f2d16e7a7c783ad81f36e457d046d1f1c8af70b22aec8a13238efe531977c41", size = 874748, upload-time = "2026-05-15T04:50:39.587Z" }, + { url = "https://files.pythonhosted.org/packages/9c/83/b096c859c2a47c11731bf2f5885f4028b809dfe2396582883eed9cae372f/tiktoken-0.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5df5d1507bd245f1ccad4a074698240021239e455eb0bb4ced4e3d7181872154", size = 1034228, upload-time = "2026-05-15T04:50:40.988Z" }, + { url = "https://files.pythonhosted.org/packages/53/61/c68e123b6d753e3fc2751e9b18e732c9d8bf1e1926762e736eee935d931c/tiktoken-0.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8fe806a50664e83a6ffd56cbd1e4f5dcc6cd32a3e7538f70dc38b1a271384545", size = 982978, upload-time = "2026-05-15T04:50:42.195Z" }, + { url = "https://files.pythonhosted.org/packages/ef/8b/96cc178cc584e65d363134500f297790b06cd48cdeb1e8fcf7bbe60f4715/tiktoken-0.13.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:125bc05005e747f993a83dc67934249932d6e4209854452cd4c0b1d53fba3ba2", size = 1116355, upload-time = "2026-05-15T04:50:43.564Z" }, + { url = "https://files.pythonhosted.org/packages/86/f5/bab735d2c72ea55404b295d02d092644eb5f7cc6205e34d35eb9abfb9ab2/tiktoken-0.13.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:5e6358911cab4adee6712da27d65573496a4f68cf8a2b5fca6a4ad10fc5748cf", size = 1135772, upload-time = "2026-05-15T04:50:44.782Z" }, + { url = "https://files.pythonhosted.org/packages/4e/b9/6de04ebdf904edfaad87788011b3735087a0c9ea671b9027e1e4e965e8c8/tiktoken-0.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:975cbd78d085d75d26b59660e262736dcaed1e35f8f142cd6291025c01d25486", size = 1182415, upload-time = "2026-05-15T04:50:46.422Z" }, + { url = "https://files.pythonhosted.org/packages/0d/9c/470a05f3b1caf038f44880e334d47ab674e0c80d514c66b375d14d5afa10/tiktoken-0.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:75ab9bc99fa020a4c283424590ecd7f3afd70c1c281cb3fa3192a6c3af9f9615", size = 1239879, upload-time = "2026-05-15T04:50:48.052Z" }, + { url = "https://files.pythonhosted.org/packages/42/a6/c1936d16055436cb32e6c6128d68629622e00f4768562f55653752d34768/tiktoken-0.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:6b1615f0ff71953d19729ceb18865429c185b0a23c5353f1bbca34a394bf60f7", size = 874829, upload-time = "2026-05-15T04:50:49.202Z" }, + { url = "https://files.pythonhosted.org/packages/d6/07/acb5992c3772b5a36284f742cfb7a5895aa4471d1848ac31464ad50d7fdf/tiktoken-0.13.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6eb4a5bfbc6426938026b1a334e898ac53541360d62d8c689870160cc80abd67", size = 1033600, upload-time = "2026-05-15T04:50:50.4Z" }, + { url = "https://files.pythonhosted.org/packages/14/e9/742e9aec30f59b9f161f7ff7cd072e02ea836c9e1c0854a8076dfcd40d5c/tiktoken-0.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:43cee3e5400573b2046fbf092cc7a5bc30164f9e4c95ce20714da929df48737a", size = 982516, upload-time = "2026-05-15T04:50:52.03Z" }, + { url = "https://files.pythonhosted.org/packages/72/74/ca1541b053e7648254d2e4b42a253e1bb4359f2c91a0a8d49228c794e1a0/tiktoken-0.13.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:7de52e3f566d19b3b11bd37eea552c6c305ad74081f736882bd44d148ed4c48d", size = 1115518, upload-time = "2026-05-15T04:50:53.543Z" }, + { url = "https://files.pythonhosted.org/packages/46/e3/93825eaf5a4a504795b787e5d5dea07fbeb3dabf97aa7b450be8bde59c89/tiktoken-0.13.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:51384448aa508e4df84c0f7c1dc3211c7f7b8096325660ee5fc82f3e11b381ce", size = 1136867, upload-time = "2026-05-15T04:50:55.191Z" }, + { url = "https://files.pythonhosted.org/packages/8c/46/002b68de6827091d5ae90b048f326e8aad8d953520950e5ce1508879414f/tiktoken-0.13.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e28157350f7ebf35008dd8e9e0fdb621f976e4230c881099c85e8cf07eaa50e2", size = 1181826, upload-time = "2026-05-15T04:50:56.296Z" }, + { url = "https://files.pythonhosted.org/packages/db/c6/d393e3185a276505182f7abd93fe714f3c444a2be9180798fa052347504e/tiktoken-0.13.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:165cf1820ea4a354985c2490a5205d4cc74661c934aca79dd0368232fff94e0f", size = 1239489, upload-time = "2026-05-15T04:50:57.918Z" }, + { url = "https://files.pythonhosted.org/packages/b7/4d/bc07d1f1635d4897a202acc0ae11c2886eaa7325c359ba4741b47bf8e225/tiktoken-0.13.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6c43a675ca14f6f2749ba7f12075d37456015a24b859f2517b9beb4ef30807ec", size = 873820, upload-time = "2026-05-15T04:50:59.528Z" }, + { url = "https://files.pythonhosted.org/packages/8c/93/0dd6adca026a616c3a92974566b43381eea4b475ce1f36c062b8271a9ac5/tiktoken-0.13.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaaaef47c2406277181d2086484c317bf7fc433e2d5d03ff94f56b0dcec87471", size = 1034977, upload-time = "2026-05-15T04:51:00.957Z" }, + { url = "https://files.pythonhosted.org/packages/d9/77/5ec6e6bc5b30bed6d93f7f2162d8f6b32437b3ba27cb527cfe004f6109c9/tiktoken-0.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ca8b310bd93b3772cb1b7922d915446864860f562bdfe4825c63a0aed3fb28cd", size = 983635, upload-time = "2026-05-15T04:51:02.629Z" }, + { url = "https://files.pythonhosted.org/packages/94/b0/c8ae9aff00d625c50659b4513e707a0462c4bf5d4d6cc1b802103225c02e/tiktoken-0.13.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:32e0c12305105002c047b3bb1070b0dd9a73b0cb3b2856a8972b810e7a4f5881", size = 1116036, upload-time = "2026-05-15T04:51:04.082Z" }, + { url = "https://files.pythonhosted.org/packages/1b/ac/6a5dddd1d0a6018ecb389bd0353e6b4a515eb4d2286611bd0ace1937b9e1/tiktoken-0.13.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:5ba5fd62507a932d1241346179e3b39bc7bf7408f03c272652d93b3bedf5db24", size = 1135544, upload-time = "2026-05-15T04:51:05.229Z" }, + { url = "https://files.pythonhosted.org/packages/f4/b8/585032b4384b2f7dcdaddcb52865c83a701a420d09e3c2b4a2be1c450c57/tiktoken-0.13.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d108bc2d470fc53c8ecd24f2c0fd2b5f98c33e87cdb6aa2e9b8c5dced703d273", size = 1182217, upload-time = "2026-05-15T04:51:06.517Z" }, + { url = "https://files.pythonhosted.org/packages/cd/b6/993ff1ded3958215fd341a847b8e5ffeb5de473f435296870d314fc91ac4/tiktoken-0.13.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cb99cb5127449f58d0a2d5f5ccfb390d8dbdfd919c221246caaee29d8725ed51", size = 1239404, upload-time = "2026-05-15T04:51:07.843Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3d/fef7e06e3b33e7538db0ced734cf9fe23b6832d2ac4990c119c377aec55e/tiktoken-0.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:115c4f26ffa11caac8b54eea35c2ad38c612c20a48d35dd15d70a02ac6f51f58", size = 918686, upload-time = "2026-05-15T04:51:08.925Z" }, + { url = "https://files.pythonhosted.org/packages/c1/82/a7fc44582bc32ab00de988a2299bf77c077f59068b233109e34b7d6ca7e6/tiktoken-0.13.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:472527e9132952f2fbf77cd290658bacf003d4d5a3fabc18e5fbd407cbae4d9b", size = 1034454, upload-time = "2026-05-15T04:51:10.035Z" }, + { url = "https://files.pythonhosted.org/packages/37/d0/24d8a890c14f432a05cea669c17bebeaa99f96a7c79523b590f564246411/tiktoken-0.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:4e2f67d27c9626cdd25fe33d9313c5cdb3d8d82da646b68d6eb8e7e9c20e6448", size = 982976, upload-time = "2026-05-15T04:51:11.23Z" }, + { url = "https://files.pythonhosted.org/packages/49/b7/2ab43f62788a9266187a9bfc1d3af99ad83e5eaa25fbef168a69cd5ad14f/tiktoken-0.13.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:2b920b35805cd64585a37c3dc7ce65fba4d2d36016be01e1d7942482ca29093a", size = 1115526, upload-time = "2026-05-15T04:51:12.608Z" }, + { url = "https://files.pythonhosted.org/packages/64/39/1494321ed323ce7a14d88e3cd6cb9058625977df1c6961ddc492bd10a9f3/tiktoken-0.13.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:493af3aa28a4aaf2e3d2600a2ee717252c9bf5ab38fff94eb5a02db5ab77e5ad", size = 1136466, upload-time = "2026-05-15T04:51:13.926Z" }, + { url = "https://files.pythonhosted.org/packages/96/d9/dfd086aa2d918c563a140720e0ce296cada1634efd2783d5cf51e05f984e/tiktoken-0.13.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6644c9c2b5cf3916f5a3641d7d12fdb3f006a7b3d9ff6acdaec44e29ab1ff91e", size = 1181863, upload-time = "2026-05-15T04:51:15.025Z" }, + { url = "https://files.pythonhosted.org/packages/2f/68/a18b4f307086954fdae32714cb4f85562e34f9d34ab206e61f1816aa6018/tiktoken-0.13.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5cb65b60b9408563676d874a3a4ee573370066f0dc4e29d84e82e989c6517424", size = 1239218, upload-time = "2026-05-15T04:51:16.103Z" }, + { url = "https://files.pythonhosted.org/packages/16/5b/f2aa703a4fc5d2dff73460a7d46cc2f3f44aa0f3dd8eeb20d2a0ecf68862/tiktoken-0.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:85b78cc3a2c3d48723ca751fa981f1fedccd54194ca0471b957364353a898b07", size = 918110, upload-time = "2026-05-15T04:51:17.237Z" }, +] + +[[package]] +name = "tldextract" +version = "5.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "idna" }, + { name = "requests" }, + { name = "requests-file" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/65/7b/644fbbb49564a6cb124a8582013315a41148dba2f72209bba14a84242bf0/tldextract-5.3.1.tar.gz", hash = "sha256:a72756ca170b2510315076383ea2993478f7da6f897eef1f4a5400735d5057fb", size = 126105, upload-time = "2025-12-28T23:58:05.532Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/42/0e49d6d0aac449ca71952ec5bae764af009754fcb2e76a5cc097543747b3/tldextract-5.3.1-py3-none-any.whl", hash = "sha256:6bfe36d518de569c572062b788e16a659ccaceffc486d243af0484e8ecf432d9", size = 105886, upload-time = "2025-12-28T23:58:04.071Z" }, +] + +[[package]] +name = "tokenizers" +version = "0.23.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c1/60/21f715d9faba5f5407ff759472ade058ec4a507ad62bcea47cb847239a73/tokenizers-0.23.1.tar.gz", hash = "sha256:1feeeadf865a7915adc25445dea30e9933e593c31bb96c277cee36de227c8bfa", size = 365748, upload-time = "2026-04-27T14:43:25.606Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/39/b87a87d5bb9470610b80a2d31df42fcffeaf35118b8b97952b2aff598cc7/tokenizers-0.23.1-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:e03d6ffcbe0d56ee9c1ccd070e70a13fa750727c0277e138152acbc0252c2224", size = 3146732, upload-time = "2026-04-27T14:43:15.427Z" }, + { url = "https://files.pythonhosted.org/packages/e2/6a/068ed9f6e444c9d7e9d55ce134181325700f3d7f30410721bdc8f848d727/tokenizers-0.23.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:e0948bbb1ac1d7cdfc9fb6d62c596e3b7550036ad60ecd654a66ad273326324e", size = 3054954, upload-time = "2026-04-27T14:43:13.745Z" }, + { url = "https://files.pythonhosted.org/packages/6c/36/e006edf031154cba92b8416057d92c3abe3635e4c4b0aa0b5b9bb39dde70/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bf13402aff9bc533c89cb849ec3b412dc3fbeacc9744840e423d7bf3f7dc0e3", size = 3374081, upload-time = "2026-04-27T14:43:01.241Z" }, + { url = "https://files.pythonhosted.org/packages/a2/ef/7735d226f9c7f874a6bee5e3f27fb25ecabdf207d37b8cf45286d0795893/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f836ca703b89ae07919a309f9651f7a88fd5a33d5f718ba5ad0870ec0256bad6", size = 3247641, upload-time = "2026-04-27T14:43:03.856Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d9/24827036f6e21297bfffda0768e58eb6096a4f411e932964a01707857931/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae848657742035523fdf261773630cb819a26995fcd3d9ecae0c1daf6e5a4959", size = 3585624, upload-time = "2026-04-27T14:43:10.664Z" }, + { url = "https://files.pythonhosted.org/packages/0c/9a/22f3582b3a4f49358293a5206e25317621ee4526bfe9cdaa0f07a12e770e/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:53b09e85775d5187941e7bab30e941b4134ab4a7dd8c68e783d231fb7ca27c51", size = 3844062, upload-time = "2026-04-27T14:43:05.643Z" }, + { url = "https://files.pythonhosted.org/packages/7e/65/b8f8814eef95800f20721384136d9a1d22241d50b2874357cb70542c392f/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea5a0ce170074329faaa8ea3f6400ecde604b6678192688533af80980daae71a", size = 3460098, upload-time = "2026-04-27T14:43:08.854Z" }, + { url = "https://files.pythonhosted.org/packages/0d/d5/1353e5f677ec27c2494fb6a6725e82d56c985f53e90ec511369e7e4f02c6/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5075b405006415ea148a992d093699c66eb01952bf59f4d5727089a98bda45a4", size = 3346235, upload-time = "2026-04-27T14:43:12.377Z" }, + { url = "https://files.pythonhosted.org/packages/71/89/39b6b8fc073fb6d413d0147aa333dc7eff7be65639ac9d19930a0b21bf33/tokenizers-0.23.1-cp310-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:56f3a77de629917652f876294dc9fe6bad4a0c43bc229dc72e59bb23a0f4729a", size = 3426398, upload-time = "2026-04-27T14:43:07.264Z" }, + { url = "https://files.pythonhosted.org/packages/0f/80/127c854da64827e5b79264ce524993a90dddcb320e5cd42412c5c02f9e8a/tokenizers-0.23.1-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9d10a6d957ef01896dc274e890eee27d41bd0e74ef31e60616f0fc311345184e", size = 9823279, upload-time = "2026-04-27T14:43:17.222Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ba/44c2502feb1a058f096ddfb4e0996ef3225a01a388e1a9b094e91689fe93/tokenizers-0.23.1-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:1974288a609c343774f1b897c8b482c791ab17b75ab5c8c2b1737565c1d82288", size = 9644986, upload-time = "2026-04-27T14:43:19.45Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c1/464019a9fb059870bfe4eebb4ba12208f3042035e258bf5e782906bd3847/tokenizers-0.23.1-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:120468fb4c24faf0543c835a4fabafa4deb3f20a035c9b6e83d0b553a97615d4", size = 9976181, upload-time = "2026-04-27T14:43:21.463Z" }, + { url = "https://files.pythonhosted.org/packages/79/94/3ac1432bda31626071e9b6a12709b97ae05131c804b94c8f3ac622c5da32/tokenizers-0.23.1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e3d8f40ea6268047de7046906326abed5134f27d4e8447b23763afe5808c8a96", size = 10113853, upload-time = "2026-04-27T14:43:23.617Z" }, + { url = "https://files.pythonhosted.org/packages/6a/dd/631b21433c771b1382535326f0eca80b9c9cee2e64961dd993bc9ac4669e/tokenizers-0.23.1-cp310-abi3-win32.whl", hash = "sha256:93120a930b919416da7cd10a2f606ac9919cc69cacae7980fa2140e277660948", size = 2536263, upload-time = "2026-04-27T14:43:29.888Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/2553f72aaf65a2797d4229e37fa7fbe38ffbf3e32912d31bdd78b3323e59/tokenizers-0.23.1-cp310-abi3-win_amd64.whl", hash = "sha256:e7bfaf995c1bdbbd21d13539decb6650967013759318627d85daeb7881af16b7", size = 2798223, upload-time = "2026-04-27T14:43:28.51Z" }, + { url = "https://files.pythonhosted.org/packages/cd/2b/2be299bab55fc595e3d38567edb1a87f86e594842968fa9515a07bdcf422/tokenizers-0.23.1-cp310-abi3-win_arm64.whl", hash = "sha256:a26197957d8e4425dfba746315f3c425ea00cfa8367c5fbc4ec73447893dcea9", size = 2664127, upload-time = "2026-04-27T14:43:26.949Z" }, +] + +[[package]] +name = "tqdm" +version = "4.67.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" }, +] + +[[package]] +name = "typer" +version = "0.25.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "click" }, + { name = "rich" }, + { name = "shellingham" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/51/9aed62104cea109b820bbd6c14245af756112017d309da813ef107d42e7e/typer-0.25.1.tar.gz", hash = "sha256:9616eb8853a09ffeabab1698952f33c6f29ffdbceb4eaeecf571880e8d7664cc", size = 122276, upload-time = "2026-04-30T19:32:16.964Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/f9/2b3ff4e56e5fa7debfaf9eb135d0da96f3e9a1d5b27222223c7296336e5f/typer-0.25.1-py3-none-any.whl", hash = "sha256:75caa44ed46a03fb2dab8808753ffacdbfea88495e74c85a28c5eefcf5f39c89", size = 58409, upload-time = "2026-04-30T19:32:18.271Z" }, +] + +[[package]] +name = "types-requests" +version = "2.33.0.20260518" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e0/01/c5a19253fe1ac159159ddf9a3a07cec8bb5e486ec4d9002ad2821da0e5d2/types_requests-2.33.0.20260518.tar.gz", hash = "sha256:df7bd3bfe0ca8402dfb841e7d9be714bb5578203283d66d7dc4ef69343449a5e", size = 24752, upload-time = "2026-05-18T06:07:37.966Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1c/bc/b139710a3b6018f7fb2b9508b35c8af564e61bf2bf4fa619d088f3e16f85/types_requests-2.33.0.20260518-py3-none-any.whl", hash = "sha256:626d697d1adaaff76e2044dc8c5c051d8f21abc157bdfe204a75558076fe0bf0", size = 21391, upload-time = "2026-05-18T06:07:37.044Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] + +[[package]] +name = "tzdata" +version = "2026.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/19/1b9b0e29f30c6d35cb345486df41110984ea67ae69dddbc0e8a100999493/tzdata-2026.2.tar.gz", hash = "sha256:9173fde7d80d9018e02a662e168e5a2d04f87c41ea174b139fbef642eda62d10", size = 198254, upload-time = "2026-04-24T15:22:08.651Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/e4/dccd7f47c4b64213ac01ef921a1337ee6e30e8c6466046018326977efd95/tzdata-2026.2-py2.py3-none-any.whl", hash = "sha256:bbe9af844f658da81a5f95019480da3a89415801f6cc966806612cc7169bffe7", size = 349321, upload-time = "2026-04-24T15:22:05.876Z" }, +] + +[[package]] +name = "urllib3" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, +] + +[[package]] +name = "uuid-utils" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/a1/822ceef22d1c139cffebe4b1b660cfaa10253d5c770aa2598dc8e9497593/uuid_utils-0.16.0.tar.gz", hash = "sha256:d6902d4375dfba4c9902c736bb82d3c040417b67f7d0fa48910ddfdb1ac95de7", size = 42596, upload-time = "2026-05-19T07:44:23.28Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/4c/b4cf43a5d22bcdb91727acdf54be0d78e83e595b73c5a9a8a4291875f059/uuid_utils-0.16.0-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:727fae3f0682191ec9c8ce1cd0f71e81b471a2e26b7c5fd66712fc0f11640aa0", size = 562183, upload-time = "2026-05-19T07:45:02.683Z" }, + { url = "https://files.pythonhosted.org/packages/d6/fb/4b0d1c4b5e9f8679ca41b9cdbce5749e1d5db3d3d42a07060d6ce61ac583/uuid_utils-0.16.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:66a9c8cedf7695c28e700f6a66bde0809c3b2e0d8a70968be7bfd47c908952e5", size = 289018, upload-time = "2026-05-19T07:44:07.726Z" }, + { url = "https://files.pythonhosted.org/packages/de/43/2dc6c7401c8fab86e46b0b33ada6dcfde949b2fd48877ba6f880862be80e/uuid_utils-0.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9152bff801ec2ccf630df06d67389090a2c612dea87fbf9a887ab4b222929f6f", size = 326171, upload-time = "2026-05-19T07:45:25.186Z" }, + { url = "https://files.pythonhosted.org/packages/9b/f5/48f11fb91f36453611ca148bc441436f279870b1ec6b576dc5167fb6e680/uuid_utils-0.16.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:06fc7db470c37e5c1ab3fd2cd159697d6f8b279d7d23b5b96bd418b115f8caa9", size = 332222, upload-time = "2026-05-19T07:45:09.036Z" }, + { url = "https://files.pythonhosted.org/packages/30/cb/b2b49528521e4a097f129e8bf7850a26f00af46afba778832cf3458a5c00/uuid_utils-0.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e1a1f57fe3631e164dad27b24aa81267810e20575f705af3b0fa734f3a21247", size = 444801, upload-time = "2026-05-19T07:45:37.517Z" }, + { url = "https://files.pythonhosted.org/packages/a9/b3/a28d9c6f7c701dfe01c8020b30e33899a28eb9e4d056b07e7388f50ebf67/uuid_utils-0.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ee392fe59808a731b7b6bf4d453fb6e833774921331cceae5f254d1e9c5b97d", size = 325594, upload-time = "2026-05-19T07:44:44.682Z" }, + { url = "https://files.pythonhosted.org/packages/cf/65/e1ff41dc44966e396ead86e104ba21b35ddb07ff7a64bb55013074ee77fe/uuid_utils-0.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b2e981b1258db444df4cf4bf4c79673570d081d48d35f22d0f86471e0ad795c5", size = 349312, upload-time = "2026-05-19T07:45:15.582Z" }, + { url = "https://files.pythonhosted.org/packages/ed/57/fb19b7951f66a46e03bd1943a61ee9d59c83e994e56e8c97d79aff1f0e47/uuid_utils-0.16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bbb92feb4db08cd76e27b4d3b1a82bfde708447317150c614eb9f761a43b387e", size = 502115, upload-time = "2026-05-19T07:43:38.756Z" }, + { url = "https://files.pythonhosted.org/packages/2f/8e/9a129c469b7b77afb62da5c6b7e92591073b845bd0c3108c0d0aa65389fb/uuid_utils-0.16.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1c3c5afaaa68b1d6393d653e9fc93a2fde9da1681da01f74b4593f41d31fb5f1", size = 607433, upload-time = "2026-05-19T07:44:11.675Z" }, + { url = "https://files.pythonhosted.org/packages/4a/56/2ef71fad168cc3d894f7094fa458086c093635d7835381c91470b19c9ad3/uuid_utils-0.16.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:38126b353527c5f001e4b24db9e62351eb768d0367febcd68100a4b39a035109", size = 566076, upload-time = "2026-05-19T07:44:35.453Z" }, + { url = "https://files.pythonhosted.org/packages/95/bf/68e60ea053ca30f35df877b96001331398140d5c4983561affa1350331b1/uuid_utils-0.16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41a67e546d9adf11c4e4cb5c8e81f000f8b1f000c17912ced089b499855719a5", size = 530645, upload-time = "2026-05-19T07:45:49.278Z" }, + { url = "https://files.pythonhosted.org/packages/42/19/b521f7d73094fca4c0c44002f4a42bfcbcf0b770fdc3c4b9a596dda25734/uuid_utils-0.16.0-cp312-cp312-win32.whl", hash = "sha256:52d2cc8c12a3466cd1727883e0746d8bad5dddd670369eb553ba17fdc3b565ca", size = 168887, upload-time = "2026-05-19T07:45:45.502Z" }, + { url = "https://files.pythonhosted.org/packages/87/1f/4126c3ccbc2d98a613664e55f6ab6d7bd4b98424a04486e4fcc76549af15/uuid_utils-0.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:c97625e5edfda8b118160ce1e88756f92b1635775f836c168be7bf10928d97fa", size = 174607, upload-time = "2026-05-19T07:43:52.938Z" }, + { url = "https://files.pythonhosted.org/packages/74/62/b83ccc8446ae39dcc0bda2cb3b525b6af6a2036383afe1d1d5fe7b234c2c/uuid_utils-0.16.0-cp312-cp312-win_arm64.whl", hash = "sha256:baf79c8050eb784b252dd34807df73f61130fe8676b61231baccab62530f20ec", size = 173021, upload-time = "2026-05-19T07:45:10.204Z" }, + { url = "https://files.pythonhosted.org/packages/60/9b/74c1f47a9b4f138a254e51528e5ffaeba6bf99ecead9f0c4b6fccccfbfcb/uuid_utils-0.16.0-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:d34cf9681e8892fad2a63e393068e544505408748cd8bf0c3517d753a01528d4", size = 563166, upload-time = "2026-05-19T07:44:10.494Z" }, + { url = "https://files.pythonhosted.org/packages/7c/1c/009e37b70f1f0ff17e7103a36bafde33d503d9ea7fe739761aa3e3c9fde6/uuid_utils-0.16.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0681d1bdb7956e0c6d581e7601dabcfb2b08c25d2a65189f4e9b102c94f5ff46", size = 289529, upload-time = "2026-05-19T07:43:54.466Z" }, + { url = "https://files.pythonhosted.org/packages/5e/5e/e0323d54321166639eb2be5e8a464f5cb0fc04d72d91f3e78944bb6a1da8/uuid_utils-0.16.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed45fb8732d216426227096b55accbb87cba57febc86a044d90780b090eb99d0", size = 326328, upload-time = "2026-05-19T07:45:31.901Z" }, + { url = "https://files.pythonhosted.org/packages/f0/a3/046f6cb958467c3bf4a163a8a53b178b64a62e21ed8ad5b2c1dacb3a2cfc/uuid_utils-0.16.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b617a334bb01ef2ff8c22900f5a14125eb9063f602131494cc9dc59519beaa5b", size = 332322, upload-time = "2026-05-19T07:43:41.284Z" }, + { url = "https://files.pythonhosted.org/packages/67/80/01914e3949744db7acd0006885e5542fbebb6e39114857d007d29b3265c2/uuid_utils-0.16.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a750d8aeb8ae880aa9a2529606bde0e994bcc7448730c953107f357a28e6102e", size = 445787, upload-time = "2026-05-19T07:45:36.102Z" }, + { url = "https://files.pythonhosted.org/packages/14/ef/f6908f41279f205d70c8a0d5dcb25dd6802741d7f88e3f0123453c3584d3/uuid_utils-0.16.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a250e111903c4368745fce5ac2aa607bd477c62d3307e45347338fdb64b38e0", size = 324678, upload-time = "2026-05-19T07:45:12.77Z" }, + { url = "https://files.pythonhosted.org/packages/11/4a/bf841ba90f829c7779d82155e0f4b88ef6726ccc25507d064d50ac2cd329/uuid_utils-0.16.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:95b7f480010ea98a29ee809857a98aa923008c68129af1b39244adccff7377fb", size = 349704, upload-time = "2026-05-19T07:44:47.172Z" }, + { url = "https://files.pythonhosted.org/packages/e6/31/3b5c60172b8c57bf4ca485484b8e4edef550ca324f9287f1183be97422e2/uuid_utils-0.16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:420aa3ca403cedb73490b6ea3aeefeea7e0455f5ce60bbf856390ee872ae3306", size = 502456, upload-time = "2026-05-19T07:45:00.821Z" }, + { url = "https://files.pythonhosted.org/packages/88/bf/3da8d497af80fd51d8bf85551c77ede67f07825924ec5987bf9b6031014a/uuid_utils-0.16.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:b8a9a7b1065a12d40f2cc25b7d705ab34954cc57095034367bca39ebcf4a876b", size = 607727, upload-time = "2026-05-19T07:44:30.058Z" }, + { url = "https://files.pythonhosted.org/packages/bd/4e/7c8cf03ec15cd6f40e4cbab81b2b4a625461327f68c7971e54723280ec3e/uuid_utils-0.16.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f235ac5827d74ac630cc87f29278cdaa5d2f273613a6e05bbd96df7aa4170776", size = 566204, upload-time = "2026-05-19T07:44:51.225Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5f/af955feae69cce7fd2121ca3f790ff4b85ad2e17b2149546f50753e1a047/uuid_utils-0.16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c8083284488b84ad178e74add64cfd1e74e8be5e30821e5acbc5019281c658b0", size = 529986, upload-time = "2026-05-19T07:45:57.85Z" }, + { url = "https://files.pythonhosted.org/packages/10/cf/3fec757e51bef10eb41ae8075f5442c60e85ff456b42d16a3063f5dc6c80/uuid_utils-0.16.0-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:27a071a899ba46a551d6524dbbc5a98b88be176d0f55ddf72cf71c005326ac10", size = 98683, upload-time = "2026-05-19T07:44:16.369Z" }, + { url = "https://files.pythonhosted.org/packages/40/a7/cd1adbea7ef882a70db064c00cd93b12e11027b4cdd7ffd79e95c35fc3e3/uuid_utils-0.16.0-cp313-cp313-win32.whl", hash = "sha256:924a8de04460e4cf65998ad0b6568084f7c51740ebd3254d07a0bcde35a84af6", size = 168822, upload-time = "2026-05-19T07:44:24.09Z" }, + { url = "https://files.pythonhosted.org/packages/74/99/617ceb9e3a95b23837012740979baf71afad723b70daf34862da3f7c17a1/uuid_utils-0.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:5279bc7ab3c6683f1c67314695bee14d869015acbbc677bdb0015190fe753d16", size = 174967, upload-time = "2026-05-19T07:44:56.022Z" }, + { url = "https://files.pythonhosted.org/packages/d9/d8/148ae707bfc36d482e39db679c86b81bdce264d4feb9df5d40a03b7687e3/uuid_utils-0.16.0-cp313-cp313-win_arm64.whl", hash = "sha256:61a9c4c26ad12ac66fa4bfd0fdb8494724fe7a5b98a9fcd43e78e2b388663dbb", size = 173142, upload-time = "2026-05-19T07:43:50.171Z" }, + { url = "https://files.pythonhosted.org/packages/21/05/ca6d60705e71fdeaa3431dad94e279a8213c5573cb2925e1aabf3dc0330a/uuid_utils-0.16.0-cp313-cp313t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:73486b6aa3f755a6c97000f5ea67e7ac78d6df89bf22980789a1e943e24b74f0", size = 564408, upload-time = "2026-05-19T07:44:38.351Z" }, + { url = "https://files.pythonhosted.org/packages/eb/8c/b9a0462c38535c1662acb1025768e2d626bee5ce9e1790bad6b5381162ea/uuid_utils-0.16.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:f1614572fd9345cdc3dde3f40c237345719fabca1aa87d2d87b321d523cfa34d", size = 289923, upload-time = "2026-05-19T07:45:19.611Z" }, + { url = "https://files.pythonhosted.org/packages/f2/33/a53afeef1a56051551a0f5a801e4bce411dd73c6a8c99bad16902651256d/uuid_utils-0.16.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9346ce6eb1fbd8b03a6b331d66016afcb4edcdff6eac708e21391600529a016a", size = 325762, upload-time = "2026-05-19T07:45:18.261Z" }, + { url = "https://files.pythonhosted.org/packages/72/ca/4462a4f36365d7ee72d41e05e6bcfe127e861b073ab37c25b2c8a518317c/uuid_utils-0.16.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a0fc6eb3fd821466fbab69cf356c6ec2b7327266bbbc740a2eb57c77c4bef965", size = 332359, upload-time = "2026-05-19T07:45:34.886Z" }, + { url = "https://files.pythonhosted.org/packages/c5/67/9d3373fa7c5a746fdecc64e30caf915c29eb632203508d87676f9243ed03/uuid_utils-0.16.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:13a797e5e8f0dadc18351a5aa013815ddac25dce6864072a539d510910c95f71", size = 445483, upload-time = "2026-05-19T07:44:49.598Z" }, + { url = "https://files.pythonhosted.org/packages/57/08/ce01aa6d897fc7f875844fe58cad0a542c8ebf089d9242b654b56260ecb8/uuid_utils-0.16.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57c3583b1f1c00a94f59726a5e2b988fa209221143919a1af5c2fc24e318fc98", size = 326281, upload-time = "2026-05-19T07:44:59.677Z" }, + { url = "https://files.pythonhosted.org/packages/76/ef/2c719b2c26bb5b5e5061a1435c11ad2bd33ac3cd6d4cd0c7c3ac1d3396ed/uuid_utils-0.16.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:caac9c8b1d50e8fbddc76e93bfefbef472978eb45adbfdb6289d578816992953", size = 350809, upload-time = "2026-05-19T07:45:28.076Z" }, + { url = "https://files.pythonhosted.org/packages/e0/9b/c1ed447328b32229cca38ac4c62d309eab006e5e9c4020e2056a175bc607/uuid_utils-0.16.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:91db59bad97ed2b9d2c6ed25082fe9762b2c422e694fe06786b28cf4e776ac4c", size = 502088, upload-time = "2026-05-19T07:44:09.208Z" }, + { url = "https://files.pythonhosted.org/packages/c1/e0/8442f4efe7bde72f0b4ae5f675d0c7fbe209ad0b54718b8ddf43c46c6fae/uuid_utils-0.16.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:41985e342a30e76366a8becc60bbdb07d72cd1b86ec657b1f31654e9fb1baada", size = 607631, upload-time = "2026-05-19T07:44:19.384Z" }, + { url = "https://files.pythonhosted.org/packages/f1/1e/9a9fa261edf4c972f28ae83421377e3ab8dbd0bd7db58fd316e782d09a3b/uuid_utils-0.16.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:1b0dcedf9266bf34a54d5cbe78648eaa627e02352f2a6923ed647530aea2f661", size = 567618, upload-time = "2026-05-19T07:43:58.478Z" }, + { url = "https://files.pythonhosted.org/packages/cc/f7/1bcfdb9d539bd42736dd6076470a42fbb5db23f79712c0a06aa0a3752f7b/uuid_utils-0.16.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:26fe23ab60f05de4ad70aaa5b6a4c2a7bbd43055e3dd6f6b31efba0532ac9c71", size = 530971, upload-time = "2026-05-19T07:45:06.348Z" }, + { url = "https://files.pythonhosted.org/packages/24/0c/18945f417d6bb4d0dd2b7652fe36c58c4e83bcf593b9b326b83aa40b853a/uuid_utils-0.16.0-cp313-cp313t-win32.whl", hash = "sha256:7f8cf49c05d58523a0f977cb7f11afc05791a0fa164d7303b8365a34750638e7", size = 169369, upload-time = "2026-05-19T07:44:32.581Z" }, + { url = "https://files.pythonhosted.org/packages/cc/cc/c0eb0c3fab2ed80d706369b750029143b53126809b77b36bcbb77da66bab/uuid_utils-0.16.0-cp313-cp313t-win_amd64.whl", hash = "sha256:e99f9a8b2420b228faba23a637e96efaf5c6a678b2e225870f24431c82707f50", size = 175384, upload-time = "2026-05-19T07:45:56.623Z" }, + { url = "https://files.pythonhosted.org/packages/b7/77/50ac87b6e18b1c686f700aa38c9471a990683c6a955f71ac1a6677ed8145/uuid_utils-0.16.0-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:6853b627983aa1b4fd95aa52d9e87136eb94a7b3b7de0fbb1db8a498d457eeec", size = 564108, upload-time = "2026-05-19T07:43:55.609Z" }, + { url = "https://files.pythonhosted.org/packages/83/16/65046676de246bb5334d9f58aa96d2feb9fc347fda3556aaff7da1c2fc7a/uuid_utils-0.16.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:f44b65ae0c329843817d9c90e36a7a3c677b413bf407c99e67db874dac49dad3", size = 289967, upload-time = "2026-05-19T07:45:38.886Z" }, + { url = "https://files.pythonhosted.org/packages/91/d6/54fa988606a15dfd2028e925d8eb9c3ee6edbf1eb7692a67b37282880b56/uuid_utils-0.16.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de8a365795a76f347f5622621c2bee543cffa0c70949f3ee093bdefc9d926dcc", size = 325835, upload-time = "2026-05-19T07:44:42.02Z" }, + { url = "https://files.pythonhosted.org/packages/d5/1b/50622f967ceacea1f89fd065d9bfd395b51acb02cfb0a4ddc8fa9ff0c983/uuid_utils-0.16.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:426a8c9af90242d879706ccf29da56f0b0712e7739fb0bbe16baacabc75596e2", size = 332607, upload-time = "2026-05-19T07:43:42.42Z" }, + { url = "https://files.pythonhosted.org/packages/12/f5/4059706be6617e2787e375ea52994ce3c3fa3920b7d4a9c8ebf7895681a5/uuid_utils-0.16.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:833bc4b3c3fc24be541f67b01b4a75b6b9942a9b7137395b4eb35435948bd6da", size = 444287, upload-time = "2026-05-19T07:43:37.106Z" }, + { url = "https://files.pythonhosted.org/packages/65/d5/f44b2710563da687a368f0ce4dcbd462dfb6708bcd46439d831991d595c7/uuid_utils-0.16.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efb5252d7c00d586077f10e169d6e6d0b0d0f806d8a085073f0d19b4737aef4e", size = 324949, upload-time = "2026-05-19T07:45:33.175Z" }, + { url = "https://files.pythonhosted.org/packages/3a/a7/a69e859e37d26c5603f0bc0ae481860f691224f140e5a832f325b804770d/uuid_utils-0.16.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b3377ce388fd7bf8d231ec9d1d4f58c8e87888ddea93581f60ed6f878a4f722", size = 349651, upload-time = "2026-05-19T07:43:59.998Z" }, + { url = "https://files.pythonhosted.org/packages/db/73/4139cd3ca7b81ea283c1c8769373e9b2008241c0744a8ffb25f0a1b31325/uuid_utils-0.16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:12b6310beb38adc173ec5dc89e98812fd7e3d98f87f3ef01d2ea6ecb5d87994f", size = 502326, upload-time = "2026-05-19T07:45:40.292Z" }, + { url = "https://files.pythonhosted.org/packages/cb/8c/858101583fbad1b3fa04da88b1f7170836aa0f00b4cb712063325c44466d/uuid_utils-0.16.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a49b5a75497643479c919e2e537a4a36224ac3aaa0fada61b75d87024021ac3e", size = 607689, upload-time = "2026-05-19T07:44:48.355Z" }, + { url = "https://files.pythonhosted.org/packages/5e/bd/8f3d54a4763dd91ebd0f3d7b0c2ec434e4e0b1fc667b03a44d611a465ec6/uuid_utils-0.16.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:63bfdf00be51b6b3b79275d6767d034ea5c7a0caa067a35d72861284100cb60a", size = 566214, upload-time = "2026-05-19T07:44:53.519Z" }, + { url = "https://files.pythonhosted.org/packages/54/76/4c9a8d9baaa243c7902d84dbba4d51b1ab51c379c66d3fd6368ff6933ecf/uuid_utils-0.16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7525bc59ac4579c32317d2493dd42cf134b9bb50cd0bc6a41dd9f77e4740dde6", size = 529989, upload-time = "2026-05-19T07:44:43.141Z" }, + { url = "https://files.pythonhosted.org/packages/6d/13/d32cea997f880cedde415730ce0e872ebfd7a040155ae0bbda70eccd208e/uuid_utils-0.16.0-cp314-cp314-win32.whl", hash = "sha256:fbcac6e6710aa2e4bfbb81762758e01470dc56d5048ba4253acc77c9833568ff", size = 169146, upload-time = "2026-05-19T07:45:46.655Z" }, + { url = "https://files.pythonhosted.org/packages/1c/19/9fc55172d8fe59e1f27a14d598b427fa508a7ebb35fa7b7b99c24fa0ef13/uuid_utils-0.16.0-cp314-cp314-win_amd64.whl", hash = "sha256:d23fcaf37368a1647319187ef6f8b741bf079f033065899bc2d00a44b0a1214a", size = 175364, upload-time = "2026-05-19T07:45:55.335Z" }, + { url = "https://files.pythonhosted.org/packages/89/5d/fcd9226b715c5aa0638fcdd6deaf0de6c6c3c451c692cd76bfca810c6512/uuid_utils-0.16.0-cp314-cp314-win_arm64.whl", hash = "sha256:ea3265f8e2b452a4870f3298cb1d183dc4e36a3682cbb264dbe46af31267e706", size = 173268, upload-time = "2026-05-19T07:44:31.19Z" }, + { url = "https://files.pythonhosted.org/packages/c1/64/97ec9af95e58b8187f2934008ffab26e1604d149e34fe01c388b0543a24f/uuid_utils-0.16.0-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:99f8420c3ed59f89a086782ac197e257f4b1debb4545dffa90cf5db23f96c892", size = 564464, upload-time = "2026-05-19T07:44:40.856Z" }, + { url = "https://files.pythonhosted.org/packages/3e/6d/e4082f407484ac28923c0bf8e861e71d277118d8b7542d0a350340e45350/uuid_utils-0.16.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:259bab73c241743d684dcc3507feb76f484d720545e4e4805582aeff8e19700b", size = 290087, upload-time = "2026-05-19T07:44:01.084Z" }, + { url = "https://files.pythonhosted.org/packages/8c/43/c5c5f273c0ff889f20f10344784f9197dd00eb81ccc294330d4b949fea7e/uuid_utils-0.16.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:897e8ef0dc5e4ac0b17cf9cae84bb41e560d806280ec5b93db7475b504022105", size = 325532, upload-time = "2026-05-19T07:43:47.508Z" }, + { url = "https://files.pythonhosted.org/packages/13/7f/669aa899ab5378374d28a28231e6978f739921a1af394c7ebd6cc86e2639/uuid_utils-0.16.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c5af79cde16a7600dfccb7d431aec0afd3088ff170b6a09887bf3f7ab3cc7c81", size = 332209, upload-time = "2026-05-19T07:43:51.528Z" }, + { url = "https://files.pythonhosted.org/packages/2b/57/a2a32406d79a222794ef98a19254fd9a81a029a0f32d7740fba9873bff1f/uuid_utils-0.16.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bece1a6f677ca36047442c465d8166643eed9818b9e43e0bf42d3cf73e92dcff", size = 445507, upload-time = "2026-05-19T07:44:20.541Z" }, + { url = "https://files.pythonhosted.org/packages/26/6b/85459a35bfa7d73e79acbc4eab1cf6aa6e4d9d022c3260ed9dea539c7f0b/uuid_utils-0.16.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb3444498e7b099499c8a607d7771377020fa55f7274e46f54106af19f752d7", size = 326154, upload-time = "2026-05-19T07:45:23.587Z" }, + { url = "https://files.pythonhosted.org/packages/84/9e/e965efdbb503ed14d6e57aec1a22b98326ed24cc2fb48e750c4d192267a0/uuid_utils-0.16.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:542098f6cb6874aebeff98715f3ab7646fbe0f2ffb24509ca372828c68c4ed0e", size = 350905, upload-time = "2026-05-19T07:44:36.957Z" }, + { url = "https://files.pythonhosted.org/packages/23/ae/4321867888a783d03b7c053c0b68ca45d03974d86fcebf44d4ec268db397/uuid_utils-0.16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7207b25fe534bcf4d57e0110f90670e61c1c38b6f4598ba855af69ab428fc118", size = 502098, upload-time = "2026-05-19T07:44:17.696Z" }, + { url = "https://files.pythonhosted.org/packages/9d/9a/914a47bf42479bff0ce3e1fa1cbe3585354708edc928e27687cf91de9c26/uuid_utils-0.16.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:16dc5c6e439f75b0456114e955983e2156c1f38887733e54d54205d3005223e4", size = 607032, upload-time = "2026-05-19T07:44:22.151Z" }, + { url = "https://files.pythonhosted.org/packages/85/4c/2abacd6badba61a047eaa39c8347656229d12843bd9bbe4906daa6dc752c/uuid_utils-0.16.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a6d3ee32c57898d8415242b08d5dd086bc4f7bcbbb3fc102ef257f3d793eb294", size = 567664, upload-time = "2026-05-19T07:45:21.043Z" }, + { url = "https://files.pythonhosted.org/packages/53/1f/9d1a09521276424da19dc0d74456aed3311170fec181b28fa6acba45d963/uuid_utils-0.16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7555f120a2282d1901c9a632c2398a614101af4fe3f7c8114aa0f1d8c1978855", size = 530996, upload-time = "2026-05-19T07:45:44.229Z" }, + { url = "https://files.pythonhosted.org/packages/b4/22/14dbedb6b61f492d5524077fd10bbfb137583b0f0aafa6cd870ccb43f39a/uuid_utils-0.16.0-cp314-cp314t-win32.whl", hash = "sha256:756575d082ea4cb7d2f923d5b640c0efe7c82573aab49220c4e09b62d13737ff", size = 169358, upload-time = "2026-05-19T07:45:05.146Z" }, + { url = "https://files.pythonhosted.org/packages/25/f4/a636806c98401a1108f2456e9cc3fa39a618145bfb1d0860c57203159cfe/uuid_utils-0.16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:aa50261a83991dbb570a00573741455bd8f3249444f7329e5bdcd494799d1504", size = 174813, upload-time = "2026-05-19T07:45:59.579Z" }, + { url = "https://files.pythonhosted.org/packages/75/12/3823742459d87a100deb24bb6b41692aa961b267abd130fa7739cdf7d409/uuid_utils-0.16.0-cp314-cp314t-win_arm64.whl", hash = "sha256:22a17e93a371d850ffce8fcdbacc2239f890efe73aa3262b6170c1febc08afe1", size = 171733, upload-time = "2026-05-19T07:45:29.283Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.49.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c4/1f/fa18009dea8469069cca78a4e877a008ab78f08b064bfc9ab891579077ff/uvicorn-0.49.0.tar.gz", hash = "sha256:ebf4271aa580d9de97f93192d4595176df6e91f9aae919ca73e4fc07df1e66a3", size = 91284, upload-time = "2026-06-03T22:01:30.448Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/fa/e1388bbcf24ef3274f45c0c1c7b501fd14971037c1b6ee23610553307497/uvicorn-0.49.0-py3-none-any.whl", hash = "sha256:ba3d14c3ee7e41c6c654c46c9eb489d33213cdd30aa1696eab1374337c13f68f", size = 71376, upload-time = "2026-06-03T22:01:29.037Z" }, +] + +[[package]] +name = "wasabi" +version = "1.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/f9/054e6e2f1071e963b5e746b48d1e3727470b2a490834d18ad92364929db3/wasabi-1.1.3.tar.gz", hash = "sha256:4bb3008f003809db0c3e28b4daf20906ea871a2bb43f9914197d540f4f2e0878", size = 30391, upload-time = "2024-05-31T16:56:18.99Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/7c/34330a89da55610daa5f245ddce5aab81244321101614751e7537f125133/wasabi-1.1.3-py3-none-any.whl", hash = "sha256:f76e16e8f7e79f8c4c8be49b4024ac725713ab10cd7f19350ad18a8e3f71728c", size = 27880, upload-time = "2024-05-31T16:56:16.699Z" }, +] + +[[package]] +name = "weasel" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cloudpathlib" }, + { name = "confection" }, + { name = "httpx" }, + { name = "packaging" }, + { name = "pydantic" }, + { name = "smart-open" }, + { name = "srsly" }, + { name = "typer" }, + { name = "wasabi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/e5/e272bb9a045105a1fdf4b798d8086f5932a178f4d738f17a74f5c9e0ae9a/weasel-1.0.0.tar.gz", hash = "sha256:7b129b44c90cc543b760532974ca1e4eb30dad2aa2026f57bdce66354ae610fc", size = 38682, upload-time = "2026-03-20T08:10:25.266Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/07/57ebf7a6798b016c064bd0ca81b4c6a99daa4dc377b898bc7b41eb6b5af0/weasel-1.0.0-py3-none-any.whl", hash = "sha256:89518acee027f49d743126c3502d35e6dd14f5768be5c37c9af47c171b6005cc", size = 50713, upload-time = "2026-03-20T08:10:23.637Z" }, +] + +[[package]] +name = "websockets" +version = "16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/04/24/4b2031d72e840ce4c1ccb255f693b15c334757fc50023e4db9537080b8c4/websockets-16.0.tar.gz", hash = "sha256:5f6261a5e56e8d5c42a4497b364ea24d94d9563e8fbd44e78ac40879c60179b5", size = 179346, upload-time = "2026-01-10T09:23:47.181Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/7b/bac442e6b96c9d25092695578dda82403c77936104b5682307bd4deb1ad4/websockets-16.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:71c989cbf3254fbd5e84d3bff31e4da39c43f884e64f2551d14bb3c186230f00", size = 177365, upload-time = "2026-01-10T09:22:46.787Z" }, + { url = "https://files.pythonhosted.org/packages/b0/fe/136ccece61bd690d9c1f715baaeefd953bb2360134de73519d5df19d29ca/websockets-16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8b6e209ffee39ff1b6d0fa7bfef6de950c60dfb91b8fcead17da4ee539121a79", size = 175038, upload-time = "2026-01-10T09:22:47.999Z" }, + { url = "https://files.pythonhosted.org/packages/40/1e/9771421ac2286eaab95b8575b0cb701ae3663abf8b5e1f64f1fd90d0a673/websockets-16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:86890e837d61574c92a97496d590968b23c2ef0aeb8a9bc9421d174cd378ae39", size = 175328, upload-time = "2026-01-10T09:22:49.809Z" }, + { url = "https://files.pythonhosted.org/packages/18/29/71729b4671f21e1eaa5d6573031ab810ad2936c8175f03f97f3ff164c802/websockets-16.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9b5aca38b67492ef518a8ab76851862488a478602229112c4b0d58d63a7a4d5c", size = 184915, upload-time = "2026-01-10T09:22:51.071Z" }, + { url = "https://files.pythonhosted.org/packages/97/bb/21c36b7dbbafc85d2d480cd65df02a1dc93bf76d97147605a8e27ff9409d/websockets-16.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e0334872c0a37b606418ac52f6ab9cfd17317ac26365f7f65e203e2d0d0d359f", size = 186152, upload-time = "2026-01-10T09:22:52.224Z" }, + { url = "https://files.pythonhosted.org/packages/4a/34/9bf8df0c0cf88fa7bfe36678dc7b02970c9a7d5e065a3099292db87b1be2/websockets-16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a0b31e0b424cc6b5a04b8838bbaec1688834b2383256688cf47eb97412531da1", size = 185583, upload-time = "2026-01-10T09:22:53.443Z" }, + { url = "https://files.pythonhosted.org/packages/47/88/4dd516068e1a3d6ab3c7c183288404cd424a9a02d585efbac226cb61ff2d/websockets-16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:485c49116d0af10ac698623c513c1cc01c9446c058a4e61e3bf6c19dff7335a2", size = 184880, upload-time = "2026-01-10T09:22:55.033Z" }, + { url = "https://files.pythonhosted.org/packages/91/d6/7d4553ad4bf1c0421e1ebd4b18de5d9098383b5caa1d937b63df8d04b565/websockets-16.0-cp312-cp312-win32.whl", hash = "sha256:eaded469f5e5b7294e2bdca0ab06becb6756ea86894a47806456089298813c89", size = 178261, upload-time = "2026-01-10T09:22:56.251Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f0/f3a17365441ed1c27f850a80b2bc680a0fa9505d733fe152fdf5e98c1c0b/websockets-16.0-cp312-cp312-win_amd64.whl", hash = "sha256:5569417dc80977fc8c2d43a86f78e0a5a22fee17565d78621b6bb264a115d4ea", size = 178693, upload-time = "2026-01-10T09:22:57.478Z" }, + { url = "https://files.pythonhosted.org/packages/cc/9c/baa8456050d1c1b08dd0ec7346026668cbc6f145ab4e314d707bb845bf0d/websockets-16.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:878b336ac47938b474c8f982ac2f7266a540adc3fa4ad74ae96fea9823a02cc9", size = 177364, upload-time = "2026-01-10T09:22:59.333Z" }, + { url = "https://files.pythonhosted.org/packages/7e/0c/8811fc53e9bcff68fe7de2bcbe75116a8d959ac699a3200f4847a8925210/websockets-16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:52a0fec0e6c8d9a784c2c78276a48a2bdf099e4ccc2a4cad53b27718dbfd0230", size = 175039, upload-time = "2026-01-10T09:23:01.171Z" }, + { url = "https://files.pythonhosted.org/packages/aa/82/39a5f910cb99ec0b59e482971238c845af9220d3ab9fa76dd9162cda9d62/websockets-16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e6578ed5b6981005df1860a56e3617f14a6c307e6a71b4fff8c48fdc50f3ed2c", size = 175323, upload-time = "2026-01-10T09:23:02.341Z" }, + { url = "https://files.pythonhosted.org/packages/bd/28/0a25ee5342eb5d5f297d992a77e56892ecb65e7854c7898fb7d35e9b33bd/websockets-16.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:95724e638f0f9c350bb1c2b0a7ad0e83d9cc0c9259f3ea94e40d7b02a2179ae5", size = 184975, upload-time = "2026-01-10T09:23:03.756Z" }, + { url = "https://files.pythonhosted.org/packages/f9/66/27ea52741752f5107c2e41fda05e8395a682a1e11c4e592a809a90c6a506/websockets-16.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0204dc62a89dc9d50d682412c10b3542d748260d743500a85c13cd1ee4bde82", size = 186203, upload-time = "2026-01-10T09:23:05.01Z" }, + { url = "https://files.pythonhosted.org/packages/37/e5/8e32857371406a757816a2b471939d51c463509be73fa538216ea52b792a/websockets-16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:52ac480f44d32970d66763115edea932f1c5b1312de36df06d6b219f6741eed8", size = 185653, upload-time = "2026-01-10T09:23:06.301Z" }, + { url = "https://files.pythonhosted.org/packages/9b/67/f926bac29882894669368dc73f4da900fcdf47955d0a0185d60103df5737/websockets-16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6e5a82b677f8f6f59e8dfc34ec06ca6b5b48bc4fcda346acd093694cc2c24d8f", size = 184920, upload-time = "2026-01-10T09:23:07.492Z" }, + { url = "https://files.pythonhosted.org/packages/3c/a1/3d6ccdcd125b0a42a311bcd15a7f705d688f73b2a22d8cf1c0875d35d34a/websockets-16.0-cp313-cp313-win32.whl", hash = "sha256:abf050a199613f64c886ea10f38b47770a65154dc37181bfaff70c160f45315a", size = 178255, upload-time = "2026-01-10T09:23:09.245Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ae/90366304d7c2ce80f9b826096a9e9048b4bb760e44d3b873bb272cba696b/websockets-16.0-cp313-cp313-win_amd64.whl", hash = "sha256:3425ac5cf448801335d6fdc7ae1eb22072055417a96cc6b31b3861f455fbc156", size = 178689, upload-time = "2026-01-10T09:23:10.483Z" }, + { url = "https://files.pythonhosted.org/packages/f3/1d/e88022630271f5bd349ed82417136281931e558d628dd52c4d8621b4a0b2/websockets-16.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8cc451a50f2aee53042ac52d2d053d08bf89bcb31ae799cb4487587661c038a0", size = 177406, upload-time = "2026-01-10T09:23:12.178Z" }, + { url = "https://files.pythonhosted.org/packages/f2/78/e63be1bf0724eeb4616efb1ae1c9044f7c3953b7957799abb5915bffd38e/websockets-16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:daa3b6ff70a9241cf6c7fc9e949d41232d9d7d26fd3522b1ad2b4d62487e9904", size = 175085, upload-time = "2026-01-10T09:23:13.511Z" }, + { url = "https://files.pythonhosted.org/packages/bb/f4/d3c9220d818ee955ae390cf319a7c7a467beceb24f05ee7aaaa2414345ba/websockets-16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fd3cb4adb94a2a6e2b7c0d8d05cb94e6f1c81a0cf9dc2694fb65c7e8d94c42e4", size = 175328, upload-time = "2026-01-10T09:23:14.727Z" }, + { url = "https://files.pythonhosted.org/packages/63/bc/d3e208028de777087e6fb2b122051a6ff7bbcca0d6df9d9c2bf1dd869ae9/websockets-16.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:781caf5e8eee67f663126490c2f96f40906594cb86b408a703630f95550a8c3e", size = 185044, upload-time = "2026-01-10T09:23:15.939Z" }, + { url = "https://files.pythonhosted.org/packages/ad/6e/9a0927ac24bd33a0a9af834d89e0abc7cfd8e13bed17a86407a66773cc0e/websockets-16.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:caab51a72c51973ca21fa8a18bd8165e1a0183f1ac7066a182ff27107b71e1a4", size = 186279, upload-time = "2026-01-10T09:23:17.148Z" }, + { url = "https://files.pythonhosted.org/packages/b9/ca/bf1c68440d7a868180e11be653c85959502efd3a709323230314fda6e0b3/websockets-16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19c4dc84098e523fd63711e563077d39e90ec6702aff4b5d9e344a60cb3c0cb1", size = 185711, upload-time = "2026-01-10T09:23:18.372Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f8/fdc34643a989561f217bb477cbc47a3a07212cbda91c0e4389c43c296ebf/websockets-16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a5e18a238a2b2249c9a9235466b90e96ae4795672598a58772dd806edc7ac6d3", size = 184982, upload-time = "2026-01-10T09:23:19.652Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d1/574fa27e233764dbac9c52730d63fcf2823b16f0856b3329fc6268d6ae4f/websockets-16.0-cp314-cp314-win32.whl", hash = "sha256:a069d734c4a043182729edd3e9f247c3b2a4035415a9172fd0f1b71658a320a8", size = 177915, upload-time = "2026-01-10T09:23:21.458Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f1/ae6b937bf3126b5134ce1f482365fde31a357c784ac51852978768b5eff4/websockets-16.0-cp314-cp314-win_amd64.whl", hash = "sha256:c0ee0e63f23914732c6d7e0cce24915c48f3f1512ec1d079ed01fc629dab269d", size = 178381, upload-time = "2026-01-10T09:23:22.715Z" }, + { url = "https://files.pythonhosted.org/packages/06/9b/f791d1db48403e1f0a27577a6beb37afae94254a8c6f08be4a23e4930bc0/websockets-16.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a35539cacc3febb22b8f4d4a99cc79b104226a756aa7400adc722e83b0d03244", size = 177737, upload-time = "2026-01-10T09:23:24.523Z" }, + { url = "https://files.pythonhosted.org/packages/bd/40/53ad02341fa33b3ce489023f635367a4ac98b73570102ad2cdd770dacc9a/websockets-16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b784ca5de850f4ce93ec85d3269d24d4c82f22b7212023c974c401d4980ebc5e", size = 175268, upload-time = "2026-01-10T09:23:25.781Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/6158d4e459b984f949dcbbb0c5d270154c7618e11c01029b9bbd1bb4c4f9/websockets-16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:569d01a4e7fba956c5ae4fc988f0d4e187900f5497ce46339c996dbf24f17641", size = 175486, upload-time = "2026-01-10T09:23:27.033Z" }, + { url = "https://files.pythonhosted.org/packages/e5/2d/7583b30208b639c8090206f95073646c2c9ffd66f44df967981a64f849ad/websockets-16.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:50f23cdd8343b984957e4077839841146f67a3d31ab0d00e6b824e74c5b2f6e8", size = 185331, upload-time = "2026-01-10T09:23:28.259Z" }, + { url = "https://files.pythonhosted.org/packages/45/b0/cce3784eb519b7b5ad680d14b9673a31ab8dcb7aad8b64d81709d2430aa8/websockets-16.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:152284a83a00c59b759697b7f9e9cddf4e3c7861dd0d964b472b70f78f89e80e", size = 186501, upload-time = "2026-01-10T09:23:29.449Z" }, + { url = "https://files.pythonhosted.org/packages/19/60/b8ebe4c7e89fb5f6cdf080623c9d92789a53636950f7abacfc33fe2b3135/websockets-16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bc59589ab64b0022385f429b94697348a6a234e8ce22544e3681b2e9331b5944", size = 186062, upload-time = "2026-01-10T09:23:31.368Z" }, + { url = "https://files.pythonhosted.org/packages/88/a8/a080593f89b0138b6cba1b28f8df5673b5506f72879322288b031337c0b8/websockets-16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32da954ffa2814258030e5a57bc73a3635463238e797c7375dc8091327434206", size = 185356, upload-time = "2026-01-10T09:23:32.627Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b6/b9afed2afadddaf5ebb2afa801abf4b0868f42f8539bfe4b071b5266c9fe/websockets-16.0-cp314-cp314t-win32.whl", hash = "sha256:5a4b4cc550cb665dd8a47f868c8d04c8230f857363ad3c9caf7a0c3bf8c61ca6", size = 178085, upload-time = "2026-01-10T09:23:33.816Z" }, + { url = "https://files.pythonhosted.org/packages/9f/3e/28135a24e384493fa804216b79a6a6759a38cc4ff59118787b9fb693df93/websockets-16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b14dc141ed6d2dde437cddb216004bcac6a1df0935d79656387bd41632ba0bbd", size = 178531, upload-time = "2026-01-10T09:23:35.016Z" }, + { url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" }, +] + +[[package]] +name = "wrapt" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/9f/06263fcd8ad6c405f05a3905fd7a84dd3176eb5ad46e44bccc0cd16348bb/wrapt-2.2.1.tar.gz", hash = "sha256:6744f504375775d7609c82c8d3d94af1c9a6f05586984536905908ba905277b9", size = 127620, upload-time = "2026-05-22T14:49:43.056Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/0c/bfae7b9401583b6d05938cd16dedc43857d96da2f8a3d50d78cc515bf6ff/wrapt-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3ffad790d9d11d8ecf9f17c4bb671a5b4089e4d8b575c46c5129597f41f836b0", size = 81021, upload-time = "2026-05-22T14:48:00.313Z" }, + { url = "https://files.pythonhosted.org/packages/26/58/80f6a6599f933f4caecc1cb3ee88a04faf81e8b9bddbd6109c688dd63e0f/wrapt-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:628f5220c7a904d5fc78f7075c8d7871433eb6d035c94728a22fdf85f193d2a8", size = 81692, upload-time = "2026-05-22T14:48:01.49Z" }, + { url = "https://files.pythonhosted.org/packages/17/93/fb357cc7847c58a8ae790be718903afa81a28d23e642c843dc4129e8a0b2/wrapt-2.2.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:61acce4257a9883669703c525447c5b4c392edf0f987ae77ec32668440158f0e", size = 169364, upload-time = "2026-05-22T14:48:02.791Z" }, + { url = "https://files.pythonhosted.org/packages/aa/0b/76b601ee309a8bd556af0eecb184394c20b3c49aa9c8e085aa1ffacc2568/wrapt-2.2.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:727ab4244622cd6ad2390f322642090c877d2e83a608d2653a7643ae5368d926", size = 171079, upload-time = "2026-05-22T14:48:04.22Z" }, + { url = "https://files.pythonhosted.org/packages/cd/87/ee3f32d5658e3e26d3e0e457922b47a36dd3bfbdfee7f97bb3e802344a66/wrapt-2.2.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:03df9ebed4c73ab93fa8c07e3d41d818dfca1852b15731a3de59457b27814624", size = 160205, upload-time = "2026-05-22T14:48:05.553Z" }, + { url = "https://files.pythonhosted.org/packages/b1/d0/ae2fd64277a67f5d7bffcf2d05eea1e476263fb2a072baf0b0129ab85984/wrapt-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0d9ff006f420b2ec8296aa56ade43ea7da3e997e85769f0aafc5e0661aacb710", size = 168922, upload-time = "2026-05-22T14:48:07.132Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f3/2d541a060c5bbafb9400bca4917e4d78bfd1f239f404782c86831a8f6b29/wrapt-2.2.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:844c858fc3bb7eacc0ba8efa904935d16aac6a4470948ad1e7e55c9f5a2a665f", size = 158388, upload-time = "2026-05-22T14:48:08.629Z" }, + { url = "https://files.pythonhosted.org/packages/1d/68/8d92c8800c57e93cb116ae9e9d6cbafc34fade5ee9f9107b6f203fb4dc35/wrapt-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:87bacdaf225117a342a20d9c03438d701c02112f6e3f351ce9b7f32354f14797", size = 167682, upload-time = "2026-05-22T14:48:10.042Z" }, + { url = "https://files.pythonhosted.org/packages/30/72/83ea3790ea352439442349388e29ff07b76e0686265f9088bbb505d1608d/wrapt-2.2.1-cp312-cp312-win32.whl", hash = "sha256:2f8c90c8afde51969487be4e1343ae049b268854877d415c2510baf833775052", size = 77857, upload-time = "2026-05-22T14:48:11.782Z" }, + { url = "https://files.pythonhosted.org/packages/ef/cb/99450668dd3502d62a54a1c8aa56e44f34cb8c1261b381cfe2e7926c3b75/wrapt-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ce32763ac31ce94fe9aada947e479b1975012bff166da409b4b9e4e376cf7e5", size = 80825, upload-time = "2026-05-22T14:48:13.046Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3a/87512881be64e743f9ee4c66f4cbe8e884974bef2a5989af71f999653ac7/wrapt-2.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d1b4d0e0c2119587a31f5c029abd547e0c81d93b89d394566fe1588659eb579", size = 79087, upload-time = "2026-05-22T14:48:14.323Z" }, + { url = "https://files.pythonhosted.org/packages/88/d1/a1b08f8f4fac8cbb156fa51cf64ee2c7f7f74f9875ba3cf70b3c58368694/wrapt-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d2beb1c7cab10603aecdc42f8edd6ff013f9a32e4543474e38e6b77ce9975aeb", size = 80831, upload-time = "2026-05-22T14:48:15.598Z" }, + { url = "https://files.pythonhosted.org/packages/54/ce/57890814991446a845e09b3445ce8b694f27eb0577004f2c2a36a9772ed4/wrapt-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e0cb7e4dd71f4c32e5e84843cd3c4cd65dda034314004bbe1d7f99af2426ab80", size = 81375, upload-time = "2026-05-22T14:48:17.071Z" }, + { url = "https://files.pythonhosted.org/packages/38/65/08d7a6c76ac4493bdb668205ee9c1de1bd5daca61717c3e9aa49b4c01499/wrapt-2.2.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:95821352042722cd9f1108874579a47989d0a7e12a37d87d2fc4af20fd99ab8a", size = 167417, upload-time = "2026-05-22T14:48:18.303Z" }, + { url = "https://files.pythonhosted.org/packages/62/ce/f1ccbee7a1bfe5cdc6b3da6bab4b45713d628b9294da32a39f563d648140/wrapt-2.2.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:abd621552ede77c4c69be7fac44ba911225b0c812b6ba604e5964cf98085b474", size = 166948, upload-time = "2026-05-22T14:48:19.768Z" }, + { url = "https://files.pythonhosted.org/packages/86/2a/f85d48d1cd4869aee6704028d257d740a47c1c467b457ce396b4b5b55d07/wrapt-2.2.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e3677c7146ce694874941ba82b57092cc4875445aadf29d72807351023105143", size = 158148, upload-time = "2026-05-22T14:48:21.96Z" }, + { url = "https://files.pythonhosted.org/packages/fe/5c/93939ad11d4a12358ab1aab219a2ef5efa5612e0db6b9fc65af8af1a891b/wrapt-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9a5934eaea872e17936b5f45501eba5ab0bce9a74122e172b663d7c28c459c4a", size = 165905, upload-time = "2026-05-22T14:48:23.373Z" }, + { url = "https://files.pythonhosted.org/packages/e0/22/b8c2aa89862ff58605934d7abf4b70e6a5a1c33df96656f49035ccdf1c8a/wrapt-2.2.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f5b9daf6b629fce418e0cc3dd0436eac045188fa35deadb7a7f3941d5b8203f9", size = 156712, upload-time = "2026-05-22T14:48:24.767Z" }, + { url = "https://files.pythonhosted.org/packages/5d/78/bf00a7b02239c12bb02ddcc3c0b971bfcc36e578c5a44f1ccfef5b458545/wrapt-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f53ac9f3ef573326d009ed809beff4efcac6451931c2b8132586da4b9e53ff31", size = 166560, upload-time = "2026-05-22T14:48:26.83Z" }, + { url = "https://files.pythonhosted.org/packages/fe/93/6390ca9c5b787683cef588d04f57c8d41b9a2323b5597a65f18638c90ef2/wrapt-2.2.1-cp313-cp313-win32.whl", hash = "sha256:1ffa9cfd4bdb581539951b14ae661ff20ed0c3599b3e911a131ee0ec5ac11337", size = 77817, upload-time = "2026-05-22T14:48:28.221Z" }, + { url = "https://files.pythonhosted.org/packages/97/73/ce10f0e71c0cfaa1a65faadb8efd4852028b3bb9ba28932b8889df769d38/wrapt-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:368eac1e20fd0bb03dd3cc42bf9887154c3861b60989389ccb5fac032617d215", size = 80736, upload-time = "2026-05-22T14:48:30.139Z" }, + { url = "https://files.pythonhosted.org/packages/c7/4c/89f4a6818fafbbd840330e4fa3873073e1bfc166133a64cac7f8fde7a5e3/wrapt-2.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:c754dafdf5aaf0b401b644a90a30046929a0dd1a536e0ff0ec959a59155d9c7f", size = 79099, upload-time = "2026-05-22T14:48:31.405Z" }, + { url = "https://files.pythonhosted.org/packages/bf/f2/9a8741c46f8c208ac0a45b25ba170bcb4fb72a2781d5fb97dbd7b6be73cb/wrapt-2.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ed928d0fda15fc0adc8d13305c8b3c0f2fba5b0669950c9e6d019d9162a3b3e8", size = 82802, upload-time = "2026-05-22T14:48:33.307Z" }, + { url = "https://files.pythonhosted.org/packages/9c/0d/e9c855716a3705eef1416456bdf062b60620726fdc59428ff670fc3c60dc/wrapt-2.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fafb4e739e43544d12cb4abd1605fd4683b6ca6a9ad682b7fd8f4d21973eafa8", size = 83329, upload-time = "2026-05-22T14:48:34.593Z" }, + { url = "https://files.pythonhosted.org/packages/3b/d6/a88f1c13112b7831adac75cea65d8310e0d696d570c8961844c90a57b865/wrapt-2.2.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:74d6a0c31472fe5d814917266b9f46495d7c61ed890af08b468acea92fb89a8d", size = 202937, upload-time = "2026-05-22T14:48:35.859Z" }, + { url = "https://files.pythonhosted.org/packages/42/65/e29d54aef06a4d898a5b8a25589a0b3769bde454f922fad8f6f89fbfb650/wrapt-2.2.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab5be648d5a0b86b7438864f8df3c705a65cef35a2fd3e5561e3e203167e0f27", size = 209997, upload-time = "2026-05-22T14:48:38.153Z" }, + { url = "https://files.pythonhosted.org/packages/2a/91/e4454263516cf0e12640912fbca9a83654e424f0a6ddb79f5cd7ce14bf33/wrapt-2.2.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9d8f204c8e3a8bf9ece17e0a83d137fd807440977f8a5e762d59306795011440", size = 194856, upload-time = "2026-05-22T14:48:39.69Z" }, + { url = "https://files.pythonhosted.org/packages/de/d0/fe0ee202286afdf4a7f77dd29f195703145764d572aec209c5086e57d924/wrapt-2.2.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d047f6498c973874ba08ac3f97c69a2c4b2211c8de6f4c205f75cb1c9522596e", size = 205654, upload-time = "2026-05-22T14:48:43.456Z" }, + { url = "https://files.pythonhosted.org/packages/23/b6/87d860dfc6460c246af70b1fd5c8b76df77571b42a493459423ded94fd7d/wrapt-2.2.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:7a4fdb9326aab4a5a477a1640e5ad786a8495901009d7e7b038371edd23a9d2b", size = 192206, upload-time = "2026-05-22T14:48:44.858Z" }, + { url = "https://files.pythonhosted.org/packages/df/46/3eea8cde077d985f239a38c0257087b8064fd9ee9b1a99e282d2c86da4ef/wrapt-2.2.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c8cc5094b08abeae52da9c73c8a32003623be691a5193df2f4e3eac3d557c394", size = 198428, upload-time = "2026-05-22T14:48:46.319Z" }, + { url = "https://files.pythonhosted.org/packages/18/dc/b927ee9c7fc67adc3a5658f246a0d275425eb840ba36e7b702e70f18bde8/wrapt-2.2.1-cp313-cp313t-win32.whl", hash = "sha256:9907a4402ab6db12b7077a0ea5d7a4d028ecb22c8eee2b53527080d347cd1562", size = 79448, upload-time = "2026-05-22T14:48:47.901Z" }, + { url = "https://files.pythonhosted.org/packages/ec/b3/fd30b473fe498c70e6b9a5f328b8d3fbaf1b8c3c481465f59724bba8eb70/wrapt-2.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:5590d63f5243251641cf543009b4c9314a79d0598fdb8a8e4cfc918494536c53", size = 83021, upload-time = "2026-05-22T14:48:49.201Z" }, + { url = "https://files.pythonhosted.org/packages/ee/f3/96c39153a8737a6e9aa85adef254ac4195bea3f2d24efc60472ccc3c9e2e/wrapt-2.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:c318a64b53d97b841d7b5e637517e50a27be64bc695128422953d4b21710954e", size = 80295, upload-time = "2026-05-22T14:48:50.479Z" }, + { url = "https://files.pythonhosted.org/packages/0a/a3/11d7f34ebbf3231bc907a3e6d5ee051b14d034c1bc7b65a97d5cc00516df/wrapt-2.2.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6f56a647e4eaf5f0ca40330fb070f566bdf9f7b0db89a1af20d71c28dcd7a0ab", size = 80879, upload-time = "2026-05-22T14:48:51.802Z" }, + { url = "https://files.pythonhosted.org/packages/13/3c/b74cfd984cef560b900fb1a727af20352d89e1f06bf2e1114dd3f00f5f5a/wrapt-2.2.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:64b7deeda4b70408e382328d8bbe52a256fe9bc63ae3db86d804608367e5422c", size = 81462, upload-time = "2026-05-22T14:48:53.18Z" }, + { url = "https://files.pythonhosted.org/packages/15/a3/7c8f704b8dc07dfe0a5d01c2edbfd88317aa8e5e3fa7c743eb7a085ae767/wrapt-2.2.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b9cf53ba90717db2e292401de290776c498d4bbfb0d4a559ca2895db8b9dcb5c", size = 167251, upload-time = "2026-05-22T14:48:54.562Z" }, + { url = "https://files.pythonhosted.org/packages/80/85/a34d1888d97247da6c2ff6118c3a721c73ed8cc4dd198c00208bb73b6f80/wrapt-2.2.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cf3638274ab9d9b724c9baa0b4c04e132cd6faefb78b4dd3dd1a02a4bdaad41e", size = 166316, upload-time = "2026-05-22T14:48:56.065Z" }, + { url = "https://files.pythonhosted.org/packages/e9/d7/72ffaeb01eebc704afe3fb99e840480f4bda45f0fa66e3381b6a39251c8f/wrapt-2.2.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aed9658797d0b45d6c49adcfc6b41f66e6f2d0c6de3ec79e16cf4b1855df240f", size = 157952, upload-time = "2026-05-22T14:48:57.924Z" }, + { url = "https://files.pythonhosted.org/packages/24/5b/36f5d6b024e4edfdd90b140742d11ebcf7836daf5c9daf326c55c24db412/wrapt-2.2.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1d676ee388bc42a04d56dd7deb5605244dac2e35cc2fadbb43c9fa25bbd93508", size = 166130, upload-time = "2026-05-22T14:48:59.384Z" }, + { url = "https://files.pythonhosted.org/packages/81/06/9296d9e97bfdef5483dfcc859d57b095b257144b2bc5300ab521e06f4bc7/wrapt-2.2.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e395f7bc31851ef9b612050368cb446e9bc14cd7454b025018980349caf25ae5", size = 156604, upload-time = "2026-05-22T14:49:00.921Z" }, + { url = "https://files.pythonhosted.org/packages/53/37/16953929ed6776175720e58fc966e779926d8d71e2c7b2273230590ca71f/wrapt-2.2.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5f1845c2a8cc1180ccccfa45785dd06f562730d19ef75be180334254012b6283", size = 166007, upload-time = "2026-05-22T14:49:02.332Z" }, + { url = "https://files.pythonhosted.org/packages/b9/73/20ee58c0612dae7c31131a7095345812ed2c7b389019e175f68cde34e5b4/wrapt-2.2.1-cp314-cp314-win32.whl", hash = "sha256:436addbc4bb4fc0a88c702577f51195d7d73683a7f3e0e5b253d8404d7847243", size = 78327, upload-time = "2026-05-22T14:49:03.722Z" }, + { url = "https://files.pythonhosted.org/packages/22/b3/ef7c3295d02e0448a71c639a36a057f46d524d057c9486291a7a3039e65c/wrapt-2.2.1-cp314-cp314-win_amd64.whl", hash = "sha256:50972a1d974ea07725a7f6b1cec5f8759008afd030a0024843ebe7d52de47f2b", size = 81144, upload-time = "2026-05-22T14:49:05.093Z" }, + { url = "https://files.pythonhosted.org/packages/ac/dc/7bdf336953f99f4ceb0a584bb8870e42c8f26f93ea10c87834dad62f1668/wrapt-2.2.1-cp314-cp314-win_arm64.whl", hash = "sha256:1c9934ea5d92957e3cd0adbc0845539dccfd62710ebe16195a8c66c53954db36", size = 79569, upload-time = "2026-05-22T14:49:06.413Z" }, + { url = "https://files.pythonhosted.org/packages/6a/6d/6dfae80150ff1919c356d1dd528f049bcdfaae29b4d284bc957e022caef4/wrapt-2.2.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:17de18fc12cea55b8a9587314cb830573e37fb33b247a7515696350863714188", size = 82892, upload-time = "2026-05-22T14:49:07.925Z" }, + { url = "https://files.pythonhosted.org/packages/82/7b/4e34766a7d7804ffce9e71befe47e9b3225dc350c49c94493c4ab39fd3a5/wrapt-2.2.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a9dec1aca52dddde7df94818310fa2fe79739c8f385b2014c4cb1035f5508199", size = 83333, upload-time = "2026-05-22T14:49:09.257Z" }, + { url = "https://files.pythonhosted.org/packages/9d/57/0b34db3e8de44ccfece62d7b337abd1631dd810f5adc5f3db571727836b5/wrapt-2.2.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:69f2e9244542cb34dd59c7f073445b9e54ad9f3fce8d93606c368a1b499fc413", size = 202899, upload-time = "2026-05-22T14:49:10.572Z" }, + { url = "https://files.pythonhosted.org/packages/e5/45/ac0c459f154b99d92789a6cba7ca727185b83513b986f8ec7fe2aacddcbf/wrapt-2.2.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d83966dc7f4f45e8b97b5933685ac2e6e67fc0e19246ea314bceb9a8970c956", size = 209986, upload-time = "2026-05-22T14:49:12.229Z" }, + { url = "https://files.pythonhosted.org/packages/b7/e4/77e37ff33ad018fa81ade52c25fa327b80b56f81d734279a63614fcb4cbc/wrapt-2.2.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:78b0aa6bfb7be8deed0ab23e7aa028cc5210c29bc2d32a04d52b50e517a7307e", size = 194893, upload-time = "2026-05-22T14:49:14.139Z" }, + { url = "https://files.pythonhosted.org/packages/dd/9d/7ea651d1ab032fc5fa222fbec91d0f8a1397f6ae04ebb93fa7219aa921d7/wrapt-2.2.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:05d5cb74d1b232ec8cfa130a8f900708699ff2491d97b8f85a4cdc5996294b85", size = 205636, upload-time = "2026-05-22T14:49:15.714Z" }, + { url = "https://files.pythonhosted.org/packages/09/af/8e88031a701275b9085c54e64bc88c0b1cd55c77eadd400691c371cd76c4/wrapt-2.2.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f6518b94edb9150452e9aba08027d4cc293433753ec1fbefb4629a21cbc74181", size = 192267, upload-time = "2026-05-22T14:49:17.283Z" }, + { url = "https://files.pythonhosted.org/packages/bf/a8/e657ca876b06710194f243d81c4b0896ade646e244bdbec2d87c8c56a8bd/wrapt-2.2.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ed55af48b3eb28f43228ca2306788892bcb629eb2b5c4876e2a3659872c2f17a", size = 198378, upload-time = "2026-05-22T14:49:18.785Z" }, + { url = "https://files.pythonhosted.org/packages/c8/59/822efe4ea722a3961331bfa35b7d90937790d2c20f0616de1997ccc3aebd/wrapt-2.2.1-cp314-cp314t-win32.whl", hash = "sha256:2e08688ab16525897da6589d56d0aebaf417bbe91c2d8e3b96203b1efa596e85", size = 80226, upload-time = "2026-05-22T14:49:20.264Z" }, + { url = "https://files.pythonhosted.org/packages/ab/31/2a7dc5f6abb2fca0b6e1610e120419f603650aceb4f1d3ac4cae0354e162/wrapt-2.2.1-cp314-cp314t-win_amd64.whl", hash = "sha256:fd0135d34387f5fd087d9be368ea77ea89cf2451dc1cd1c622d35021bcb3ab50", size = 83835, upload-time = "2026-05-22T14:49:21.634Z" }, + { url = "https://files.pythonhosted.org/packages/9f/c0/782b86e28d1ceebeb74cccea12d2cd3d2ba0bd68e3dec20b1bc5873f6127/wrapt-2.2.1-cp314-cp314t-win_arm64.whl", hash = "sha256:f70db64e8266d7c45d3b735f2e08eeb434b5e03da9a479ae42b2e2e486a21a00", size = 80722, upload-time = "2026-05-22T14:49:23.59Z" }, + { url = "https://files.pythonhosted.org/packages/53/46/29ac9daf11a86c22a8c38cd9236c62928ccae83f7ceb06bd3b0467cf9d05/wrapt-2.2.1-py3-none-any.whl", hash = "sha256:3aafea2975caef8ca49400640dde02cc7426e798f24870ed01f490bc3cffd32f", size = 61000, upload-time = "2026-05-22T14:49:41.593Z" }, +] + +[[package]] +name = "xxhash" +version = "3.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/2f/e183a1b407002f5af81822bee18b61cdb94b8670208ef34734d8d2b8ebe9/xxhash-3.7.0.tar.gz", hash = "sha256:6cc4eefbb542a5d6ffd6d70ea9c502957c925e800f998c5630ecc809d6702bae", size = 82022, upload-time = "2026-04-25T11:10:32.553Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/8a/51a14cdef4728c6c2337db8a7d8704422cc65676d9199d77215464c880af/xxhash-3.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:082c87bfdd2b9f457606c7a4a53457f4c4b48b0cdc48de0277f4349d79bb3d7a", size = 33357, upload-time = "2026-04-25T11:06:20.44Z" }, + { url = "https://files.pythonhosted.org/packages/b9/1b/0c2c933809421ffd9bf42b59315552c143c755db5d9a816b2f1ae273e884/xxhash-3.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5e7ce913b61f35b0c1c839a49ac9c8e75dd8d860150688aed353b0ce1bf409d8", size = 30869, upload-time = "2026-04-25T11:06:21.989Z" }, + { url = "https://files.pythonhosted.org/packages/03/a8/89d5fdd6ee12d70ba99451de46dd0e8010167468dcd913ec855653f4dd50/xxhash-3.7.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3beb1de3b1e9694fcdd853e570ee64c631c7062435d2f8c69c1adf809bc086f0", size = 194100, upload-time = "2026-04-25T11:06:23.586Z" }, + { url = "https://files.pythonhosted.org/packages/87/ee/2f9f2ed993e77206d1e66991290a1ebe22e843351ca3ebec8e49e01ba186/xxhash-3.7.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3e7b689c3bce16699efcf736066f5c6cc4472c3840fe4b22bd8279daf4abdac", size = 212977, upload-time = "2026-04-25T11:06:25.019Z" }, + { url = "https://files.pythonhosted.org/packages/de/60/5a91644615a9e9d4e42c2e9925f1908e3a24e4e691d9de7340d565bea024/xxhash-3.7.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a6545e6b409e3d5cbafc850fb84c55a1ca26ed15a6b11e3bf07a0e0cd84517c8", size = 236373, upload-time = "2026-04-25T11:06:26.482Z" }, + { url = "https://files.pythonhosted.org/packages/22/c0/f3a9384eaaed9d14d4d062a5d953aa0da489bfe9747877aa994caa87cd0b/xxhash-3.7.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:31ab1461c77a11461d703c88eb949e132a1c6515933cf675d97ec680f4bd18de", size = 212229, upload-time = "2026-04-25T11:06:28.065Z" }, + { url = "https://files.pythonhosted.org/packages/2e/67/02f07a9fd79726804190f2172c4894c3ed9a4ebccaca05653c84beb58025/xxhash-3.7.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7c4d596b7676f811172687ec567cbafb9e4dea2f9be1bbb4f622410cb7f40f40", size = 445462, upload-time = "2026-04-25T11:06:30.048Z" }, + { url = "https://files.pythonhosted.org/packages/40/37/558f5a90c0672fc9b4402dc25d87ac5b7406616e8969430c9ca4e52ee74d/xxhash-3.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13805f0461cba0a857924e70ff91ae6d52d2598f79a884e788db80532614a4a1", size = 193932, upload-time = "2026-04-25T11:06:31.857Z" }, + { url = "https://files.pythonhosted.org/packages/d5/90/aaa09cd58661d32044dbbad7df55bbe22a623032b810e7ed3b8c569a2a6f/xxhash-3.7.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1d398f372496152f1c6933a33566373f8d1b37b98b8c9d608fa6edc0976f23b2", size = 284807, upload-time = "2026-04-25T11:06:33.697Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f3/53df3719ab127a02c174f0c1c74924fcd110866e89c966bc7909cfa8fa84/xxhash-3.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d610aa62cdb7d4d497740741772a24a794903bf3e79eaa51d2e800082abe11e5", size = 210445, upload-time = "2026-04-25T11:06:35.488Z" }, + { url = "https://files.pythonhosted.org/packages/72/33/d219975c0e8b6fa2eb9ccd486fe47e21bf1847985b878dd2fbc3126e0d5c/xxhash-3.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:073c23900a9fbf3d26616c17c830db28af9803677cd5b33aea3224d824111514", size = 241273, upload-time = "2026-04-25T11:06:37.24Z" }, + { url = "https://files.pythonhosted.org/packages/3e/50/49b1afe610eb3964cedcb90a4d4c3d46a261ee8669cbd4f060652619ae3c/xxhash-3.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:418a463c3e6a590c0cdc890f8be19adb44a8c8acd175ca5b2a6de77e61d0b386", size = 197950, upload-time = "2026-04-25T11:06:39.148Z" }, + { url = "https://files.pythonhosted.org/packages/c6/75/5f42a1a4c78717d906a4b6a140c6dbf837ab1f547a54d23c4e2903310936/xxhash-3.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:03f8ff4474ee61c845758ce00711d7087a770d77efb36f7e74a6e867301000b8", size = 210709, upload-time = "2026-04-25T11:06:40.958Z" }, + { url = "https://files.pythonhosted.org/packages/8a/85/237e446c25abced71e9c53d269f2cef5bab8a82b3f88a12e00c5368e7368/xxhash-3.7.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:44fba4a5f1d179b7ddc7b3dc40f56f9209046421679b57025d4d8821b376fd8d", size = 275345, upload-time = "2026-04-25T11:06:42.525Z" }, + { url = "https://files.pythonhosted.org/packages/62/34/c2c26c0a6a9cc739bc2a5f0ae03ba8b87deb12b8bce35f7ac495e790dc6d/xxhash-3.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31e3516a0f829d06ded4a2c0f3c7c5561993256bfa1c493975fb9dc7bfa828a1", size = 414056, upload-time = "2026-04-25T11:06:44.343Z" }, + { url = "https://files.pythonhosted.org/packages/a0/aa/5c58e9bc8071b8afd8dcf297ff362f723c4892168faba149f19904132bf4/xxhash-3.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b59ee2ac81de57771a09ecad09191e840a1d2fae1ef684208320591055768f83", size = 191485, upload-time = "2026-04-25T11:06:46.262Z" }, + { url = "https://files.pythonhosted.org/packages/d4/69/a929cf9d1e2e65a48b818cdce72cb6b69eab2e6877f21436d0a1942aff43/xxhash-3.7.0-cp312-cp312-win32.whl", hash = "sha256:74bbd92f8c7fcc397ba0a11bfdc106bc72ad7f11e3a60277753f87e7532b4d81", size = 30671, upload-time = "2026-04-25T11:06:48.039Z" }, + { url = "https://files.pythonhosted.org/packages/b9/1b/104b41a8947f4e1d4a66ce1e628eea752f37d1890bfd7453559ca7a3d950/xxhash-3.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:7bd7bc82dd4f185f28f35193c2e968ef46131628e3cac62f639dadf321cba4d1", size = 31514, upload-time = "2026-04-25T11:06:49.279Z" }, + { url = "https://files.pythonhosted.org/packages/98/a0/1fd0ea1f1b886d9e7c73f0397571e22333a7d79e31da6d7127c2a4a71d75/xxhash-3.7.0-cp312-cp312-win_arm64.whl", hash = "sha256:7d7148180ec99ba36585b42c8c5de25e9b40191613bc4be68909b4d25a77a852", size = 27761, upload-time = "2026-04-25T11:06:50.448Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ca/d5174b4c36d10f64d4ca7050563138c5a599efb01a765858ddefc9c1202a/xxhash-3.7.0-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:4b6d6b33f141158692bd4eafbb96edbc5aa0dabdb593a962db01a91983d4f8fa", size = 36813, upload-time = "2026-04-25T11:06:51.73Z" }, + { url = "https://files.pythonhosted.org/packages/41/d0/abc6c9d347ba1f1e1e1d98125d0881a0452c7f9a76a9dd03a7b5d2197f23/xxhash-3.7.0-cp313-cp313-android_21_x86_64.whl", hash = "sha256:845d347df254d6c619f616afa921331bada8614b8d373d58725c663ba97c3605", size = 35121, upload-time = "2026-04-25T11:06:53.048Z" }, + { url = "https://files.pythonhosted.org/packages/bf/11/4cc834eb3d79f2f2b3a6ef7324195208bcdfbdcf7534d2b17267aa5f3a8f/xxhash-3.7.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:fddbbb69a6fff4f421e7a0d1fa28f894b20112e9e3fab306af451e2dfd0e459b", size = 29624, upload-time = "2026-04-25T11:06:54.311Z" }, + { url = "https://files.pythonhosted.org/packages/23/83/e97d3e7b635fe73a1dfb1e91f805324dd6d930bb42041cbf18f183bc0b6d/xxhash-3.7.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:54876a4e45101cec2bf8f31a973cda073a23e2e108538dad224ba07f85f22487", size = 30638, upload-time = "2026-04-25T11:06:55.864Z" }, + { url = "https://files.pythonhosted.org/packages/f4/40/d84951d80c35db1f4c40a29a64a8520eea5d56e764c603906b4fe763580f/xxhash-3.7.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:0c72fe9c7e3d6dfd7f1e21e224a877917fa09c465694ba4e06464b9511b65544", size = 33323, upload-time = "2026-04-25T11:06:57.336Z" }, + { url = "https://files.pythonhosted.org/packages/89/cc/c7dc6558d97e9ab023f663d69ab28b340ed9bf4d2d94f2c259cf896bb354/xxhash-3.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a6d73a830b17ef49bc04e00182bd839164c1b3c59c127cd7c54fcb10c7ed8ee8", size = 33362, upload-time = "2026-04-25T11:06:58.656Z" }, + { url = "https://files.pythonhosted.org/packages/2a/6e/46b84017b1301d54091430353d4ad5901654a3e0871649877a416f7f1644/xxhash-3.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:91c3b07cf3362086d8f126c6aecd8e5e9396ad8b2f2219ea7e49a8250c318acd", size = 30874, upload-time = "2026-04-25T11:06:59.834Z" }, + { url = "https://files.pythonhosted.org/packages/df/5e/8f9158e3ab906ad3fec51e09b5ea0093e769f12207bfa42a368ca204e7ab/xxhash-3.7.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:50e879ebbac351c81565ca108db766d7832f5b8b6a5b14b8c0151f7190028e3d", size = 194185, upload-time = "2026-04-25T11:07:01.658Z" }, + { url = "https://files.pythonhosted.org/packages/f3/29/a804ded9f5d3d3758292678d23e7528b08fda7b7e750688d08b052322475/xxhash-3.7.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:921c14e93817842dd0dd9f372890a0f0c72e534650b6ab13c5be5cd0db11d47e", size = 213033, upload-time = "2026-04-25T11:07:03.606Z" }, + { url = "https://files.pythonhosted.org/packages/8b/91/1ce5a7d2fdc975267320e2c78fc1cecfe7ab735ccbcf6993ec5dd541cb2c/xxhash-3.7.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e64a7c9d7dfca3e0fafcbc5e455519090706a3e36e95d655cec3e04e79f95aaa", size = 236140, upload-time = "2026-04-25T11:07:05.396Z" }, + { url = "https://files.pythonhosted.org/packages/34/04/fd595a4fd8617b05fa27bd9b684ecb4985bfed27917848eea85d54036d06/xxhash-3.7.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2220af08163baf5fa36c2b8af079dc2cbe6e66ae061385267f9472362dfd53c6", size = 212291, upload-time = "2026-04-25T11:07:06.966Z" }, + { url = "https://files.pythonhosted.org/packages/03/fb/f1a379cbc372ae5b9f4ab36154c48a849ca6ebe3ac477067a57865bf3bc6/xxhash-3.7.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f14bb8b22a4a91325813e3d553b8963c10cf8c756cff65ee50c194431296c655", size = 445532, upload-time = "2026-04-25T11:07:08.525Z" }, + { url = "https://files.pythonhosted.org/packages/65/59/172424b79f8cfd4b6d8a122b2193e6b8ad4b11f7159bb3b6f9b3191329bb/xxhash-3.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:496736f86a9bedaf64b0dc70e3539d0766df01c71ea22032698e88f3f04a1ce9", size = 193990, upload-time = "2026-04-25T11:07:10.315Z" }, + { url = "https://files.pythonhosted.org/packages/b9/19/aeac22161d953f139f07ba5586cb4a17c5b7b6dff985122803bb12933500/xxhash-3.7.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0ff71596bd79816975b3de7130ab1ff4541410285a3c084584eeb1c8239996fd", size = 284876, upload-time = "2026-04-25T11:07:12.15Z" }, + { url = "https://files.pythonhosted.org/packages/77/d5/4fd0b59e7a02242953da05ff679fbb961b0a4368eac97a217e11dae110c1/xxhash-3.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1ad86695c19b1d46fe106925db3c7a37f16be37669dcf58dcc70a9dd6e324676", size = 210495, upload-time = "2026-04-25T11:07:13.952Z" }, + { url = "https://files.pythonhosted.org/packages/aa/fb/976a3165c728c7faf74aa1b5ab3cf6a85e6d731612894741840524c7d28c/xxhash-3.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:970f9f8c50961d639cbd0d988c96f80ddf66006de93641719282c4fe7a87c5e6", size = 241331, upload-time = "2026-04-25T11:07:15.557Z" }, + { url = "https://files.pythonhosted.org/packages/4a/2c/6763d5901d53ac9e6ba296e5717ae599025c9d268396e8faa8b4b0a8e0ac/xxhash-3.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5886ad85e9e347911783760a1d16cb6b393e8f9e3b52c982568226cb56927bdc", size = 198037, upload-time = "2026-04-25T11:07:17.563Z" }, + { url = "https://files.pythonhosted.org/packages/61/2b/876e722d533833f5f9a83473e6ba993e48745701096944e77bbecf29b2c3/xxhash-3.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:6e934bbae1e0ec74e27d5f0d7f37ef547ce5ff9f0a7e63fb39e559fc99526734", size = 210744, upload-time = "2026-04-25T11:07:19.055Z" }, + { url = "https://files.pythonhosted.org/packages/21/e6/d7e7baef7ce24166b4668d3c48557bb35a23b92ecadcac7e7718d099ab69/xxhash-3.7.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:3b6b3d28228af044ebcded71c4a3dd86e1dbd7e2f4645bf40f7b5da65bb5fb5a", size = 275406, upload-time = "2026-04-25T11:07:20.908Z" }, + { url = "https://files.pythonhosted.org/packages/92/fe/198b3763b2e01ca908f2154969a2352ec99bda892b574a11a9a151c5ede4/xxhash-3.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:6be4d70d9ab76c9f324ead9c01af6ff52c324745ea0c3731682a0cf99720f1fe", size = 414125, upload-time = "2026-04-25T11:07:23.037Z" }, + { url = "https://files.pythonhosted.org/packages/3a/6d/019a11affd5a5499137cacca53808659964785439855b5aa40dfd3412916/xxhash-3.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:151d7520838d4465461a0b7f4ae488b3b00de16183dd3214c1a6b14bf89d7fb6", size = 191555, upload-time = "2026-04-25T11:07:24.991Z" }, + { url = "https://files.pythonhosted.org/packages/76/21/b96d58568df2d01533244c3e0e5cbdd0c8b2b25c4bec4d72f19259a292d7/xxhash-3.7.0-cp313-cp313-win32.whl", hash = "sha256:d798c1e291bffb8e37b5bbe0dda77fc767cd19e89cadaf66e6ed5d0ff88c9fe6", size = 30668, upload-time = "2026-04-25T11:07:26.665Z" }, + { url = "https://files.pythonhosted.org/packages/99/57/d849a8d3afa1f8f4bc6a831cd89f49f9706fbbad94d2975d6140a171988c/xxhash-3.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:875811ba23c543b1a1c3143c926e43996eb27ebb8f52d3500744aa608c275aed", size = 31524, upload-time = "2026-04-25T11:07:27.92Z" }, + { url = "https://files.pythonhosted.org/packages/81/52/bacc753e92dee78b058af8dcef0a50815f5f860986c664a92d75f965b6a5/xxhash-3.7.0-cp313-cp313-win_arm64.whl", hash = "sha256:54a675cb300dda83d71daae2a599389d22db8021a0f8db0dd659e14626eb3ecc", size = 27768, upload-time = "2026-04-25T11:07:29.113Z" }, + { url = "https://files.pythonhosted.org/packages/1c/47/ddbd683b7fc7e592c1a8d9d65f73ce9ab513f082b3967eee2baf549b8fc6/xxhash-3.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a3b19a42111c4057c1547a4a1396a53961dca576a0f6b82bfa88a2d1561764b2", size = 33576, upload-time = "2026-04-25T11:07:30.469Z" }, + { url = "https://files.pythonhosted.org/packages/07/f2/36d3310161db7f72efb4562aadde0ed429f1d0531782dd6345b12d2da527/xxhash-3.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8f4608a06e4d61b7a3425665a46d00e0579122e1a2fae97a0c52953a3aad9aa3", size = 31123, upload-time = "2026-04-25T11:07:31.989Z" }, + { url = "https://files.pythonhosted.org/packages/0d/3f/75937a5c69556ed213021e43cbedd84c8e0279d0d74e7d41a255d84ba4b1/xxhash-3.7.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ad37c7792479e49cf96c1ab25517d7003fe0d93687a772ba19a097d235bbe41e", size = 196491, upload-time = "2026-04-25T11:07:33.358Z" }, + { url = "https://files.pythonhosted.org/packages/22/29/f10d7ff8c7a733d4403a43b9de18c8fabc005f98cec054644f04418659ee/xxhash-3.7.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc026e3b89d98e30a8288c95cb696e77d150b3f0fb7a51f73dcd49ee6b5577fa", size = 215793, upload-time = "2026-04-25T11:07:34.919Z" }, + { url = "https://files.pythonhosted.org/packages/8b/fd/778f60aa295f58907938f030a8b514611f391405614a525cccd2ffc00eb5/xxhash-3.7.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c9b31ab1f28b078a6a1ac1a54eb35e7d5390deddd56870d0be3a0a733d1c321c", size = 237993, upload-time = "2026-04-25T11:07:36.638Z" }, + { url = "https://files.pythonhosted.org/packages/70/f5/736db5de387b4a540e37a05b84b40dc58a1ce974bfd2b4e5754ce29b68c3/xxhash-3.7.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3bb5fd680c038fd5229e44e9c493782f90df9bef632fd0499d442374688ff70b", size = 214887, upload-time = "2026-04-25T11:07:38.564Z" }, + { url = "https://files.pythonhosted.org/packages/4d/aa/09a095f22fdb9a27fbb716841fbff52119721f9ca4261952d07a912f7839/xxhash-3.7.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:030c0fd688fce3569fbb49a2feefd4110cbb0b650186fb4610759ecfac677548", size = 448407, upload-time = "2026-04-25T11:07:40.552Z" }, + { url = "https://files.pythonhosted.org/packages/74/8a/b745efeeca9e34a91c26fdc97ad8514c43d5a81ac78565cba80a1353870a/xxhash-3.7.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5b1bde10324f4c31812ae0d0502e92d916ae8917cad7209353f122b8b8f610c3", size = 196119, upload-time = "2026-04-25T11:07:42.101Z" }, + { url = "https://files.pythonhosted.org/packages/8a/5c/0cfceb024af90c191f665c7933b1f318ee234f4797858383bebd1881d52f/xxhash-3.7.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:503722d52a615f2604f5e7611de7d43878df010dc0053094ef91cb9a9ac3d987", size = 286751, upload-time = "2026-04-25T11:07:43.568Z" }, + { url = "https://files.pythonhosted.org/packages/0b/0a/0793e405dc3cf8f4ebe2c1acec1e4e4608cd9e7e50ea691dabbc2a95ccbb/xxhash-3.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c72500a3b6d6c30ebfc135035bcace9eb5884f2dc220804efcaaba43e9f611dd", size = 212961, upload-time = "2026-04-25T11:07:45.388Z" }, + { url = "https://files.pythonhosted.org/packages/0c/7e/721118ffc63bfff94aa565bcf2555a820f9f4bdb0f001e0d609bdfad70de/xxhash-3.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:43475925a766d01ca8cd9a857fd87f3d50406983c8506a4c07c4df12adcc867f", size = 243703, upload-time = "2026-04-25T11:07:47.053Z" }, + { url = "https://files.pythonhosted.org/packages/6e/18/16f6267160488b8276fd3d449d425712512add292ba545c1b6946bfdb7dd/xxhash-3.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8d09dfd2ab135b985daf868b594315ebe11ad86cd9fea46e6c69f19b28f7d25a", size = 200894, upload-time = "2026-04-25T11:07:48.657Z" }, + { url = "https://files.pythonhosted.org/packages/2d/94/80ba841287fd97e3e9cac1d228788c8ef623746f570404961eec748ecb5c/xxhash-3.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c50269d0055ac1faecfd559886d2cbe4b730de236585aba0e873f9d9dadbe585", size = 213357, upload-time = "2026-04-25T11:07:50.257Z" }, + { url = "https://files.pythonhosted.org/packages/a1/7e/106d4067130c59f1e18a55ffadcd876d8c68534883a1e02685b29d3d8153/xxhash-3.7.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:1910df4756a5ab58cfad8744fc2d0f23926e3efcc346ee76e87b974abab922f4", size = 277600, upload-time = "2026-04-25T11:07:51.745Z" }, + { url = "https://files.pythonhosted.org/packages/c5/86/a081dd30da71d720b2612a792bfd55e45fa9a07ac76a0507f60487473c25/xxhash-3.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:d006faf3b491957efcb433489be3c149efe4787b7063d5cddb8ddaefdc60e0c1", size = 416980, upload-time = "2026-04-25T11:07:53.504Z" }, + { url = "https://files.pythonhosted.org/packages/35/29/1a95221a029a3c1293773869e1ab47b07cbbdd82444a42809e8c60156626/xxhash-3.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:abb65b4e947e958f7b3b0d71db3ce447d1bc5f37f5eab871ce7223bda8768a04", size = 193840, upload-time = "2026-04-25T11:07:55.103Z" }, + { url = "https://files.pythonhosted.org/packages/c5/e0/db909dd0823285de2286f67e10ee4d81e96ad35d7d8e964ecb07fccd8af9/xxhash-3.7.0-cp313-cp313t-win32.whl", hash = "sha256:178959906cb1716a1ce08e0d69c82886c70a15a6f2790fc084fdd146ca30cd49", size = 30966, upload-time = "2026-04-25T11:07:56.524Z" }, + { url = "https://files.pythonhosted.org/packages/7b/ff/d705b15b22f21ee106adce239cb65d35067a158c630b240270f09b17c2e6/xxhash-3.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2524a1e20d4c231d13b50f7cf39e44265b055669a64a7a4b9a2a44faa03f19b6", size = 31784, upload-time = "2026-04-25T11:07:57.758Z" }, + { url = "https://files.pythonhosted.org/packages/a2/1f/b2cf83c3638fd0588e0b17f22e5a9400bdfb1a3e3755324ac0aee2250b88/xxhash-3.7.0-cp313-cp313t-win_arm64.whl", hash = "sha256:37d994d0ffe81ef087bb330d392caa809bb5853c77e22ea3f71db024a0543dba", size = 27932, upload-time = "2026-04-25T11:07:59.109Z" }, + { url = "https://files.pythonhosted.org/packages/0e/cc/431db584f6fbb9312e40a173af027644e5580d39df1f73603cbb9dca4d6b/xxhash-3.7.0-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:8c5fcfd806c335bfa2adf1cd0b3110a44fc7b6995c3a648c27489bae85801465", size = 36644, upload-time = "2026-04-25T11:08:00.658Z" }, + { url = "https://files.pythonhosted.org/packages/bc/01/255ec513e0a705d1f9a61413e78dfce4e3235203f0ed525a24c2b4b56345/xxhash-3.7.0-cp314-cp314-android_24_x86_64.whl", hash = "sha256:506a0b488f190f0a06769575e30caf71615c898ed93ab18b0dbcb6dec5c3713c", size = 35003, upload-time = "2026-04-25T11:08:02.338Z" }, + { url = "https://files.pythonhosted.org/packages/68/70/c55fc33c93445b44d8fc5a17b41ed99e3cebe92bcf8396809e63fc9a1165/xxhash-3.7.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:ec68dbba21532c0173a9872298e65c89749f7c9d21538c3a78b5bb6105871568", size = 29655, upload-time = "2026-04-25T11:08:03.701Z" }, + { url = "https://files.pythonhosted.org/packages/c2/72/ff8de73df000d74467d12a59ce6d6e2b2a368b978d41ab7b1fba5ed442be/xxhash-3.7.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:fa77e7ec1450d415d20129961814787c9abd9a07f98872f070b1fe96c5084611", size = 30664, upload-time = "2026-04-25T11:08:05.011Z" }, + { url = "https://files.pythonhosted.org/packages/b6/91/08416d9bd9bc3bf39d831abe8a5631ac2db5141dfd6fe81c3fe59a1f9264/xxhash-3.7.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:fe32736295ea38e43e7d9424053c8c47c9f64fecfc7c895fb3da9b30b131c9ee", size = 33317, upload-time = "2026-04-25T11:08:06.413Z" }, + { url = "https://files.pythonhosted.org/packages/0e/3b/86b1caa4dee10a99f4bf9521e623359341c5e50d05158fa10c275b2bd079/xxhash-3.7.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ab9dd2c83c4bbd63e422181a76f13502d049d3ddcac9a1bdc29196263d692bb8", size = 33457, upload-time = "2026-04-25T11:08:08.099Z" }, + { url = "https://files.pythonhosted.org/packages/ed/38/98ea14ad1517e1461292a65906951458d520689782bfbae111050145bdba/xxhash-3.7.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3afec3a336a2286601a437cb07562ab0227685e6fbb9ec17e8c18457ff348ecf", size = 30894, upload-time = "2026-04-25T11:08:09.429Z" }, + { url = "https://files.pythonhosted.org/packages/61/a2/074654d0b893606541199993c7db70067d9fc63b748e0d60020a52a1bd36/xxhash-3.7.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:565df64437a9390f84465dcca33e7377114c7ede8d05cd2cf20081f831ea788e", size = 194409, upload-time = "2026-04-25T11:08:10.91Z" }, + { url = "https://files.pythonhosted.org/packages/e2/26/6d2a1afc468189f77ca28c32e1c83e1b9da1178231e05641dbc1b350e332/xxhash-3.7.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:12eca820a5d558633d423bf8bb78ce72a55394823f64089247f788a7e0ae691e", size = 213135, upload-time = "2026-04-25T11:08:12.575Z" }, + { url = "https://files.pythonhosted.org/packages/8e/0e/d8aecf95e09c42547453137be74d2f7b8b14e08f5177fa2fab6144a19061/xxhash-3.7.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f262b8f7599516567e070abf607b9af649052b2c4bd6f9be02b0cb41b7024805", size = 236379, upload-time = "2026-04-25T11:08:14.206Z" }, + { url = "https://files.pythonhosted.org/packages/f2/74/8140e8210536b3dd0cc816c4faaeb5ba6e63e8125ab25af4bcddd6a037b3/xxhash-3.7.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1598916cb197681e03e601901e4ab96a9a963de398c59d0964f8a6f44a2b361", size = 212447, upload-time = "2026-04-25T11:08:15.79Z" }, + { url = "https://files.pythonhosted.org/packages/a0/d2/462001d2903b4bee5a5689598a0a55e5e7cd1ac7f4247a5545cff10d3ebb/xxhash-3.7.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:322b2f0622230f526aeb1738149948a7ae357a9e2ceb1383c6fd1fdaecdafa16", size = 445660, upload-time = "2026-04-25T11:08:17.441Z" }, + { url = "https://files.pythonhosted.org/packages/23/09/2bd1ed7f8689b20e51727952cac8329d50c694dc32b2eba06ba5bc742b37/xxhash-3.7.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24cc22070880cc57b830a65cde4e65fa884c6d9b28ae4803b5ee05911e7bafba", size = 194076, upload-time = "2026-04-25T11:08:19.134Z" }, + { url = "https://files.pythonhosted.org/packages/c9/6e/692302cd0a5f4ac4e6289f37fa888dc2e1e07750b68fe3e4bfe939b8cea3/xxhash-3.7.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb5a888a968b2434abf9ecda357b5d43f10d7b5a6da6fdbbe036208473aff0e2", size = 284990, upload-time = "2026-04-25T11:08:20.618Z" }, + { url = "https://files.pythonhosted.org/packages/05/d9/e54b159b3d9df7999d2a7c676ce7b323d1b5588a64f8f51ed8172567bd87/xxhash-3.7.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a999771ff97bec27d18341be4f3a36b163bb1ac41ec17bef6d2dabd84acd33c7", size = 210590, upload-time = "2026-04-25T11:08:22.24Z" }, + { url = "https://files.pythonhosted.org/packages/50/93/0e0df1a3a196ced4ca71de76d65ead25d8e87bbfb87b64306ea47a40c00d/xxhash-3.7.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:ed4a6efe2dee1655adb73e7ad40c6aa955a6892422b1e3b95de6a34de56e3cbb", size = 241442, upload-time = "2026-04-25T11:08:23.844Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a9/d917a7a814e90b218f8a0d37967105eea91bf752c3303683c99a1f7bfc1f/xxhash-3.7.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9fd17f14ac0faa12126c2f9ca774a8cf342957265ec3c8669c144e5e6cdb478c", size = 198356, upload-time = "2026-04-25T11:08:25.99Z" }, + { url = "https://files.pythonhosted.org/packages/89/5e/f2ba1877c39469abbefc72991d6ebdcbd4c0880db01ae8cb1f553b0c537d/xxhash-3.7.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:05fd1254268c59b5cb2a029dfc204275e9fc52de2913f1e53aa8d01442c96b4d", size = 210898, upload-time = "2026-04-25T11:08:27.608Z" }, + { url = "https://files.pythonhosted.org/packages/90/c6/be56b58e73de531f39a10de1355bb77ceb663900dc4bf2d6d3002a9c3f9e/xxhash-3.7.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:a2eae53197c6276d5b317f75a1be226bbf440c20b58bf525f36b5d0e1f657ca6", size = 275519, upload-time = "2026-04-25T11:08:29.301Z" }, + { url = "https://files.pythonhosted.org/packages/92/e2/17ddc85d5765b9c709f192009ed8f5a1fc876f4eb35bba7c307b5b1169f9/xxhash-3.7.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:bfe6f92e3522dcbe8c4281efd74fa7542a336cb00b0e3272c4ec0edabeaeaf67", size = 414191, upload-time = "2026-04-25T11:08:31.16Z" }, + { url = "https://files.pythonhosted.org/packages/9c/42/85f5b79f4bf1ec7ba052491164adfd4f4e9519f5dc7246de4fbd64a1bd56/xxhash-3.7.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7ab9a49c410d8c6c786ab99e79c529938d894c01433130353dd0fe999111077a", size = 191604, upload-time = "2026-04-25T11:08:32.862Z" }, + { url = "https://files.pythonhosted.org/packages/b8/d0/6127b623aa4cca18d8b7743592b048d689fd6c6e37ff26a22cddf6cd9d7f/xxhash-3.7.0-cp314-cp314-win32.whl", hash = "sha256:040ea63668f9185b92bc74942df09c7e65703deed71431333678fc6e739a9955", size = 31271, upload-time = "2026-04-25T11:08:34.651Z" }, + { url = "https://files.pythonhosted.org/packages/64/4f/44fc4788568004c43921701cbc127f48218a1eede2c9aea231115323564d/xxhash-3.7.0-cp314-cp314-win_amd64.whl", hash = "sha256:2a61e2a3fb23c892496d587b470dee7fa1b58b248a187719c65ea8e94ec13257", size = 32284, upload-time = "2026-04-25T11:08:35.987Z" }, + { url = "https://files.pythonhosted.org/packages/6d/77/18bb895eb60a49453d16e17d67990e5caff557c78eafc90ad4e2eabf4570/xxhash-3.7.0-cp314-cp314-win_arm64.whl", hash = "sha256:c7741c7524961d8c0cb4d4c21b28957ff731a3fd5b5cd8b856dc80a40e9e5acc", size = 28701, upload-time = "2026-04-25T11:08:37.767Z" }, + { url = "https://files.pythonhosted.org/packages/45/a0/46f72244570c550fbbb7db1ef554183dd5ebe9136385f30e032b781ae8f6/xxhash-3.7.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:fc84bf7aa7592f31ec63a3e7b11d624f468a3f19f5238cec7282a42e838ab1d7", size = 33646, upload-time = "2026-04-25T11:08:39.109Z" }, + { url = "https://files.pythonhosted.org/packages/4a/3a/453846a7eceea11e75def361eed01ec6a0205b9822c19927ed364ccae7cc/xxhash-3.7.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9f1563fdc8abfc389748e6932c7e4e99c89a53e4ec37d4563c24fc06f5e5644b", size = 31125, upload-time = "2026-04-25T11:08:40.467Z" }, + { url = "https://files.pythonhosted.org/packages/bd/3e/49434aba738885d512f9e486db1bdd19db28dfa40372b56da26ef7a4e738/xxhash-3.7.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2d415f18becf6f153046ab6adc97da77e3643a0ee205dae61c4012604113a020", size = 196633, upload-time = "2026-04-25T11:08:41.943Z" }, + { url = "https://files.pythonhosted.org/packages/a4/e9/006cb6127baeb9f8abe6d15e62faa01349f09b34e2bfd65175b2422d026b/xxhash-3.7.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bb16aa13ed175bc9be5c2491ba031b85a9b51c4ed90e0b3d4ebe63cf3fb54f8e", size = 215899, upload-time = "2026-04-25T11:08:43.645Z" }, + { url = "https://files.pythonhosted.org/packages/27/e4/cc57d72e66df0ae29b914335f1c6dcf61e8f3746ddf0ae3c471aa4f15e00/xxhash-3.7.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f9fd595f1e5941b3d7863e4774e4b30caa6731fc34b9277da032295aa5656ee5", size = 238116, upload-time = "2026-04-25T11:08:45.698Z" }, + { url = "https://files.pythonhosted.org/packages/af/78/3531d4a3fd8a0038cc6be1f265a69c1b3587f557a10b677dd736de2202c1/xxhash-3.7.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1295325c5a98d552333fa53dc2b026b0ef0ec9c8e73ca3a952990b4c7d65d459", size = 215012, upload-time = "2026-04-25T11:08:47.355Z" }, + { url = "https://files.pythonhosted.org/packages/b4/f6/259fb1eaaec921f59b17203b0daee69829761226d3b980d5191d7723dd83/xxhash-3.7.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3573a651d146912da9daa9e29e5fbc45994420daaa9ef1e2fa5823e1dc485513", size = 448534, upload-time = "2026-04-25T11:08:49.149Z" }, + { url = "https://files.pythonhosted.org/packages/7b/16/a66d0eaf6a7e68532c07714361ddc904c663ec940f3b028c1ae4a21a7b9d/xxhash-3.7.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ec1e080a3d02d94ea9335bfab0e3374b877e25411422c18f51a943fa4b46381", size = 196217, upload-time = "2026-04-25T11:08:50.805Z" }, + { url = "https://files.pythonhosted.org/packages/8d/ef/d2efc7fc51756dc52509109d1a25cefc859d74bc4b19a167b12dbd8c2786/xxhash-3.7.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:84415265192072d8638a3afc3c1bc5995e310570cd9acb54dc46d3939e364fe0", size = 286906, upload-time = "2026-04-25T11:08:52.418Z" }, + { url = "https://files.pythonhosted.org/packages/fc/67/25decd1d4a4018582ec4db2a868a2b7e40640f4adb20dfeb19ac923aa825/xxhash-3.7.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d4dea659b57443989ef32f4295104fd6912c73d0bf26d1d148bb88a9f159b02", size = 213057, upload-time = "2026-04-25T11:08:54.105Z" }, + { url = "https://files.pythonhosted.org/packages/0d/5d/17651eb29d06786cdc40c60ae3d27d645aa5d61d2eca6237a7ba0b94789b/xxhash-3.7.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:05ece0fe4d9c9c2728912d1981ae1566cfc83a011571b24732cbf76e1fb70dca", size = 243886, upload-time = "2026-04-25T11:08:56.109Z" }, + { url = "https://files.pythonhosted.org/packages/8a/d4/174d9cf7502243d586e6a9ae842b1ae23026620995114f85f1380e588bc9/xxhash-3.7.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:fd880353cf1ffaf321bc18dd663e111976dbd0d3bbd8a66d58d2b470dfa7f396", size = 201015, upload-time = "2026-04-25T11:08:57.777Z" }, + { url = "https://files.pythonhosted.org/packages/91/8c/2254e2d06c3ac5e6fe22eaf3da791b87ea823ae9f2c17b4af66755c5752d/xxhash-3.7.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:4e15cc9e2817f6481160f930c62842b3ff419e20e13072bcbab12230943092bc", size = 213457, upload-time = "2026-04-25T11:08:59.826Z" }, + { url = "https://files.pythonhosted.org/packages/79/a2/e3daa762545921173e3360f3b4ff7fc63c2d27359f7230ec1a7a74e117f6/xxhash-3.7.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:90b9d1a8bd37d768ffc92a1f651ec69afc532a96fa1ac2ea7abbed5d630b3237", size = 277738, upload-time = "2026-04-25T11:09:01.423Z" }, + { url = "https://files.pythonhosted.org/packages/e1/4c/e186da2c46b87f5204640e008d42730bf3c1ee9f0efb71ae1ebcdfeac681/xxhash-3.7.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:157c49475b34ecea8809e51123d9769a534e139d1247942f7a4bc67710bb2533", size = 417127, upload-time = "2026-04-25T11:09:03.592Z" }, + { url = "https://files.pythonhosted.org/packages/17/28/3798e15007a3712d0da3d3fe70f8e11916569858b5cc371053bc26270832/xxhash-3.7.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5a6ddec83325685e729ca119d1f5c518ec39294212ecd770e60693cdc5f7eb79", size = 193962, upload-time = "2026-04-25T11:09:06.228Z" }, + { url = "https://files.pythonhosted.org/packages/ad/95/a26baa93b5241fd7630998816a4ec47a5a0bad193b3f8fc8f3593e1a4a67/xxhash-3.7.0-cp314-cp314t-win32.whl", hash = "sha256:a04a6cab47e2166435aaf5b9e5ee41d1532cc8300efdef87f2a4d0acb7db19ed", size = 31643, upload-time = "2026-04-25T11:09:08.153Z" }, + { url = "https://files.pythonhosted.org/packages/44/36/5454f13c447e395f9b06a3e91274c59f503d31fad84e1836efe3bdb71f6a/xxhash-3.7.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8653dd7c2eda020545bb2c71c7f7039b53fe7434d0fc1a0a9deb79ab3f1a4fc1", size = 32522, upload-time = "2026-04-25T11:09:09.534Z" }, + { url = "https://files.pythonhosted.org/packages/74/35/698e7e3ff38e22992ea24870a511d8762474fb6783627a2910ff22a185c2/xxhash-3.7.0-cp314-cp314t-win_arm64.whl", hash = "sha256:468f0fc114faaa4b36699f8e328bbc3bb11dc418ba94ac52c26dd736d4b6c637", size = 28807, upload-time = "2026-04-25T11:09:11.234Z" }, +] + +[[package]] +name = "yarl" +version = "1.24.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/12/1e8f37460ea0f7eb59c221fdaf0ed75e7ac43e97f8093b9c6f411df50a78/yarl-1.24.2.tar.gz", hash = "sha256:9ac374123c6fd7abf64d1fec93962b0bd4ee2c19751755a762a72dd96c0378f8", size = 210798, upload-time = "2026-05-19T21:31:05.599Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/da/866bcb01076ba49d2b42b309867bed3826421f1c479655eb7a607b44f20b/yarl-1.24.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b975866c184564c827e0877380f0dae57dcca7e52782128381b72feff6dfceb8", size = 129957, upload-time = "2026-05-19T21:28:51.695Z" }, + { url = "https://files.pythonhosted.org/packages/bf/1d/fcefb70922ea2268a8971d8e5874d9a8218644200fb8465f1dcad55e6851/yarl-1.24.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3b075301a2836a0e297b1b658cb6d6135df535d62efefdd60366bd589c2c82f2", size = 92164, upload-time = "2026-05-19T21:28:53.242Z" }, + { url = "https://files.pythonhosted.org/packages/29/b6/170e2b8d4e3bc30e6bfdcca53556537f5bf595e938632dfcb059311f3ff6/yarl-1.24.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8ae44649b00947634ab0dab2a374a638f52923a6e67083f2c156cd5cbd1a881d", size = 91688, upload-time = "2026-05-19T21:28:54.865Z" }, + { url = "https://files.pythonhosted.org/packages/fe/a5/c9f655d5553ea0b99fdac9d6a99ad3f9b3e73b8e5758bb46f58c9831f74c/yarl-1.24.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:507cc19f0b45454e2d6dcd62ff7d062b9f77a2812404e62dbdaec05b50faa035", size = 102902, upload-time = "2026-05-19T21:28:56.963Z" }, + { url = "https://files.pythonhosted.org/packages/5d/bc/6b9664d815d79af4ee553337f9d606c56bbf269186ada9172de45f1b5f60/yarl-1.24.2-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c4c17bad5a530912d2111825d3f05e89bab2dd376aaa8cbc77e449e6db63e576", size = 97931, upload-time = "2026-05-19T21:28:58.56Z" }, + { url = "https://files.pythonhosted.org/packages/98/ec/32ba48acae30fecd60928f5791188b80a9d6ee3840507ffda29fecd37b71/yarl-1.24.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f5f0cbb112838a4a293985b6ed73948a547dadcc1ba6d2089938e7abdedceef8", size = 111030, upload-time = "2026-05-19T21:29:00.148Z" }, + { url = "https://files.pythonhosted.org/packages/82/5a/6f4cd081e5f4934d2ae3a8ef4abe3afacc010d26f0035ee91b35cd7d7c37/yarl-1.24.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ec8356b8a6afcf81fc7aeeef13b1ff7a49dec00f313394bbb9e83830d32ccd7", size = 110392, upload-time = "2026-05-19T21:29:02.155Z" }, + { url = "https://files.pythonhosted.org/packages/7a/da/323a01c349bd5fb01bb6652e314d9bb218cee630a736bdb810ad50e4013f/yarl-1.24.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e7ebcdef69dec6c6451e616f32b622a6d4a2e92b445c992f7c8e5274a6bbc4c", size = 105612, upload-time = "2026-05-19T21:29:04.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/80/264ab684f181e1a876389374519ff05d10248725535ae2ac4e8ac4e563d6/yarl-1.24.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:47a55d6cf6db2f401017a9e96e5288844e5051911fb4e0c8311a3980f5e59a7d", size = 104487, upload-time = "2026-05-19T21:29:06.491Z" }, + { url = "https://files.pythonhosted.org/packages/41/07/efabe5df87e96d7ad5959760b888344be48cd6884db127b407c6b5503adc/yarl-1.24.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3065657c80a2321225e804048597ad55658a7e76b32d6f5ee4074d04c50401db", size = 102333, upload-time = "2026-05-19T21:29:08.267Z" }, + { url = "https://files.pythonhosted.org/packages/44/0c/bcf7c42603e1009295f586d8890f2ba032c8b53310e815adf0a202c73d9f/yarl-1.24.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:cb84b80d88e19ede158619b80813968713d8d008b0e2497a576e6a0557d50712", size = 99025, upload-time = "2026-05-19T21:29:10.682Z" }, + { url = "https://files.pythonhosted.org/packages/4f/82/84482ab1a57a0f21a08afe6a7004c61d741f8f2ecc3b05c321577c612164/yarl-1.24.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:990de4f680b1c217e77ff0d6aa0029f9eb79889c11fb3e9a3942c7eba29c1996", size = 110507, upload-time = "2026-05-19T21:29:12.954Z" }, + { url = "https://files.pythonhosted.org/packages/c4/8d/a546ba1dfe1b0f290e05fef145cd07614c0f15df1a707195e512d1e39d1d/yarl-1.24.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:abb8ec0323b80161e3802da3150ef660b41d0e9be2048b76a363d93eee992c2b", size = 103719, upload-time = "2026-05-19T21:29:14.893Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b6/267f2a09213138473adfce6b8a6e17791d7fee70bd4d9003218e4dec58b0/yarl-1.24.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e7977781f83638a4c73e0f88425563d70173e0dfd90ac006a45c65036293ee3c", size = 110438, upload-time = "2026-05-19T21:29:16.485Z" }, + { url = "https://files.pythonhosted.org/packages/48/2d/1c8d89c7c5f9cad9fb2902445d94e2ab1d7aa35de029afbb8ae95c42d00f/yarl-1.24.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e30dd55825dc554ec5b66a94953b8eda8745926514c5089dfcacecb9c99b5bd1", size = 105719, upload-time = "2026-05-19T21:29:18.367Z" }, + { url = "https://files.pythonhosted.org/packages/a7/25/722e3b93bd687009afb2d59a35e13d30ddd8f80571445bb0c4e4ce26ec66/yarl-1.24.2-cp312-cp312-win_amd64.whl", hash = "sha256:7dafe10c12ddd4d120d528c4b5599c953bd7b12845347d507b95451195bb6cad", size = 92901, upload-time = "2026-05-19T21:29:20.014Z" }, + { url = "https://files.pythonhosted.org/packages/39/47/4486ccfb674c04854a1ef8aa77868b6a6f765feaf69633409d7ca4f02cb8/yarl-1.24.2-cp312-cp312-win_arm64.whl", hash = "sha256:044a09d8401fcf8681977faef6d286b8ade1e2d2e9dceda175d1cfa5ca496f30", size = 87229, upload-time = "2026-05-19T21:29:22.1Z" }, + { url = "https://files.pythonhosted.org/packages/82/62/fcf0ce677f17e5c471c06311dd25964be38a4c586993632910d2e75278bc/yarl-1.24.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:491ac9141decf49ee8030199e1ee251cdff0e131f25678817ff6aa5f837a3536", size = 128978, upload-time = "2026-05-19T21:29:23.83Z" }, + { url = "https://files.pythonhosted.org/packages/d3/58/8e63299bb71ed61a834121d9d3fe6c9fcf2a6a5d09754ff4f20f2d20baf5/yarl-1.24.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e89418f65eda18f99030386305bd44d7d504e328a7945db1ead514fbe03a0607", size = 91733, upload-time = "2026-05-19T21:29:25.375Z" }, + { url = "https://files.pythonhosted.org/packages/c1/24/16748d5dab6daec8b0ed81ccec639a1cded0f18dcc62a4f696b4fe366c37/yarl-1.24.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cdfcce633b4a4bb8281913c57fcafd4b5933fbc19111a5e3930bbd299d6102f1", size = 91113, upload-time = "2026-05-19T21:29:26.928Z" }, + { url = "https://files.pythonhosted.org/packages/1b/66/b63fff7b71211e866624b21432d5943cbb633eb0c2872d9ee3070648f22c/yarl-1.24.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:863297ddede92ee49024e9a9b11ecb59f310ca85b60d8537f56bed9bbb5b1986", size = 103899, upload-time = "2026-05-19T21:29:28.842Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ac/ba1974b8533909636f7733fe86cf677e3619527c3c2fa913e0ea89c48757/yarl-1.24.2-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:374423f70754a2c96942ede36a29d37dc6b0cb8f92f8d009ddf3ed78d3da5488", size = 97862, upload-time = "2026-05-19T21:29:31.086Z" }, + { url = "https://files.pythonhosted.org/packages/1b/a5/123ac993b5c2ba6f554a140305620cb8f150fa543711bbc49be3ec0a65a4/yarl-1.24.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:33a29b5d00ccbf3219bb3e351d7875739c19481e030779f48cc46a7a71681a9b", size = 111060, upload-time = "2026-05-19T21:29:32.657Z" }, + { url = "https://files.pythonhosted.org/packages/23/37/c472d3af3509688392134a88a825276770a187f1daa4de3f6dc0a327a751/yarl-1.24.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a9532c57211730c515341af11fef6e9b61d157487272a096d0c04da445642592", size = 110613, upload-time = "2026-05-19T21:29:34.379Z" }, + { url = "https://files.pythonhosted.org/packages/df/88/09c28dad91e662ccfaa1b78f1c57badde74fc9d0b23e74aef644750ecd73/yarl-1.24.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:91e72cf093fd833483a97ee648e0c053c7c629f51ff4a0e7edd84f806b0c5617", size = 107012, upload-time = "2026-05-19T21:29:36.216Z" }, + { url = "https://files.pythonhosted.org/packages/07/ab/9d4f69d571a94f4d112fa7e2e007200f5a54d319f58c82ac7b7baa61f5c6/yarl-1.24.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b3177bc0a768ef3bacceb4f272632990b7bea352f1b2f1eee9d6d6ff16516f92", size = 105887, upload-time = "2026-05-19T21:29:38.746Z" }, + { url = "https://files.pythonhosted.org/packages/8e/9a/000b2b66c0d772a499fc531d21dab92dfeb73b640a12eed6ba89f49bb2d0/yarl-1.24.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e196952aacaf3b232e265ff02980b64d483dc0972bd49bcb061171ff22ac203a", size = 103620, upload-time = "2026-05-19T21:29:40.368Z" }, + { url = "https://files.pythonhosted.org/packages/41/7c/7c1050f73450fbdaa3f0c72017059f00ce5e13366692f3dba25275a1083d/yarl-1.24.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:204e7a61ce99919c0de1bf904ab5d7aa188a129ea8f690a8f76cfb6e2844dc44", size = 100599, upload-time = "2026-05-19T21:29:42.66Z" }, + { url = "https://files.pythonhosted.org/packages/ec/b1/29e5756b3926705f5f6089bd5b9f50a56eaac550da6e260bf713ead44d04/yarl-1.24.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b156914620f0b9d78dc1adb3751141daee561cfec796088abb89ed49d220f1a", size = 110604, upload-time = "2026-05-19T21:29:44.632Z" }, + { url = "https://files.pythonhosted.org/packages/a3/4b/8415bc96e9b150cde942fbac9a8182985e58f40ce5c54c34ed015407d3ee/yarl-1.24.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8372a2b976cf70654b2be6619ab6068acabb35f724c0fda7b277fbf53d66a5cf", size = 105161, upload-time = "2026-05-19T21:29:46.755Z" }, + { url = "https://files.pythonhosted.org/packages/8b/d4/cde059abfa229553b7298a2eadde2752e723d50aeedaef86ce59da2718ee/yarl-1.24.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f9a1e9b622ca284143aab5d885848686dcd85453bb1ca9abcdb7503e64dc0056", size = 110619, upload-time = "2026-05-19T21:29:48.972Z" }, + { url = "https://files.pythonhosted.org/packages/e7/2c/d6a6c9a61549f7b6c7e6dc6937d195bcf069582b47b7200dcd0e7b256acf/yarl-1.24.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:810e19b685c8c3c5862f6a38160a1f4e4c0916c9390024ec347b6157a45a0992", size = 107362, upload-time = "2026-05-19T21:29:51Z" }, + { url = "https://files.pythonhosted.org/packages/92/dd/3ae5fe417e9d1c353a548553326eb9935e76b6b727161563b424cc296df3/yarl-1.24.2-cp313-cp313-win_amd64.whl", hash = "sha256:7d37fb7c38f2b6edab0f845c4f85148d4c44204f52bc127021bd2bc9fdbf1656", size = 92667, upload-time = "2026-05-19T21:29:52.743Z" }, + { url = "https://files.pythonhosted.org/packages/10/cc/a7beb239f78f27fca1b053c8e8595e4179c02e62249b4687ec218c370c50/yarl-1.24.2-cp313-cp313-win_arm64.whl", hash = "sha256:1e831894be7c2954240e49791fa4b50c05a0dc881de2552cfe3ffd8631c7f461", size = 87069, upload-time = "2026-05-19T21:29:54.442Z" }, + { url = "https://files.pythonhosted.org/packages/40/0e/e08087695fc12789263821c5dc0f8dc52b5b17efd0887cacf419f8a43ba3/yarl-1.24.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:f9312b3c02d9b3d23840f67952913c9c8721d7f1b7db305289faefa878f364c2", size = 129670, upload-time = "2026-05-19T21:29:56.631Z" }, + { url = "https://files.pythonhosted.org/packages/3a/98/ab4b5ed1b1b5cd973c8a3eb994c3a6aefb6ce6d399e21bb5f0316c33815c/yarl-1.24.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a4f4d6cd615823bfc7fb7e9b5987c3f41666371d870d51058f77e2680fbe9630", size = 91916, upload-time = "2026-05-19T21:29:58.645Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b1/5297bb6a7df4782f7605bffc43b31f5044070935fbbcaa6c705a07e6ac65/yarl-1.24.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0c3063e5c0a8e8e62fae6c2596fa01da1561e4cd1da6fec5789f5cf99a8aefd8", size = 91625, upload-time = "2026-05-19T21:30:00.412Z" }, + { url = "https://files.pythonhosted.org/packages/02/a7/45baabfff76829264e623b185cff0c340d7e11bf3e1cd9ea37e7d17934bd/yarl-1.24.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fecd17873a096036c1c87ab3486f1aef7f269ada7f23f7f856f93b1cc7744f14", size = 104574, upload-time = "2026-05-19T21:30:02.544Z" }, + { url = "https://files.pythonhosted.org/packages/f3/40/3a5ab144d3d650ca37d4f4b57e56169be8af3ca34c448793e064b30baaed/yarl-1.24.2-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a46d1ab4ba4d32e6dc80daf8a28ce0bd83d08df52fbc32f3e288663427734535", size = 97534, upload-time = "2026-05-19T21:30:04.319Z" }, + { url = "https://files.pythonhosted.org/packages/9c/b5/5658fef3681fb5776b4513b052bec750009f47b3a592251c705d75375798/yarl-1.24.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:73e68edf6dfd5f73f9ca127d84e2a6f9213c65bdffb736bda19524c0564fcd14", size = 111481, upload-time = "2026-05-19T21:30:05.988Z" }, + { url = "https://files.pythonhosted.org/packages/4c/06/fdcd7dde037f00866dce123ed4ba23dba94beb56fc4cf561668d27be37f2/yarl-1.24.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a296ca617f2d25fbceafb962b88750d627e5984e75732c712154d058ae8d79a3", size = 111529, upload-time = "2026-05-19T21:30:07.738Z" }, + { url = "https://files.pythonhosted.org/packages/c2/53/d81269aaafccea0d33396c03035de997b743f11e648e6e27a0df99c72980/yarl-1.24.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e51b2cf5ec89a8b8470177641ed62a3ba22d74e1e898e06ad53aa77972487208", size = 107338, upload-time = "2026-05-19T21:30:09.713Z" }, + { url = "https://files.pythonhosted.org/packages/ae/04/23049463f729bd899df203a7960505a75333edd499cda8aa1d5a82b64df5/yarl-1.24.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:310fc687f7b2044ec54e372c8cbe923bb88f5c37bded0d3079e5791c2fc3cf50", size = 106147, upload-time = "2026-05-19T21:30:11.365Z" }, + { url = "https://files.pythonhosted.org/packages/14/18/04a4b5830b43ed5e4c5015b40e9f6241ad91487d71611061b4e111d6ac80/yarl-1.24.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:297a2fe352ecf858b30a98f87948746ec16f001d279f84aebdbd3bd965e2f1bd", size = 104272, upload-time = "2026-05-19T21:30:12.978Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f7/8cffdf319aee7a7c1dbd07b61d91c3e3fda460c7a93b5f93e445f3806c4c/yarl-1.24.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2a263e76b97bc42bdcd7c5f4953dec1f7cd62a1112fa7f869e57255229390d67", size = 99962, upload-time = "2026-05-19T21:30:15.001Z" }, + { url = "https://files.pythonhosted.org/packages/d7/39/b3cce3b7dbef64ac700ad4cea156a207d01bede0f507587616c364b5468e/yarl-1.24.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:822519b64cf0b474f1a0aaef1dc621438ea46bb77c94df97a5b4d213a7d8a8b1", size = 111063, upload-time = "2026-05-19T21:30:16.683Z" }, + { url = "https://files.pythonhosted.org/packages/a1/ea/100818505e7ebf165c7242ff17fdf7d9fee79e27234aeca871c1082920d7/yarl-1.24.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:b6067060d9dc594899ba83e6db6c48c68d1e494a6dab158156ed86977ca7bcb1", size = 105438, upload-time = "2026-05-19T21:30:18.769Z" }, + { url = "https://files.pythonhosted.org/packages/8f/d2/e075a0b32aa6625087de9e653087df0759fed5de4a435fef594181102a77/yarl-1.24.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:0063adad533e57171b79db3943b229d40dfafeeee579767f96541f106bac5f1b", size = 111458, upload-time = "2026-05-19T21:30:21.024Z" }, + { url = "https://files.pythonhosted.org/packages/e6/5c/ceea7ba98b65c8eb8d947fdc52f9bedfcd43c6a57c9e3c90c17be8f324a3/yarl-1.24.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ee8e3fb34513e8dc082b586ef4910c98335d43a6fab688cd44d4851bacfce3e8", size = 107589, upload-time = "2026-05-19T21:30:23.412Z" }, + { url = "https://files.pythonhosted.org/packages/fa/d9/5582d57e2b2db9b85eb6663a22efdd78e08805f3f5389566e9fcad254d1b/yarl-1.24.2-cp314-cp314-win_amd64.whl", hash = "sha256:afb00d7fd8e0f285ca29a44cc50df2d622ff2f7a6d933fa641577b5f9d5f3db0", size = 94424, upload-time = "2026-05-19T21:30:25.425Z" }, + { url = "https://files.pythonhosted.org/packages/92/10/7dc07a0e22806a9280f42a57361395506e800c64e22737cd7b0886feab42/yarl-1.24.2-cp314-cp314-win_arm64.whl", hash = "sha256:68cf6eacd6028ef1142bc4b48376b81566385ca6f9e7dde3b0fa91be08ffcb57", size = 88690, upload-time = "2026-05-19T21:30:27.623Z" }, + { url = "https://files.pythonhosted.org/packages/9e/13/d5b8e2c8667db955bcb3de233f18798fefe7edf1d7429c2c9d4f9c401114/yarl-1.24.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:221ce1dd921ac4f603957f17d7c18c5cc0797fbb52f156941f92e04605d1d67b", size = 136248, upload-time = "2026-05-19T21:30:29.297Z" }, + { url = "https://files.pythonhosted.org/packages/de/46/a4a97c05c9c9b8fd266bb2a0df12992c7fbd02391eb9640583411b6dab32/yarl-1.24.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5f3224db28173a00d7afacdee07045cc4673dfab2b15492c7ae10deddbece761", size = 95084, upload-time = "2026-05-19T21:30:31.031Z" }, + { url = "https://files.pythonhosted.org/packages/95/b2/845cf2074a015e6fe0d0808cf1a2d9e868386c4220d657ebd8302b199043/yarl-1.24.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c557165320d6244ebe3a02431b2a201a20080e02f41f0cfa0ccc47a183765da8", size = 95272, upload-time = "2026-05-19T21:30:33.062Z" }, + { url = "https://files.pythonhosted.org/packages/fe/16/e69d4aa244aef45235ddfebc0e04036a6829842bc5a6a795aedc6c998d23/yarl-1.24.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:904065e6e85b1fa54d0d87438bd58c14c0bad97aad654ad1077fd9d87e8478ed", size = 101497, upload-time = "2026-05-19T21:30:34.842Z" }, + { url = "https://files.pythonhosted.org/packages/15/94/c07107715d621076863ee88b3ddf183fa5e9d4aba5769623c9979828410a/yarl-1.24.2-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8cec2a38d70edc10e0e856ceda886af5327a017ccbde8e1de1bd44d300357543", size = 94002, upload-time = "2026-05-19T21:30:37.724Z" }, + { url = "https://files.pythonhosted.org/packages/a9/35/fc1bbdd895b5e4010b8fdd037f7ed3aa289d3863e08231b30231ca9a0815/yarl-1.24.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e7484b9361ed222ee1ca5b4337aa4cbdcc4618ce5aff57d9ef1582fd95893fc0", size = 106524, upload-time = "2026-05-19T21:30:40.196Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f2/32b66d0a4ba47c296cf86d03e2c67bff58399fe6d6d84d5205c04c66cc6d/yarl-1.24.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:84f9670b89f34db07f81e53aee83e0b938a3412329d51c8f922488be7fcc4024", size = 106165, upload-time = "2026-05-19T21:30:41.888Z" }, + { url = "https://files.pythonhosted.org/packages/95/47/37cb5ff50c5e825d4d38e81bb04d1b7e96bf960f7ab89f9850b162f3f114/yarl-1.24.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:abb2759733d63a28b4956500a5dd57140f26486c92b2caedfb964ab7d9b79dbf", size = 103010, upload-time = "2026-05-19T21:30:43.985Z" }, + { url = "https://files.pythonhosted.org/packages/6f/d2/4597912315096f7bb359e46e13bf8b60994fcbb2db29b804c0902ef4eff5/yarl-1.24.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:081c2bf54efe03774d0311172bc04fedf9ca01e644d4cd8c805688e527209bdc", size = 101128, upload-time = "2026-05-19T21:30:46.291Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d5/c8e86e120521e646013d02a8e3b8884392e28494be8f392366e50d208efc/yarl-1.24.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:86746bef442aa479107fe28132e1277237f9c24c2f00b0b0cf22b3ee0904f2bb", size = 101382, upload-time = "2026-05-19T21:30:48.085Z" }, + { url = "https://files.pythonhosted.org/packages/fa/98/70b229236118f89dbeb739b76f10225bbf53b5497725502594c9a01d699a/yarl-1.24.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:2d07d21d0bc4b17558e8de0b02fbfdf1e347d3bb3699edd00bb92e7c57925420", size = 95964, upload-time = "2026-05-19T21:30:49.785Z" }, + { url = "https://files.pythonhosted.org/packages/87/f8/56c386981e3c8648d279fdef2397ffec577e8320fd5649745e34d54faeb7/yarl-1.24.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:4fb1ac3fc5fecd8ae7453ea237e4d22b49befa70266dfe1629924245c21a0c7f", size = 106204, upload-time = "2026-05-19T21:30:51.862Z" }, + { url = "https://files.pythonhosted.org/packages/1a/1e/765afe97811ca35933e2a7de70ac57b1997ea2e4ee895719ee7a231fb7e5/yarl-1.24.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4da31a5512ed1729ca8d8aacde3f7faeb8843cde3165d6bcf7f88f74f17bb8aa", size = 101510, upload-time = "2026-05-19T21:30:53.62Z" }, + { url = "https://files.pythonhosted.org/packages/ee/78/393913f4b9039e1edd09ae8a9bbb9d539be909a8abf6d8a2084585bed4b7/yarl-1.24.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:533ded4dceb5f1f3da7906244f4e82cf46cfd40d84c69a1faf5ac506aa65ecbe", size = 105584, upload-time = "2026-05-19T21:30:55.962Z" }, + { url = "https://files.pythonhosted.org/packages/78/87/deb17b7049bbe74ea11a713b86f8f27800cc1c8648b0b797243ebb4830ba/yarl-1.24.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7b3a85525f6e7eeabcfdd372862b21ee1915db1b498a04e8bf0e389b607ff0bd", size = 103410, upload-time = "2026-05-19T21:30:57.962Z" }, + { url = "https://files.pythonhosted.org/packages/8f/be/f9f7594e23b5b93affff0318e4593c1920331bcaefda326cabcad94296a1/yarl-1.24.2-cp314-cp314t-win_amd64.whl", hash = "sha256:a7624b1ca46ca5d7b864ef0d2f8efe3091454085ee1855b4e992314529972215", size = 102980, upload-time = "2026-05-19T21:30:59.735Z" }, + { url = "https://files.pythonhosted.org/packages/65/a4/ba80dccd3593ff1f01051a818694d07b58cb8232677ee9a22a5a1f93a9fc/yarl-1.24.2-cp314-cp314t-win_arm64.whl", hash = "sha256:e434a45ce2e7a947f951fc5a8944c8cc080b7e59f9c50ae80fd39107cf88126d", size = 91219, upload-time = "2026-05-19T21:31:01.934Z" }, + { url = "https://files.pythonhosted.org/packages/fd/4d/4b880086bd0d3e034d25647be1d830afc3e3f610e98c4ab3490af6b1b6d5/yarl-1.24.2-py3-none-any.whl", hash = "sha256:2783d9226db8797636cd6896e4de81feed252d1db72265686c9558d97a4d94b9", size = 53576, upload-time = "2026-05-19T21:31:03.909Z" }, +] + +[[package]] +name = "zipp" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/d8/eab98a517c14134c0b2eb4e2387bc5f457334293ec5d2dd3857ec2966802/zipp-4.1.0.tar.gz", hash = "sha256:4cb57381f544315db7688e976e922a2b18cdb513d21cc194eb42232ba2a3e602", size = 26214, upload-time = "2026-05-18T20:08:57.967Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/13/547360d81e6d88d58492968ffda9f9542854f11310ee556fef14260cc886/zipp-4.1.0-py3-none-any.whl", hash = "sha256:25ad4e16390cd314347dd8f1de67a2ac538ae658ed4ab9db16029c07c188e97f", size = 10238, upload-time = "2026-05-18T20:08:57.045Z" }, +] + +[[package]] +name = "zstandard" +version = "0.25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/aa/3e0508d5a5dd96529cdc5a97011299056e14c6505b678fd58938792794b1/zstandard-0.25.0.tar.gz", hash = "sha256:7713e1179d162cf5c7906da876ec2ccb9c3a9dcbdffef0cc7f70c3667a205f0b", size = 711513, upload-time = "2025-09-14T22:15:54.002Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/fc/f26eb6ef91ae723a03e16eddb198abcfce2bc5a42e224d44cc8b6765e57e/zstandard-0.25.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7b3c3a3ab9daa3eed242d6ecceead93aebbb8f5f84318d82cee643e019c4b73b", size = 795738, upload-time = "2025-09-14T22:16:56.237Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1c/d920d64b22f8dd028a8b90e2d756e431a5d86194caa78e3819c7bf53b4b3/zstandard-0.25.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:913cbd31a400febff93b564a23e17c3ed2d56c064006f54efec210d586171c00", size = 640436, upload-time = "2025-09-14T22:16:57.774Z" }, + { url = "https://files.pythonhosted.org/packages/53/6c/288c3f0bd9fcfe9ca41e2c2fbfd17b2097f6af57b62a81161941f09afa76/zstandard-0.25.0-cp312-cp312-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:011d388c76b11a0c165374ce660ce2c8efa8e5d87f34996aa80f9c0816698b64", size = 5343019, upload-time = "2025-09-14T22:16:59.302Z" }, + { url = "https://files.pythonhosted.org/packages/1e/15/efef5a2f204a64bdb5571e6161d49f7ef0fffdbca953a615efbec045f60f/zstandard-0.25.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dffecc361d079bb48d7caef5d673c88c8988d3d33fb74ab95b7ee6da42652ea", size = 5063012, upload-time = "2025-09-14T22:17:01.156Z" }, + { url = "https://files.pythonhosted.org/packages/b7/37/a6ce629ffdb43959e92e87ebdaeebb5ac81c944b6a75c9c47e300f85abdf/zstandard-0.25.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7149623bba7fdf7e7f24312953bcf73cae103db8cae49f8154dd1eadc8a29ecb", size = 5394148, upload-time = "2025-09-14T22:17:03.091Z" }, + { url = "https://files.pythonhosted.org/packages/e3/79/2bf870b3abeb5c070fe2d670a5a8d1057a8270f125ef7676d29ea900f496/zstandard-0.25.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6a573a35693e03cf1d67799fd01b50ff578515a8aeadd4595d2a7fa9f3ec002a", size = 5451652, upload-time = "2025-09-14T22:17:04.979Z" }, + { url = "https://files.pythonhosted.org/packages/53/60/7be26e610767316c028a2cbedb9a3beabdbe33e2182c373f71a1c0b88f36/zstandard-0.25.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5a56ba0db2d244117ed744dfa8f6f5b366e14148e00de44723413b2f3938a902", size = 5546993, upload-time = "2025-09-14T22:17:06.781Z" }, + { url = "https://files.pythonhosted.org/packages/85/c7/3483ad9ff0662623f3648479b0380d2de5510abf00990468c286c6b04017/zstandard-0.25.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:10ef2a79ab8e2974e2075fb984e5b9806c64134810fac21576f0668e7ea19f8f", size = 5046806, upload-time = "2025-09-14T22:17:08.415Z" }, + { url = "https://files.pythonhosted.org/packages/08/b3/206883dd25b8d1591a1caa44b54c2aad84badccf2f1de9e2d60a446f9a25/zstandard-0.25.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aaf21ba8fb76d102b696781bddaa0954b782536446083ae3fdaa6f16b25a1c4b", size = 5576659, upload-time = "2025-09-14T22:17:10.164Z" }, + { url = "https://files.pythonhosted.org/packages/9d/31/76c0779101453e6c117b0ff22565865c54f48f8bd807df2b00c2c404b8e0/zstandard-0.25.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1869da9571d5e94a85a5e8d57e4e8807b175c9e4a6294e3b66fa4efb074d90f6", size = 4953933, upload-time = "2025-09-14T22:17:11.857Z" }, + { url = "https://files.pythonhosted.org/packages/18/e1/97680c664a1bf9a247a280a053d98e251424af51f1b196c6d52f117c9720/zstandard-0.25.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:809c5bcb2c67cd0ed81e9229d227d4ca28f82d0f778fc5fea624a9def3963f91", size = 5268008, upload-time = "2025-09-14T22:17:13.627Z" }, + { url = "https://files.pythonhosted.org/packages/1e/73/316e4010de585ac798e154e88fd81bb16afc5c5cb1a72eeb16dd37e8024a/zstandard-0.25.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f27662e4f7dbf9f9c12391cb37b4c4c3cb90ffbd3b1fb9284dadbbb8935fa708", size = 5433517, upload-time = "2025-09-14T22:17:16.103Z" }, + { url = "https://files.pythonhosted.org/packages/5b/60/dd0f8cfa8129c5a0ce3ea6b7f70be5b33d2618013a161e1ff26c2b39787c/zstandard-0.25.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99c0c846e6e61718715a3c9437ccc625de26593fea60189567f0118dc9db7512", size = 5814292, upload-time = "2025-09-14T22:17:17.827Z" }, + { url = "https://files.pythonhosted.org/packages/fc/5f/75aafd4b9d11b5407b641b8e41a57864097663699f23e9ad4dbb91dc6bfe/zstandard-0.25.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:474d2596a2dbc241a556e965fb76002c1ce655445e4e3bf38e5477d413165ffa", size = 5360237, upload-time = "2025-09-14T22:17:19.954Z" }, + { url = "https://files.pythonhosted.org/packages/ff/8d/0309daffea4fcac7981021dbf21cdb2e3427a9e76bafbcdbdf5392ff99a4/zstandard-0.25.0-cp312-cp312-win32.whl", hash = "sha256:23ebc8f17a03133b4426bcc04aabd68f8236eb78c3760f12783385171b0fd8bd", size = 436922, upload-time = "2025-09-14T22:17:24.398Z" }, + { url = "https://files.pythonhosted.org/packages/79/3b/fa54d9015f945330510cb5d0b0501e8253c127cca7ebe8ba46a965df18c5/zstandard-0.25.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffef5a74088f1e09947aecf91011136665152e0b4b359c42be3373897fb39b01", size = 506276, upload-time = "2025-09-14T22:17:21.429Z" }, + { url = "https://files.pythonhosted.org/packages/ea/6b/8b51697e5319b1f9ac71087b0af9a40d8a6288ff8025c36486e0c12abcc4/zstandard-0.25.0-cp312-cp312-win_arm64.whl", hash = "sha256:181eb40e0b6a29b3cd2849f825e0fa34397f649170673d385f3598ae17cca2e9", size = 462679, upload-time = "2025-09-14T22:17:23.147Z" }, + { url = "https://files.pythonhosted.org/packages/35/0b/8df9c4ad06af91d39e94fa96cc010a24ac4ef1378d3efab9223cc8593d40/zstandard-0.25.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec996f12524f88e151c339688c3897194821d7f03081ab35d31d1e12ec975e94", size = 795735, upload-time = "2025-09-14T22:17:26.042Z" }, + { url = "https://files.pythonhosted.org/packages/3f/06/9ae96a3e5dcfd119377ba33d4c42a7d89da1efabd5cb3e366b156c45ff4d/zstandard-0.25.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a1a4ae2dec3993a32247995bdfe367fc3266da832d82f8438c8570f989753de1", size = 640440, upload-time = "2025-09-14T22:17:27.366Z" }, + { url = "https://files.pythonhosted.org/packages/d9/14/933d27204c2bd404229c69f445862454dcc101cd69ef8c6068f15aaec12c/zstandard-0.25.0-cp313-cp313-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:e96594a5537722fdfb79951672a2a63aec5ebfb823e7560586f7484819f2a08f", size = 5343070, upload-time = "2025-09-14T22:17:28.896Z" }, + { url = "https://files.pythonhosted.org/packages/6d/db/ddb11011826ed7db9d0e485d13df79b58586bfdec56e5c84a928a9a78c1c/zstandard-0.25.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bfc4e20784722098822e3eee42b8e576b379ed72cca4a7cb856ae733e62192ea", size = 5063001, upload-time = "2025-09-14T22:17:31.044Z" }, + { url = "https://files.pythonhosted.org/packages/db/00/87466ea3f99599d02a5238498b87bf84a6348290c19571051839ca943777/zstandard-0.25.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:457ed498fc58cdc12fc48f7950e02740d4f7ae9493dd4ab2168a47c93c31298e", size = 5394120, upload-time = "2025-09-14T22:17:32.711Z" }, + { url = "https://files.pythonhosted.org/packages/2b/95/fc5531d9c618a679a20ff6c29e2b3ef1d1f4ad66c5e161ae6ff847d102a9/zstandard-0.25.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:fd7a5004eb1980d3cefe26b2685bcb0b17989901a70a1040d1ac86f1d898c551", size = 5451230, upload-time = "2025-09-14T22:17:34.41Z" }, + { url = "https://files.pythonhosted.org/packages/63/4b/e3678b4e776db00f9f7b2fe58e547e8928ef32727d7a1ff01dea010f3f13/zstandard-0.25.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8e735494da3db08694d26480f1493ad2cf86e99bdd53e8e9771b2752a5c0246a", size = 5547173, upload-time = "2025-09-14T22:17:36.084Z" }, + { url = "https://files.pythonhosted.org/packages/4e/d5/ba05ed95c6b8ec30bd468dfeab20589f2cf709b5c940483e31d991f2ca58/zstandard-0.25.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3a39c94ad7866160a4a46d772e43311a743c316942037671beb264e395bdd611", size = 5046736, upload-time = "2025-09-14T22:17:37.891Z" }, + { url = "https://files.pythonhosted.org/packages/50/d5/870aa06b3a76c73eced65c044b92286a3c4e00554005ff51962deef28e28/zstandard-0.25.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:172de1f06947577d3a3005416977cce6168f2261284c02080e7ad0185faeced3", size = 5576368, upload-time = "2025-09-14T22:17:40.206Z" }, + { url = "https://files.pythonhosted.org/packages/5d/35/398dc2ffc89d304d59bc12f0fdd931b4ce455bddf7038a0a67733a25f550/zstandard-0.25.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3c83b0188c852a47cd13ef3bf9209fb0a77fa5374958b8c53aaa699398c6bd7b", size = 4954022, upload-time = "2025-09-14T22:17:41.879Z" }, + { url = "https://files.pythonhosted.org/packages/9a/5c/36ba1e5507d56d2213202ec2b05e8541734af5f2ce378c5d1ceaf4d88dc4/zstandard-0.25.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1673b7199bbe763365b81a4f3252b8e80f44c9e323fc42940dc8843bfeaf9851", size = 5267889, upload-time = "2025-09-14T22:17:43.577Z" }, + { url = "https://files.pythonhosted.org/packages/70/e8/2ec6b6fb7358b2ec0113ae202647ca7c0e9d15b61c005ae5225ad0995df5/zstandard-0.25.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0be7622c37c183406f3dbf0cba104118eb16a4ea7359eeb5752f0794882fc250", size = 5433952, upload-time = "2025-09-14T22:17:45.271Z" }, + { url = "https://files.pythonhosted.org/packages/7b/01/b5f4d4dbc59ef193e870495c6f1275f5b2928e01ff5a81fecb22a06e22fb/zstandard-0.25.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:5f5e4c2a23ca271c218ac025bd7d635597048b366d6f31f420aaeb715239fc98", size = 5814054, upload-time = "2025-09-14T22:17:47.08Z" }, + { url = "https://files.pythonhosted.org/packages/b2/e5/fbd822d5c6f427cf158316d012c5a12f233473c2f9c5fe5ab1ae5d21f3d8/zstandard-0.25.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f187a0bb61b35119d1926aee039524d1f93aaf38a9916b8c4b78ac8514a0aaf", size = 5360113, upload-time = "2025-09-14T22:17:48.893Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e0/69a553d2047f9a2c7347caa225bb3a63b6d7704ad74610cb7823baa08ed7/zstandard-0.25.0-cp313-cp313-win32.whl", hash = "sha256:7030defa83eef3e51ff26f0b7bfb229f0204b66fe18e04359ce3474ac33cbc09", size = 436936, upload-time = "2025-09-14T22:17:52.658Z" }, + { url = "https://files.pythonhosted.org/packages/d9/82/b9c06c870f3bd8767c201f1edbdf9e8dc34be5b0fbc5682c4f80fe948475/zstandard-0.25.0-cp313-cp313-win_amd64.whl", hash = "sha256:1f830a0dac88719af0ae43b8b2d6aef487d437036468ef3c2ea59c51f9d55fd5", size = 506232, upload-time = "2025-09-14T22:17:50.402Z" }, + { url = "https://files.pythonhosted.org/packages/d4/57/60c3c01243bb81d381c9916e2a6d9e149ab8627c0c7d7abb2d73384b3c0c/zstandard-0.25.0-cp313-cp313-win_arm64.whl", hash = "sha256:85304a43f4d513f5464ceb938aa02c1e78c2943b29f44a750b48b25ac999a049", size = 462671, upload-time = "2025-09-14T22:17:51.533Z" }, + { url = "https://files.pythonhosted.org/packages/3d/5c/f8923b595b55fe49e30612987ad8bf053aef555c14f05bb659dd5dbe3e8a/zstandard-0.25.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e29f0cf06974c899b2c188ef7f783607dbef36da4c242eb6c82dcd8b512855e3", size = 795887, upload-time = "2025-09-14T22:17:54.198Z" }, + { url = "https://files.pythonhosted.org/packages/8d/09/d0a2a14fc3439c5f874042dca72a79c70a532090b7ba0003be73fee37ae2/zstandard-0.25.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:05df5136bc5a011f33cd25bc9f506e7426c0c9b3f9954f056831ce68f3b6689f", size = 640658, upload-time = "2025-09-14T22:17:55.423Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7c/8b6b71b1ddd517f68ffb55e10834388d4f793c49c6b83effaaa05785b0b4/zstandard-0.25.0-cp314-cp314-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:f604efd28f239cc21b3adb53eb061e2a205dc164be408e553b41ba2ffe0ca15c", size = 5379849, upload-time = "2025-09-14T22:17:57.372Z" }, + { url = "https://files.pythonhosted.org/packages/a4/86/a48e56320d0a17189ab7a42645387334fba2200e904ee47fc5a26c1fd8ca/zstandard-0.25.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223415140608d0f0da010499eaa8ccdb9af210a543fac54bce15babbcfc78439", size = 5058095, upload-time = "2025-09-14T22:17:59.498Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ad/eb659984ee2c0a779f9d06dbfe45e2dc39d99ff40a319895df2d3d9a48e5/zstandard-0.25.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e54296a283f3ab5a26fc9b8b5d4978ea0532f37b231644f367aa588930aa043", size = 5551751, upload-time = "2025-09-14T22:18:01.618Z" }, + { url = "https://files.pythonhosted.org/packages/61/b3/b637faea43677eb7bd42ab204dfb7053bd5c4582bfe6b1baefa80ac0c47b/zstandard-0.25.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ca54090275939dc8ec5dea2d2afb400e0f83444b2fc24e07df7fdef677110859", size = 6364818, upload-time = "2025-09-14T22:18:03.769Z" }, + { url = "https://files.pythonhosted.org/packages/31/dc/cc50210e11e465c975462439a492516a73300ab8caa8f5e0902544fd748b/zstandard-0.25.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e09bb6252b6476d8d56100e8147b803befa9a12cea144bbe629dd508800d1ad0", size = 5560402, upload-time = "2025-09-14T22:18:05.954Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ae/56523ae9c142f0c08efd5e868a6da613ae76614eca1305259c3bf6a0ed43/zstandard-0.25.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a9ec8c642d1ec73287ae3e726792dd86c96f5681eb8df274a757bf62b750eae7", size = 4955108, upload-time = "2025-09-14T22:18:07.68Z" }, + { url = "https://files.pythonhosted.org/packages/98/cf/c899f2d6df0840d5e384cf4c4121458c72802e8bda19691f3b16619f51e9/zstandard-0.25.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a4089a10e598eae6393756b036e0f419e8c1d60f44a831520f9af41c14216cf2", size = 5269248, upload-time = "2025-09-14T22:18:09.753Z" }, + { url = "https://files.pythonhosted.org/packages/1b/c0/59e912a531d91e1c192d3085fc0f6fb2852753c301a812d856d857ea03c6/zstandard-0.25.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f67e8f1a324a900e75b5e28ffb152bcac9fbed1cc7b43f99cd90f395c4375344", size = 5430330, upload-time = "2025-09-14T22:18:11.966Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/7e31db1240de2df22a58e2ea9a93fc6e38cc29353e660c0272b6735d6669/zstandard-0.25.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:9654dbc012d8b06fc3d19cc825af3f7bf8ae242226df5f83936cb39f5fdc846c", size = 5811123, upload-time = "2025-09-14T22:18:13.907Z" }, + { url = "https://files.pythonhosted.org/packages/f6/49/fac46df5ad353d50535e118d6983069df68ca5908d4d65b8c466150a4ff1/zstandard-0.25.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4203ce3b31aec23012d3a4cf4a2ed64d12fea5269c49aed5e4c3611b938e4088", size = 5359591, upload-time = "2025-09-14T22:18:16.465Z" }, + { url = "https://files.pythonhosted.org/packages/c2/38/f249a2050ad1eea0bb364046153942e34abba95dd5520af199aed86fbb49/zstandard-0.25.0-cp314-cp314-win32.whl", hash = "sha256:da469dc041701583e34de852d8634703550348d5822e66a0c827d39b05365b12", size = 444513, upload-time = "2025-09-14T22:18:20.61Z" }, + { url = "https://files.pythonhosted.org/packages/3a/43/241f9615bcf8ba8903b3f0432da069e857fc4fd1783bd26183db53c4804b/zstandard-0.25.0-cp314-cp314-win_amd64.whl", hash = "sha256:c19bcdd826e95671065f8692b5a4aa95c52dc7a02a4c5a0cac46deb879a017a2", size = 516118, upload-time = "2025-09-14T22:18:17.849Z" }, + { url = "https://files.pythonhosted.org/packages/f0/ef/da163ce2450ed4febf6467d77ccb4cd52c4c30ab45624bad26ca0a27260c/zstandard-0.25.0-cp314-cp314-win_arm64.whl", hash = "sha256:d7541afd73985c630bafcd6338d2518ae96060075f9463d7dc14cfb33514383d", size = 476940, upload-time = "2025-09-14T22:18:19.088Z" }, +] From b17ebc11005b939388573930055fa2844ef3f395 Mon Sep 17 00:00:00 2001 From: yl-nuwan Date: Fri, 5 Jun 2026 10:03:54 +0530 Subject: [PATCH 02/90] fixed the yaml --- .github/integration-test-config.yaml | 36 ++++++++++++++-------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/integration-test-config.yaml b/.github/integration-test-config.yaml index 63ff4c764..5ac8914aa 100644 --- a/.github/integration-test-config.yaml +++ b/.github/integration-test-config.yaml @@ -12,24 +12,24 @@ nightly: # For AWS projects, only deploy.sh and pytest are executed (no terraform destroy) tests: # Social integrations - # - type: api - # path: examples/api/slack - # - type: api - # path: examples/api/telegram - # - type: api - # path: examples/api/messenger - # - type: api - # path: examples/api/whatsapp - # - type: api - # path: examples/api/instagram - # - type: api - # path: examples/api/gmail - # - type: api - # path: examples/api/multimodal/dynamodb - # requires_aws: true - # - type: api - # path: examples/api/multimodal/redis - # requires_redis: true + - type: api + path: examples/api/slack + - type: api + path: examples/api/telegram + - type: api + path: examples/api/messenger + - type: api + path: examples/api/whatsapp + - type: api + path: examples/api/instagram + - type: api + path: examples/api/gmail + - type: api + path: examples/api/multimodal/dynamodb + requires_aws: true + - type: api + path: examples/api/multimodal/redis + requires_redis: true weekly: # Tests that run once a week From b23c3184cdd418954581d177391ff0d4029ec140 Mon Sep 17 00:00:00 2001 From: yl-nuwan Date: Fri, 5 Jun 2026 10:07:58 +0530 Subject: [PATCH 03/90] removed the conditional exit added for testing --- .../aws-containerized/openai-dynamodb-scalable/deploy/deploy.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/aws-containerized/openai-dynamodb-scalable/deploy/deploy.sh b/examples/aws-containerized/openai-dynamodb-scalable/deploy/deploy.sh index 485704ce1..57ce7dc36 100755 --- a/examples/aws-containerized/openai-dynamodb-scalable/deploy/deploy.sh +++ b/examples/aws-containerized/openai-dynamodb-scalable/deploy/deploy.sh @@ -36,6 +36,6 @@ create_deployment_packages() { } create_deployment_packages "$1" -exit 1 + terrafor init terraform apply From 1c7ba00bb51a8dbea7eabc2e2ac6ab902dd446bb Mon Sep 17 00:00:00 2001 From: yl-nuwan Date: Fri, 5 Jun 2026 10:12:44 +0530 Subject: [PATCH 04/90] fixed the typo --- .../aws-containerized/openai-dynamodb-scalable/deploy/deploy.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/aws-containerized/openai-dynamodb-scalable/deploy/deploy.sh b/examples/aws-containerized/openai-dynamodb-scalable/deploy/deploy.sh index 57ce7dc36..4c13d85ae 100755 --- a/examples/aws-containerized/openai-dynamodb-scalable/deploy/deploy.sh +++ b/examples/aws-containerized/openai-dynamodb-scalable/deploy/deploy.sh @@ -37,5 +37,5 @@ create_deployment_packages() { create_deployment_packages "$1" -terrafor init +terraform init terraform apply From 9b228cc42ca56f6ca8bd0262c82179b3da89586d Mon Sep 17 00:00:00 2001 From: yl-nuwan Date: Fri, 5 Jun 2026 11:38:09 +0530 Subject: [PATCH 05/90] feat(aws-queue-mode): separate ECS execution and task roles with DynamoDB support - Separate agent runner execution role (image pull, logs) from task role (SQS, DynamoDB access) - Add CloudWatch Logs policy to agent runner task role for container log writes - Add DynamoDB memory table access policy for agent runner task role - Update ECS task definition to use execution role for image operations - Add comprehensive docstring updates to akagentrunner.py for boto3 and Lambda message format handling - Update sqs_handler.py to handle both Lambda camelCase and boto3 PascalCase attribute keys - Add queue-mode-guide.md documentation for queue mode deployment and configuration --- ak-deployment/ak-aws/containerized/sqs.tf | 81 +++++- .../aws/containerized/akagentrunner.py | 4 + .../deployment/aws/core/sqs_handler.py | 30 ++- docs/queue-mode-guide.md | 236 ++++++++++++++++++ 4 files changed, 340 insertions(+), 11 deletions(-) create mode 100644 docs/queue-mode-guide.md diff --git a/ak-deployment/ak-aws/containerized/sqs.tf b/ak-deployment/ak-aws/containerized/sqs.tf index 59ebaea66..93279644b 100644 --- a/ak-deployment/ak-aws/containerized/sqs.tf +++ b/ak-deployment/ak-aws/containerized/sqs.tf @@ -184,6 +184,30 @@ resource "aws_iam_role_policy_attachment" "rest_service_response_store_attachmen } # ---------- IAM — Agent Runner ECS Task Role ---------- +# Separate execution role (pull image, write logs) and task role (SQS, DynamoDB). + +resource "aws_iam_role" "agent_runner_execution_role" { + count = var.enable_queue_mode ? 1 : 0 + + name = "${local.prefix}-agent-runner-exec-role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + Effect = "Allow" + Action = "sts:AssumeRole" + Principal = { Service = "ecs-tasks.amazonaws.com" } + }] + }) + + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "agent_runner_execution_policy" { + count = var.enable_queue_mode ? 1 : 0 + role = aws_iam_role.agent_runner_execution_role[0].name + policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" +} resource "aws_iam_role" "agent_runner_task_role" { count = var.enable_queue_mode ? 1 : 0 @@ -202,10 +226,29 @@ resource "aws_iam_role" "agent_runner_task_role" { tags = var.tags } -resource "aws_iam_role_policy_attachment" "agent_runner_task_execution" { +# CloudWatch Logs — agent runner needs to write its own logs +resource "aws_iam_policy" "agent_runner_logs_policy" { + count = var.enable_queue_mode ? 1 : 0 + name = "${local.prefix}-agent-runner-logs" + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + Effect = "Allow" + Action = [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ] + Resource = "arn:aws:logs:${var.region}:*:log-group:/ecs/${local.prefix}-agent-runner:*" + }] + }) +} + +resource "aws_iam_role_policy_attachment" "agent_runner_logs_attachment" { count = var.enable_queue_mode ? 1 : 0 role = aws_iam_role.agent_runner_task_role[0].name - policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" + policy_arn = aws_iam_policy.agent_runner_logs_policy[0].arn } resource "aws_iam_policy" "agent_runner_sqs_policy" { @@ -251,6 +294,38 @@ resource "aws_iam_role_policy_attachment" "agent_runner_sqs_attachment" { policy_arn = aws_iam_policy.agent_runner_sqs_policy[0].arn } +# DynamoDB session store access for agent runner (same table as REST Service) +resource "aws_iam_policy" "agent_runner_dynamodb_memory_policy" { + count = var.enable_queue_mode && var.create_dynamodb_memory_table ? 1 : 0 + name = "${local.prefix}-agent-runner-dynamodb-memory" + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + Effect = "Allow" + Action = [ + "dynamodb:DescribeTable", + "dynamodb:GetItem", + "dynamodb:PutItem", + "dynamodb:UpdateItem", + "dynamodb:DeleteItem", + "dynamodb:Query", + "dynamodb:Scan" + ] + Resource = [ + local.dynamodb_memory_table_arn, + "${local.dynamodb_memory_table_arn}/index/*" + ] + }] + }) +} + +resource "aws_iam_role_policy_attachment" "agent_runner_dynamodb_memory_attachment" { + count = var.enable_queue_mode && var.create_dynamodb_memory_table ? 1 : 0 + role = aws_iam_role.agent_runner_task_role[0].name + policy_arn = aws_iam_policy.agent_runner_dynamodb_memory_policy[0].arn +} + # ---------- ECS Agent Runner Service ---------- resource "aws_security_group" "agent_runner" { @@ -285,7 +360,7 @@ resource "aws_ecs_task_definition" "agent_runner" { network_mode = "awsvpc" cpu = var.agent_runner_cpu memory = var.agent_runner_memory - execution_role_arn = aws_iam_role.agent_runner_task_role[0].arn + execution_role_arn = aws_iam_role.agent_runner_execution_role[0].arn task_role_arn = aws_iam_role.agent_runner_task_role[0].arn container_definitions = jsonencode([ diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/akagentrunner.py b/ak-py/src/agentkernel/deployment/aws/containerized/akagentrunner.py index ea5f83e92..e4b316099 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/akagentrunner.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/akagentrunner.py @@ -72,6 +72,9 @@ def _get_record_attributes(cls, raw_queue_message: dict) -> dict: """ Extract routing attributes from a raw SQS message. + Works with both boto3 receive_message records (PascalCase keys) + and Lambda event records (camelCase keys). + :param raw_queue_message: boto3 SQS message dict :return: Extracted attributes dict :raises ValueError: If request_id is missing @@ -84,6 +87,7 @@ def _get_record_attributes(cls, raw_queue_message: dict) -> dict: raise ValueError("request_id is required in SQS message attributes") return { + # boto3: "MessageGroupId" under Attributes; Lambda: "MessageGroupId" under attributes "message_group_id": attributes.get("MessageGroupId"), "message_deduplication_id": attributes.get("MessageDeduplicationId"), "request_id": request_id, diff --git a/ak-py/src/agentkernel/deployment/aws/core/sqs_handler.py b/ak-py/src/agentkernel/deployment/aws/core/sqs_handler.py index 87a6eee2c..bd6943887 100644 --- a/ak-py/src/agentkernel/deployment/aws/core/sqs_handler.py +++ b/ak-py/src/agentkernel/deployment/aws/core/sqs_handler.py @@ -154,27 +154,41 @@ def _build_message_attributes( @staticmethod def get_message_system_attributes(raw_queue_message_record: Mapping[str, Any]) -> Dict[str, Any]: """ - Return the SQS system attributes from a raw Lambda queue record. + Return the SQS system attributes from a raw SQS message record. - :param raw_queue_message_record: Raw SQS record from Lambda containing the ``attributes`` block - :return: A shallow copy of the record's system ``attributes`` mapping + Handles both Lambda event records (``attributes`` key, camelCase) + and boto3 receive_message records (``Attributes`` key, PascalCase). + + :param raw_queue_message_record: Raw SQS record + :return: A shallow copy of the record's system attributes mapping """ - return dict(raw_queue_message_record.get("attributes", {}) or {}) + # Lambda uses "attributes", boto3 receive_message uses "Attributes" + attrs = raw_queue_message_record.get("Attributes") or raw_queue_message_record.get("attributes") or {} + return dict(attrs) @staticmethod def get_message_custom_attributes(raw_queue_message_record: Mapping[str, Any]) -> Dict[str, Any]: """ - Return the custom SQS message attributes from a raw Lambda queue record. + Return the custom SQS message attributes from a raw SQS message record. + + Handles both Lambda event records (``messageAttributes`` key, camelCase) + and boto3 receive_message records (``MessageAttributes`` key, PascalCase). - :param raw_queue_message_record: Raw SQS record from Lambda containing ``messageAttributes`` + :param raw_queue_message_record: Raw SQS record :return: A dictionary mapping custom attribute names to their scalar values """ - message_attributes = raw_queue_message_record.get("messageAttributes", {}) or {} + # Lambda uses "messageAttributes", boto3 receive_message uses "MessageAttributes" + message_attributes = ( + raw_queue_message_record.get("MessageAttributes") + or raw_queue_message_record.get("messageAttributes") + or {} + ) flattened_attributes: Dict[str, Any] = {} for attribute_name, attribute in message_attributes.items(): if isinstance(attribute, Mapping): attribute_value = ( - attribute.get("stringValue") or attribute.get("StringValue") or attribute.get("binaryValue") or attribute.get("BinaryValue") + attribute.get("StringValue") or attribute.get("stringValue") + or attribute.get("BinaryValue") or attribute.get("binaryValue") ) else: attribute_value = attribute diff --git a/docs/queue-mode-guide.md b/docs/queue-mode-guide.md new file mode 100644 index 000000000..a28644699 --- /dev/null +++ b/docs/queue-mode-guide.md @@ -0,0 +1,236 @@ +# Queue Mode — How It Works and How to Add It to ECS + +This document explains what's already built for the Lambda (serverless) queue modes and +how to replicate the same pattern in the ECS (containerized) deployment. + +--- + +## What Is Queue Mode? + +Queue mode decouples the HTTP request from the agent processing by placing an SQS FIFO +queue between the caller and the Agent Runner. This gives you: + +- **Backpressure control** – the queue absorbs burst traffic. +- **Ordered processing per session** – `MessageGroupId = SessionID` keeps chat turns in order. +- **Automatic retries** – failed messages reappear after the visibility timeout expires. +- **Deduplication** – `MessageDeduplicationId` prevents the same message being processed twice. + +Two sub-modes are supported: + +| Mode | What the caller does | How they get the response | +|------|---------------------|---------------------------| +| **REST Sync** | POST → wait | Same HTTP response (polls DB internally) | +| **REST Async** | POST → get a job ID | Later GET to a separate endpoint | + +--- + +## How It Works in Lambda (Serverless) + +### Components + +``` +Client + │ + ▼ +API Gateway (HTTP) + │ POST /api/{version}/{endpoint} + ▼ +Request Handler Lambda + │ puts message on Input Queue + ▼ +Input SQS FIFO Queue ──────────────────────────────────────────────────────┐ + │ Event Source Mapping (batch = 1 per Lambda) │ + ▼ │ +Agent Runner Lambda (scales 1:1 with queue batches) │ visibility + │ processes message, puts result on Output Queue │ timeout + ▼ │ +Output SQS FIFO Queue ◄─────────────────────────────────────────────────────┘ + │ Event Source Mapping + ▼ +Response Handler Lambda + │ + ├─► (REST Sync / REST Async / Streaming) writes to DynamoDB Response Store + │ + └─► (Async/WebSocket) sends directly via WebSocket PostToConnection +``` + +### SQS Queue Design + +Both queues are **FIFO** with: + +| Setting | Purpose | +|---------|---------| +| `MessageGroupId = SessionID` | Preserves order within a session | +| `MessageDeduplicationId` | Prevents the agent running the same turn twice | +| `MessageVisiibilityTimeout` | Makes undeleted messages reappear for retry | +| `MessageRetentionPeriod` | Auto-deletes stuck messages, breaks infinite loops | +| DLQ (optional) | Catches messages that exceed `maxReceiveCount` | + +### REST Sync Flow + +1. Client sends `POST /api/v1/chat`. +2. **Request Handler Lambda** puts the message on the Input Queue, then **polls DynamoDB** + until the response appears, and returns it on the same HTTP connection. +3. **Agent Runner Lambda** is triggered by the Input Queue ESM, processes the message, + puts the response on the Output Queue, and returns `batchItemFailures` for anything + that failed (so those messages come back for retry). +4. **Response Handler Lambda** is triggered by the Output Queue ESM and writes the + response to DynamoDB (keyed by SessionID, with a TTL). + +Failure handling: +- If the Agent Runner Lambda crashes, the message reappears after the visibility timeout. +- Partial failures are reported via `batchItemFailures`; only those messages stay in the + queue for retry. +- If the Response Handler fails to write DynamoDB, the message stays on the Output Queue + and is retried. + +### REST Async Flow + +Same as REST Sync except: + +1. The `POST` returns immediately (202) with the session/job ID. +2. The client polls `GET /api/v1/chat/{sessionId}` to retrieve the result. +3. The Request Handler uses **separate routes** for the POST and GET. + +### WebSocket (Async) Mode + +1. Client connects via WebSocket (API Gateway WebSocket). +2. **WS Connection Handler Lambda** stores the connection ID in DynamoDB. +3. Messages are put on the Input Queue (same Agent Runner pipeline). +4. **Response Handler Lambda** reads from the Output Queue and calls + `execute-api:ManageConnections` (PostToConnection) to push the response back to the + client over the still-open WebSocket. + +### Terraform Modules (Serverless) + +Located under `ak-deployment/ak-aws/serverless/modules/`: + +| Module | Role | +|--------|------| +| `queues/` | Creates Input and Output SQS FIFO queues | +| `request-handler/` | Request Handler Lambda + optional SQS send permission | +| `agent-runner/` | Agent Runner Lambda + ESM binding to Input Queue | +| `response-handler/` | Response Handler Lambda + ESM binding to Output Queue | +| `api-gateway/` | HTTP API Gateway wiring | +| `websocket-api-gateway/` | WebSocket API Gateway | +| `ws-connection-handler/` | WebSocket connection lifecycle Lambda | + +--- + +## How to Add Queue Mode to ECS (Containerized) + +The ECS deployment is fundamentally the same pipeline, **except Lambda functions are +replaced by a long-running ECS service** that runs two threads: + +| Thread | Responsibility | +|--------|---------------| +| Thread 1 | Runs the FastAPI/REST service (receives requests, returns responses) | +| Thread 2 | Polls the Output Queue and writes to DynamoDB (REST modes) or sends via WebSocket | + +The **Agent Runner** is already a separate ECS service that polls the Input Queue. + +### REST Sync — ECS Queue Mode + +``` +Client + │ + ▼ +API Gateway (HTTP) — existing aws_apigatewayv2_api.http_api + │ routes to ALB via VPC Link — existing aws_apigatewayv2_integration.alb_proxy + ▼ +ALB → ECS REST Service (Thread 1) + │ PUT message on Input Queue + │ wait / poll DynamoDB Response Store + │ return response on same connection + │ + │ (Thread 2 running in same container) + │ poll Output Queue → write to DynamoDB Response Store + ▼ +Input SQS FIFO Queue + ▼ +Agent Runner ECS Service (separate ECS task/service) + │ polls queue, processes, puts result on Output Queue + ▼ +Output SQS FIFO Queue + ▼ +ECS REST Service Thread 2 → DynamoDB Response Store +``` + +New AWS resources needed: +- `aws_sqs_queue` — Input Queue (FIFO) +- `aws_sqs_queue` — Output Queue (FIFO) +- `aws_dynamodb_table` — Response Store (keyed by `session_id`, with TTL) +- IAM policies giving the REST Service ECS Task Role: + - `sqs:SendMessage` on Input Queue + - `sqs:ReceiveMessage / DeleteMessage / ChangeMessageVisibility` on Output Queue + - `dynamodb:PutItem / GetItem / Query / DeleteItem` on Response Store +- IAM policies giving the Agent Runner ECS Task Role: + - `sqs:ReceiveMessage / DeleteMessage / ChangeMessageVisibility` on Input Queue + - `sqs:SendMessage` on Output Queue +- Existing API Gateway + ALB routes are reused as-is; only the application code changes + (Thread 2 is added to the REST Service container). + +Environment variables to inject into the REST Service container: + +``` +AK_EXECUTION__QUEUES__INPUT__URL = +AK_EXECUTION__QUEUES__OUTPUT__URL = +AK_EXECUTION__RESPONSE_STORE__DYNAMODB__TABLE_NAME = +``` + +Environment variables for the Agent Runner container: + +``` +AK_EXECUTION__QUEUES__INPUT__URL = +AK_EXECUTION__QUEUES__OUTPUT__URL = +AK_EXECUTION__QUEUES__INPUT__MAX_RECEIVE_COUNT = +``` + +### REST Async — ECS Queue Mode + +Identical infrastructure to REST Sync. The difference is purely in the application: + +- `POST /api/v1/chat` returns **202 Accepted** with a `session_id` immediately after + enqueuing (Thread 1 does not wait). +- `GET /api/v1/chat/{sessionId}` reads from DynamoDB Response Store and returns the + result when ready (or 202 if still processing). + +No additional AWS resources are needed beyond the REST Sync setup. The API Gateway already +supports both `POST` and `GET` routes to the ALB. + +### Scaling the Agent Runner ECS Service + +Unlike Lambda (which auto-scales 1:1 with queue batches), ECS needs an explicit scaling +policy. The recommended approach is **backlog-per-task target tracking**: + +1. A Lambda function (or EventBridge scheduled rule) periodically reads + `ApproximateNumberOfMessages` from SQS and the current running task count. +2. It computes `BacklogPerTask = queueDepth / runningTasks` and publishes this as a + custom CloudWatch metric. +3. An ECS Target Tracking scaling policy scales the Agent Runner service to keep + `BacklogPerTask` at or below the configured target. + +This is covered in detail in `scalability.md` under the **SQS + ECS → Scaling** section. + +### Key Differences vs Lambda + +| Aspect | Lambda | ECS | +|--------|--------|-----| +| Input Queue trigger | Event Source Mapping (push) | Agent Runner polls the queue | +| Partial failure reporting | `batchItemFailures` return value | Failed messages not deleted (visibility timeout) | +| Scaling | Automatic, 1 Lambda per batch | Manual target tracking policy | +| Response Handler | Separate Lambda triggered by Output Queue ESM | Thread 2 inside the REST Service container | +| Session DB writes | Response Handler Lambda | REST Service Thread 2 | + +--- + +## Summary of What's Already Built + +| Component | Lambda (done) | ECS (todo) | +|-----------|--------------|------------| +| Input/Output SQS Queues | ✅ `modules/queues/` | ❌ needs new TF resources | +| Agent Runner | ✅ `modules/agent-runner/` | ❌ separate ECS service | +| Request Handler / REST Service | ✅ `modules/request-handler/` | ❌ add queue env vars + thread 2 | +| Response Handler | ✅ `modules/response-handler/` | ❌ merged into REST Service Thread 2 | +| DynamoDB Response Store | ✅ provisioned inside serverless stack | ❌ needs new TF resource | +| WebSocket Mode | ✅ `modules/websocket-api-gateway/` + `modules/ws-connection-handler/` | ❌ not yet | From b6f6d398224486704a7c7c6e87523a502d5ec53f Mon Sep 17 00:00:00 2001 From: yl-nuwan Date: Fri, 5 Jun 2026 13:22:16 +0530 Subject: [PATCH 06/90] feat(cleanup): add clean and rebuild scripts for scalable containerized example --- .../openai-dynamodb-scalable/app.py | 26 ---------- .../openai-dynamodb-scalable/clean.sh | 22 ++++++++ .../openai-dynamodb-scalable/deploy/deploy.sh | 50 ++++++++++--------- .../openai-dynamodb-scalable/rebuild.sh | 41 +++++++++++++++ 4 files changed, 89 insertions(+), 50 deletions(-) delete mode 100644 examples/aws-containerized/openai-dynamodb-scalable/app.py create mode 100755 examples/aws-containerized/openai-dynamodb-scalable/clean.sh create mode 100755 examples/aws-containerized/openai-dynamodb-scalable/rebuild.sh diff --git a/examples/aws-containerized/openai-dynamodb-scalable/app.py b/examples/aws-containerized/openai-dynamodb-scalable/app.py deleted file mode 100644 index 9d20ccecf..000000000 --- a/examples/aws-containerized/openai-dynamodb-scalable/app.py +++ /dev/null @@ -1,26 +0,0 @@ -from agentkernel.api import RESTAPI -from agentkernel.openai import OpenAIModule -from agents import Agent - -math_agent = Agent( - name="math", - handoff_description="Specialist agent for math questions", - instructions="You provide help with math problems. Explain your reasoning at each step and include examples. \ - If prompted for anything else you refuse to answer.", -) - -history_agent = Agent( - name="history", - handoff_description="Specialist agent for historical questions", - instructions="You provide assistance with historical queries. Explain important events and context clearly.", -) - -triage_agent = Agent( - name="triage", - instructions="You determine which agent to use based on the user's question.", - handoffs=[history_agent, math_agent], -) - -OpenAIModule([triage_agent, math_agent, history_agent]) - -runner = RESTAPI.run diff --git a/examples/aws-containerized/openai-dynamodb-scalable/clean.sh b/examples/aws-containerized/openai-dynamodb-scalable/clean.sh new file mode 100755 index 000000000..af0f2f677 --- /dev/null +++ b/examples/aws-containerized/openai-dynamodb-scalable/clean.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +# Clean all build artifacts and dist directories + +set -euo pipefail + +echo "Cleaning build artifacts..." + +# Remove dist directories +rm -rf dist dist-rest-service dist-agent-runner + +# Remove virtual environment +rm -rf .venv + +# Remove Python cache +find . -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true +find . -type f -name "*.pyc" -delete 2>/dev/null || true + +# Remove requirements.txt if it exists +rm -f requirements.txt + +echo "Clean complete!" diff --git a/examples/aws-containerized/openai-dynamodb-scalable/deploy/deploy.sh b/examples/aws-containerized/openai-dynamodb-scalable/deploy/deploy.sh index 4c13d85ae..f22614d45 100755 --- a/examples/aws-containerized/openai-dynamodb-scalable/deploy/deploy.sh +++ b/examples/aws-containerized/openai-dynamodb-scalable/deploy/deploy.sh @@ -3,39 +3,41 @@ set -e create_deployment_packages() { - local mode="${1-}" pushd ../ uv export --no-hashes > requirements.txt - for target in dist-rest-service dist-agent-runner; do - rm -rf "$target" - mkdir -p "$target/data" - - if [[ "$mode" != "local" ]]; then - uv pip install -r requirements.txt --target="$target/data" - else - uv pip install -r requirements.txt --target="$target/data" \ - --find-links ../../../ak-py/dist - uv pip install --force-reinstall --target="$target/data" \ - --find-links ../../../ak-py/dist \ - agentkernel[adk,api,aws,test] || true - fi - - cp config.yaml "$target/data/" - done - - cp app_rest_service.py dist-rest-service/data/ - cp deploy/Dockerfile.rest-service dist-rest-service/Dockerfile - - cp app_agent_runner.py dist-agent-runner/data/ - cp deploy/Dockerfile.agent-runner dist-agent-runner/Dockerfile + # REST Service dist + rm -rf dist-rest-service + mkdir -p dist-rest-service/data + if [[ ${1-} != "local" ]]; then + uv pip install -r requirements.txt --target=dist-rest-service/data + else + uv pip install -r requirements.txt --target=dist-rest-service/data --find-links ../../../ak-py/dist + uv pip install --force-reinstall --target=dist-rest-service/data --find-links ../../../ak-py/dist agentkernel[adk,api,aws,test] || true + fi + cp config.yaml app_rest_service.py dist-rest-service/data/ + + # Agent Runner dist + rm -rf dist-agent-runner + mkdir -p dist-agent-runner/data + if [[ ${1-} != "local" ]]; then + uv pip install -r requirements.txt --target=dist-agent-runner/data + else + uv pip install -r requirements.txt --target=dist-agent-runner/data --find-links ../../../ak-py/dist + uv pip install --force-reinstall --target=dist-agent-runner/data --find-links ../../../ak-py/dist agentkernel[adk,api,aws,test] || true + fi + cp config.yaml app_agent_runner.py dist-agent-runner/data/ rm -f requirements.txt popd || exit 1 + + # Copy Dockerfiles into dist directories (must run from deploy/ after popd) + cp Dockerfile.rest-service ../dist-rest-service/Dockerfile + cp Dockerfile.agent-runner ../dist-agent-runner/Dockerfile } -create_deployment_packages "$1" +create_deployment_packages $1 terraform init terraform apply diff --git a/examples/aws-containerized/openai-dynamodb-scalable/rebuild.sh b/examples/aws-containerized/openai-dynamodb-scalable/rebuild.sh new file mode 100755 index 000000000..678f51b16 --- /dev/null +++ b/examples/aws-containerized/openai-dynamodb-scalable/rebuild.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +# Clean rebuild script for the scalable containerized example +# This ensures a fresh build without any cached artifacts + +set -euo pipefail + +echo "=========================================" +echo "Clean Rebuild - Scalable Containerized" +echo "=========================================" +echo "" + +# Step 1: Clean +echo "Step 1: Cleaning build artifacts..." +./clean.sh +echo "✓ Clean complete" +echo "" + +# Step 2: Build venv and sync dependencies +echo "Step 2: Setting up Python environment..." +./build.sh "$@" +echo "✓ Python environment ready" +echo "" + +# Step 3: Deploy +echo "Step 3: Building deployment packages and deploying..." +cd deploy +./deploy.sh "$@" +cd .. +echo "✓ Deployment complete" +echo "" + +echo "=========================================" +echo "Rebuild complete!" +echo "=========================================" +echo "" +echo "Next steps:" +echo "1. Check CloudWatch logs for the agent-runner service" +echo "2. Test the API endpoint" +echo "3. Monitor SQS queue metrics" +echo "" From 8fb53297ac401c252cc7f5e55ed7f3e1ca74dc3d Mon Sep 17 00:00:00 2001 From: yl-nuwan Date: Fri, 5 Jun 2026 14:24:58 +0530 Subject: [PATCH 07/90] feat(ecs): add command override variables for ECS tasks and update main.tf --- ak-deployment/ak-aws/containerized/ecs.tf | 4 ++++ ak-deployment/ak-aws/containerized/sqs.tf | 3 +++ ak-deployment/ak-aws/containerized/variables.tf | 12 ++++++++++++ .../openai-dynamodb-scalable/deploy/Dockerfile | 8 -------- .../openai-dynamodb-scalable/deploy/main.tf | 8 ++++++++ examples/aws-serverless/scalable-openai/uv.lock | 16 ++-------------- 6 files changed, 29 insertions(+), 22 deletions(-) delete mode 100644 examples/aws-containerized/openai-dynamodb-scalable/deploy/Dockerfile diff --git a/ak-deployment/ak-aws/containerized/ecs.tf b/ak-deployment/ak-aws/containerized/ecs.tf index 0ab781182..77bb339ee 100644 --- a/ak-deployment/ak-aws/containerized/ecs.tf +++ b/ak-deployment/ak-aws/containerized/ecs.tf @@ -85,6 +85,10 @@ module "ecs" { image = module.docker_image[0].docker_image_uri essential = true readonlyRootFilesystem = false + + # Command override - if provided, replaces the Docker image's CMD + command = var.ecs_container_command + portMappings = [ { name = local.container_name, diff --git a/ak-deployment/ak-aws/containerized/sqs.tf b/ak-deployment/ak-aws/containerized/sqs.tf index 93279644b..335f7c89b 100644 --- a/ak-deployment/ak-aws/containerized/sqs.tf +++ b/ak-deployment/ak-aws/containerized/sqs.tf @@ -369,6 +369,9 @@ resource "aws_ecs_task_definition" "agent_runner" { image = var.agent_runner_image_uri != null ? var.agent_runner_image_uri : module.docker_image[0].docker_image_uri essential = true + # Command override - if provided, replaces the Docker image's CMD + command = var.agent_runner_command + environment = [ for k, v in merge( var.environment_variables, diff --git a/ak-deployment/ak-aws/containerized/variables.tf b/ak-deployment/ak-aws/containerized/variables.tf index 2f69e25c1..1dc6bbdf1 100644 --- a/ak-deployment/ak-aws/containerized/variables.tf +++ b/ak-deployment/ak-aws/containerized/variables.tf @@ -163,6 +163,12 @@ variable "container_type" { } } +variable "ecs_container_command" { + type = list(string) + description = "Command override for the main ECS task (REST service). If not set, uses the Docker image's CMD. Example: [\"python\", \"app_rest_service.py\"]" + default = null +} + # CORS configuration for HTTP API (API Gateway v2) variable "enable_cors" { type = bool @@ -358,3 +364,9 @@ variable "agent_runner_image_uri" { description = "Docker image URI for the Agent Runner ECS task. Defaults to the same image as the REST Service if not set." default = null } + +variable "agent_runner_command" { + type = list(string) + description = "Command override for the Agent Runner ECS task. If not set, uses the Docker image's CMD. Example: [\"python\", \"app_agent_runner.py\"]" + default = null +} diff --git a/examples/aws-containerized/openai-dynamodb-scalable/deploy/Dockerfile b/examples/aws-containerized/openai-dynamodb-scalable/deploy/Dockerfile deleted file mode 100644 index b69be5076..000000000 --- a/examples/aws-containerized/openai-dynamodb-scalable/deploy/Dockerfile +++ /dev/null @@ -1,8 +0,0 @@ -FROM python:3.12-slim - -WORKDIR /app -COPY data/ /app/ - -EXPOSE 8000 - -CMD ["python", "-c", "from app import runner; runner()"] \ No newline at end of file diff --git a/examples/aws-containerized/openai-dynamodb-scalable/deploy/main.tf b/examples/aws-containerized/openai-dynamodb-scalable/deploy/main.tf index 4f6b97d90..02d5d27b8 100644 --- a/examples/aws-containerized/openai-dynamodb-scalable/deploy/main.tf +++ b/examples/aws-containerized/openai-dynamodb-scalable/deploy/main.tf @@ -75,6 +75,10 @@ module "containerized_agents" { package_path = "../dist-rest-service" container_type = "ecs" ecs_container_port = 8000 + + # Override the command to use the correct entrypoint + # The ECR module adds a Lambda-style CMD, so we override it here + ecs_container_command = ["python", "app_rest_service.py"] # ---- agent memory (session store) ---- create_dynamodb_memory_table = true @@ -85,6 +89,10 @@ module "containerized_agents" { # Agent Runner uses its own image (different CMD) agent_runner_image_uri = module.agent_runner_image.docker_image_uri + + # Override the command to use the correct entrypoint + # The ECR module adds a Lambda-style CMD, so we override it here + agent_runner_command = ["python", "app_agent_runner.py"] # SQS visibility timeout should exceed agent processing time sqs_input_visibility_timeout = 120 diff --git a/examples/aws-serverless/scalable-openai/uv.lock b/examples/aws-serverless/scalable-openai/uv.lock index 12ff9991d..b95cc6d94 100644 --- a/examples/aws-serverless/scalable-openai/uv.lock +++ b/examples/aws-serverless/scalable-openai/uv.lock @@ -38,9 +38,6 @@ openai = [ { name = "openai-guardrails" }, { name = "openinference-instrumentation-openai-agents" }, ] -redis = [ - { name = "redis" }, -] test = [ { name = "datasets" }, { name = "litellm" }, @@ -215,7 +212,7 @@ name = "aws-serverless-scalable-openai" version = "0.1.0" source = { virtual = "." } dependencies = [ - { name = "agentkernel", extra = ["aws", "openai", "redis"] }, + { name = "agentkernel", extra = ["aws", "openai"] }, ] [package.dev-dependencies] @@ -227,7 +224,7 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "agentkernel", extras = ["aws", "openai", "redis"], specifier = ">=0.4.0" }] +requires-dist = [{ name = "agentkernel", extras = ["aws", "openai"], specifier = ">=0.4.0" }] [package.metadata.requires-dev] dev = [ @@ -2963,15 +2960,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/70/a6/51fc1b0e61e3326e1c68a61cfd0c6b3c34c843681c4b1eefbf0596f59162/rapidfuzz-3.14.5-cp314-cp314t-win_arm64.whl", hash = "sha256:3e91dcd2549b8f8d843f98ba03a17e01f3d8b72ce942adbbb6761bc58ffce813", size = 855409, upload-time = "2026-04-07T11:16:15.787Z" }, ] -[[package]] -name = "redis" -version = "7.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/c8/983d5c6579a411d8a99bc5823cc5712768859b5ce2c8afe1a65b37832c81/redis-7.1.0.tar.gz", hash = "sha256:b1cc3cfa5a2cb9c2ab3ba700864fb0ad75617b41f01352ce5779dabf6d5f9c3c", size = 4796669, upload-time = "2025-11-19T15:54:39.961Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/89/f0/8956f8a86b20d7bb9d6ac0187cf4cd54d8065bc9a1a09eb8011d4d326596/redis-7.1.0-py3-none-any.whl", hash = "sha256:23c52b208f92b56103e17c5d06bdc1a6c2c0b3106583985a76a18f83b265de2b", size = 354159, upload-time = "2025-11-19T15:54:38.064Z" }, -] - [[package]] name = "referencing" version = "0.37.0" From cc5ef7291cdc69f61b46c3f7943a2b49f5d9e8a0 Mon Sep 17 00:00:00 2001 From: yl-nuwan Date: Fri, 5 Jun 2026 15:42:44 +0530 Subject: [PATCH 08/90] feat(aws-containerized): add queue-aware REST handler for ECS deployments - Add ECSQueueRequestHandler class that bypasses ChatService and directly enqueues requests to SQS - Implement sync mode (REST_SYNC) to wait for responses in DynamoDB Response Store - Implement async mode (REST_ASYNC) to return request_id for polling - Add GET /api/v1/chat/{session_id} endpoint for async response polling - Update ECSRESTService to use queue-aware handler instead of default REST API - Export ECSQueueRequestHandler from containerized module __init__.py - Update example app_rest_service.py to demonstrate queue-based request handling - Enables scalable ECS deployments with asynchronous agent execution and DynamoDB response storage --- .../deployment/aws/containerized/__init__.py | 1 + .../aws/containerized/akrestservice.py | 8 +- .../aws/containerized/ecs_queue_handler.py | 198 ++++++++++++++++++ .../app_rest_service.py | 7 +- 4 files changed, 209 insertions(+), 5 deletions(-) create mode 100644 ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/__init__.py b/ak-py/src/agentkernel/deployment/aws/containerized/__init__.py index bdb06997c..a8350dc8f 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/__init__.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/__init__.py @@ -1,2 +1,3 @@ from .akagentrunner import ECSAgentRunner from .akrestservice import ECSRESTService +from .ecs_queue_handler import ECSQueueRequestHandler diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/akrestservice.py b/ak-py/src/agentkernel/deployment/aws/containerized/akrestservice.py index 4e7c574fd..47112103c 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/akrestservice.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/akrestservice.py @@ -91,8 +91,12 @@ def run(cls) -> None: t2.start() cls._log.info("ECSRESTService: Thread 2 (output-queue poller) started") - cls._log.info("ECSRESTService: starting REST API (Thread 1)") - RESTAPI.run() + # Use queue-aware request handler that bypasses ChatService + # This directly enqueues to SQS without agent validation + from .ecs_queue_handler import ECSQueueRequestHandler + + cls._log.info("ECSRESTService: starting REST API with queue-aware handler (Thread 1)") + RESTAPI.run(handlers=[ECSQueueRequestHandler()]) # ------------------------------------------------------------------ # Lazy initialisers diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py b/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py new file mode 100644 index 000000000..f0d2441ea --- /dev/null +++ b/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py @@ -0,0 +1,198 @@ +""" +ECS Queue-aware REST Request Handler + +This handler bypasses ChatService and directly enqueues requests to SQS, +similar to how the Lambda serverless DefaultEndpointsHandler works. + +Used by ECSRESTService when queue mode is enabled. +""" + +import logging +from typing import Dict, Any +from fastapi import APIRouter, HTTPException + +from ....core.config import AKConfig +from ....core.model import BaseRunRequest, ExecutionMode +from ....api.handler import RESTRequestHandler +from ..core.response_store import ResponseDBHandler +from ..core.sqs_handler import SQSHandler + + +class ECSQueueRequestHandler(RESTRequestHandler): + """ + Queue-aware REST request handler for ECS deployments. + + Handles POST /api/v1/chat by: + 1. Enqueuing the request to Input SQS Queue + 2. Waiting for response in DynamoDB Response Store (sync mode) + 3. Returning the response to the client + + This bypasses ChatService entirely - NO agent validation happens here. + Agent validation and execution occurs in the Agent Runner service. + """ + + def __init__(self): + self._log = logging.getLogger("ak.ecs.queue_handler") + self._config = AKConfig.get() + self._response_store = None + + def _get_response_store(self): + """Lazy initialization of response store.""" + if self._response_store is None: + self._response_store = ResponseDBHandler().get_store() + return self._response_store + + def get_router(self) -> APIRouter: + """ + Returns the APIRouter for queue-based endpoints. + + - POST /api/v1/chat: Enqueue request and wait for response (sync mode) + - GET /api/v1/chat/{session_id}: Poll for response (async mode) + """ + router = APIRouter() + + @router.post("/api/v1/chat") + async def enqueue_and_wait(body: BaseRunRequest): + """ + Enqueue request to Input Queue. + + In REST_SYNC mode: Wait for response in DynamoDB Response Store. + In REST_ASYNC mode: Return request_id immediately. + """ + try: + # Validate required fields + if not body.session_id: + raise HTTPException(status_code=400, detail="session_id is required") + if not body.prompt: + raise HTTPException(status_code=400, detail="prompt is required") + + # Generate request_id + request_id = body.request_id if hasattr(body, 'request_id') and body.request_id else body.session_id + + self._log.info(f"Enqueuing request: session_id={body.session_id}, agent={body.agent}") + + # Send to Input Queue + queue_result = SQSHandler.send_message_to_input_queue( + message_body=body.model_dump(), + message_group_id=body.session_id, + message_deduplication_id=request_id, + message_attributes={"request_id": request_id} + ) + + self._log.info(f"Message enqueued: {queue_result}") + + # Handle based on execution mode + if self._config.execution.mode == ExecutionMode.REST_SYNC: + # Wait for response in DynamoDB + self._log.info(f"Waiting for response: request_id={request_id}") + + response = self._wait_for_response( + request_id=request_id, + session_id=body.session_id + ) + + if not response: + raise HTTPException( + status_code=504, + detail={ + "error": f"No response received for request_id: {request_id}", + "session_id": body.session_id + } + ) + + # Return the response body + return response.get("body", response) + + elif self._config.execution.mode == ExecutionMode.REST_ASYNC: + # Return request_id for polling + return { + "status": "ACCEPTED", + "request_id": request_id, + "session_id": body.session_id + } + + else: + raise HTTPException( + status_code=500, + detail=f"Unsupported execution mode: {self._config.execution.mode}" + ) + + except HTTPException: + raise + except Exception as e: + self._log.error(f"Error processing request: {e}", exc_info=True) + raise HTTPException( + status_code=500, + detail={"error": str(e), "session_id": body.session_id if body else None} + ) + + @router.get("/api/v1/chat/{session_id}") + async def poll_response(session_id: str, request_id: str = None): + """ + Poll for response (REST_ASYNC mode only). + + :param session_id: Session identifier + :param request_id: Optional specific request to poll for + """ + try: + if self._config.execution.mode != ExecutionMode.REST_ASYNC: + raise HTTPException( + status_code=404, + detail="GET endpoint only available in REST_ASYNC mode" + ) + + effective_request_id = request_id or session_id + self._log.info(f"Polling for response: request_id={effective_request_id}") + + response = self._get_response_store().get_message(effective_request_id) + + if not response: + return { + "status": "PENDING", + "request_id": effective_request_id, + "session_id": session_id + } + + # Return the response body + return response.get("body", response) + + except HTTPException: + raise + except Exception as e: + self._log.error(f"Error polling response: {e}", exc_info=True) + raise HTTPException( + status_code=500, + detail={"error": str(e), "session_id": session_id} + ) + + return router + + def _wait_for_response(self, request_id: str, session_id: str, max_retries: int = None, delay: int = None) -> Dict[str, Any]: + """ + Wait for response to appear in DynamoDB Response Store. + + :param request_id: Request identifier to wait for + :param session_id: Session identifier + :param max_retries: Maximum number of retry attempts (from config if None) + :param delay: Delay between retries in seconds (from config if None) + :return: Response dict from DynamoDB, or None if not found + """ + if max_retries is None: + max_retries = self._config.execution.response_store.retry_count + if delay is None: + delay = self._config.execution.response_store.delay + + import time + + for attempt in range(max_retries): + response = self._get_response_store().get_message(request_id) + if response: + self._log.info(f"Response found on attempt {attempt + 1}/{max_retries}") + return response + + if attempt < max_retries - 1: + self._log.debug(f"Response not ready, waiting {delay}s (attempt {attempt + 1}/{max_retries})") + time.sleep(delay) + + self._log.warning(f"Response not found after {max_retries} attempts") + return None diff --git a/examples/aws-containerized/openai-dynamodb-scalable/app_rest_service.py b/examples/aws-containerized/openai-dynamodb-scalable/app_rest_service.py index a4947f137..75b8a0760 100644 --- a/examples/aws-containerized/openai-dynamodb-scalable/app_rest_service.py +++ b/examples/aws-containerized/openai-dynamodb-scalable/app_rest_service.py @@ -1,10 +1,11 @@ from agentkernel.deployment.aws.containerized import ECSRESTService -# REST Service entrypoint — no agent definitions needed here. +# REST Service entrypoint — NO agent definitions. +# Agents are ONLY defined in the Agent Runner (app_agent_runner.py). +# # Thread 1: FastAPI handles POST /chat (enqueues to Input Queue, waits on DynamoDB) -# and GET /chat/{sessionId} (REST Async mode only) +# Agent validation happens in the Agent Runner, not here. # Thread 2: Output-queue poller — writes responses to DynamoDB (sync/async) -# or pushes via WebSocket PostToConnection (async/WS mode) runner = ECSRESTService.run if __name__ == "__main__": From dfcc8fe4f4dc559762da4aab1c08540a5569c902 Mon Sep 17 00:00:00 2001 From: yl-nuwan Date: Fri, 5 Jun 2026 15:55:16 +0530 Subject: [PATCH 09/90] fix(ecs-queue-handler): correct message attribute parameter in queue submission - Change message_attributes dict parameter to request_id positional argument - Align with SQS API expectations for FIFO queue message attributes - Simplify queue message construction by using native request_id parameter - Maintains backward compatibility with existing queue processing logic --- .../deployment/aws/containerized/ecs_queue_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py b/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py index f0d2441ea..7559e4c98 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py @@ -76,7 +76,7 @@ async def enqueue_and_wait(body: BaseRunRequest): message_body=body.model_dump(), message_group_id=body.session_id, message_deduplication_id=request_id, - message_attributes={"request_id": request_id} + request_id=request_id # This becomes a custom message attribute ) self._log.info(f"Message enqueued: {queue_result}") From 2c981bbef576f359b1a323f7562017bf8ca11a4d Mon Sep 17 00:00:00 2001 From: yl-nuwan Date: Fri, 5 Jun 2026 17:14:32 +0530 Subject: [PATCH 10/90] feat(aws-containerized): add structured logging for agent request lifecycle - Add detailed lifecycle logging with stage markers ([AGENT START], [AGENT PROCESSING], [AGENT RESPONSE], [AGENT DONE]) to akagentrunner.py for improved traceability - Add structured logging for output processing pipeline ([OUTPUT START], [OUTPUT STORE], [OUTPUT DONE]) in akrestservice.py with request/session tracking - Generate unique request_id using uuid.uuid4() instead of falling back to session_id in ecs_queue_handler.py for proper request isolation - Add comprehensive logging at each stage of request lifecycle ([REQUEST START], [ENQUEUED], [WAITING], [RESPONSE FOUND], [WAIT START], [WAIT SUCCESS], [WAIT RETRY], [WAIT TIMEOUT]) in ecs_queue_handler.py - Add debug_response_store.py utility script for inspecting DynamoDB response store state during development - Include request_id, session_id, agent, and prompt preview in logs for better debugging and tracing across async/sync execution modes --- .../aws/containerized/akagentrunner.py | 22 +++- .../aws/containerized/akrestservice.py | 9 +- .../aws/containerized/ecs_queue_handler.py | 24 ++-- .../debug_response_store.py | 123 ++++++++++++++++++ 4 files changed, 164 insertions(+), 14 deletions(-) create mode 100644 examples/aws-containerized/openai-dynamodb-scalable/debug_response_store.py diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/akagentrunner.py b/ak-py/src/agentkernel/deployment/aws/containerized/akagentrunner.py index e4b316099..88ae04630 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/akagentrunner.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/akagentrunner.py @@ -112,13 +112,29 @@ def process_message(cls, record: dict) -> None: :param record: boto3 SQS message dict """ - cls._log.info(f"Processing message {record.get('MessageId')}") + message_id = record.get('MessageId') + cls._log.info(f"[AGENT START] Processing message {message_id}") + body = BaseRunRequest.model_validate(json.loads(record["Body"])) - _, agent_response = cls._get_chat_service().process_chat_request(req=body) record_attributes = cls._get_record_attributes(raw_queue_message=record) + + cls._log.info( + f"[AGENT PROCESSING] request_id={record_attributes['request_id']}, " + f"session_id={body.session_id}, agent={body.agent}, prompt={body.prompt[:50] if body.prompt else 'N/A'}" + ) + + _, agent_response = cls._get_chat_service().process_chat_request(req=body) + + cls._log.info( + f"[AGENT RESPONSE] request_id={record_attributes['request_id']}, " + f"response_keys={list(agent_response.keys()) if isinstance(agent_response, dict) else 'N/A'}" + ) + cls._send_to_output_queue(message_body=agent_response, record_attributes=record_attributes) + cls._log.info( - f"Sent response to output queue: {SQSHandler.get_output_queue_url()}" + f"[AGENT DONE] Sent to output queue: {SQSHandler.get_output_queue_url()}, " + f"request_id={record_attributes['request_id']}" ) @classmethod diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/akrestservice.py b/ak-py/src/agentkernel/deployment/aws/containerized/akrestservice.py index 47112103c..f63570f5e 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/akrestservice.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/akrestservice.py @@ -140,15 +140,20 @@ def process_output_message(cls, record: Dict[str, Any]) -> None: :param record: boto3 SQS ``receive_message`` record """ - cls._log.info(f"Processing output message {record.get('MessageId')}") + message_id = record.get('MessageId') + cls._log.info(f"[OUTPUT START] Processing output message {message_id}") if cls._config.execution.mode == ExecutionMode.ASYNC: cls._broadcast_via_websocket(record) else: message = cls._construct_message_for_store(record) + cls._log.info( + f"[OUTPUT STORE] Writing to DynamoDB — request_id={message['request_id']}, " + f"session_id={message['session_id']}, body_keys={list(message.get('body', {}).keys()) if isinstance(message.get('body'), dict) else 'N/A'}" + ) cls._get_response_store().add_message(message) cls._log.info( - f"Stored response — session_id={message['session_id']} " + f"[OUTPUT DONE] Stored response — session_id={message['session_id']} " f"request_id={message['request_id']}" ) diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py b/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py index 7559e4c98..66b22bb89 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py @@ -66,10 +66,11 @@ async def enqueue_and_wait(body: BaseRunRequest): if not body.prompt: raise HTTPException(status_code=400, detail="prompt is required") - # Generate request_id - request_id = body.request_id if hasattr(body, 'request_id') and body.request_id else body.session_id + # Generate unique request_id (different from session_id) + import uuid + request_id = str(uuid.uuid4()) - self._log.info(f"Enqueuing request: session_id={body.session_id}, agent={body.agent}") + self._log.info(f"[REQUEST START] session_id={body.session_id}, request_id={request_id}, agent={body.agent}, prompt={body.prompt[:50]}") # Send to Input Queue queue_result = SQSHandler.send_message_to_input_queue( @@ -79,12 +80,12 @@ async def enqueue_and_wait(body: BaseRunRequest): request_id=request_id # This becomes a custom message attribute ) - self._log.info(f"Message enqueued: {queue_result}") + self._log.info(f"[ENQUEUED] MessageId={queue_result.get('MessageId')}, request_id={request_id}") # Handle based on execution mode if self._config.execution.mode == ExecutionMode.REST_SYNC: # Wait for response in DynamoDB - self._log.info(f"Waiting for response: request_id={request_id}") + self._log.info(f"[WAITING] Polling DynamoDB for request_id={request_id}") response = self._wait_for_response( request_id=request_id, @@ -96,10 +97,13 @@ async def enqueue_and_wait(body: BaseRunRequest): status_code=504, detail={ "error": f"No response received for request_id: {request_id}", - "session_id": body.session_id + "session_id": body.session_id, + "request_id": request_id } ) + self._log.info(f"[RESPONSE FOUND] request_id={request_id}, response_keys={list(response.keys())}") + # Return the response body return response.get("body", response) @@ -184,15 +188,17 @@ def _wait_for_response(self, request_id: str, session_id: str, max_retries: int import time + self._log.info(f"[WAIT START] request_id={request_id}, max_retries={max_retries}, delay={delay}s") + for attempt in range(max_retries): response = self._get_response_store().get_message(request_id) if response: - self._log.info(f"Response found on attempt {attempt + 1}/{max_retries}") + self._log.info(f"[WAIT SUCCESS] Found response on attempt {attempt + 1}/{max_retries} for request_id={request_id}") return response if attempt < max_retries - 1: - self._log.debug(f"Response not ready, waiting {delay}s (attempt {attempt + 1}/{max_retries})") + self._log.debug(f"[WAIT RETRY] Response not ready, waiting {delay}s (attempt {attempt + 1}/{max_retries}) for request_id={request_id}") time.sleep(delay) - self._log.warning(f"Response not found after {max_retries} attempts") + self._log.warning(f"[WAIT TIMEOUT] Response not found after {max_retries} attempts for request_id={request_id}") return None diff --git a/examples/aws-containerized/openai-dynamodb-scalable/debug_response_store.py b/examples/aws-containerized/openai-dynamodb-scalable/debug_response_store.py new file mode 100644 index 000000000..1c3794371 --- /dev/null +++ b/examples/aws-containerized/openai-dynamodb-scalable/debug_response_store.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +""" +Debug script to inspect DynamoDB Response Store. + +This helps diagnose issues with response caching and UUID generation. + +Usage: + python debug_response_store.py [--table-name ] [--list] [--clear] +""" + +import argparse +import sys +import boto3 +from boto3.dynamodb.conditions import Attr +from decimal import Decimal + + +def decimal_default(obj): + """JSON serializer for Decimal objects.""" + if isinstance(obj, Decimal): + return float(obj) + raise TypeError + + +def list_responses(table_name: str): + """List all responses in the DynamoDB table.""" + dynamodb = boto3.resource('dynamodb') + table = dynamodb.Table(table_name) + + print(f"\n=== Scanning DynamoDB Table: {table_name} ===\n") + + try: + response = table.scan() + items = response.get('Items', []) + + if not items: + print("No items found in the table.") + return + + print(f"Found {len(items)} item(s):\n") + + for idx, item in enumerate(items, 1): + print(f"--- Item {idx} ---") + print(f"request_id: {item.get('request_id', 'N/A')}") + print(f"session_id: {item.get('session_id', 'N/A')}") + + body = item.get('body', {}) + if isinstance(body, dict): + print(f"body.session_id: {body.get('session_id', 'N/A')}") + print(f"body.message: {body.get('message', 'N/A')[:100] if body.get('message') else 'N/A'}...") + else: + print(f"body: {str(body)[:100]}...") + + if 'expiry_time' in item: + print(f"expiry_time: {item['expiry_time']}") + + print() + + except Exception as e: + print(f"Error scanning table: {e}") + sys.exit(1) + + +def clear_responses(table_name: str): + """Clear all responses from the DynamoDB table.""" + dynamodb = boto3.resource('dynamodb') + table = dynamodb.Table(table_name) + + print(f"\n=== Clearing DynamoDB Table: {table_name} ===\n") + + try: + response = table.scan() + items = response.get('Items', []) + + if not items: + print("No items to delete.") + return + + print(f"Deleting {len(items)} item(s)...") + + with table.batch_writer() as batch: + for item in items: + request_id = item.get('request_id') + if request_id: + batch.delete_item(Key={'request_id': request_id}) + print(f" Deleted: request_id={request_id}, session_id={item.get('session_id', 'N/A')}") + + print(f"\nSuccessfully deleted {len(items)} item(s).") + + except Exception as e: + print(f"Error clearing table: {e}") + sys.exit(1) + + +def main(): + parser = argparse.ArgumentParser(description='Debug DynamoDB Response Store') + parser.add_argument('--table-name', default='ak-oai-scl-ecs-dev-response-store', + help='DynamoDB table name (default: ak-oai-scl-ecs-dev-response-store)') + parser.add_argument('--list', action='store_true', + help='List all responses in the table') + parser.add_argument('--clear', action='store_true', + help='Clear all responses from the table') + + args = parser.parse_args() + + if not args.list and not args.clear: + parser.print_help() + print("\nError: Please specify --list or --clear") + sys.exit(1) + + if args.list: + list_responses(args.table_name) + + if args.clear: + confirm = input(f"\nAre you sure you want to clear all items from {args.table_name}? (yes/no): ") + if confirm.lower() == 'yes': + clear_responses(args.table_name) + else: + print("Clear operation cancelled.") + + +if __name__ == '__main__': + main() From ed49d7961f7d83230b7af72797931a60fb44a368 Mon Sep 17 00:00:00 2001 From: yl-nuwan Date: Mon, 8 Jun 2026 10:50:27 +0530 Subject: [PATCH 11/90] testing with the rest async mode --- examples/aws-containerized/openai-dynamodb-scalable/config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/aws-containerized/openai-dynamodb-scalable/config.yaml b/examples/aws-containerized/openai-dynamodb-scalable/config.yaml index 35a959364..0cea3e1ff 100644 --- a/examples/aws-containerized/openai-dynamodb-scalable/config.yaml +++ b/examples/aws-containerized/openai-dynamodb-scalable/config.yaml @@ -8,7 +8,7 @@ session: table_name: "ak-oai-scl-ecs-dev-scalable-session_store" execution: - mode: rest_sync + mode: rest_async queues: input: url: "" # injected by Terraform via AK_EXECUTION__QUEUES__INPUT__URL From 8efdd03fc8f4d206101fbfa590bd4d4b2f18a470 Mon Sep 17 00:00:00 2001 From: yl-nuwan Date: Mon, 8 Jun 2026 11:18:57 +0530 Subject: [PATCH 12/90] Fixed the mode type --- .../aws-containerized/openai-dynamodb-scalable/deploy/main.tf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/aws-containerized/openai-dynamodb-scalable/deploy/main.tf b/examples/aws-containerized/openai-dynamodb-scalable/deploy/main.tf index 02d5d27b8..60c5e8dc2 100644 --- a/examples/aws-containerized/openai-dynamodb-scalable/deploy/main.tf +++ b/examples/aws-containerized/openai-dynamodb-scalable/deploy/main.tf @@ -85,7 +85,8 @@ module "containerized_agents" { # ---- queue mode ---- enable_queue_mode = true - queue_mode_type = "sync" + queue_mode_type = "async" + # Agent Runner uses its own image (different CMD) agent_runner_image_uri = module.agent_runner_image.docker_image_uri From 1afc5e6a5296f370edc406eb9b679dfcb175d6d8 Mon Sep 17 00:00:00 2001 From: yl-nuwan Date: Mon, 8 Jun 2026 11:30:07 +0530 Subject: [PATCH 13/90] fix(aws-containerized): correct path parameter substitution in async GET integration - Fix path parameter mapping from hardcoded sessionId to dynamic $request.path.sessionId - Add blank line for improved readability in resource configuration - Ensure API Gateway correctly forwards session ID from request path to backend service --- ak-deployment/ak-aws/containerized/sqs.tf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ak-deployment/ak-aws/containerized/sqs.tf b/ak-deployment/ak-aws/containerized/sqs.tf index 335f7c89b..7e713b694 100644 --- a/ak-deployment/ak-aws/containerized/sqs.tf +++ b/ak-deployment/ak-aws/containerized/sqs.tf @@ -431,8 +431,9 @@ resource "aws_apigatewayv2_integration" "async_get" { connection_type = "VPC_LINK" connection_id = aws_apigatewayv2_vpc_link.ecs_alb.id passthrough_behavior = "WHEN_NO_MATCH" + request_parameters = { - "overwrite:path" = "/api/v1/chat/{sessionId}" + "overwrite:path" = "/api/v1/chat/$request.path.sessionId" } } From 36b67940f3837f5f01528da03308e78a73ff928c Mon Sep 17 00:00:00 2001 From: yl-nuwan Date: Mon, 8 Jun 2026 12:49:13 +0530 Subject: [PATCH 14/90] chore(aws-containerized): rename sqs.tf to queue.tf and remove redundant comments - Rename sqs.tf to queue.tf for clearer module scope - Remove SQS Queue Mode header comment (redundant with file purpose) - Remove IAM section header comment (redundant with resource names) - Remove CloudWatch Logs section comment (redundant with resource names) - Improve code clarity by reducing comment clutter while maintaining resource documentation --- .../ak-aws/containerized/{sqs.tf => queue.tf} | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) rename ak-deployment/ak-aws/containerized/{sqs.tf => queue.tf} (96%) diff --git a/ak-deployment/ak-aws/containerized/sqs.tf b/ak-deployment/ak-aws/containerized/queue.tf similarity index 96% rename from ak-deployment/ak-aws/containerized/sqs.tf rename to ak-deployment/ak-aws/containerized/queue.tf index 7e713b694..41eb6f2f0 100644 --- a/ak-deployment/ak-aws/containerized/sqs.tf +++ b/ak-deployment/ak-aws/containerized/queue.tf @@ -1,10 +1,3 @@ -# --------------------------------------------------------------------------- -# SQS Queue Mode — ECS Containerized -# -# Uses the shared common/modules/sqs module (same as the serverless stack). -# Enable with: var.enable_queue_mode = true -# --------------------------------------------------------------------------- - data "aws_region" "current" {} # ---------- Input Queue ---------- @@ -101,7 +94,6 @@ resource "aws_dynamodb_table" "response_store" { tags = merge(var.tags, { Type = "ResponseStore" }) } -# ---------- IAM — REST Service ECS Task Role ---------- resource "aws_iam_policy" "rest_service_sqs_policy" { count = var.enable_queue_mode ? 1 : 0 @@ -226,7 +218,7 @@ resource "aws_iam_role" "agent_runner_task_role" { tags = var.tags } -# CloudWatch Logs — agent runner needs to write its own logs + resource "aws_iam_policy" "agent_runner_logs_policy" { count = var.enable_queue_mode ? 1 : 0 name = "${local.prefix}-agent-runner-logs" From 8faeac4983f47d4e64c54ee7b062b5da40fa7342 Mon Sep 17 00:00:00 2001 From: yl-nuwan Date: Mon, 8 Jun 2026 14:13:50 +0530 Subject: [PATCH 15/90] docs(aws-containerized): add queue mode documentation and update example README - Add comprehensive Queue Mode section to containerized module README with architecture diagram and configuration example - Document scalable queue mode use cases and processing architecture (REST Service threads + Agent Runner) - Add Queue Mode input variables section covering SQS visibility timeouts and Agent Runner configuration - Restructure openai-dynamodb-scalable example README with improved architecture overview and deployed resources documentation - Remove clean.sh and rebuild.sh scripts from scalable example (moved to root) - Add .terraform.lock.hcl file to deploy directory for reproducible Terraform deployments - Provides clear guidance for implementing high-throughput, asynchronously-processed agent workloads --- ak-deployment/ak-aws/containerized/README.md | 90 +++++ .../openai-dynamodb-scalable/README.md | 326 +++++++++++++++--- .../openai-dynamodb-scalable/clean.sh | 22 -- .../deploy/.terraform.lock.hcl | 67 ++++ .../openai-dynamodb-scalable/rebuild.sh | 41 --- 5 files changed, 428 insertions(+), 118 deletions(-) delete mode 100755 examples/aws-containerized/openai-dynamodb-scalable/clean.sh create mode 100644 examples/aws-containerized/openai-dynamodb-scalable/deploy/.terraform.lock.hcl delete mode 100755 examples/aws-containerized/openai-dynamodb-scalable/rebuild.sh diff --git a/ak-deployment/ak-aws/containerized/README.md b/ak-deployment/ak-aws/containerized/README.md index 85f455c99..2715cdfe6 100644 --- a/ak-deployment/ak-aws/containerized/README.md +++ b/ak-deployment/ak-aws/containerized/README.md @@ -262,6 +262,85 @@ resource "aws_cloudwatch_metric_alarm" "high_cpu" { } ``` +### Scalable Queue Mode with SQS + +For high-throughput workloads with asynchronous processing: + +```hcl +module "agent_runner_image" { + source = "yaalalabs/ak-common/aws//modules/ecr" + + env_alias = var.env_alias + module_name = "${var.module_name}-runner" + product_alias = var.product_alias + source_path = "${path.module}/dist-agent-runner" +} + +module "scalable_queue_app" { + source = "yaalalabs/ak-containerized/aws" + + region = "us-west-2" + product_alias = "myapp" + env_alias = "prod" + product_display_name = "Scalable Agent Service" + + module_name = "scalable" + package_path = "${path.module}/dist-rest-service" + + vpc_id = var.vpc_id + private_subnet_ids = var.private_subnet_ids + + # ECS Configuration for REST Service + ecs_cpu = 1024 + ecs_memory = 2048 + ecs_desired_count = 2 + ecs_container_port = 8000 + + # Override container command (ECR module injects Lambda-style CMD) + ecs_container_command = ["python", "app_rest_service.py"] + + # Session storage + create_dynamodb_memory_table = true + + # Enable Queue Mode + enable_queue_mode = true + queue_mode_type = "sync" # or "async" for polling mode + + # Agent Runner Configuration + agent_runner_image_uri = module.agent_runner_image.docker_image_uri + agent_runner_command = ["python", "app_agent_runner.py"] + agent_runner_cpu = 1024 + agent_runner_memory = 2048 + agent_runner_desired_count = 2 + + # SQS Configuration + sqs_input_visibility_timeout = 120 # Should exceed agent processing time + sqs_output_visibility_timeout = 60 + + environment_variables = { + OPENAI_API_KEY = var.openai_api_key + } + + api_version = "v1" + agent_endpoint = "chat" +} +``` + +**Queue Mode Architecture:** +- **REST Service** (Thread 1): Accepts HTTP requests, enqueues to Input Queue +- **REST Service** (Thread 2): Polls Output Queue, writes to DynamoDB Response Store +- **Agent Runner**: Polls Input Queue, executes agents, sends to Output Queue +- **REST_SYNC**: Client blocks until response ready in DynamoDB +- **REST_ASYNC**: Client receives `request_id`, polls GET endpoint for result + +**Use Cases:** +- Long-running agent workflows (>30s) +- Workloads requiring independent scaling of API and processing +- High-throughput scenarios with bursty traffic +- Decoupling request handling from agent execution + +See [examples/aws-containerized/openai-dynamodb-scalable](../../../examples/aws-containerized/openai-dynamodb-scalable/) for complete implementation. + ## 📥 Inputs | Name | Description | Type | Default | Required | @@ -304,6 +383,17 @@ resource "aws_cloudwatch_metric_alarm" "high_cpu" { | **State Management** | | `create_redis_cluster` | Enable Redis ElastiCache cluster | `bool` | `false` | no | | `create_dynamodb_memory_table` | Enable DynamoDB table for session storage | `bool` | `false` | no | +| **Queue Mode (Scalable Architecture)** | +| `enable_queue_mode` | Enable SQS queue mode with separate Agent Runner service | `bool` | `false` | no | +| `queue_mode_type` | Queue mode type: `sync` (client blocks) or `async` (client polls) | `string` | `"sync"` | no | +| `agent_runner_image_uri` | Docker image URI for Agent Runner (required when `enable_queue_mode=true`) | `string` | `null` | conditional | +| `agent_runner_command` | Command override for Agent Runner container | `list(string)` | `null` | no | +| `agent_runner_cpu` | Fargate CPU units for Agent Runner | `number` | `1024` | no | +| `agent_runner_memory` | Fargate memory in MiB for Agent Runner | `number` | `2048` | no | +| `agent_runner_desired_count` | Number of Agent Runner tasks | `number` | `1` | no | +| `sqs_input_visibility_timeout` | SQS Input Queue visibility timeout (seconds) | `number` | `30` | no | +| `sqs_output_visibility_timeout` | SQS Output Queue visibility timeout (seconds) | `number` | `30` | no | +| `ecs_container_command` | Command override for REST Service container | `list(string)` | `null` | no | ## 📤 Outputs diff --git a/examples/aws-containerized/openai-dynamodb-scalable/README.md b/examples/aws-containerized/openai-dynamodb-scalable/README.md index 3f72ea56b..7682fa57a 100644 --- a/examples/aws-containerized/openai-dynamodb-scalable/README.md +++ b/examples/aws-containerized/openai-dynamodb-scalable/README.md @@ -1,80 +1,296 @@ -# OpenAI Agents on ECS — Scalable (SQS Queue Mode) - -Agent Kernel running OpenAI Agents SDK agents on AWS ECS in **REST Sync queue mode**, -using SQS queues to decouple the HTTP request from agent processing and DynamoDB as -both agent memory (session store) and the response store. - -## Architecture - -``` -Client - │ POST /api/v1/chat - ▼ -API Gateway → ALB - ▼ -REST Service ECS task ──── sends to ──► Input SQS Queue - │ (Thread 1: FastAPI) │ - │ (Thread 2: output-queue poller) ▼ - │ Agent Runner ECS task - │ (polls Input Queue, - │ runs agent, - │ sends to Output Queue) - │ │ - │ ▼ - │ Output SQS Queue - │ │ - │◄─── Thread 2 writes ───────────────────────┘ - │ to DynamoDB Response Store - │ - └─── Thread 1 reads from DynamoDB Response Store - and returns response to client -``` - -Two Docker images are built: +# Agent Kernel Scalable OpenAI Agents with AWS ECS Architecture -| Image | Source | Role | -|-------|--------|------| -| `dist-rest-service/` | `app_rest_service.py` | FastAPI API + output-queue poller | -| `dist-agent-runner/` | `app_agent_runner.py` | Input-queue consumer, runs the agent | +This package demonstrates a scalable Agent Kernel implementation running OpenAI Agents SDK on AWS ECS with queue-based processing. + +## Architecture Overview + +This deployment uses a scalable containerized architecture with the following components: + +- **REST Service ECS Task**: Receives HTTP requests (Thread 1) and processes output queue messages (Thread 2) +- **Agent Runner ECS Task**: Processes agent requests from the input queue +- **SQS Queues**: Input and output queues for asynchronous processing +- **DynamoDB Tables**: For session memory and response storage +- **API Gateway + ALB**: REST API endpoint routing to ECS services ## Deployed Resources -- API Gateway (HTTP) + ALB → REST Service ECS task -- REST Service ECS service (Thread 1 + Thread 2) -- Agent Runner ECS service (separate Fargate task, no ALB) -- Input SQS FIFO Queue -- Output SQS FIFO Queue -- DynamoDB Response Store (TTL-enabled, temporary response buffer) -- DynamoDB Session Store (agent memory) +This demo deploys the following AWS resources: + +- **ECS Services**: + - REST Service: FastAPI server + output queue poller (2 threads) + - Agent Runner: Input queue consumer and agent executor +- **SQS Queues**: FIFO input and output queues +- **DynamoDB**: Session storage and response store tables +- **API Gateway + ALB**: HTTP API with VPC Link to Application Load Balancer +- **VPC**: Private networking for ECS tasks +- **CloudWatch**: Logging and monitoring + +## Execution Mode + +This example supports two queue modes configured via `queue_mode_type` in `deploy/main.tf`: + +### REST_SYNC Mode (default) +```hcl +queue_mode_type = "sync" +``` + +Client request blocks until agent completes. Response returned on same HTTP connection. + +### REST_ASYNC Mode (optional) +```hcl +queue_mode_type = "async" +``` + +Client request returns immediately with `request_id`. Client polls separate GET endpoint for result. + +Both modes use the same 2-image architecture (REST Service + Agent Runner) with queue-based processing. ## Prerequisites - AWS CLI configured with appropriate credentials -- Terraform `>= 1.9.5` -- `uv` Python package manager +- Terraform (`1.9.5` or higher) installed +- Docker installed (for building container images) +- UV package manager installed -## Deployment +## Deployment Steps -1. Set environment variables: - ```bash - export TF_VAR_openai_api_key= - export TF_VAR_vpc_id= - export TF_VAR_private_subnet_ids='["subnet-xxx","subnet-yyy"]' +1. Configure environment variables: + ```bash + export TF_VAR_openai_api_key= + export TF_VAR_vpc_id= + export TF_VAR_private_subnet_ids='["subnet-xxx","subnet-yyy"]' + export TF_VAR_product_alias="ak-oai-scl-ecs" + export TF_VAR_env_alias="dev" + export TF_VAR_module_name="scalable" + export TF_VAR_region="us-east-1" + ``` + +2. Build the deployment packages: + ```bash + ./build.sh + ``` + +3. Navigate to the deployment directory and run the deployment script: + ```bash + cd deploy && ./deploy.sh + ``` + +## Testing the Deployment + +After deployment, you can test the scalable agent: + +### REST_SYNC Mode (default): + +**Submit request (blocks until complete)**: +```bash +curl -X POST https://your-api-gateway-url/agents/api/v1/chat \ + -H "Content-Type: application/json" \ + -d '{ + "prompt": "What is 12 * 9?", + "session_id": "test-1", + "agent": "math" + }' +``` + +Response: +```json +{ + "result": "108", + "session_id": "test-1" +} +``` + +### REST_ASYNC Mode (optional): + +**Step 1 - Submit request (returns immediately)**: +```bash +curl -X POST https://your-api-gateway-url/agents/api/v1/chat \ + -H "Content-Type: application/json" \ + -d '{ + "prompt": "What is 12 * 9?", + "session_id": "test-1", + "agent": "math" + }' +``` + +Response: +```json +{ + "status": "ACCEPTED", + "request_id": "9fce843f-f8bb-4818-bf35-5167247e17c8", + "session_id": "test-1" +} +``` + +**Step 2 - Poll for result**: +```bash +curl -X GET https://your-api-gateway-url/agents/api/v1/chat/test-1?request_id=9fce843f-f8bb-4818-bf35-5167247e17c8 +``` + +Response (while processing): +```json +{ + "status": "PENDING", + "request_id": "9fce843f-f8bb-4818-bf35-5167247e17c8", + "session_id": "test-1" +} +``` + +Response (when complete): +```json +{ + "result": "108", + "session_id": "test-1" +} +``` + +## Architecture Details + +### Two-Image Pattern + +| Image | Source | Role | +|-------|--------|------| +| REST Service | `app_rest_service.py` | Thread 1: FastAPI API server
Thread 2: Output queue poller → DynamoDB | +| Agent Runner | `app_agent_runner.py` | Input queue consumer → Agent executor → Output queue | + +### Request Flow + +**REST_SYNC Mode:** +1. Client → API Gateway → ALB → REST Service (Thread 1) +2. REST Service → Input SQS Queue (enqueue request) +3. Agent Runner polls Input Queue → Executes agent → Sends to Output Queue +4. REST Service Thread 2 polls Output Queue → Writes to DynamoDB Response Store +5. REST Service Thread 1 polls DynamoDB → Returns response to client + +**REST_ASYNC Mode:** +1. Client → API Gateway → ALB → REST Service (Thread 1) +2. REST Service → Input SQS Queue → Returns `request_id` immediately +3. Agent Runner polls Input Queue → Executes agent → Sends to Output Queue +4. REST Service Thread 2 polls Output Queue → Writes to DynamoDB Response Store +5. Client polls GET endpoint → REST Service reads DynamoDB → Returns response + +### CMD Override + +The ECR module (`terraform-aws-modules/lambda/aws//modules/docker-build`) is Lambda-oriented and automatically injects: +```dockerfile +CMD ["python","-c","from app import runner; runner()"] +``` + +This is overridden at ECS task definition level using: +- `ecs_container_command = ["python", "app_rest_service.py"]` +- `agent_runner_command = ["python", "app_agent_runner.py"]` + +See [COMMAND_OVERRIDE.md](../../ak-deployment/ak-aws/containerized/COMMAND_OVERRIDE.md) for details. + +## Monitoring and Scaling + +The architecture automatically scales based on: +- **REST Service**: Scales with ALB target group metrics +- **Agent Runner**: Scales based on SQS queue depth + +Monitor through CloudWatch: +- ECS task metrics and logs +- SQS queue depth and processing rates +- DynamoDB read/write metrics +- API Gateway and ALB request metrics + +## Troubleshooting + +### ModuleNotFoundError: No module named 'app' + +Old Docker images cached in ECR. Redeploy: +```bash +cd deploy && ./deploy.sh +``` + +### Service Not Starting + +Check CloudWatch logs: +```bash +# REST Service +aws logs tail /ecs/-- --follow + +# Agent Runner +aws logs tail /ecs/---agent-runner --follow +``` + +### Debug Response Store + +Inspect or clear DynamoDB response store: +```bash +python debug_response_store.py --list +python debug_response_store.py --clear +``` + +## Related Documentation + +- **[ROOT_CAUSE_ANALYSIS.md](ROOT_CAUSE_ANALYSIS.md)** - Deep dive into CMD override issue +- **[COMMAND_OVERRIDE.md](../../ak-deployment/ak-aws/containerized/COMMAND_OVERRIDE.md)** - Command override usage +- **[Serverless Reference](../aws-serverless/scalable-openai/)** - Lambda implementation +- **[Queue Mode Guide](../../docs/queue-mode-guide.md)** - Queue architecture details ``` -2. Build and deploy: +3. Build and deploy: ```bash cd deploy && ./deploy.sh # from PyPI cd deploy && ./deploy.sh local # from local ak-py build ``` +**Note:** The deploy script builds two separate Docker images: +- `dist-rest-service/` containing `app_rest_service.py` (FastAPI + output queue poller) +- `dist-agent-runner/` containing `app_agent_runner.py` (input queue consumer) + +If you encounter "ModuleNotFoundError: No module named 'app'" errors, old Docker images +may be cached in ECR. Run `./clean.sh` and redeploy to rebuild from scratch. + ## Testing +### REST_SYNC Mode (Default) + +Request blocks until agent completes: + ```bash -curl -X POST \ +curl -X POST /api/v1/chat \ -H "Content-Type: application/json" \ - -d '{"prompt": "What is 12 * 9?", "session_id": "test-1"}' + -d '{ + "prompt": "What is 12 * 9?", + "session_id": "test-1", + "agent": "math" + }' ``` The request blocks on the same HTTP connection until the Agent Runner finishes processing and the response is available in DynamoDB. + +### REST_ASYNC Mode + +**Step 1:** Submit request (returns immediately): + +```bash +curl -X POST /api/v1/chat \ + -H "Content-Type: application/json" \ + -d '{ + "prompt": "What is 12 * 9?", + "session_id": "test-1", + "agent": "math" + }' +``` + +Response: +```json +{ + "status": "ACCEPTED", + "request_id": "9fce843f-f8bb-4818-bf35-5167247e17c8", + "session_id": "test-1" +} +``` + +**Step 2:** Poll for result: + +```bash +curl -X GET /api/v1/chat/test-1?request_id=9fce843f-f8bb-4818-bf35-5167247e17c8 +``` + +Returns `{"status": "PENDING", ...}` while processing, then the agent response. + +**To enable REST_ASYNC mode:** Set `AK_EXECUTION__MODE=rest_async` in `deploy/main.tf` environment variables. diff --git a/examples/aws-containerized/openai-dynamodb-scalable/clean.sh b/examples/aws-containerized/openai-dynamodb-scalable/clean.sh deleted file mode 100755 index af0f2f677..000000000 --- a/examples/aws-containerized/openai-dynamodb-scalable/clean.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash - -# Clean all build artifacts and dist directories - -set -euo pipefail - -echo "Cleaning build artifacts..." - -# Remove dist directories -rm -rf dist dist-rest-service dist-agent-runner - -# Remove virtual environment -rm -rf .venv - -# Remove Python cache -find . -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true -find . -type f -name "*.pyc" -delete 2>/dev/null || true - -# Remove requirements.txt if it exists -rm -f requirements.txt - -echo "Clean complete!" diff --git a/examples/aws-containerized/openai-dynamodb-scalable/deploy/.terraform.lock.hcl b/examples/aws-containerized/openai-dynamodb-scalable/deploy/.terraform.lock.hcl new file mode 100644 index 000000000..89086931e --- /dev/null +++ b/examples/aws-containerized/openai-dynamodb-scalable/deploy/.terraform.lock.hcl @@ -0,0 +1,67 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "6.48.0" + constraints = ">= 4.22.0, >= 6.11.0, >= 6.21.0, >= 6.22.0, >= 6.28.0" + hashes = [ + "h1:Tq63eUMoXAaeEBg6wQjAPe4cnWaTnIg+epzmyiAQdVI=", + "zh:0db2cc5d1f93792ac52e142cb9f510e66372ae0f88be02657456fad37f5c5cee", + "zh:21d0dca547cb43beb794133eb507ff75aa268a8ec46c51579bcfaa4c6c8ca7e1", + "zh:3e9708b6a3dde630fca0a9069a69b80dc4fd124a2663395a4e696d01ecca6f50", + "zh:4592c17b388c2021c267c7418c6819ae0b3c6c83d904c203a4191b13047b4d5f", + "zh:463507e81d9ad8f363d6d4b327042f585bec14fceb69001ab7176b050642847f", + "zh:5319571a7f035d9b0b28aa2b3ca2f00e1713c1d933b2d9e4af508bb4ff392b3f", + "zh:8bbf6ff573187d1cab4f7b9106c7e7ee595ce468773c8bf099df2db58e1fc35d", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:ae07c6b11e944d5a921184a7c620e67f1be10baea352f9cabf3433bfe22e54f5", + "zh:ccd63dbdcf3ccb876227130b6e244d9ecebae66a78044b1808ad195bbe41947a", + "zh:d215519deac50227640714826b9d57b96bdf5e55a78680126a58131b2ad401ed", + "zh:e07f7710864bc61c66830b3d37f8e7a3986e6bc4e0b797bf9646e5d258972260", + "zh:e1e5840be2eee67bac80cf02777a110dab643d23a115a791dfada26e81b11a29", + "zh:f4f5b543ba692926a3353048f69822aad837c7799522d9bdf592f1381543e5c6", + "zh:f8e5adeb20c06978421c6759478935a236ea0121ef2f0c65352de14deb8ee8e1", + ] +} + +provider "registry.terraform.io/hashicorp/null" { + version = "3.3.0" + constraints = ">= 2.0.0" + hashes = [ + "h1:l+dm3lhmu4ys7GbvIldfn544olSPH0DOiYruuFSfQkY=", + "zh:021748b5ea3b5f6956f2e75c42c5cdc113b391fb98ac71364a4965d23b37000f", + "zh:3b27956f8541d46704fda234e0d535c2ae2a4b33411848b1ee262a1ec03568b0", + "zh:3de4ed47d6d0f4d8edba4a5092c7c9799950eda63989d8d0d2586e6afcb0aa20", + "zh:57ed8935c7d56dbc91cf2673534582cacfaab7a2f105f51d9f797e99df0c0c47", + "zh:58e176ba1d142827089e30e0711e007309a9f2726e8881986da5026e9778fdf4", + "zh:5949c4a3d4a93f841f155cdb7e991c087e637145c1630572e21948224f8f4923", + "zh:76d60f366b743003c1b085afa769b45b2198ee919927e45807d7d44fb42c067d", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:79cd1bab1261a07f84e917191d7ddc4340ac5f5524283767256f7ffd7f87caf0", + "zh:8ec9083038cf710b30e319eaa467c9df7fa52bbd9969b61053a35bc2cdd2e0a6", + "zh:a6e502cb579685ab7aeb886c2bb11ddd9cfed74b41008592d57cbc3351a9218b", + "zh:acb74d6b4f66ff6acfcda315df802a7432170ef3955c9b432cb4580767004006", + "zh:f0ce55d8d9ffdb33dab612b1246f9bab060a9d54fc32ce2b4a038646155660af", + ] +} + +provider "registry.terraform.io/kreuzwerker/docker" { + version = "3.6.2" + constraints = ">= 3.0.0, 3.6.2" + hashes = [ + "h1:1K3j0xUY2D0+E+DBDQc6k1u6Al9MkuNWrIC9rnvwFSM=", + "zh:22b51a8fb63481d290bdad9a221bc8c9e45d66d1a0cd45beed3f3627bf1debd8", + "zh:2b902eb80a1ae033af1135cc165d192668820a7f8ea15beb5472f811c18bea1f", + "zh:57815dcea28aedb86ed33924cd186aaee8bd31670bd78437a2a2daf2b00ce2ae", + "zh:583af9c6fe7e3bfc04f50aec046a9b4f98b7eddd6d1e143454e5d06a66afcf87", + "zh:80f8cba54f639a53c4d7714edb7246064b7f4f48ba93a70f18c914d656d799db", + "zh:894709f0c393c4ee91fdb849128e7f0bce688f293cd1643a6d4e39c842367278", + "zh:a91b41dbcb203d6dae2bb72b98c4c21c41255026b35df01895882784c4650071", + "zh:aec40a8157aae093412a1fb9a71ab2bea370db152e285c2d81e37ed378444b9c", + "zh:b87d7def2485dde6e57723c1265158f371440a8a84954c9fdb0580cf89de66bf", + "zh:b9dc243200ad9cd00250cb8c793ecea4ee3c57a121faf8efdb289f30008b5778", + "zh:dcb103831db6d3ef95468685cd104be3928793996542a1f675dc34a2ce67951d", + "zh:e59b4a0f2b5881016896d4417b1ab2fb87f34450663efeb01f3bcf7c3606fbbb", + "zh:fbd068c01114f0712578cf02f363b5521338ab1befedddf7090da532298b43d0", + ] +} diff --git a/examples/aws-containerized/openai-dynamodb-scalable/rebuild.sh b/examples/aws-containerized/openai-dynamodb-scalable/rebuild.sh deleted file mode 100755 index 678f51b16..000000000 --- a/examples/aws-containerized/openai-dynamodb-scalable/rebuild.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/bash - -# Clean rebuild script for the scalable containerized example -# This ensures a fresh build without any cached artifacts - -set -euo pipefail - -echo "=========================================" -echo "Clean Rebuild - Scalable Containerized" -echo "=========================================" -echo "" - -# Step 1: Clean -echo "Step 1: Cleaning build artifacts..." -./clean.sh -echo "✓ Clean complete" -echo "" - -# Step 2: Build venv and sync dependencies -echo "Step 2: Setting up Python environment..." -./build.sh "$@" -echo "✓ Python environment ready" -echo "" - -# Step 3: Deploy -echo "Step 3: Building deployment packages and deploying..." -cd deploy -./deploy.sh "$@" -cd .. -echo "✓ Deployment complete" -echo "" - -echo "=========================================" -echo "Rebuild complete!" -echo "=========================================" -echo "" -echo "Next steps:" -echo "1. Check CloudWatch logs for the agent-runner service" -echo "2. Test the API endpoint" -echo "3. Monitor SQS queue metrics" -echo "" From d3858f37cbda34ba5612a0730e3dc3fe8163824c Mon Sep 17 00:00:00 2001 From: yl-nuwan Date: Mon, 8 Jun 2026 14:52:04 +0530 Subject: [PATCH 16/90] style(aws-containerized): format code and improve string consistency - Convert single quotes to double quotes for string literals - Consolidate multi-line string concatenations to single lines - Remove trailing whitespace and normalize blank lines - Simplify multi-line function calls and error messages - Apply consistent formatting across ECSAgentRunner, ECSRESTService, ECS queue handler, and SQS poller modules - Ensure consistent code style across containerized deployment infrastructure --- .../aws/containerized/akagentrunner.py | 40 ++++------ .../aws/containerized/akrestservice.py | 66 ++++------------ .../aws/containerized/ecs_queue_handler.py | 77 +++++++----------- .../aws/containerized/sqs_poller.py | 10 +-- .../deployment/aws/core/sqs_handler.py | 9 +-- .../app_agent_runner.py | 2 +- .../app_rest_service.py | 6 +- .../debug_response_store.py | 78 ++++++++++--------- .../openai-dynamodb-scalable/deploy/main.tf | 29 ++----- 9 files changed, 114 insertions(+), 203 deletions(-) diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/akagentrunner.py b/ak-py/src/agentkernel/deployment/aws/containerized/akagentrunner.py index 88ae04630..aaf6aebad 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/akagentrunner.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/akagentrunner.py @@ -43,9 +43,7 @@ def run(cls) -> None: max_receive_count = config.execution.queues.input.max_receive_count if not input_queue_url: - raise ValueError( - "AK_EXECUTION__QUEUES__INPUT__URL is required for ECSAgentRunner" - ) + raise ValueError("AK_EXECUTION__QUEUES__INPUT__URL is required for ECSAgentRunner") cls._log.info(f"ECSAgentRunner starting — input queue: {input_queue_url}") @@ -112,30 +110,27 @@ def process_message(cls, record: dict) -> None: :param record: boto3 SQS message dict """ - message_id = record.get('MessageId') + message_id = record.get("MessageId") cls._log.info(f"[AGENT START] Processing message {message_id}") - + body = BaseRunRequest.model_validate(json.loads(record["Body"])) record_attributes = cls._get_record_attributes(raw_queue_message=record) - + cls._log.info( f"[AGENT PROCESSING] request_id={record_attributes['request_id']}, " f"session_id={body.session_id}, agent={body.agent}, prompt={body.prompt[:50] if body.prompt else 'N/A'}" ) - + _, agent_response = cls._get_chat_service().process_chat_request(req=body) - + cls._log.info( f"[AGENT RESPONSE] request_id={record_attributes['request_id']}, " f"response_keys={list(agent_response.keys()) if isinstance(agent_response, dict) else 'N/A'}" ) - + cls._send_to_output_queue(message_body=agent_response, record_attributes=record_attributes) - - cls._log.info( - f"[AGENT DONE] Sent to output queue: {SQSHandler.get_output_queue_url()}, " - f"request_id={record_attributes['request_id']}" - ) + + cls._log.info(f"[AGENT DONE] Sent to output queue: {SQSHandler.get_output_queue_url()}, " f"request_id={record_attributes['request_id']}") @classmethod def on_permanent_failure(cls, record: dict) -> None: @@ -145,19 +140,10 @@ def on_permanent_failure(cls, record: dict) -> None: :param record: boto3 SQS message dict """ - cls._log.error( - f"Permanent failure for message {record.get('MessageId')}" - ) + cls._log.error(f"Permanent failure for message {record.get('MessageId')}") try: record_attributes = cls._get_record_attributes(raw_queue_message=record) - error_body = { - "error": f"Failed to process message after " - f"{cls._config.execution.queues.input.max_receive_count} retries" - } - cls._send_to_output_queue( - message_body=error_body, record_attributes=record_attributes - ) + error_body = {"error": f"Failed to process message after " f"{cls._config.execution.queues.input.max_receive_count} retries"} + cls._send_to_output_queue(message_body=error_body, record_attributes=record_attributes) except Exception: - cls._log.exception( - "Failed to send permanent-failure error to output queue" - ) + cls._log.exception("Failed to send permanent-failure error to output queue") diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/akrestservice.py b/ak-py/src/agentkernel/deployment/aws/containerized/akrestservice.py index f63570f5e..2524468d9 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/akrestservice.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/akrestservice.py @@ -70,14 +70,10 @@ def run(cls) -> None: max_receive_count = cls._config.execution.queues.output.max_receive_count if not output_queue_url: - raise ValueError( - "AK_EXECUTION__QUEUES__OUTPUT__URL is required for ECSRESTService" - ) + raise ValueError("AK_EXECUTION__QUEUES__OUTPUT__URL is required for ECSRESTService") mode = cls._config.execution.mode - cls._log.info( - f"ECSRESTService starting — mode={mode} output_queue={output_queue_url}" - ) + cls._log.info(f"ECSRESTService starting — mode={mode} output_queue={output_queue_url}") poller = SQSPoller( queue_url=output_queue_url, @@ -85,16 +81,14 @@ def run(cls) -> None: max_receive_count=max_receive_count, on_permanent_failure_fn=cls.on_permanent_failure, ) - t2 = threading.Thread( - target=poller.run, name="output-queue-poller", daemon=True - ) + t2 = threading.Thread(target=poller.run, name="output-queue-poller", daemon=True) t2.start() cls._log.info("ECSRESTService: Thread 2 (output-queue poller) started") # Use queue-aware request handler that bypasses ChatService # This directly enqueues to SQS without agent validation from .ecs_queue_handler import ECSQueueRequestHandler - + cls._log.info("ECSRESTService: starting REST API with queue-aware handler (Thread 1)") RESTAPI.run(handlers=[ECSQueueRequestHandler()]) @@ -113,10 +107,7 @@ def _get_websocket_handler(cls) -> WebSocketHandler: if cls._websocket_handler is None: ws_config = cls._config.websocket_api if not ws_config.connection_table or not ws_config.connection_table.table_name: - raise ValueError( - "websocket_api.connection_table.table_name is required " - "for ECSRESTService in WebSocket mode" - ) + raise ValueError("websocket_api.connection_table.table_name is required " "for ECSRESTService in WebSocket mode") cls._websocket_handler = WebSocketHandler( conn_table_name=ws_config.connection_table.table_name, ttl=ws_config.connection_table.ttl, @@ -140,7 +131,7 @@ def process_output_message(cls, record: Dict[str, Any]) -> None: :param record: boto3 SQS ``receive_message`` record """ - message_id = record.get('MessageId') + message_id = record.get("MessageId") cls._log.info(f"[OUTPUT START] Processing output message {message_id}") if cls._config.execution.mode == ExecutionMode.ASYNC: @@ -152,10 +143,7 @@ def process_output_message(cls, record: Dict[str, Any]) -> None: f"session_id={message['session_id']}, body_keys={list(message.get('body', {}).keys()) if isinstance(message.get('body'), dict) else 'N/A'}" ) cls._get_response_store().add_message(message) - cls._log.info( - f"[OUTPUT DONE] Stored response — session_id={message['session_id']} " - f"request_id={message['request_id']}" - ) + cls._log.info(f"[OUTPUT DONE] Stored response — session_id={message['session_id']} " f"request_id={message['request_id']}") @classmethod def on_permanent_failure(cls, record: Dict[str, Any]) -> None: @@ -170,10 +158,7 @@ def on_permanent_failure(cls, record: Dict[str, Any]) -> None: :param record: boto3 SQS ``receive_message`` record """ max_retries = cls._config.execution.queues.output.max_receive_count - cls._log.error( - f"Permanent failure for output message {record.get('MessageId')} " - f"after {max_retries} retries" - ) + cls._log.error(f"Permanent failure for output message {record.get('MessageId')} " f"after {max_retries} retries") try: message_attributes = SQSHandler.get_message_custom_attributes(record) @@ -193,32 +178,21 @@ def on_permanent_failure(cls, record: Dict[str, Any]) -> None: user_id=user_id, ) else: - cls._log.warning( - "Cannot broadcast permanent-failure error: " - "endpoint_url or user_id missing" - ) + cls._log.warning("Cannot broadcast permanent-failure error: " "endpoint_url or user_id missing") else: error_body = json.dumps(error_payload) message = cls._construct_message_for_store(record, body=error_body) cls._get_response_store().add_message(message) - cls._log.info( - f"Stored permanent-failure error — " - f"session_id={message['session_id']} " - f"request_id={message['request_id']}" - ) + cls._log.info(f"Stored permanent-failure error — " f"session_id={message['session_id']} " f"request_id={message['request_id']}") except Exception: - cls._log.exception( - "Failed to handle permanent-failure output message" - ) + cls._log.exception("Failed to handle permanent-failure output message") # ------------------------------------------------------------------ # Internal helpers # ------------------------------------------------------------------ @classmethod - def _construct_message_for_store( - cls, record: Dict[str, Any], body: Any = None - ) -> Dict[str, Any]: + def _construct_message_for_store(cls, record: Dict[str, Any], body: Any = None) -> Dict[str, Any]: """ Build the dict to write into the Response Store. @@ -254,21 +228,13 @@ def _broadcast_via_websocket(cls, record: Dict[str, Any]) -> None: user_id = message_attributes.get("user_id") if not endpoint_url: - raise ValueError( - "endpoint_url is required in SQS message attributes for ASYNC mode" - ) + raise ValueError("endpoint_url is required in SQS message attributes for ASYNC mode") if not user_id: - raise ValueError( - "user_id is required in SQS message attributes for ASYNC mode" - ) + raise ValueError("user_id is required in SQS message attributes for ASYNC mode") message_body = record.get("Body", "{}") if isinstance(message_body, str): message_body = json.loads(message_body) - cls._log.info( - f"Broadcasting via WebSocket — user_id={user_id} endpoint={endpoint_url}" - ) - cls._get_websocket_handler().broadcast( - endpoint_url=endpoint_url, message=message_body, user_id=user_id - ) + cls._log.info(f"Broadcasting via WebSocket — user_id={user_id} endpoint={endpoint_url}") + cls._get_websocket_handler().broadcast(endpoint_url=endpoint_url, message=message_body, user_id=user_id) diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py b/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py index 66b22bb89..8dca0b87b 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py @@ -8,12 +8,13 @@ """ import logging -from typing import Dict, Any +from typing import Any, Dict + from fastapi import APIRouter, HTTPException +from ....api.handler import RESTRequestHandler from ....core.config import AKConfig from ....core.model import BaseRunRequest, ExecutionMode -from ....api.handler import RESTRequestHandler from ..core.response_store import ResponseDBHandler from ..core.sqs_handler import SQSHandler @@ -21,12 +22,12 @@ class ECSQueueRequestHandler(RESTRequestHandler): """ Queue-aware REST request handler for ECS deployments. - + Handles POST /api/v1/chat by: 1. Enqueuing the request to Input SQS Queue 2. Waiting for response in DynamoDB Response Store (sync mode) 3. Returning the response to the client - + This bypasses ChatService entirely - NO agent validation happens here. Agent validation and execution occurs in the Agent Runner service. """ @@ -45,7 +46,7 @@ def _get_response_store(self): def get_router(self) -> APIRouter: """ Returns the APIRouter for queue-based endpoints. - + - POST /api/v1/chat: Enqueue request and wait for response (sync mode) - GET /api/v1/chat/{session_id}: Poll for response (async mode) """ @@ -55,7 +56,7 @@ def get_router(self) -> APIRouter: async def enqueue_and_wait(body: BaseRunRequest): """ Enqueue request to Input Queue. - + In REST_SYNC mode: Wait for response in DynamoDB Response Store. In REST_ASYNC mode: Return request_id immediately. """ @@ -68,16 +69,19 @@ async def enqueue_and_wait(body: BaseRunRequest): # Generate unique request_id (different from session_id) import uuid + request_id = str(uuid.uuid4()) - self._log.info(f"[REQUEST START] session_id={body.session_id}, request_id={request_id}, agent={body.agent}, prompt={body.prompt[:50]}") + self._log.info( + f"[REQUEST START] session_id={body.session_id}, request_id={request_id}, agent={body.agent}, prompt={body.prompt[:50]}" + ) # Send to Input Queue queue_result = SQSHandler.send_message_to_input_queue( message_body=body.model_dump(), message_group_id=body.session_id, message_deduplication_id=request_id, - request_id=request_id # This becomes a custom message attribute + request_id=request_id, # This becomes a custom message attribute ) self._log.info(f"[ENQUEUED] MessageId={queue_result.get('MessageId')}, request_id={request_id}") @@ -86,64 +90,48 @@ async def enqueue_and_wait(body: BaseRunRequest): if self._config.execution.mode == ExecutionMode.REST_SYNC: # Wait for response in DynamoDB self._log.info(f"[WAITING] Polling DynamoDB for request_id={request_id}") - - response = self._wait_for_response( - request_id=request_id, - session_id=body.session_id - ) - + + response = self._wait_for_response(request_id=request_id, session_id=body.session_id) + if not response: raise HTTPException( status_code=504, detail={ "error": f"No response received for request_id: {request_id}", "session_id": body.session_id, - "request_id": request_id - } + "request_id": request_id, + }, ) - + self._log.info(f"[RESPONSE FOUND] request_id={request_id}, response_keys={list(response.keys())}") - + # Return the response body return response.get("body", response) elif self._config.execution.mode == ExecutionMode.REST_ASYNC: # Return request_id for polling - return { - "status": "ACCEPTED", - "request_id": request_id, - "session_id": body.session_id - } + return {"status": "ACCEPTED", "request_id": request_id, "session_id": body.session_id} else: - raise HTTPException( - status_code=500, - detail=f"Unsupported execution mode: {self._config.execution.mode}" - ) + raise HTTPException(status_code=500, detail=f"Unsupported execution mode: {self._config.execution.mode}") except HTTPException: raise except Exception as e: self._log.error(f"Error processing request: {e}", exc_info=True) - raise HTTPException( - status_code=500, - detail={"error": str(e), "session_id": body.session_id if body else None} - ) + raise HTTPException(status_code=500, detail={"error": str(e), "session_id": body.session_id if body else None}) @router.get("/api/v1/chat/{session_id}") async def poll_response(session_id: str, request_id: str = None): """ Poll for response (REST_ASYNC mode only). - + :param session_id: Session identifier :param request_id: Optional specific request to poll for """ try: if self._config.execution.mode != ExecutionMode.REST_ASYNC: - raise HTTPException( - status_code=404, - detail="GET endpoint only available in REST_ASYNC mode" - ) + raise HTTPException(status_code=404, detail="GET endpoint only available in REST_ASYNC mode") effective_request_id = request_id or session_id self._log.info(f"Polling for response: request_id={effective_request_id}") @@ -151,11 +139,7 @@ async def poll_response(session_id: str, request_id: str = None): response = self._get_response_store().get_message(effective_request_id) if not response: - return { - "status": "PENDING", - "request_id": effective_request_id, - "session_id": session_id - } + return {"status": "PENDING", "request_id": effective_request_id, "session_id": session_id} # Return the response body return response.get("body", response) @@ -164,17 +148,14 @@ async def poll_response(session_id: str, request_id: str = None): raise except Exception as e: self._log.error(f"Error polling response: {e}", exc_info=True) - raise HTTPException( - status_code=500, - detail={"error": str(e), "session_id": session_id} - ) + raise HTTPException(status_code=500, detail={"error": str(e), "session_id": session_id}) return router def _wait_for_response(self, request_id: str, session_id: str, max_retries: int = None, delay: int = None) -> Dict[str, Any]: """ Wait for response to appear in DynamoDB Response Store. - + :param request_id: Request identifier to wait for :param session_id: Session identifier :param max_retries: Maximum number of retry attempts (from config if None) @@ -197,7 +178,9 @@ def _wait_for_response(self, request_id: str, session_id: str, max_retries: int return response if attempt < max_retries - 1: - self._log.debug(f"[WAIT RETRY] Response not ready, waiting {delay}s (attempt {attempt + 1}/{max_retries}) for request_id={request_id}") + self._log.debug( + f"[WAIT RETRY] Response not ready, waiting {delay}s (attempt {attempt + 1}/{max_retries}) for request_id={request_id}" + ) time.sleep(delay) self._log.warning(f"[WAIT TIMEOUT] Response not found after {max_retries} attempts for request_id={request_id}") diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/sqs_poller.py b/ak-py/src/agentkernel/deployment/aws/containerized/sqs_poller.py index 5964e6a5d..be6e8a14f 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/sqs_poller.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/sqs_poller.py @@ -92,10 +92,7 @@ def _handle_message(self, client, msg: dict) -> None: try: if receive_count > self._max_receive_count: - _log.warning( - f"Message {message_id} exceeded max receive count " - f"({receive_count} > {self._max_receive_count})" - ) + _log.warning(f"Message {message_id} exceeded max receive count " f"({receive_count} > {self._max_receive_count})") if self._on_permanent_failure_fn: self._on_permanent_failure_fn(msg) # Delete so it stops cycling @@ -106,10 +103,7 @@ def _handle_message(self, client, msg: dict) -> None: self._delete_message(client, msg) except Exception: - _log.exception( - f"Failed to process message {message_id} — " - "leaving in queue for visibility-timeout retry" - ) + _log.exception(f"Failed to process message {message_id} — " "leaving in queue for visibility-timeout retry") # Do NOT delete — visibility timeout will return it for retry def _delete_message(self, client, msg: dict) -> None: diff --git a/ak-py/src/agentkernel/deployment/aws/core/sqs_handler.py b/ak-py/src/agentkernel/deployment/aws/core/sqs_handler.py index bd6943887..4300a5e78 100644 --- a/ak-py/src/agentkernel/deployment/aws/core/sqs_handler.py +++ b/ak-py/src/agentkernel/deployment/aws/core/sqs_handler.py @@ -178,17 +178,12 @@ def get_message_custom_attributes(raw_queue_message_record: Mapping[str, Any]) - :return: A dictionary mapping custom attribute names to their scalar values """ # Lambda uses "messageAttributes", boto3 receive_message uses "MessageAttributes" - message_attributes = ( - raw_queue_message_record.get("MessageAttributes") - or raw_queue_message_record.get("messageAttributes") - or {} - ) + message_attributes = raw_queue_message_record.get("MessageAttributes") or raw_queue_message_record.get("messageAttributes") or {} flattened_attributes: Dict[str, Any] = {} for attribute_name, attribute in message_attributes.items(): if isinstance(attribute, Mapping): attribute_value = ( - attribute.get("StringValue") or attribute.get("stringValue") - or attribute.get("BinaryValue") or attribute.get("binaryValue") + attribute.get("StringValue") or attribute.get("stringValue") or attribute.get("BinaryValue") or attribute.get("binaryValue") ) else: attribute_value = attribute diff --git a/examples/aws-containerized/openai-dynamodb-scalable/app_agent_runner.py b/examples/aws-containerized/openai-dynamodb-scalable/app_agent_runner.py index 22fe8e479..90c46effe 100644 --- a/examples/aws-containerized/openai-dynamodb-scalable/app_agent_runner.py +++ b/examples/aws-containerized/openai-dynamodb-scalable/app_agent_runner.py @@ -23,7 +23,7 @@ OpenAIModule([triage_agent, math_agent, history_agent]) -# Agent Runner entrypoint — polls Input Queue, runs agent, sends to Output Queue. +# Agent Runner entrypoint - polls Input Queue, runs agent, sends to Output Queue. handler = ECSAgentRunner.run if __name__ == "__main__": diff --git a/examples/aws-containerized/openai-dynamodb-scalable/app_rest_service.py b/examples/aws-containerized/openai-dynamodb-scalable/app_rest_service.py index 75b8a0760..c75538790 100644 --- a/examples/aws-containerized/openai-dynamodb-scalable/app_rest_service.py +++ b/examples/aws-containerized/openai-dynamodb-scalable/app_rest_service.py @@ -1,11 +1,11 @@ from agentkernel.deployment.aws.containerized import ECSRESTService -# REST Service entrypoint — NO agent definitions. +# REST Service entrypoint - NO agent definitions. # Agents are ONLY defined in the Agent Runner (app_agent_runner.py). -# +# # Thread 1: FastAPI handles POST /chat (enqueues to Input Queue, waits on DynamoDB) # Agent validation happens in the Agent Runner, not here. -# Thread 2: Output-queue poller — writes responses to DynamoDB (sync/async) +# Thread 2: Output-queue poller - writes responses to DynamoDB (sync/async) runner = ECSRESTService.run if __name__ == "__main__": diff --git a/examples/aws-containerized/openai-dynamodb-scalable/debug_response_store.py b/examples/aws-containerized/openai-dynamodb-scalable/debug_response_store.py index 1c3794371..0b30cb7cb 100644 --- a/examples/aws-containerized/openai-dynamodb-scalable/debug_response_store.py +++ b/examples/aws-containerized/openai-dynamodb-scalable/debug_response_store.py @@ -10,9 +10,10 @@ import argparse import sys +from decimal import Decimal + import boto3 from boto3.dynamodb.conditions import Attr -from decimal import Decimal def decimal_default(obj): @@ -24,38 +25,38 @@ def decimal_default(obj): def list_responses(table_name: str): """List all responses in the DynamoDB table.""" - dynamodb = boto3.resource('dynamodb') + dynamodb = boto3.resource("dynamodb") table = dynamodb.Table(table_name) - + print(f"\n=== Scanning DynamoDB Table: {table_name} ===\n") - + try: response = table.scan() - items = response.get('Items', []) - + items = response.get("Items", []) + if not items: print("No items found in the table.") return - + print(f"Found {len(items)} item(s):\n") - + for idx, item in enumerate(items, 1): print(f"--- Item {idx} ---") print(f"request_id: {item.get('request_id', 'N/A')}") print(f"session_id: {item.get('session_id', 'N/A')}") - - body = item.get('body', {}) + + body = item.get("body", {}) if isinstance(body, dict): print(f"body.session_id: {body.get('session_id', 'N/A')}") print(f"body.message: {body.get('message', 'N/A')[:100] if body.get('message') else 'N/A'}...") else: print(f"body: {str(body)[:100]}...") - - if 'expiry_time' in item: + + if "expiry_time" in item: print(f"expiry_time: {item['expiry_time']}") - + print() - + except Exception as e: print(f"Error scanning table: {e}") sys.exit(1) @@ -63,61 +64,62 @@ def list_responses(table_name: str): def clear_responses(table_name: str): """Clear all responses from the DynamoDB table.""" - dynamodb = boto3.resource('dynamodb') + dynamodb = boto3.resource("dynamodb") table = dynamodb.Table(table_name) - + print(f"\n=== Clearing DynamoDB Table: {table_name} ===\n") - + try: response = table.scan() - items = response.get('Items', []) - + items = response.get("Items", []) + if not items: print("No items to delete.") return - + print(f"Deleting {len(items)} item(s)...") - + with table.batch_writer() as batch: for item in items: - request_id = item.get('request_id') + request_id = item.get("request_id") if request_id: - batch.delete_item(Key={'request_id': request_id}) + batch.delete_item(Key={"request_id": request_id}) print(f" Deleted: request_id={request_id}, session_id={item.get('session_id', 'N/A')}") - + print(f"\nSuccessfully deleted {len(items)} item(s).") - + except Exception as e: print(f"Error clearing table: {e}") sys.exit(1) def main(): - parser = argparse.ArgumentParser(description='Debug DynamoDB Response Store') - parser.add_argument('--table-name', default='ak-oai-scl-ecs-dev-response-store', - help='DynamoDB table name (default: ak-oai-scl-ecs-dev-response-store)') - parser.add_argument('--list', action='store_true', - help='List all responses in the table') - parser.add_argument('--clear', action='store_true', - help='Clear all responses from the table') - + parser = argparse.ArgumentParser(description="Debug DynamoDB Response Store") + parser.add_argument( + "--table-name", + default="ak-oai-scl-ecs-dev-response-store", + help="DynamoDB table name (default: ak-oai-scl-ecs-dev-response-store)", + ) + parser.add_argument("--list", action="store_true", help="List all responses in the table") + parser.add_argument("--clear", action="store_true", help="Clear all responses from the table") + args = parser.parse_args() - + if not args.list and not args.clear: parser.print_help() print("\nError: Please specify --list or --clear") sys.exit(1) - + if args.list: list_responses(args.table_name) - + if args.clear: confirm = input(f"\nAre you sure you want to clear all items from {args.table_name}? (yes/no): ") - if confirm.lower() == 'yes': + if confirm.lower() == "yes": clear_responses(args.table_name) else: print("Clear operation cancelled.") -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/examples/aws-containerized/openai-dynamodb-scalable/deploy/main.tf b/examples/aws-containerized/openai-dynamodb-scalable/deploy/main.tf index 60c5e8dc2..711b854c7 100644 --- a/examples/aws-containerized/openai-dynamodb-scalable/deploy/main.tf +++ b/examples/aws-containerized/openai-dynamodb-scalable/deploy/main.tf @@ -28,14 +28,14 @@ provider "docker" { } # --------------------------------------------------------------------------- -# Agent Runner image — built from dist-agent-runner/ +# Agent Runner image - built from dist-agent-runner/ # --------------------------------------------------------------------------- module "agent_runner_image" { # Points to the local ECR module. # Switch to registry once published: - # source = "yaalalabs/ak-common/aws//modules/ecr" - # version = "0.5.0" - source = "../../../../ak-deployment/ak-aws/common/modules/ecr" + source = "yaalalabs/ak-common/aws//modules/ecr" + version = "0.4.0" + env_alias = var.env_alias module_name = "${var.module_name}-runner" @@ -43,31 +43,16 @@ module "agent_runner_image" { source_path = "../dist-agent-runner" } -# --------------------------------------------------------------------------- -# Main containerized deployment — REST Sync queue mode -# -# REST Service image → dist-rest-service/ (FastAPI + output-queue poller) -# Agent Runner image → dist-agent-runner/ (Input Queue poller) -# -# Flow: client → API GW → ALB → REST Service → Input Queue -# → Agent Runner → Output Queue → REST Service (Thread 2) -# → DynamoDB Response Store → REST Service (Thread 1) → client -# --------------------------------------------------------------------------- module "containerized_agents" { - # Points to the local module which includes queue-mode support. - # Switch to the registry source once a new version is published: - # source = "yaalalabs/ak-containerized/aws" - # version = "0.5.0" - source = "../../../../ak-deployment/ak-aws/containerized" + source = "yaalalabs/ak-containerized/aws" + version = "0.4.0" - # ---- identity ---- product_alias = var.product_alias env_alias = var.env_alias module_name = var.module_name region = var.region - product_display_name = "OpenAI Agents — Scalable" + product_display_name = "OpenAI Agents - Scalable" - # ---- networking ---- vpc_id = var.vpc_id private_subnet_ids = var.private_subnet_ids From 79f123062b450749feaf621a049230797174f19c Mon Sep 17 00:00:00 2001 From: yl-nuwan Date: Mon, 8 Jun 2026 14:59:03 +0530 Subject: [PATCH 17/90] formated the documant --- .../api/multimodal/dynamodb/deploy/main.tf | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/examples/api/multimodal/dynamodb/deploy/main.tf b/examples/api/multimodal/dynamodb/deploy/main.tf index 4c2781192..c6d305ad3 100644 --- a/examples/api/multimodal/dynamodb/deploy/main.tf +++ b/examples/api/multimodal/dynamodb/deploy/main.tf @@ -1,29 +1,29 @@ - -# Lambda module configuration for deploying OpenAI Agent Lambda function -module "serverless_agents" { - source = "yaalalabs/ak-serverless/aws" - version = "0.3.3" - - # Basic lambda configuration - product_alias = var.product_alias - env_alias = var.env_alias - module_name = var.module_name - create_dynamodb_multimodal_memory_table = true - product_display_name = "Agent Kernel OpenAI Multimodal with DynamoDB" - region = var.region - - # Request handler configuration - request_handler = { - function_description = "Agent Kernel OpenAI Multimodal with DynamoDB" - function_name = "mm-ddb" - handler_path = "lambda.handler" # Set dummy valid file to skip validaton inside zip - module_name = var.module_name - package_path = "../dist" - package_type = "Image" - memory_size = 2048 - timeout = 60 - environment_variables = { - "OPENAI_API_KEY" = var.openai_api_key - } - } -} + +# Lambda module configuration for deploying OpenAI Agent Lambda function +module "serverless_agents" { + source = "yaalalabs/ak-serverless/aws" + version = "0.3.3" + + # Basic lambda configuration + product_alias = var.product_alias + env_alias = var.env_alias + module_name = var.module_name + create_dynamodb_multimodal_memory_table = true + product_display_name = "Agent Kernel OpenAI Multimodal with DynamoDB" + region = var.region + + # Request handler configuration + request_handler = { + function_description = "Agent Kernel OpenAI Multimodal with DynamoDB" + function_name = "mm-ddb" + handler_path = "lambda.handler" # Set dummy valid file to skip validaton inside zip + module_name = var.module_name + package_path = "../dist" + package_type = "Image" + memory_size = 2048 + timeout = 60 + environment_variables = { + "OPENAI_API_KEY" = var.openai_api_key + } + } +} From b6e8e5ea5f3c03c4f68110e05fc082ef335baf59 Mon Sep 17 00:00:00 2001 From: yl-nuwan Date: Mon, 8 Jun 2026 15:06:42 +0530 Subject: [PATCH 18/90] Enabled a basic containerized test to check the backward compatibility --- .github/integration-test-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/integration-test-config.yaml b/.github/integration-test-config.yaml index 5ac8914aa..7656df3fb 100644 --- a/.github/integration-test-config.yaml +++ b/.github/integration-test-config.yaml @@ -40,9 +40,9 @@ weekly: # - type: aws-containerized # path: examples/aws-containerized/adk # deploy_dir: deploy - # - type: aws-containerized - # path: examples/aws-containerized/openai-dynamodb - # deploy_dir: deploy + - type: aws-containerized + path: examples/aws-containerized/openai-dynamodb + deploy_dir: deploy # - type: aws-containerized # path: examples/aws-containerized/crewai # deploy_dir: deploy From 189275e00aba5f4267b24a10f4a4fa0558cd1089 Mon Sep 17 00:00:00 2001 From: yl-nuwan Date: Mon, 8 Jun 2026 15:47:52 +0530 Subject: [PATCH 19/90] fix(aws-deployment): add session ID validation for REST_ASYNC polling - Add session_id parameter validation in ECS queue handler GET endpoint - Implement security check to verify response session_id matches URL path session_id - Add session_id validation in serverless Lambda REST_ASYNC polling operation - Enhance logging to include session_id in poll operation for better traceability - Return 403 Forbidden with detailed error when session_id mismatch is detected - Prevent unauthorized access to responses belonging to different sessions - Improve error messages to distinguish between missing and mismatched session IDs --- .../aws/containerized/ecs_queue_handler.py | 20 +++++++++++-- .../aws/serverless/core/router/rest_lambda.py | 30 +++++++++++++++---- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py b/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py index 8dca0b87b..10d00054b 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py @@ -126,7 +126,7 @@ async def poll_response(session_id: str, request_id: str = None): """ Poll for response (REST_ASYNC mode only). - :param session_id: Session identifier + :param session_id: Session identifier (must match the session in DynamoDB) :param request_id: Optional specific request to poll for """ try: @@ -134,13 +134,29 @@ async def poll_response(session_id: str, request_id: str = None): raise HTTPException(status_code=404, detail="GET endpoint only available in REST_ASYNC mode") effective_request_id = request_id or session_id - self._log.info(f"Polling for response: request_id={effective_request_id}") + self._log.info(f"Polling for response: request_id={effective_request_id}, session_id={session_id}") response = self._get_response_store().get_message(effective_request_id) if not response: return {"status": "PENDING", "request_id": effective_request_id, "session_id": session_id} + # SECURITY: Validate that the session_id in the response matches the URL path + response_session_id = response.get("session_id") + if response_session_id != session_id: + self._log.warning( + f"Session ID mismatch: URL session_id={session_id}, " + f"response session_id={response_session_id}, request_id={effective_request_id}" + ) + raise HTTPException( + status_code=403, + detail={ + "error": "Session ID mismatch", + "message": "The request_id does not belong to this session", + "session_id": session_id, + }, + ) + # Return the response body return response.get("body", response) diff --git a/ak-py/src/agentkernel/deployment/aws/serverless/core/router/rest_lambda.py b/ak-py/src/agentkernel/deployment/aws/serverless/core/router/rest_lambda.py index a6c6f9814..665cd9764 100644 --- a/ak-py/src/agentkernel/deployment/aws/serverless/core/router/rest_lambda.py +++ b/ak-py/src/agentkernel/deployment/aws/serverless/core/router/rest_lambda.py @@ -226,17 +226,37 @@ def poll_operation(payload: BaseRequest) -> Dict[str, Any]: self._log.info(f"Performing REST_ASYNC poll operation for payload: '{payload}'") request_id = payload.request_id + if not request_id: + raise ValueError("request_id is required for polling") + + session_id_from_body = payload.body.session_id if payload.body else None + if not session_id_from_body: + raise ValueError("session_id is required in request body for polling") + message = self._get_message(payload) self._log.info(f"Fetched message from database: {message}") - response_body = ( - message - if message - else self._build_failure_body( + + if not message: + response_body = self._build_failure_body( request_id=request_id, status="NOT_FOUND", message=f"No response message found for request_id '{request_id}'. The message may be unavailable. Please try again.", ) - ) + else: + # SECURITY: Validate that the session_id in the response matches the request + response_session_id = message.get("session_id") + if response_session_id != session_id_from_body: + self._log.warning( + f"Session ID mismatch: request session_id={session_id_from_body}, " + f"response session_id={response_session_id}, request_id={request_id}" + ) + response_body = self._build_failure_body( + request_id=request_id, + status="FORBIDDEN", + message="The request_id does not belong to this session", + ) + else: + response_body = message self._log.info(f"Returning response for REST_ASYNC poll operation: '{response_body}'") return response_body From b53802a75b2e813588a33f3ae9909c46f5cce068 Mon Sep 17 00:00:00 2001 From: yl-nuwan Date: Mon, 8 Jun 2026 16:15:51 +0530 Subject: [PATCH 20/90] fix(aws-containerized): improve error handling and validation in ECS queue polling - Add validation to require either request_id or session_id in GET polling requests - Return 404 with detailed error message when no response is found instead of PENDING status - Update error response format for session ID mismatch to use FORBIDDEN status code - Include request_id and session_id in error response details for better debugging - Enhance error messages with context about message unavailability and retry guidance --- .../aws/containerized/ecs_queue_handler.py | 15 +++++++++++++-- .../openai-dynamodb-scalable/README.md | 7 +++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py b/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py index 10d00054b..5c0e810cb 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py @@ -134,12 +134,23 @@ async def poll_response(session_id: str, request_id: str = None): raise HTTPException(status_code=404, detail="GET endpoint only available in REST_ASYNC mode") effective_request_id = request_id or session_id + if not effective_request_id: + raise HTTPException(status_code=400, detail={"error": "request_id is required", "session_id": session_id}) + self._log.info(f"Polling for response: request_id={effective_request_id}, session_id={session_id}") response = self._get_response_store().get_message(effective_request_id) if not response: - return {"status": "PENDING", "request_id": effective_request_id, "session_id": session_id} + raise HTTPException( + status_code=404, + detail={ + "error": "NOT_FOUND", + "message": f"No response message found for request_id '{effective_request_id}'. The message may be unavailable. Please try again.", + "request_id": effective_request_id, + "session_id": session_id, + }, + ) # SECURITY: Validate that the session_id in the response matches the URL path response_session_id = response.get("session_id") @@ -151,7 +162,7 @@ async def poll_response(session_id: str, request_id: str = None): raise HTTPException( status_code=403, detail={ - "error": "Session ID mismatch", + "error": "FORBIDDEN", "message": "The request_id does not belong to this session", "session_id": session_id, }, diff --git a/examples/aws-containerized/openai-dynamodb-scalable/README.md b/examples/aws-containerized/openai-dynamodb-scalable/README.md index 7682fa57a..0c65add95 100644 --- a/examples/aws-containerized/openai-dynamodb-scalable/README.md +++ b/examples/aws-containerized/openai-dynamodb-scalable/README.md @@ -127,10 +127,11 @@ Response: curl -X GET https://your-api-gateway-url/agents/api/v1/chat/test-1?request_id=9fce843f-f8bb-4818-bf35-5167247e17c8 ``` -Response (while processing): +Response (while processing or if not found): ```json { - "status": "PENDING", + "error": "NOT_FOUND", + "message": "No response message found for request_id '9fce843f-f8bb-4818-bf35-5167247e17c8'. The message may be unavailable. Please try again.", "request_id": "9fce843f-f8bb-4818-bf35-5167247e17c8", "session_id": "test-1" } @@ -144,6 +145,8 @@ Response (when complete): } ``` +**Note**: The client should retry polling with exponential backoff when receiving NOT_FOUND. This status means the response isn't in DynamoDB yet (still processing) or the request_id is invalid. + ## Architecture Details ### Two-Image Pattern From e3eb26e9288bfddde571e8a71464668a2d77a25f Mon Sep 17 00:00:00 2001 From: yl-nuwan Date: Mon, 8 Jun 2026 16:37:11 +0530 Subject: [PATCH 21/90] fix(aws): correct HTTP status code and error messages for missing responses - Change HTTP status from 403 (FORBIDDEN) to 404 (NOT_FOUND) in ECS queue handler when response message is not found - Update error message to indicate message unavailability rather than session mismatch - Add request_id to error detail in ECS queue handler response - Align serverless Lambda router error response with queue handler for consistency - Improve error messaging clarity for clients when async response messages cannot be located --- .../deployment/aws/containerized/ecs_queue_handler.py | 7 ++++--- .../deployment/aws/serverless/core/router/rest_lambda.py | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py b/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py index 5c0e810cb..15bb0a82b 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py @@ -160,10 +160,11 @@ async def poll_response(session_id: str, request_id: str = None): f"response session_id={response_session_id}, request_id={effective_request_id}" ) raise HTTPException( - status_code=403, + status_code=404, detail={ - "error": "FORBIDDEN", - "message": "The request_id does not belong to this session", + "error": "NOT_FOUND", + "message": f"No response message found for request_id '{effective_request_id}'. The message may be unavailable. Please try again.", + "request_id": effective_request_id, "session_id": session_id, }, ) diff --git a/ak-py/src/agentkernel/deployment/aws/serverless/core/router/rest_lambda.py b/ak-py/src/agentkernel/deployment/aws/serverless/core/router/rest_lambda.py index 665cd9764..34d15472a 100644 --- a/ak-py/src/agentkernel/deployment/aws/serverless/core/router/rest_lambda.py +++ b/ak-py/src/agentkernel/deployment/aws/serverless/core/router/rest_lambda.py @@ -252,8 +252,8 @@ def poll_operation(payload: BaseRequest) -> Dict[str, Any]: ) response_body = self._build_failure_body( request_id=request_id, - status="FORBIDDEN", - message="The request_id does not belong to this session", + status="NOT_FOUND", + message=f"No response message found for request_id '{request_id}'. The message may be unavailable. Please try again.", ) else: response_body = message From 2d4b53b10d6cfdc90d4461b81674051c91721d6f Mon Sep 17 00:00:00 2001 From: yl-nuwan Date: Tue, 9 Jun 2026 00:35:05 +0530 Subject: [PATCH 22/90] feat(aws-containerized): add SQS-based autoscaling for Agent Runner - Add scaling.tf with Lambda-based BacklogPerTask metric calculation - Implement EventBridge trigger for metric computation every minute - Add target tracking scaling policy for Agent Runner ECS service - Add autoscaling configuration variables to variables.tf - Document autoscaling parameters and usage in README - Enable configurable min/max task counts and scale in/out cooldown periods - Add validation to require queue_mode when autoscaling is enabled - Update example deployment to demonstrate autoscaling configuration - Supports both sync and async queue modes with automatic task scaling based on queue depth --- ak-deployment/ak-aws/containerized/README.md | 12 + ak-deployment/ak-aws/containerized/scaling.tf | 262 ++++++++++++++++++ .../ak-aws/containerized/variables.tf | 38 +++ .../openai-dynamodb-scalable/README.md | 42 ++- .../openai-dynamodb-scalable/deploy/main.tf | 10 + 5 files changed, 362 insertions(+), 2 deletions(-) create mode 100644 ak-deployment/ak-aws/containerized/scaling.tf diff --git a/ak-deployment/ak-aws/containerized/README.md b/ak-deployment/ak-aws/containerized/README.md index 2715cdfe6..2173b4817 100644 --- a/ak-deployment/ak-aws/containerized/README.md +++ b/ak-deployment/ak-aws/containerized/README.md @@ -306,6 +306,12 @@ module "scalable_queue_app" { enable_queue_mode = true queue_mode_type = "sync" # or "async" for polling mode + # Enable Autoscaling (optional) + enable_agent_runner_autoscaling = true + agent_runner_min_count = 1 + agent_runner_max_count = 10 + agent_runner_backlog_target = 10 + # Agent Runner Configuration agent_runner_image_uri = module.agent_runner_image.docker_image_uri agent_runner_command = ["python", "app_agent_runner.py"] @@ -385,12 +391,18 @@ See [examples/aws-containerized/openai-dynamodb-scalable](../../../examples/aws- | `create_dynamodb_memory_table` | Enable DynamoDB table for session storage | `bool` | `false` | no | | **Queue Mode (Scalable Architecture)** | | `enable_queue_mode` | Enable SQS queue mode with separate Agent Runner service | `bool` | `false` | no | +| `enable_agent_runner_autoscaling` | Enable SQS-based autoscaling for Agent Runner (requires `enable_queue_mode=true`) | `bool` | `false` | no | | `queue_mode_type` | Queue mode type: `sync` (client blocks) or `async` (client polls) | `string` | `"sync"` | no | | `agent_runner_image_uri` | Docker image URI for Agent Runner (required when `enable_queue_mode=true`) | `string` | `null` | conditional | | `agent_runner_command` | Command override for Agent Runner container | `list(string)` | `null` | no | | `agent_runner_cpu` | Fargate CPU units for Agent Runner | `number` | `1024` | no | | `agent_runner_memory` | Fargate memory in MiB for Agent Runner | `number` | `2048` | no | | `agent_runner_desired_count` | Number of Agent Runner tasks | `number` | `1` | no | +| `agent_runner_min_count` | Minimum tasks when autoscaling enabled | `number` | `0` | no | +| `agent_runner_max_count` | Maximum tasks when autoscaling enabled | `number` | `10` | no | +| `agent_runner_backlog_target` | Target BacklogPerTask for autoscaling | `number` | `10` | no | +| `agent_runner_scale_in_cooldown` | Seconds to wait before scaling in again | `number` | `120` | no | +| `agent_runner_scale_out_cooldown` | Seconds to wait before scaling out again | `number` | `30` | no | | `sqs_input_visibility_timeout` | SQS Input Queue visibility timeout (seconds) | `number` | `30` | no | | `sqs_output_visibility_timeout` | SQS Output Queue visibility timeout (seconds) | `number` | `30` | no | | `ecs_container_command` | Command override for REST Service container | `list(string)` | `null` | no | diff --git a/ak-deployment/ak-aws/containerized/scaling.tf b/ak-deployment/ak-aws/containerized/scaling.tf new file mode 100644 index 000000000..2d71e5625 --- /dev/null +++ b/ak-deployment/ak-aws/containerized/scaling.tf @@ -0,0 +1,262 @@ +# -------------------------------------------------------------------------- +# SQS-based Auto Scaling for Agent Runner ECS Service +# +# Implements the AWS recommended BacklogPerTask metric pattern: +# - Lambda function calculates: queue_depth / running_tasks +# - EventBridge triggers Lambda every minute +# - CloudWatch custom metric: Custom/ECS/BacklogPerTask +# - Target tracking policy scales agent_runner based on backlog per task +# +# Requires: enable_queue_mode = true AND enable_agent_runner_autoscaling = true +# -------------------------------------------------------------------------- + +data "aws_caller_identity" "current" {} + +locals { + enable_autoscaling = var.enable_queue_mode && var.enable_agent_runner_autoscaling +} + +# Validation: Autoscaling requires queue mode +resource "null_resource" "validate_autoscaling_requires_queue_mode" { + count = var.enable_agent_runner_autoscaling && !var.enable_queue_mode ? 1 : 0 + + provisioner "local-exec" { + command = "echo 'ERROR: enable_agent_runner_autoscaling requires enable_queue_mode = true' && exit 1" + } +} + +# ---------- Lambda IAM Role and Policy ---------- + +resource "aws_iam_role" "backlog_metric_lambda_role" { + count = local.enable_autoscaling ? 1 : 0 + name = "${local.prefix}-backlog-metric-lambda-role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + Effect = "Allow" + Action = "sts:AssumeRole" + Principal = { Service = "lambda.amazonaws.com" } + }] + }) + + tags = var.tags +} + +resource "aws_iam_policy" "backlog_metric_lambda_policy" { + count = local.enable_autoscaling ? 1 : 0 + name = "${local.prefix}-backlog-metric-lambda-policy" + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Sid = "GetQueueDepth" + Effect = "Allow" + Action = [ + "sqs:GetQueueAttributes" + ] + Resource = module.input_queue[0].queue_arn + }, + { + Sid = "GetRunningTaskCount" + Effect = "Allow" + Action = [ + "ecs:DescribeServices" + ] + Resource = "arn:aws:ecs:${var.region}:${data.aws_caller_identity.current.account_id}:service/${module.ecs.cluster_name}/${local.prefix}-agent-runner" + }, + { + Sid = "PutCustomMetric" + Effect = "Allow" + Action = "cloudwatch:PutMetricData" + Resource = "*" + }, + { + Sid = "LambdaLogging" + Effect = "Allow" + Action = [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ] + Resource = "arn:aws:logs:${var.region}:${data.aws_caller_identity.current.account_id}:log-group:/aws/lambda/${local.prefix}-backlog-metric:*" + } + ] + }) + + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "backlog_metric_lambda" { + count = var.enable_queue_mode ? 1 : 0 + role = aws_iam_role.backlog_metric_lambda_role[0].name + policy_arn = aws_iam_policy.backlog_metric_lambda_policy[0].arn +} + + +data "archive_file" "backlog_metric_lambda" { + count = var.enable_queue_mode ? 1 : 0 + type = "zip" + output_path = "${path.module}/.terraform/lambda/backlog_metric.zip" + + source { + content = <<-PYTHON +import boto3 +import os +import json + +def handler(event, context): + """ + Calculate BacklogPerTask metric for ECS agent runner autoscaling. + + BacklogPerTask = ApproximateNumberOfMessages / max(runningCount, 1) + + This metric is used by Target Tracking Scaling to adjust the number + of agent runner tasks based on queue depth. + """ + sqs = boto3.client("sqs") + ecs = boto3.client("ecs") + cw = boto3.client("cloudwatch") + + queue_url = os.environ["QUEUE_URL"] + cluster_name = os.environ["CLUSTER_NAME"] + service_name = os.environ["SERVICE_NAME"] + namespace = os.environ.get("METRIC_NAMESPACE", "Custom/ECS") + metric_name = os.environ.get("METRIC_NAME", "BacklogPerTask") + + # --- Get queue depth --- + attrs = sqs.get_queue_attributes( + QueueUrl=queue_url, + AttributeNames=["ApproximateNumberOfMessages"] + ) + queue_depth = int(attrs["Attributes"]["ApproximateNumberOfMessages"]) + + # --- Get running task count --- + resp = ecs.describe_services(cluster=cluster_name, services=[service_name]) + running = resp["services"][0]["runningCount"] + + # Avoid division by zero; treat 0 running tasks as 1 for the metric + divisor = max(running, 1) + backlog_per_task = queue_depth / divisor + + # --- Publish custom metric --- + cw.put_metric_data( + Namespace=namespace, + MetricData=[{ + "MetricName": metric_name, + "Value": backlog_per_task, + "Unit": "Count", + "Dimensions": [ + {"Name": "ClusterName", "Value": cluster_name}, + {"Name": "ServiceName", "Value": service_name}, + ] + }] + ) + + print(f"Published metric: {metric_name}={backlog_per_task} (queue_depth={queue_depth}, running={running})") + + return { + "backlog_per_task": backlog_per_task, + "queue_depth": queue_depth, + "running": running + } +PYTHON + filename = "index.py" + } +} + +resource "aws_lambda_function" "backlog_metric" { + count = var.enable_queue_mode ? 1 : 0 + function_name = "${local.prefix}-backlog-metric" + role = aws_iam_role.backlog_metric_lambda_role[0].arn + runtime = "python3.12" + handler = "index.handler" + filename = data.archive_file.backlog_metric_lambda[0].output_path + source_code_hash = data.archive_file.backlog_metric_lambda[0].output_base64sha256 + timeout = 30 + + environment { + variables = { + QUEUE_URL = module.input_queue[0].queue_url + CLUSTER_NAME = module.ecs.cluster_name + SERVICE_NAME = "${local.prefix}-agent-runner" + METRIC_NAMESPACE = "Custom/ECS" + METRIC_NAME = "BacklogPerTask" + } + } + + tags = var.tags +} + +resource "aws_cloudwatch_log_group" "backlog_metric_lambda" { + count = var.enable_queue_mode ? 1 : 0 + name = "/aws/lambda/${local.prefix}-backlog-metric" + retention_in_days = 7 + tags = var.tags +} + +resource "aws_cloudwatch_event_rule" "backlog_metric_schedule" { + count = var.enable_queue_mode ? 1 : 0 + name = "${local.prefix}-backlog-metric-schedule" + description = "Trigger BacklogPerTask metric calculation every minute" + schedule_expression = "rate(1 minute)" + tags = var.tags +} + +resource "aws_cloudwatch_event_target" "backlog_metric_lambda" { + count = local.enable_autoscaling ? 1 : 0 + rule = aws_cloudwatch_event_rule.backlog_metric_schedule[0].name + arn = aws_lambda_function.backlog_metric[0].arn +} + +resource "aws_lambda_permission" "allow_eventbridge" { + count = var.enable_queue_mode ? 1 : 0 + statement_id = "AllowExecutionFromEventBridge" + action = "lambda:InvokeFunction" + function_name = aws_lambda_function.backlog_metric[0].function_name + principal = "events.amazonaws.com" + source_arn = aws_cloudwatch_event_rule.backlog_metric_schedule[0].arn +} + +resource "aws_appautoscaling_target" "agent_runner" { + count = var.enable_queue_mode ? 1 : 0 + max_capacity = var.agent_runner_max_count + min_capacity = var.agent_runner_min_count + resource_id = "service/${module.ecs.cluster_name}/${local.prefix}-agent-runner" + scalable_dimension = "ecs:service:DesiredCount" + service_namespace = "ecs" + + depends_on = [aws_ecs_service.agent_runner] +} + +resource "aws_appautoscaling_policy" "agent_runner_backlog" { + count = var.enable_queue_mode ? 1 : 0 + name = "${local.prefix}-agent-runner-backlog-tracking" + policy_type = "TargetTrackingScaling" + resource_id = aws_appautoscaling_target.agent_runner[0].resource_id + scalable_dimension = aws_appautoscaling_target.agent_runner[0].scalable_dimension + service_namespace = aws_appautoscaling_target.agent_runner[0].service_namespace + + target_tracking_scaling_policy_configuration { + target_value = var.agent_runner_backlog_target + scale_in_cooldown = var.agent_runner_scale_in_cooldown + scale_out_cooldown = var.agent_runner_scale_out_cooldown + + customized_metric_specification { + namespace = "Custom/ECS" + metric_name = "BacklogPerTask" + statistic = "Average" + + dimensions { + name = "ClusterName" + value = module.ecs.cluster_name + } + + dimensions { + name = "ServiceName" + value = "${local.prefix}-agent-runner" + } + } + } +} diff --git a/ak-deployment/ak-aws/containerized/variables.tf b/ak-deployment/ak-aws/containerized/variables.tf index 1dc6bbdf1..8da131b7e 100644 --- a/ak-deployment/ak-aws/containerized/variables.tf +++ b/ak-deployment/ak-aws/containerized/variables.tf @@ -370,3 +370,41 @@ variable "agent_runner_command" { description = "Command override for the Agent Runner ECS task. If not set, uses the Docker image's CMD. Example: [\"python\", \"app_agent_runner.py\"]" default = null } + +# --- Agent Runner Auto Scaling --- + +variable "enable_agent_runner_autoscaling" { + type = bool + description = "Enable SQS-based autoscaling for Agent Runner ECS service. Requires enable_queue_mode = true." + default = false +} + +variable "agent_runner_min_count" { + type = number + description = "Minimum number of agent runner tasks when autoscaling is enabled." + default = 0 +} + +variable "agent_runner_max_count" { + type = number + description = "Maximum number of agent runner tasks when autoscaling is enabled." + default = 10 +} + +variable "agent_runner_backlog_target" { + type = number + description = "Target BacklogPerTask value that triggers scaling. Lower values scale up more aggressively. Recommended: 5-20 messages per task." + default = 10 +} + +variable "agent_runner_scale_in_cooldown" { + type = number + description = "Seconds to wait after a scale-in before allowing another scale-in. Prevents flapping." + default = 120 +} + +variable "agent_runner_scale_out_cooldown" { + type = number + description = "Seconds to wait after a scale-out before allowing another scale-out. Allows new tasks to start processing before scaling again." + default = 30 +} diff --git a/examples/aws-containerized/openai-dynamodb-scalable/README.md b/examples/aws-containerized/openai-dynamodb-scalable/README.md index 0c65add95..8e382b8b1 100644 --- a/examples/aws-containerized/openai-dynamodb-scalable/README.md +++ b/examples/aws-containerized/openai-dynamodb-scalable/README.md @@ -189,13 +189,51 @@ See [COMMAND_OVERRIDE.md](../../ak-deployment/ak-aws/containerized/COMMAND_OVERR The architecture automatically scales based on: - **REST Service**: Scales with ALB target group metrics -- **Agent Runner**: Scales based on SQS queue depth +- **Agent Runner**: Scales based on SQS queue depth using custom BacklogPerTask metric + +### Agent Runner Auto Scaling + +The Agent Runner can automatically scale based on queue backlog per task. + +**Enable autoscaling:** +```hcl +enable_queue_mode = true +enable_agent_runner_autoscaling = true +``` + +**How it works:** +1. Lambda function runs every minute, calculates: `BacklogPerTask = QueueDepth / RunningTasks` +2. Publishes custom CloudWatch metric: `Custom/ECS/BacklogPerTask` +3. Target tracking policy scales agent_runner up/down to maintain target BacklogPerTask + +**Configuration:** +```hcl +enable_agent_runner_autoscaling = true # Must also enable_queue_mode = true +agent_runner_min_count = 1 # Minimum tasks (0 to scale to zero) +agent_runner_max_count = 10 # Maximum tasks +agent_runner_backlog_target = 10 # Target messages per task +agent_runner_scale_in_cooldown = 120 # Wait before scaling in +agent_runner_scale_out_cooldown = 30 # Wait before scaling out +``` + +**Example scaling behavior:** +- Queue has 100 messages, 2 running tasks → BacklogPerTask = 50 +- Target is 10 → Scales up to 10 tasks +- Queue drains to 20 messages, 10 running tasks → BacklogPerTask = 2 +- Target is 10 → Scales down to 2 tasks + +**Benefits:** +- Automatically handles traffic spikes +- Scales to zero when idle (if `min_count = 0`) +- Cost-effective: only pay for what you use +- Fast scale-out (30s cooldown), gradual scale-in (120s cooldown) Monitor through CloudWatch: -- ECS task metrics and logs +- Lambda function metrics and logs - SQS queue depth and processing rates - DynamoDB read/write metrics - API Gateway and ALB request metrics +- Custom metric: `Custom/ECS/BacklogPerTask` ## Troubleshooting diff --git a/examples/aws-containerized/openai-dynamodb-scalable/deploy/main.tf b/examples/aws-containerized/openai-dynamodb-scalable/deploy/main.tf index 711b854c7..9037e1fda 100644 --- a/examples/aws-containerized/openai-dynamodb-scalable/deploy/main.tf +++ b/examples/aws-containerized/openai-dynamodb-scalable/deploy/main.tf @@ -72,6 +72,9 @@ module "containerized_agents" { enable_queue_mode = true queue_mode_type = "async" + # Enable autoscaling for Agent Runner (optional) + enable_agent_runner_autoscaling = true + # Agent Runner uses its own image (different CMD) agent_runner_image_uri = module.agent_runner_image.docker_image_uri @@ -89,6 +92,13 @@ module "containerized_agents" { agent_runner_memory = 2048 agent_runner_desired_count = 1 + # Agent Runner Auto Scaling (optional) + agent_runner_min_count = 1 # Minimum tasks (can be 0 to scale to zero) + agent_runner_max_count = 10 # Maximum tasks + agent_runner_backlog_target = 10 # Target messages per task (scale up when exceeded) + agent_runner_scale_in_cooldown = 120 # Wait 2min before scaling in again + agent_runner_scale_out_cooldown = 30 # Wait 30s before scaling out again + # Environment variables for both containers environment_variables = { OPENAI_API_KEY = var.openai_api_key From 256fc978a8e0154e4fc5b8d0cd27fa02e26cccfd Mon Sep 17 00:00:00 2001 From: yl-nuwan Date: Tue, 9 Jun 2026 00:43:31 +0530 Subject: [PATCH 23/90] removed duplicate definition --- ak-deployment/ak-aws/containerized/scaling.tf | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/ak-deployment/ak-aws/containerized/scaling.tf b/ak-deployment/ak-aws/containerized/scaling.tf index 2d71e5625..4d6b1ee74 100644 --- a/ak-deployment/ak-aws/containerized/scaling.tf +++ b/ak-deployment/ak-aws/containerized/scaling.tf @@ -1,17 +1,3 @@ -# -------------------------------------------------------------------------- -# SQS-based Auto Scaling for Agent Runner ECS Service -# -# Implements the AWS recommended BacklogPerTask metric pattern: -# - Lambda function calculates: queue_depth / running_tasks -# - EventBridge triggers Lambda every minute -# - CloudWatch custom metric: Custom/ECS/BacklogPerTask -# - Target tracking policy scales agent_runner based on backlog per task -# -# Requires: enable_queue_mode = true AND enable_agent_runner_autoscaling = true -# -------------------------------------------------------------------------- - -data "aws_caller_identity" "current" {} - locals { enable_autoscaling = var.enable_queue_mode && var.enable_agent_runner_autoscaling } From 2d62c50d486de31e1501d7df2289b8b416a6d5df Mon Sep 17 00:00:00 2001 From: yl-nuwan Date: Tue, 9 Jun 2026 11:56:51 +0530 Subject: [PATCH 24/90] load testing --- .../app_agent_runner.py | 88 ++++++++++++------- 1 file changed, 58 insertions(+), 30 deletions(-) diff --git a/examples/aws-containerized/openai-dynamodb-scalable/app_agent_runner.py b/examples/aws-containerized/openai-dynamodb-scalable/app_agent_runner.py index 90c46effe..774c8422a 100644 --- a/examples/aws-containerized/openai-dynamodb-scalable/app_agent_runner.py +++ b/examples/aws-containerized/openai-dynamodb-scalable/app_agent_runner.py @@ -1,30 +1,58 @@ -from agentkernel.deployment.aws.containerized import ECSAgentRunner -from agentkernel.openai import OpenAIModule -from agents import Agent - -math_agent = Agent( - name="math", - handoff_description="Specialist agent for math questions", - instructions="You provide help with math problems. Explain your reasoning at each step and include examples. \ - If prompted for anything else you refuse to answer.", -) - -history_agent = Agent( - name="history", - handoff_description="Specialist agent for historical questions", - instructions="You provide assistance with historical queries. Explain important events and context clearly.", -) - -triage_agent = Agent( - name="triage", - instructions="You determine which agent to use based on the user's question.", - handoffs=[history_agent, math_agent], -) - -OpenAIModule([triage_agent, math_agent, history_agent]) - -# Agent Runner entrypoint - polls Input Queue, runs agent, sends to Output Queue. -handler = ECSAgentRunner.run - -if __name__ == "__main__": - handler() +# from agentkernel.deployment.aws.containerized import ECSAgentRunner +# from agentkernel.openai import OpenAIModule +# from agents import Agent + +# math_agent = Agent( +# name="math", +# handoff_description="Specialist agent for math questions", +# instructions="You provide help with math problems. Explain your reasoning at each step and include examples. \ +# If prompted for anything else you refuse to answer.", +# ) + +# history_agent = Agent( +# name="history", +# handoff_description="Specialist agent for historical questions", +# instructions="You provide assistance with historical queries. Explain important events and context clearly.", +# ) + +# triage_agent = Agent( +# name="triage", +# instructions="You determine which agent to use based on the user's question.", +# handoffs=[history_agent, math_agent], +# ) + +# OpenAIModule([triage_agent, math_agent, history_agent]) + +# # Agent Runner entrypoint - polls Input Queue, runs agent, sends to Output Queue. +# handler = ECSAgentRunner.run + +# if __name__ == "__main__": +# handler() + +import logging +import signal +import sys +import time + +logging.basicConfig(level=logging.INFO) + +running = True + + +def shutdown(*args): + global running + logging.info("Shutdown requested") + running = False + + +signal.signal(signal.SIGTERM, shutdown) +signal.signal(signal.SIGINT, shutdown) + +logging.info("Agent Runner starting") + +while running: + time.sleep(30) + logging.info("Agent Runner heartbeat") + +logging.info("Agent Runner stopping") +sys.exit(0) \ No newline at end of file From cfab4f3685da009666eff7c669be801b0f4d9011 Mon Sep 17 00:00:00 2001 From: yl-nuwan Date: Tue, 9 Jun 2026 13:08:06 +0530 Subject: [PATCH 25/90] disabled the cache tempararly --- .../workflows/integration-test-weekly.yaml | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/integration-test-weekly.yaml b/.github/workflows/integration-test-weekly.yaml index a2d01dabe..fc9deddc1 100644 --- a/.github/workflows/integration-test-weekly.yaml +++ b/.github/workflows/integration-test-weekly.yaml @@ -58,11 +58,11 @@ jobs: working-directory: ak-py run: ./build.sh - - name: Cache ak-py build - uses: actions/cache/save@v4 - with: - path: ak-py/dist - key: ak-py-${{ github.sha }} + # - name: Cache ak-py build + # uses: actions/cache/save@v4 + # with: + # path: ak-py/dist + # key: ak-py-${{ github.sha }} get-base-outputs: needs: [setup, build-ak-py] @@ -123,11 +123,11 @@ jobs: - name: Install dependencies run: pip install uv pyyaml - - name: Restore ak-py build - uses: actions/cache/restore@v4 - with: - path: ak-py/dist - key: ak-py-${{ github.sha }} + # - name: Restore ak-py build + # uses: actions/cache/restore@v4 + # with: + # path: ak-py/dist + # key: ak-py-${{ github.sha }} - name: Configure AWS credentials if: matrix.type == 'aws-containerized' || matrix.type == 'aws-serverless' @@ -282,11 +282,11 @@ jobs: - name: Install dependencies run: pip install uv pyyaml - - name: Restore ak-py build - uses: actions/cache/restore@v4 - with: - path: ak-py/dist - key: ak-py-${{ github.sha }} + # - name: Restore ak-py build + # uses: actions/cache/restore@v4 + # with: + # path: ak-py/dist + # key: ak-py-${{ github.sha }} - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v4 From 517dce4cfa3236be5c08f5393871a835533bc7dd Mon Sep 17 00:00:00 2001 From: yl-nuwan Date: Tue, 9 Jun 2026 13:51:05 +0530 Subject: [PATCH 26/90] fix: update agentkernel dependency version to 0.5.1 in deploy scripts and configuration files --- .../openai-dynamodb-scalable/deploy/deploy.sh | 10 +++++++--- .../openai-dynamodb-scalable/pyproject.toml | 4 ++-- .../aws-containerized/openai-dynamodb-scalable/uv.lock | 10 +++++----- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/examples/aws-containerized/openai-dynamodb-scalable/deploy/deploy.sh b/examples/aws-containerized/openai-dynamodb-scalable/deploy/deploy.sh index f22614d45..adcf4df6f 100755 --- a/examples/aws-containerized/openai-dynamodb-scalable/deploy/deploy.sh +++ b/examples/aws-containerized/openai-dynamodb-scalable/deploy/deploy.sh @@ -14,8 +14,7 @@ create_deployment_packages() { uv pip install -r requirements.txt --target=dist-rest-service/data else uv pip install -r requirements.txt --target=dist-rest-service/data --find-links ../../../ak-py/dist - uv pip install --force-reinstall --target=dist-rest-service/data --find-links ../../../ak-py/dist agentkernel[adk,api,aws,test] || true - fi + uv pip install --force-reinstall --target=dist-rest-service/data --find-links ../../../ak-py/dist agentkernel[adk,api,aws,test] cp config.yaml app_rest_service.py dist-rest-service/data/ # Agent Runner dist @@ -25,7 +24,7 @@ create_deployment_packages() { uv pip install -r requirements.txt --target=dist-agent-runner/data else uv pip install -r requirements.txt --target=dist-agent-runner/data --find-links ../../../ak-py/dist - uv pip install --force-reinstall --target=dist-agent-runner/data --find-links ../../../ak-py/dist agentkernel[adk,api,aws,test] || true + uv pip install --force-reinstall --target=dist-agent-runner/data --find-links ../../../ak-py/dist agentkernel[adk,api,aws,test] fi cp config.yaml app_agent_runner.py dist-agent-runner/data/ @@ -37,6 +36,11 @@ create_deployment_packages() { cp Dockerfile.agent-runner ../dist-agent-runner/Dockerfile } +pushd ../../../../ak-py || exit 1 +rm -rf dist +./build.sh local +popd + create_deployment_packages $1 terraform init diff --git a/examples/aws-containerized/openai-dynamodb-scalable/pyproject.toml b/examples/aws-containerized/openai-dynamodb-scalable/pyproject.toml index 76f71405c..b198be29c 100644 --- a/examples/aws-containerized/openai-dynamodb-scalable/pyproject.toml +++ b/examples/aws-containerized/openai-dynamodb-scalable/pyproject.toml @@ -5,12 +5,12 @@ description = "Demo of OpenAI Agents SDK based agents running in Agent Kernel on readme = "README.md" requires-python = ">=3.12" dependencies = [ - "agentkernel[openai,api,aws]>=0.4.0" + "agentkernel[openai,api,aws]>=0.5.1" ] [dependency-groups] dev = [ - "agentkernel[test]>=0.4.0", + "agentkernel[test]>=0.5.1", "black>=23.0.0", "isort>=5.0.0", "mypy>=1.0.0", diff --git a/examples/aws-containerized/openai-dynamodb-scalable/uv.lock b/examples/aws-containerized/openai-dynamodb-scalable/uv.lock index 2ed28e83e..53b5e719a 100644 --- a/examples/aws-containerized/openai-dynamodb-scalable/uv.lock +++ b/examples/aws-containerized/openai-dynamodb-scalable/uv.lock @@ -18,7 +18,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -27,9 +27,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -287,11 +287,11 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "agentkernel", extras = ["openai", "api", "aws"], specifier = ">=0.4.0" }] +requires-dist = [{ name = "agentkernel", extras = ["openai", "api", "aws"], specifier = ">=0.5.1" }] [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "isort", specifier = ">=5.0.0" }, { name = "mypy", specifier = ">=1.0.0" }, From 66cef748cf4fd326172e808f254cb0057938d8b6 Mon Sep 17 00:00:00 2001 From: yl-nuwan Date: Tue, 9 Jun 2026 13:57:06 +0530 Subject: [PATCH 27/90] fix: correct indentation in deploy script for config file copy --- .../openai-dynamodb-scalable/deploy/deploy.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/aws-containerized/openai-dynamodb-scalable/deploy/deploy.sh b/examples/aws-containerized/openai-dynamodb-scalable/deploy/deploy.sh index adcf4df6f..76fade7bf 100755 --- a/examples/aws-containerized/openai-dynamodb-scalable/deploy/deploy.sh +++ b/examples/aws-containerized/openai-dynamodb-scalable/deploy/deploy.sh @@ -15,7 +15,8 @@ create_deployment_packages() { else uv pip install -r requirements.txt --target=dist-rest-service/data --find-links ../../../ak-py/dist uv pip install --force-reinstall --target=dist-rest-service/data --find-links ../../../ak-py/dist agentkernel[adk,api,aws,test] - cp config.yaml app_rest_service.py dist-rest-service/data/ + fi + cp config.yaml app_rest_service.py dist-rest-service/data/ # Agent Runner dist rm -rf dist-agent-runner From 81436c32ea3bab73d341d1bd92fdbd466cbd1cbc Mon Sep 17 00:00:00 2001 From: yl-nuwan Date: Mon, 15 Jun 2026 00:14:48 +0530 Subject: [PATCH 28/90] feat: add agent runner autoscaling documentation and configuration details --- ak-deployment/ak-aws/containerized/README.md | 83 ++++++++++++++++++- ak-deployment/ak-aws/containerized/scaling.tf | 3 +- .../openai-dynamodb-scalable/README.md | 7 ++ 3 files changed, 90 insertions(+), 3 deletions(-) diff --git a/ak-deployment/ak-aws/containerized/README.md b/ak-deployment/ak-aws/containerized/README.md index 2173b4817..2b0b8f139 100644 --- a/ak-deployment/ak-aws/containerized/README.md +++ b/ak-deployment/ak-aws/containerized/README.md @@ -347,6 +347,87 @@ module "scalable_queue_app" { See [examples/aws-containerized/openai-dynamodb-scalable](../../../examples/aws-containerized/openai-dynamodb-scalable/) for complete implementation. +### Agent Runner Autoscaling + +When `enable_agent_runner_autoscaling = true`, the Agent Runner automatically scales based on queue backlog. + +#### How It Works + +A Lambda function runs every **1 minute** and calculates: + +``` +BacklogPerTask = ApproximateNumberOfMessages / max(RunningTasks, 1) +``` + +AWS Target Tracking monitors this metric and scales using: + +``` +New Capacity = Current Capacity × (Current Metric / Target) +``` + +- **Scale-out**: Aggressive and fast (~3-4 minutes) +- **Scale-in**: Conservative and gradual (~15-30 minutes, multiple cycles) + +#### Configuration + +```hcl +enable_agent_runner_autoscaling = true +agent_runner_min_count = 1 # Minimum tasks (0 = scale to zero) +agent_runner_max_count = 10 # Maximum tasks +agent_runner_backlog_target = 10 # Target messages per task +agent_runner_scale_in_cooldown = 120 # Seconds between scale-in +agent_runner_scale_out_cooldown = 30 # Seconds between scale-out +``` + +**Choosing Target Value:** +- **Lower target** (e.g., 5): Scales out sooner, more tasks, faster processing, higher cost +- **Higher target** (e.g., 20): Scales out later, fewer tasks, slower processing, lower cost +- Start with default (10), monitor queue depth and task count, then tune + +#### Scaling Examples + +**Scale-Out:** +``` +Queue=100, Tasks=1, BacklogPerTask=100, Target=10 +→ Scales to ~10 tasks quickly (2-3 minutes) +``` + +**Scale-In (gradual over multiple cycles):** +``` +Queue=20, Tasks=10, BacklogPerTask=2, Target=10 + +Cycle 1: Remove 3 tasks → 7 tasks +Wait 2min (cooldown) +Cycle 2: Remove 2 tasks → 5 tasks +...continues until stable +``` + +#### Monitoring + +**CloudWatch Metrics:** +- `Custom/ECS/BacklogPerTask` - Custom metric +- `RunningTaskCount` - ECS service +- `ApproximateNumberOfMessages` - SQS queue + +**View Lambda Logs:** +```bash +aws logs tail /aws/lambda/-backlog-metric --follow +``` + +**View Scaling Activity:** +```bash +aws application-autoscaling describe-scaling-activities \ + --service-namespace ecs \ + --resource-id service// +``` + +#### Troubleshooting + +- **Scaling too aggressively**: Increase `backlog_target` or cooldown periods +- **Not scaling fast enough**: Decrease `backlog_target`, verify Lambda is running +- **Stuck at min/max**: Check capacity limits, verify messages are being processed +- **Scale to zero not working**: Set `min_count = 0`, wait 30+ minutes for gradual scale-in + ## 📥 Inputs | Name | Description | Type | Default | Required | @@ -400,7 +481,7 @@ See [examples/aws-containerized/openai-dynamodb-scalable](../../../examples/aws- | `agent_runner_desired_count` | Number of Agent Runner tasks | `number` | `1` | no | | `agent_runner_min_count` | Minimum tasks when autoscaling enabled | `number` | `0` | no | | `agent_runner_max_count` | Maximum tasks when autoscaling enabled | `number` | `10` | no | -| `agent_runner_backlog_target` | Target BacklogPerTask for autoscaling | `number` | `10` | no | +| `agent_runner_backlog_target` | Target messages per task for autoscaling (lower=more aggressive) | `number` | `10` | no | | `agent_runner_scale_in_cooldown` | Seconds to wait before scaling in again | `number` | `120` | no | | `agent_runner_scale_out_cooldown` | Seconds to wait before scaling out again | `number` | `30` | no | | `sqs_input_visibility_timeout` | SQS Input Queue visibility timeout (seconds) | `number` | `30` | no | diff --git a/ak-deployment/ak-aws/containerized/scaling.tf b/ak-deployment/ak-aws/containerized/scaling.tf index 4d6b1ee74..3e8be0403 100644 --- a/ak-deployment/ak-aws/containerized/scaling.tf +++ b/ak-deployment/ak-aws/containerized/scaling.tf @@ -11,7 +11,6 @@ resource "null_resource" "validate_autoscaling_requires_queue_mode" { } } -# ---------- Lambda IAM Role and Policy ---------- resource "aws_iam_role" "backlog_metric_lambda_role" { count = local.enable_autoscaling ? 1 : 0 @@ -217,7 +216,7 @@ resource "aws_appautoscaling_target" "agent_runner" { } resource "aws_appautoscaling_policy" "agent_runner_backlog" { - count = var.enable_queue_mode ? 1 : 0 + count = local.enable_autoscaling ? 1 : 0 name = "${local.prefix}-agent-runner-backlog-tracking" policy_type = "TargetTrackingScaling" resource_id = aws_appautoscaling_target.agent_runner[0].resource_id diff --git a/examples/aws-containerized/openai-dynamodb-scalable/README.md b/examples/aws-containerized/openai-dynamodb-scalable/README.md index 8e382b8b1..1fc8d27c8 100644 --- a/examples/aws-containerized/openai-dynamodb-scalable/README.md +++ b/examples/aws-containerized/openai-dynamodb-scalable/README.md @@ -227,6 +227,13 @@ agent_runner_scale_out_cooldown = 30 # Wait before scaling out - Scales to zero when idle (if `min_count = 0`) - Cost-effective: only pay for what you use - Fast scale-out (30s cooldown), gradual scale-in (120s cooldown) +- **Unique per deployment**: Metrics use ClusterName and ServiceName dimensions. + +**Detailed documentation:** See the [containerized deployment README](../../../ak-deployment/ak-aws/containerized/README.md#agent-runner-autoscaling) for: +- Complete timing breakdown and scaling behavior +- How to choose the right `backlog_target` value +- Troubleshooting and cost optimization +- Monitoring examples Monitor through CloudWatch: - Lambda function metrics and logs From c61869e57fadce19ffcd370277e58e9518f05375 Mon Sep 17 00:00:00 2001 From: yl-nuwan Date: Mon, 15 Jun 2026 08:35:56 +0530 Subject: [PATCH 29/90] refactor: clean up comments and enabled integration test configuration --- .github/integration-test-config.yaml | 106 +++++++++--------- ak-deployment/ak-aws/containerized/queue.tf | 9 -- .../ak-aws/containerized/variables.tf | 14 ++- .../aws/containerized/akagentrunner.py | 8 -- .../aws/containerized/akrestservice.py | 16 +-- .../app_agent_runner.py | 31 ----- .../openai-dynamodb-scalable/build.sh | 4 +- 7 files changed, 67 insertions(+), 121 deletions(-) diff --git a/.github/integration-test-config.yaml b/.github/integration-test-config.yaml index 7656df3fb..6ad1c68e2 100644 --- a/.github/integration-test-config.yaml +++ b/.github/integration-test-config.yaml @@ -37,74 +37,74 @@ weekly: # then each project is deployed and tested tests: #AWS Containerized - # - type: aws-containerized - # path: examples/aws-containerized/adk - # deploy_dir: deploy + - type: aws-containerized + path: examples/aws-containerized/adk + deploy_dir: deploy - type: aws-containerized path: examples/aws-containerized/openai-dynamodb deploy_dir: deploy - # - type: aws-containerized - # path: examples/aws-containerized/crewai - # deploy_dir: deploy - # - type: aws-containerized - # path: examples/aws-containerized/mcp/multi - # deploy_dir: deploy + - type: aws-containerized + path: examples/aws-containerized/crewai + deploy_dir: deploy + - type: aws-containerized + path: examples/aws-containerized/mcp/multi + deploy_dir: deploy - type: aws-containerized path: examples/aws-containerized/openai-dynamodb-scalable deploy_dir: deploy # AWS Serverless - # - type: aws-serverless - # path: examples/aws-serverless/adk - # deploy_dir: deploy - # - type: aws-serverless - # path: examples/aws-serverless/crewai - # deploy_dir: deploy - # - type: aws-serverless - # path: examples/aws-serverless/langgraph - # deploy_dir: deploy - # - type: aws-serverless - # path: examples/aws-serverless/openai-auth - # deploy_dir: deploy - # - type: aws-serverless - # path: examples/aws-serverless/scalable-openai - # deploy_dir: deploy + - type: aws-serverless + path: examples/aws-serverless/adk + deploy_dir: deploy + - type: aws-serverless + path: examples/aws-serverless/crewai + deploy_dir: deploy + - type: aws-serverless + path: examples/aws-serverless/langgraph + deploy_dir: deploy + - type: aws-serverless + path: examples/aws-serverless/openai-auth + deploy_dir: deploy + - type: aws-serverless + path: examples/aws-serverless/scalable-openai + deploy_dir: deploy # Memory options - # - type: aws-serverless - # path: examples/memory/redis - # - type: aws-serverless - # path: examples/memory/dynamodb - # - type: azure-serverless - # path: examples/memory/cosmos - # deploy_dir: deploy + - type: aws-serverless + path: examples/memory/redis + - type: aws-serverless + path: examples/memory/dynamodb + - type: azure-serverless + path: examples/memory/cosmos + deploy_dir: deploy # Azure Serverless - # - type: azure-serverless - # path: examples/azure-serverless/openai - # deploy_dir: deploy + - type: azure-serverless + path: examples/azure-serverless/openai + deploy_dir: deploy # Azure Containerized - # - type: azure-containerized - # path: examples/azure-containerized/openai-cosmos - # deploy_dir: deploy + - type: azure-containerized + path: examples/azure-containerized/openai-cosmos + deploy_dir: deploy # GCP Serverless - # - type: gcp-serverless - # path: examples/gcp-serverless/openai-firestore - # deploy_dir: deploy - # - type: gcp-serverless - # path: examples/gcp-serverless/openai - # deploy_dir: deploy - # - type: gcp-serverless - # path: examples/gcp-serverless/openai-auth - # deploy_dir: deploy + - type: gcp-serverless + path: examples/gcp-serverless/openai-firestore + deploy_dir: deploy + - type: gcp-serverless + path: examples/gcp-serverless/openai + deploy_dir: deploy + - type: gcp-serverless + path: examples/gcp-serverless/openai-auth + deploy_dir: deploy - # # GCP Containerized - # - type: gcp-containerized - # path: examples/gcp-containerized/openai - # deploy_dir: deploy - # - type: gcp-containerized - # path: examples/gcp-containerized/openai-auth - # deploy_dir: deploy + # GCP Containerized + - type: gcp-containerized + path: examples/gcp-containerized/openai + deploy_dir: deploy + - type: gcp-containerized + path: examples/gcp-containerized/openai-auth + deploy_dir: deploy diff --git a/ak-deployment/ak-aws/containerized/queue.tf b/ak-deployment/ak-aws/containerized/queue.tf index 41eb6f2f0..b8c8e3ee3 100644 --- a/ak-deployment/ak-aws/containerized/queue.tf +++ b/ak-deployment/ak-aws/containerized/queue.tf @@ -1,6 +1,5 @@ data "aws_region" "current" {} -# ---------- Input Queue ---------- module "input_queue" { count = var.enable_queue_mode ? 1 : 0 @@ -36,8 +35,6 @@ module "input_queue" { tags = merge(var.tags, { Type = "InputQueue" }) } -# ---------- Output Queue ---------- - module "output_queue" { count = var.enable_queue_mode ? 1 : 0 source = "../common/modules/sqs" @@ -71,9 +68,6 @@ module "output_queue" { tags = merge(var.tags, { Type = "OutputQueue" }) } -# ---------- DynamoDB Response Store ---------- -# Temporary response buffer. Thread 2 writes here; Thread 1 reads from here. - resource "aws_dynamodb_table" "response_store" { count = var.enable_queue_mode ? 1 : 0 @@ -175,8 +169,6 @@ resource "aws_iam_role_policy_attachment" "rest_service_response_store_attachmen policy_arn = aws_iam_policy.rest_service_response_store_policy[0].arn } -# ---------- IAM — Agent Runner ECS Task Role ---------- -# Separate execution role (pull image, write logs) and task role (SQS, DynamoDB). resource "aws_iam_role" "agent_runner_execution_role" { count = var.enable_queue_mode ? 1 : 0 @@ -411,7 +403,6 @@ resource "aws_ecs_service" "agent_runner" { tags = var.tags } -# ---------- API Gateway — REST Async GET route ---------- # Only needed for rest_async mode — adds GET /api/{version}/{endpoint}/{sessionId} resource "aws_apigatewayv2_integration" "async_get" { diff --git a/ak-deployment/ak-aws/containerized/variables.tf b/ak-deployment/ak-aws/containerized/variables.tf index 8da131b7e..2c4476808 100644 --- a/ak-deployment/ak-aws/containerized/variables.tf +++ b/ak-deployment/ak-aws/containerized/variables.tf @@ -374,9 +374,17 @@ variable "agent_runner_command" { # --- Agent Runner Auto Scaling --- variable "enable_agent_runner_autoscaling" { - type = bool - description = "Enable SQS-based autoscaling for Agent Runner ECS service. Requires enable_queue_mode = true." - default = false + type = bool + default = false + + validation { + condition = ( + !var.enable_agent_runner_autoscaling || + var.enable_queue_mode + ) + + error_message = "enable_agent_runner_autoscaling requires enable_queue_mode = true." + } } variable "agent_runner_min_count" { diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/akagentrunner.py b/ak-py/src/agentkernel/deployment/aws/containerized/akagentrunner.py index aaf6aebad..52b176c61 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/akagentrunner.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/akagentrunner.py @@ -29,10 +29,6 @@ class ECSAgentRunner: _chat_service: ChatService | None = None _config = AKConfig.get() - # ------------------------------------------------------------------ - # Public entrypoint - # ------------------------------------------------------------------ - @classmethod def run(cls) -> None: """ @@ -55,10 +51,6 @@ def run(cls) -> None: ) poller.run() # blocks forever - # ------------------------------------------------------------------ - # Message processing (same logic as ServerlessAgentRunner) - # ------------------------------------------------------------------ - @classmethod def _get_chat_service(cls) -> ChatService: if cls._chat_service is None: diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/akrestservice.py b/ak-py/src/agentkernel/deployment/aws/containerized/akrestservice.py index 2524468d9..7f4c82a72 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/akrestservice.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/akrestservice.py @@ -55,9 +55,6 @@ class ECSRESTService: _response_store = None _websocket_handler = None - # ------------------------------------------------------------------ - # Public entrypoint - # ------------------------------------------------------------------ @classmethod def run(cls) -> None: @@ -85,16 +82,12 @@ def run(cls) -> None: t2.start() cls._log.info("ECSRESTService: Thread 2 (output-queue poller) started") - # Use queue-aware request handler that bypasses ChatService - # This directly enqueues to SQS without agent validation + from .ecs_queue_handler import ECSQueueRequestHandler cls._log.info("ECSRESTService: starting REST API with queue-aware handler (Thread 1)") RESTAPI.run(handlers=[ECSQueueRequestHandler()]) - # ------------------------------------------------------------------ - # Lazy initialisers - # ------------------------------------------------------------------ @classmethod def _get_response_store(cls): @@ -114,10 +107,6 @@ def _get_websocket_handler(cls) -> WebSocketHandler: ) return cls._websocket_handler - # ------------------------------------------------------------------ - # Output-queue message handlers (Thread 2) - # These are public so callers can also invoke them directly if needed. - # ------------------------------------------------------------------ @classmethod def process_output_message(cls, record: Dict[str, Any]) -> None: @@ -187,9 +176,6 @@ def on_permanent_failure(cls, record: Dict[str, Any]) -> None: except Exception: cls._log.exception("Failed to handle permanent-failure output message") - # ------------------------------------------------------------------ - # Internal helpers - # ------------------------------------------------------------------ @classmethod def _construct_message_for_store(cls, record: Dict[str, Any], body: Any = None) -> Dict[str, Any]: diff --git a/examples/aws-containerized/openai-dynamodb-scalable/app_agent_runner.py b/examples/aws-containerized/openai-dynamodb-scalable/app_agent_runner.py index 774c8422a..aa6306ff3 100644 --- a/examples/aws-containerized/openai-dynamodb-scalable/app_agent_runner.py +++ b/examples/aws-containerized/openai-dynamodb-scalable/app_agent_runner.py @@ -1,34 +1,3 @@ -# from agentkernel.deployment.aws.containerized import ECSAgentRunner -# from agentkernel.openai import OpenAIModule -# from agents import Agent - -# math_agent = Agent( -# name="math", -# handoff_description="Specialist agent for math questions", -# instructions="You provide help with math problems. Explain your reasoning at each step and include examples. \ -# If prompted for anything else you refuse to answer.", -# ) - -# history_agent = Agent( -# name="history", -# handoff_description="Specialist agent for historical questions", -# instructions="You provide assistance with historical queries. Explain important events and context clearly.", -# ) - -# triage_agent = Agent( -# name="triage", -# instructions="You determine which agent to use based on the user's question.", -# handoffs=[history_agent, math_agent], -# ) - -# OpenAIModule([triage_agent, math_agent, history_agent]) - -# # Agent Runner entrypoint - polls Input Queue, runs agent, sends to Output Queue. -# handler = ECSAgentRunner.run - -# if __name__ == "__main__": -# handler() - import logging import signal import sys diff --git a/examples/aws-containerized/openai-dynamodb-scalable/build.sh b/examples/aws-containerized/openai-dynamodb-scalable/build.sh index 09cc68e4e..82b2ab67d 100755 --- a/examples/aws-containerized/openai-dynamodb-scalable/build.sh +++ b/examples/aws-containerized/openai-dynamodb-scalable/build.sh @@ -1,4 +1,5 @@ #!/bin/bash +set -e set -euo pipefail if command -v pyenv >/dev/null 2>&1; then @@ -10,7 +11,6 @@ fi if [[ ${1-} != "local" ]]; then uv sync --all-extras else - # For local development of agentkernel, you can force reinstall from local dist uv sync --find-links ../../../ak-py/dist --all-extras - uv pip install --force-reinstall --find-links ../../../ak-py/dist agentkernel[api,openai,aws,test] || true + uv pip install --force-reinstall --find-links ../../../ak-py/dist agentkernel[api,openai,aws,test] fi \ No newline at end of file From fd84cc31e99c51765ec35e715b2b07a04b1ed2ea Mon Sep 17 00:00:00 2001 From: yl-nuwan Date: Mon, 15 Jun 2026 08:37:36 +0530 Subject: [PATCH 30/90] re enabled the cache --- .../workflows/integration-test-weekly.yaml | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/integration-test-weekly.yaml b/.github/workflows/integration-test-weekly.yaml index fc9deddc1..a2d01dabe 100644 --- a/.github/workflows/integration-test-weekly.yaml +++ b/.github/workflows/integration-test-weekly.yaml @@ -58,11 +58,11 @@ jobs: working-directory: ak-py run: ./build.sh - # - name: Cache ak-py build - # uses: actions/cache/save@v4 - # with: - # path: ak-py/dist - # key: ak-py-${{ github.sha }} + - name: Cache ak-py build + uses: actions/cache/save@v4 + with: + path: ak-py/dist + key: ak-py-${{ github.sha }} get-base-outputs: needs: [setup, build-ak-py] @@ -123,11 +123,11 @@ jobs: - name: Install dependencies run: pip install uv pyyaml - # - name: Restore ak-py build - # uses: actions/cache/restore@v4 - # with: - # path: ak-py/dist - # key: ak-py-${{ github.sha }} + - name: Restore ak-py build + uses: actions/cache/restore@v4 + with: + path: ak-py/dist + key: ak-py-${{ github.sha }} - name: Configure AWS credentials if: matrix.type == 'aws-containerized' || matrix.type == 'aws-serverless' @@ -282,11 +282,11 @@ jobs: - name: Install dependencies run: pip install uv pyyaml - # - name: Restore ak-py build - # uses: actions/cache/restore@v4 - # with: - # path: ak-py/dist - # key: ak-py-${{ github.sha }} + - name: Restore ak-py build + uses: actions/cache/restore@v4 + with: + path: ak-py/dist + key: ak-py-${{ github.sha }} - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v4 From 85dc2ce9922ef0c57c8bb1eb608db832b0234598 Mon Sep 17 00:00:00 2001 From: yl-nuwan Date: Mon, 15 Jun 2026 08:42:21 +0530 Subject: [PATCH 31/90] linted --- .../aws/containerized/akrestservice.py | 5 - examples/api/a2a/multi/uv.lock | 10 +- examples/api/adk/uv.lock | 10 +- examples/api/gmail/uv.lock | 10 +- examples/api/hooks/uv.lock | 10 +- examples/api/instagram/uv.lock | 10 +- examples/api/mcp/multi/uv.lock | 487 +++++++++++++----- examples/api/messenger/uv.lock | 10 +- examples/api/multimodal/adk/uv.lock | 15 +- examples/api/multimodal/crewai/uv.lock | 15 +- examples/api/multimodal/dynamodb/uv.lock | 15 +- examples/api/multimodal/langgraph/uv.lock | 15 +- examples/api/multimodal/openai/uv.lock | 15 +- examples/api/multimodal/redis/uv.lock | 15 +- examples/api/multimodal/smolagents/uv.lock | 22 +- examples/api/openai/uv.lock | 10 +- examples/api/slack/uv.lock | 10 +- examples/api/teams/uv.lock | 10 +- examples/api/telegram/uv.lock | 10 +- examples/api/whatsapp/uv.lock | 10 +- examples/aws-containerized/adk/uv.lock | 10 +- .../aws-containerized/crewai-auth/uv.lock | 8 +- examples/aws-containerized/crewai/uv.lock | 10 +- examples/aws-containerized/mcp/multi/uv.lock | 79 ++- .../app_agent_runner.py | 2 +- .../aws-containerized/openai-dynamodb/uv.lock | 10 +- examples/aws-serverless/adk/uv.lock | 10 +- examples/aws-serverless/crewai/uv.lock | 10 +- examples/aws-serverless/langgraph/uv.lock | 10 +- examples/aws-serverless/openai-auth/uv.lock | 10 +- examples/aws-serverless/openai/uv.lock | 10 +- .../aws-serverless/scalable-openai/uv.lock | 10 +- .../aws-serverless/websocket-openai/uv.lock | 22 +- .../azure-containerized/openai-cosmos/uv.lock | 8 +- examples/azure-serverless/openai/uv.lock | 10 +- examples/cli/adk/uv.lock | 10 +- examples/cli/crewai/uv.lock | 10 +- examples/cli/guardrail/bedrock/uv.lock | 10 +- examples/cli/guardrail/openai/uv.lock | 10 +- examples/cli/guardrail/walledai/uv.lock | 10 +- examples/cli/langgraph/uv.lock | 10 +- examples/cli/multi/uv.lock | 10 +- examples/cli/openai-dynamic/uv.lock | 10 +- examples/cli/openai/uv.lock | 10 +- examples/cli/smolagents/uv.lock | 21 +- examples/containerized/openai/uv.lock | 10 +- .../gcp-containerized/openai-auth/uv.lock | 10 +- examples/gcp-containerized/openai/uv.lock | 195 ++++++- examples/gcp-serverless/openai-auth/uv.lock | 10 +- .../gcp-serverless/openai-firestore/uv.lock | 195 ++++++- examples/gcp-serverless/openai/uv.lock | 10 +- examples/memory/cosmos/uv.lock | 8 +- examples/memory/dynamodb/uv.lock | 10 +- examples/memory/key-value-cache/uv.lock | 10 +- examples/memory/redis/uv.lock | 10 +- 55 files changed, 1035 insertions(+), 477 deletions(-) diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/akrestservice.py b/ak-py/src/agentkernel/deployment/aws/containerized/akrestservice.py index 7f4c82a72..37e7b7c4d 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/akrestservice.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/akrestservice.py @@ -55,7 +55,6 @@ class ECSRESTService: _response_store = None _websocket_handler = None - @classmethod def run(cls) -> None: """ @@ -82,13 +81,11 @@ def run(cls) -> None: t2.start() cls._log.info("ECSRESTService: Thread 2 (output-queue poller) started") - from .ecs_queue_handler import ECSQueueRequestHandler cls._log.info("ECSRESTService: starting REST API with queue-aware handler (Thread 1)") RESTAPI.run(handlers=[ECSQueueRequestHandler()]) - @classmethod def _get_response_store(cls): if cls._response_store is None: @@ -107,7 +104,6 @@ def _get_websocket_handler(cls) -> WebSocketHandler: ) return cls._websocket_handler - @classmethod def process_output_message(cls, record: Dict[str, Any]) -> None: """ @@ -176,7 +172,6 @@ def on_permanent_failure(cls, record: Dict[str, Any]) -> None: except Exception: cls._log.exception("Failed to handle permanent-failure output message") - @classmethod def _construct_message_for_store(cls, record: Dict[str, Any], body: Any = None) -> Dict[str, Any]: """ diff --git a/examples/api/a2a/multi/uv.lock b/examples/api/a2a/multi/uv.lock index 38348d673..9cba2d1e8 100644 --- a/examples/api/a2a/multi/uv.lock +++ b/examples/api/a2a/multi/uv.lock @@ -31,7 +31,7 @@ http-server = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -40,9 +40,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -185,11 +185,11 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "agentkernel", extras = ["cli", "crewai", "openai", "a2a"], specifier = ">=0.4.0" }] +requires-dist = [{ name = "agentkernel", extras = ["cli", "crewai", "openai", "a2a"], specifier = ">=0.5.1" }] [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "isort", specifier = ">=5.0.0" }, { name = "mypy", specifier = ">=1.0.0" }, diff --git a/examples/api/adk/uv.lock b/examples/api/adk/uv.lock index 74cb5f441..580c9cbfb 100644 --- a/examples/api/adk/uv.lock +++ b/examples/api/adk/uv.lock @@ -18,7 +18,7 @@ wheels = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -27,9 +27,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -472,11 +472,11 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "agentkernel", extras = ["adk", "api"], specifier = ">=0.4.0" }] +requires-dist = [{ name = "agentkernel", extras = ["adk", "api"], specifier = ">=0.5.1" }] [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "isort", specifier = ">=5.0.0" }, { name = "mypy", specifier = ">=1.0.0" }, diff --git a/examples/api/gmail/uv.lock b/examples/api/gmail/uv.lock index e12b4eae4..50737f444 100644 --- a/examples/api/gmail/uv.lock +++ b/examples/api/gmail/uv.lock @@ -9,7 +9,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -18,9 +18,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -921,11 +921,11 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "agentkernel", extras = ["api", "openai", "gmail"], specifier = ">=0.4.0" }] +requires-dist = [{ name = "agentkernel", extras = ["api", "openai", "gmail"], specifier = ">=0.5.1" }] [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "isort", specifier = ">=5.0.0" }, { name = "mypy", specifier = ">=1.0.0" }, diff --git a/examples/api/hooks/uv.lock b/examples/api/hooks/uv.lock index 9f2882dcd..e9432febf 100644 --- a/examples/api/hooks/uv.lock +++ b/examples/api/hooks/uv.lock @@ -9,7 +9,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -18,9 +18,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -2007,11 +2007,11 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "agentkernel", extras = ["openai", "api"], specifier = ">=0.4.0" }] +requires-dist = [{ name = "agentkernel", extras = ["openai", "api"], specifier = ">=0.5.1" }] [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "isort", specifier = ">=5.0.0" }, { name = "mypy", specifier = ">=1.0.0" }, diff --git a/examples/api/instagram/uv.lock b/examples/api/instagram/uv.lock index d36afa566..e0f8a16f0 100644 --- a/examples/api/instagram/uv.lock +++ b/examples/api/instagram/uv.lock @@ -9,7 +9,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -18,9 +18,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -205,11 +205,11 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "agentkernel", extras = ["api", "openai", "instagram"], specifier = ">=0.4.0" }] +requires-dist = [{ name = "agentkernel", extras = ["api", "openai", "instagram"], specifier = ">=0.5.1" }] [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "isort", specifier = ">=5.0.0" }, { name = "mypy", specifier = ">=1.0.0" }, diff --git a/examples/api/mcp/multi/uv.lock b/examples/api/mcp/multi/uv.lock index f9684ff1b..124876a27 100644 --- a/examples/api/mcp/multi/uv.lock +++ b/examples/api/mcp/multi/uv.lock @@ -8,7 +8,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -17,9 +17,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -167,11 +167,11 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "agentkernel", extras = ["api", "crewai", "openai", "mcp"], specifier = ">=0.4.0" }] +requires-dist = [{ name = "agentkernel", extras = ["api", "crewai", "openai", "mcp"], specifier = ">=0.5.1" }] [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "isort", specifier = ">=5.0.0" }, { name = "mypy", specifier = ">=1.0.0" }, @@ -275,6 +275,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a9/cf/45fb5261ece3e6b9817d3d82b2f343a505fd58674a92577923bc500bd1aa/bcrypt-4.3.0-cp39-abi3-win_amd64.whl", hash = "sha256:e53e074b120f2877a35cc6c736b8eb161377caae8925c17688bd46ba56daaa5b", size = 152799, upload-time = "2025-02-28T01:23:53.139Z" }, ] +[[package]] +name = "beartype" +version = "0.22.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/94/1009e248bbfbab11397abca7193bea6626806be9a327d399810d523a07cb/beartype-0.22.9.tar.gz", hash = "sha256:8f82b54aa723a2848a56008d18875f91c1db02c32ef6a62319a002e3e25a975f", size = 1608866, upload-time = "2025-12-13T06:50:30.72Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl", hash = "sha256:d16c9bbc61ea14637596c5f6fbff2ee99cbe3573e46a716401734ef50c3060c2", size = 1333658, upload-time = "2025-12-13T06:50:28.266Z" }, +] + [[package]] name = "black" version = "25.9.0" @@ -355,6 +364,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-py3-none-any.whl", hash = "sha256:7145f0b5061ba90a1500d60bd1b13ca0a8a4cebdd0cc16ed8adf1c0e739f43b4", size = 23382, upload-time = "2025-08-01T21:27:07.844Z" }, ] +[[package]] +name = "burner-redis" +version = "0.1.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/89/54706febafc135095b2a9d797cfbd4eed2ab1ad7819808b99b587020471b/burner_redis-0.1.7.tar.gz", hash = "sha256:7474ff092669fd11ef765411572cdafcc3d89b8054aef4ca0617be6d6be4c680", size = 638644, upload-time = "2026-05-08T15:01:42.961Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/5d/198bd1d22e504b3034353430703afbdb3efe6e25cb90bf52d896e1d266a7/burner_redis-0.1.7-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:f80c866996e0455d584eb3c0f3b067e411c632fb0519eab454e0968edf01e62c", size = 1288888, upload-time = "2026-05-08T15:01:26.103Z" }, + { url = "https://files.pythonhosted.org/packages/2f/4e/ce5c91b884ac37fcd380756402536f8810964014097950900517ce8bd30c/burner_redis-0.1.7-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:a3d9569a376b690fb5876d454e4904443332dc3ad5c0057e149fc2ad220bf599", size = 1234282, upload-time = "2026-05-08T15:01:28.286Z" }, + { url = "https://files.pythonhosted.org/packages/6f/c0/31c25cc88143eac2dddcc394151a0db627923d44c94376a83768552c9f13/burner_redis-0.1.7-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20eba1917e3bca9eea5957d5700ff8defcb5a209e57a7841d005549aa0151f44", size = 1337341, upload-time = "2026-05-08T15:01:30.397Z" }, + { url = "https://files.pythonhosted.org/packages/e1/32/95cfa1833316ca2b6b2e58150a4900bc1ad256043cdd36198f1887618ccc/burner_redis-0.1.7-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39111467059b8a28f15ea061d2414ec25c3e57c65759983f90f4d358e7d6a72d", size = 1366800, upload-time = "2026-05-08T15:01:32.891Z" }, + { url = "https://files.pythonhosted.org/packages/34/ad/93c3916f053f89b7b5760da5bf855cd78b7885d480f9cfcc64f3732c1dc2/burner_redis-0.1.7-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9b5adfe99aeb8407f468078f3769b2a63e9168fea12f7709df5d2a3b152706e4", size = 1538160, upload-time = "2026-05-08T15:01:34.667Z" }, + { url = "https://files.pythonhosted.org/packages/5c/b9/19bae42cb124932d71168bc8e5bcb1da33aa62b908e5e632b3d298d7cb15/burner_redis-0.1.7-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:591a9d20685f9d6d22bf0c863b50b12dfcf328b06111b3f62c33cd3185d48ce0", size = 1591491, upload-time = "2026-05-08T15:01:36.708Z" }, + { url = "https://files.pythonhosted.org/packages/f5/30/207f47f406619a5b564355d2946c3171f84231a28b800709b5645b06a5ae/burner_redis-0.1.7-cp310-abi3-win_amd64.whl", hash = "sha256:f6cf4ac666766b32fd63940aad0c120847905fd3102c17e5b6b305f91a21d079", size = 1117564, upload-time = "2026-05-08T15:01:39.221Z" }, + { url = "https://files.pythonhosted.org/packages/76/6f/e9beaf46c5e9fd10dfcdb889ebf7d3aa85142c650c0ab17ab284194f58e1/burner_redis-0.1.7-cp310-abi3-win_arm64.whl", hash = "sha256:458f88feeddfb40a586cc3fcbd8e9384bbdfd2a4512a695af4900e06052570d4", size = 1040407, upload-time = "2026-05-08T15:01:41.235Z" }, +] + [[package]] name = "cachetools" version = "5.5.2" @@ -553,6 +578,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ae/8a/c4bb04426d608be4a3171efa2e233d2c59a5c8937850c10d098e126df18e/cloudpathlib-0.23.0-py3-none-any.whl", hash = "sha256:8520b3b01468fee77de37ab5d50b1b524ea6b4a8731c35d1b7407ac0cd716002", size = 62755, upload-time = "2025-10-07T22:47:54.905Z" }, ] +[[package]] +name = "cloudpickle" +version = "3.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/27/fb/576f067976d320f5f0114a8d9fa1215425441bb35627b1993e5afd8111e5/cloudpickle-3.1.2.tar.gz", hash = "sha256:7fda9eb655c9c230dab534f1983763de5835249750e85fbcef43aaa30a9a2414", size = 22330, upload-time = "2025-11-03T09:25:26.604Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl", hash = "sha256:9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a", size = 22228, upload-time = "2025-11-03T09:25:25.534Z" }, +] + [[package]] name = "colorama" version = "0.4.6" @@ -688,6 +722,14 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cd/65/7f9d2ee674fd517126668f240d02656bbae2c5e307ec2ed5fd9a32f1bb89/crewai-0.177.0-py3-none-any.whl", hash = "sha256:4718335f0c8236ef42740166fe86f9636a9cf628eeea87205d85599ed1b771b4", size = 418731, upload-time = "2025-09-04T00:26:59.34Z" }, ] +[[package]] +name = "cronsim" +version = "2.7" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/1a/02f105147f7f2e06ed4f734ff5a6439590bb275a53dd91fc73df6312298a/cronsim-2.7-py3-none-any.whl", hash = "sha256:1e1431fa08c51dc7f72e67e571c7c7a09af26420169b607badd4ca9677ffad1e", size = 14213, upload-time = "2025-10-21T16:38:20.431Z" }, +] + [[package]] name = "cryptography" version = "45.0.7" @@ -725,17 +767,17 @@ wheels = [ [[package]] name = "cyclopts" -version = "3.24.0" +version = "4.18.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, - { name = "docstring-parser", marker = "python_full_version < '4'" }, + { name = "docstring-parser" }, { name = "rich" }, { name = "rich-rst" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/30/ca/7782da3b03242d5f0a16c20371dff99d4bd1fedafe26bc48ff82e42be8c9/cyclopts-3.24.0.tar.gz", hash = "sha256:de6964a041dfb3c57bf043b41e68c43548227a17de1bad246e3a0bfc5c4b7417", size = 76131, upload-time = "2025-09-08T15:40:57.75Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/19/5c438b428b3dca208eb920804dc16aeb3ca1e85d6163d17e8fb0785ead19/cyclopts-4.18.0.tar.gz", hash = "sha256:fb7b730f21932e0784f7e54462df0447aaa1fbf034d65b605bd8a25dce58b188", size = 182821, upload-time = "2026-06-11T19:55:05.326Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/8b/2c95f0645c6f40211896375e6fa51f504b8ccb29c21f6ae661fe87ab044e/cyclopts-3.24.0-py3-none-any.whl", hash = "sha256:809d04cde9108617106091140c3964ee6fceb33cecdd537f7ffa360bde13ed71", size = 86154, upload-time = "2025-09-08T15:40:56.41Z" }, + { url = "https://files.pythonhosted.org/packages/5d/9f/b67f14c6b686ca90d317c0358f1a52ae171f43f83c808683fae3ba0b1f90/cyclopts-4.18.0-py3-none-any.whl", hash = "sha256:18ba2912e48e890a97ecc8a05c9beddf30a407b43f4e14cccfd40efddc41f029", size = 221216, upload-time = "2026-06-11T19:55:03.773Z" }, ] [[package]] @@ -960,6 +1002,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, ] +[[package]] +name = "fakeredis" +version = "2.34.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "redis" }, + { name = "sortedcontainers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/11/40/fd09efa66205eb32253d2b2ebc63537281384d2040f0a88bcd2289e120e4/fakeredis-2.34.1.tar.gz", hash = "sha256:4ff55606982972eecce3ab410e03d746c11fe5deda6381d913641fbd8865ea9b", size = 177315, upload-time = "2026-02-25T13:17:51.315Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/b5/82f89307d0d769cd9bf46a54fb9136be08e4e57c5570ae421db4c9a2ba62/fakeredis-2.34.1-py3-none-any.whl", hash = "sha256:0107ec99d48913e7eec2a5e3e2403d1bd5f8aa6489d1a634571b975289c48f12", size = 122160, upload-time = "2026-02-25T13:17:49.701Z" }, +] + +[package.optional-dependencies] +lua = [ + { name = "lupa" }, +] + [[package]] name = "fastapi" version = "0.118.2" @@ -976,24 +1036,32 @@ wheels = [ [[package]] name = "fastmcp" -version = "2.12.4" +version = "2.14.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "authlib" }, { name = "cyclopts" }, { name = "exceptiongroup" }, + { name = "fakeredis", extra = ["lua"] }, { name = "httpx" }, + { name = "jsonref" }, + { name = "jsonschema-path" }, { name = "mcp" }, - { name = "openapi-core" }, { name = "openapi-pydantic" }, + { name = "packaging" }, + { name = "platformdirs" }, + { name = "py-key-value-aio", extra = ["disk", "keyring", "memory"] }, { name = "pydantic", extra = ["email"] }, + { name = "pydocket" }, { name = "pyperclip" }, { name = "python-dotenv" }, { name = "rich" }, + { name = "uvicorn" }, + { name = "websockets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a8/b2/57845353a9bc63002995a982e66f3d0be4ec761e7bcb89e7d0638518d42a/fastmcp-2.12.4.tar.gz", hash = "sha256:b55fe89537038f19d0f4476544f9ca5ac171033f61811cc8f12bdeadcbea5016", size = 7167745, upload-time = "2025-09-26T16:43:27.71Z" } +sdist = { url = "https://files.pythonhosted.org/packages/41/c7/6b5202e427b87b14a34ef8b65eba0f5c60a8fbc71919e98a13d646b4a641/fastmcp-2.14.7.tar.gz", hash = "sha256:0d2e83b9d1c5fe0af32036979de3bb99a38f05dc1476d60f1b3259168e139cd0", size = 8296810, upload-time = "2026-04-13T01:12:53.098Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/c7/562ff39f25de27caec01e4c1e88cbb5fcae5160802ba3d90be33165df24f/fastmcp-2.12.4-py3-none-any.whl", hash = "sha256:56188fbbc1a9df58c537063f25958c57b5c4d715f73e395c41b51550b247d140", size = 329090, upload-time = "2025-09-26T16:43:25.314Z" }, + { url = "https://files.pythonhosted.org/packages/a6/f1/56310847b0bdd5b14c2af8f0a39082af078deff60d0dc43efef4e366a83e/fastmcp-2.14.7-py3-none-any.whl", hash = "sha256:e081a5222a6d302a148871d89fd714b13130cee6594f53a3e1afd7518f0096c0", size = 418162, upload-time = "2026-04-13T01:12:51.325Z" }, ] [[package]] @@ -1422,21 +1490,45 @@ wheels = [ ] [[package]] -name = "isodate" -version = "0.7.2" +name = "isort" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/63/53/4f3c058e3bace40282876f9b553343376ee687f3c35a525dc79dbd450f88/isort-7.0.0.tar.gz", hash = "sha256:5513527951aadb3ac4292a41a16cbc50dd1642432f5e8c20057d414bdafb4187", size = 805049, upload-time = "2025-10-11T13:30:59.107Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/ed/e3705d6d02b4f7aea715a353c8ce193efd0b5db13e204df895d38734c244/isort-7.0.0-py3-none-any.whl", hash = "sha256:1bcabac8bc3c36c7fb7b98a76c8abb18e0f841a3ba81decac7691008592499c1", size = 94672, upload-time = "2025-10-11T13:30:57.665Z" }, +] + +[[package]] +name = "jaraco-classes" +version = "3.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/4d/e940025e2ce31a8ce1202635910747e5a87cc3a6a6bb2d00973375014749/isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6", size = 29705, upload-time = "2024-10-08T23:04:11.5Z" } +dependencies = [ + { name = "more-itertools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/c0/ed4a27bc5571b99e3cff68f8a9fa5b56ff7df1c2251cc715a652ddd26402/jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", size = 11780, upload-time = "2024-03-31T07:27:36.643Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320, upload-time = "2024-10-08T23:04:09.501Z" }, + { url = "https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790", size = 6777, upload-time = "2024-03-31T07:27:34.792Z" }, ] [[package]] -name = "isort" -version = "7.0.0" +name = "jaraco-context" +version = "6.1.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/63/53/4f3c058e3bace40282876f9b553343376ee687f3c35a525dc79dbd450f88/isort-7.0.0.tar.gz", hash = "sha256:5513527951aadb3ac4292a41a16cbc50dd1642432f5e8c20057d414bdafb4187", size = 805049, upload-time = "2025-10-11T13:30:59.107Z" } +sdist = { url = "https://files.pythonhosted.org/packages/af/50/4763cd07e722bb6285316d390a164bc7e479db9d90daa769f22578f698b4/jaraco_context-6.1.2.tar.gz", hash = "sha256:f1a6c9d391e661cc5b8d39861ff077a7dc24dc23833ccee564b234b81c82dfe3", size = 16801, upload-time = "2026-03-20T22:13:33.922Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/ed/e3705d6d02b4f7aea715a353c8ce193efd0b5db13e204df895d38734c244/isort-7.0.0-py3-none-any.whl", hash = "sha256:1bcabac8bc3c36c7fb7b98a76c8abb18e0f841a3ba81decac7691008592499c1", size = 94672, upload-time = "2025-10-11T13:30:57.665Z" }, + { url = "https://files.pythonhosted.org/packages/f2/58/bc8954bda5fcda97bd7c19be11b85f91973d67a706ed4a3aec33e7de22db/jaraco_context-6.1.2-py3-none-any.whl", hash = "sha256:bf8150b79a2d5d91ae48629d8b427a8f7ba0e1097dd6202a9059f29a36379535", size = 7871, upload-time = "2026-03-20T22:13:32.808Z" }, +] + +[[package]] +name = "jaraco-functools" +version = "4.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "more-itertools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/36/cf/ea4ef2920830dea3f5ab2ea4da6fb67724e6dca80ee2553788c3607243d0/jaraco_functools-4.5.0.tar.gz", hash = "sha256:3bb5665ea4a020cf78a7040e89154c77edadb3ca74f366479669c5999aa70b03", size = 20272, upload-time = "2026-05-15T21:34:10.025Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/9a/982e48afcffcd727a9144506720ffd4224b6b7e355c98641866f38b7c043/jaraco_functools-4.5.0-py3-none-any.whl", hash = "sha256:79ce39246eddbde4b3a03b77ea5f0f7878dc669b166a66cf3fa8e266aa3fa2f4", size = 10594, upload-time = "2026-05-15T21:34:08.595Z" }, ] [[package]] @@ -1451,6 +1543,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278, upload-time = "2024-11-11T01:41:40.175Z" }, ] +[[package]] +name = "jeepney" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/6f/357efd7602486741aa73ffc0617fb310a29b588ed0fd69c2399acbb85b0c/jeepney-0.9.0.tar.gz", hash = "sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732", size = 106758, upload-time = "2025-02-27T18:51:01.684Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683", size = 49010, upload-time = "2025-02-27T18:51:00.104Z" }, +] + [[package]] name = "jinja2" version = "3.1.6" @@ -1610,6 +1711,23 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/01/0e/b27cdbaccf30b890c40ed1da9fd4a3593a5cf94dae54fb34f8a4b74fcd3f/jsonschema_specifications-2025.4.1-py3-none-any.whl", hash = "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af", size = 18437, upload-time = "2025-04-23T12:34:05.422Z" }, ] +[[package]] +name = "keyring" +version = "25.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jaraco-classes" }, + { name = "jaraco-context" }, + { name = "jaraco-functools" }, + { name = "jeepney", marker = "sys_platform == 'linux'" }, + { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, + { name = "secretstorage", marker = "sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/4b/674af6ef2f97d56f0ab5153bf0bfa28ccb6c3ed4d1babf4305449668807b/keyring-25.7.0.tar.gz", hash = "sha256:fe01bd85eb3f8fb3dd0405defdeac9a5b4f6f0439edbb3149577f244a2e8245b", size = 63516, upload-time = "2025-11-16T16:26:09.482Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl", hash = "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f", size = 39160, upload-time = "2025-11-16T16:26:08.402Z" }, +] + [[package]] name = "kubernetes" version = "33.1.0" @@ -1807,38 +1925,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ee/8a/d9bc95607846bc82fbe0b98d2592ffb5e036c97a362735ae926e3d519df7/langsmith-0.5.0-py3-none-any.whl", hash = "sha256:a83750cb3dccb33148d4ffe005e3e03080fad13e01671efbb74c9a68813bfef8", size = 273711, upload-time = "2025-12-16T17:35:37.165Z" }, ] -[[package]] -name = "lazy-object-proxy" -version = "1.12.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/08/a2/69df9c6ba6d316cfd81fe2381e464db3e6de5db45f8c43c6a23504abf8cb/lazy_object_proxy-1.12.0.tar.gz", hash = "sha256:1f5a462d92fd0cfb82f1fab28b51bfb209fabbe6aabf7f0d51472c0c124c0c61", size = 43681, upload-time = "2025-08-22T13:50:06.783Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/1b/b5f5bd6bda26f1e15cd3232b223892e4498e34ec70a7f4f11c401ac969f1/lazy_object_proxy-1.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8ee0d6027b760a11cc18281e702c0309dd92da458a74b4c15025d7fc490deede", size = 26746, upload-time = "2025-08-22T13:42:37.572Z" }, - { url = "https://files.pythonhosted.org/packages/55/64/314889b618075c2bfc19293ffa9153ce880ac6153aacfd0a52fcabf21a66/lazy_object_proxy-1.12.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4ab2c584e3cc8be0dfca422e05ad30a9abe3555ce63e9ab7a559f62f8dbc6ff9", size = 71457, upload-time = "2025-08-22T13:42:38.743Z" }, - { url = "https://files.pythonhosted.org/packages/11/53/857fc2827fc1e13fbdfc0ba2629a7d2579645a06192d5461809540b78913/lazy_object_proxy-1.12.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:14e348185adbd03ec17d051e169ec45686dcd840a3779c9d4c10aabe2ca6e1c0", size = 71036, upload-time = "2025-08-22T13:42:40.184Z" }, - { url = "https://files.pythonhosted.org/packages/2b/24/e581ffed864cd33c1b445b5763d617448ebb880f48675fc9de0471a95cbc/lazy_object_proxy-1.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c4fcbe74fb85df8ba7825fa05eddca764138da752904b378f0ae5ab33a36c308", size = 69329, upload-time = "2025-08-22T13:42:41.311Z" }, - { url = "https://files.pythonhosted.org/packages/78/be/15f8f5a0b0b2e668e756a152257d26370132c97f2f1943329b08f057eff0/lazy_object_proxy-1.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:563d2ec8e4d4b68ee7848c5ab4d6057a6d703cb7963b342968bb8758dda33a23", size = 70690, upload-time = "2025-08-22T13:42:42.51Z" }, - { url = "https://files.pythonhosted.org/packages/5d/aa/f02be9bbfb270e13ee608c2b28b8771f20a5f64356c6d9317b20043c6129/lazy_object_proxy-1.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:53c7fd99eb156bbb82cbc5d5188891d8fdd805ba6c1e3b92b90092da2a837073", size = 26563, upload-time = "2025-08-22T13:42:43.685Z" }, - { url = "https://files.pythonhosted.org/packages/f4/26/b74c791008841f8ad896c7f293415136c66cc27e7c7577de4ee68040c110/lazy_object_proxy-1.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:86fd61cb2ba249b9f436d789d1356deae69ad3231dc3c0f17293ac535162672e", size = 26745, upload-time = "2025-08-22T13:42:44.982Z" }, - { url = "https://files.pythonhosted.org/packages/9b/52/641870d309e5d1fb1ea7d462a818ca727e43bfa431d8c34b173eb090348c/lazy_object_proxy-1.12.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:81d1852fb30fab81696f93db1b1e55a5d1ff7940838191062f5f56987d5fcc3e", size = 71537, upload-time = "2025-08-22T13:42:46.141Z" }, - { url = "https://files.pythonhosted.org/packages/47/b6/919118e99d51c5e76e8bf5a27df406884921c0acf2c7b8a3b38d847ab3e9/lazy_object_proxy-1.12.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be9045646d83f6c2664c1330904b245ae2371b5c57a3195e4028aedc9f999655", size = 71141, upload-time = "2025-08-22T13:42:47.375Z" }, - { url = "https://files.pythonhosted.org/packages/e5/47/1d20e626567b41de085cf4d4fb3661a56c159feaa73c825917b3b4d4f806/lazy_object_proxy-1.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:67f07ab742f1adfb3966c40f630baaa7902be4222a17941f3d85fd1dae5565ff", size = 69449, upload-time = "2025-08-22T13:42:48.49Z" }, - { url = "https://files.pythonhosted.org/packages/58/8d/25c20ff1a1a8426d9af2d0b6f29f6388005fc8cd10d6ee71f48bff86fdd0/lazy_object_proxy-1.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:75ba769017b944fcacbf6a80c18b2761a1795b03f8899acdad1f1c39db4409be", size = 70744, upload-time = "2025-08-22T13:42:49.608Z" }, - { url = "https://files.pythonhosted.org/packages/c0/67/8ec9abe15c4f8a4bcc6e65160a2c667240d025cbb6591b879bea55625263/lazy_object_proxy-1.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:7b22c2bbfb155706b928ac4d74c1a63ac8552a55ba7fff4445155523ea4067e1", size = 26568, upload-time = "2025-08-22T13:42:57.719Z" }, - { url = "https://files.pythonhosted.org/packages/23/12/cd2235463f3469fd6c62d41d92b7f120e8134f76e52421413a0ad16d493e/lazy_object_proxy-1.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4a79b909aa16bde8ae606f06e6bbc9d3219d2e57fb3e0076e17879072b742c65", size = 27391, upload-time = "2025-08-22T13:42:50.62Z" }, - { url = "https://files.pythonhosted.org/packages/60/9e/f1c53e39bbebad2e8609c67d0830cc275f694d0ea23d78e8f6db526c12d3/lazy_object_proxy-1.12.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:338ab2f132276203e404951205fe80c3fd59429b3a724e7b662b2eb539bb1be9", size = 80552, upload-time = "2025-08-22T13:42:51.731Z" }, - { url = "https://files.pythonhosted.org/packages/4c/b6/6c513693448dcb317d9d8c91d91f47addc09553613379e504435b4cc8b3e/lazy_object_proxy-1.12.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c40b3c9faee2e32bfce0df4ae63f4e73529766893258eca78548bac801c8f66", size = 82857, upload-time = "2025-08-22T13:42:53.225Z" }, - { url = "https://files.pythonhosted.org/packages/12/1c/d9c4aaa4c75da11eb7c22c43d7c90a53b4fca0e27784a5ab207768debea7/lazy_object_proxy-1.12.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:717484c309df78cedf48396e420fa57fc8a2b1f06ea889df7248fdd156e58847", size = 80833, upload-time = "2025-08-22T13:42:54.391Z" }, - { url = "https://files.pythonhosted.org/packages/0b/ae/29117275aac7d7d78ae4f5a4787f36ff33262499d486ac0bf3e0b97889f6/lazy_object_proxy-1.12.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a6b7ea5ea1ffe15059eb44bcbcb258f97bcb40e139b88152c40d07b1a1dfc9ac", size = 79516, upload-time = "2025-08-22T13:42:55.812Z" }, - { url = "https://files.pythonhosted.org/packages/19/40/b4e48b2c38c69392ae702ae7afa7b6551e0ca5d38263198b7c79de8b3bdf/lazy_object_proxy-1.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:08c465fb5cd23527512f9bd7b4c7ba6cec33e28aad36fbbe46bf7b858f9f3f7f", size = 27656, upload-time = "2025-08-22T13:42:56.793Z" }, - { url = "https://files.pythonhosted.org/packages/ef/3a/277857b51ae419a1574557c0b12e0d06bf327b758ba94cafc664cb1e2f66/lazy_object_proxy-1.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c9defba70ab943f1df98a656247966d7729da2fe9c2d5d85346464bf320820a3", size = 26582, upload-time = "2025-08-22T13:49:49.366Z" }, - { url = "https://files.pythonhosted.org/packages/1a/b6/c5e0fa43535bb9c87880e0ba037cdb1c50e01850b0831e80eb4f4762f270/lazy_object_proxy-1.12.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6763941dbf97eea6b90f5b06eb4da9418cc088fce0e3883f5816090f9afcde4a", size = 71059, upload-time = "2025-08-22T13:49:50.488Z" }, - { url = "https://files.pythonhosted.org/packages/06/8a/7dcad19c685963c652624702f1a968ff10220b16bfcc442257038216bf55/lazy_object_proxy-1.12.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fdc70d81235fc586b9e3d1aeef7d1553259b62ecaae9db2167a5d2550dcc391a", size = 71034, upload-time = "2025-08-22T13:49:54.224Z" }, - { url = "https://files.pythonhosted.org/packages/12/ac/34cbfb433a10e28c7fd830f91c5a348462ba748413cbb950c7f259e67aa7/lazy_object_proxy-1.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0a83c6f7a6b2bfc11ef3ed67f8cbe99f8ff500b05655d8e7df9aab993a6abc95", size = 69529, upload-time = "2025-08-22T13:49:55.29Z" }, - { url = "https://files.pythonhosted.org/packages/6f/6a/11ad7e349307c3ca4c0175db7a77d60ce42a41c60bcb11800aabd6a8acb8/lazy_object_proxy-1.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:256262384ebd2a77b023ad02fbcc9326282bcfd16484d5531154b02bc304f4c5", size = 70391, upload-time = "2025-08-22T13:49:56.35Z" }, - { url = "https://files.pythonhosted.org/packages/59/97/9b410ed8fbc6e79c1ee8b13f8777a80137d4bc189caf2c6202358e66192c/lazy_object_proxy-1.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:7601ec171c7e8584f8ff3f4e440aa2eebf93e854f04639263875b8c2971f819f", size = 26988, upload-time = "2025-08-22T13:49:57.302Z" }, -] - [[package]] name = "litellm" version = "1.74.9" @@ -1861,6 +1947,58 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5f/e4/f1546746049c99c6b8b247e2f34485b9eae36faa9322b84e2a17262e6712/litellm-1.74.9-py3-none-any.whl", hash = "sha256:ab8f8a6e4d8689d3c7c4f9c3bbc7e46212cc3ebc74ddd0f3c0c921bb459c9874", size = 8740449, upload-time = "2025-07-28T16:42:36.8Z" }, ] +[[package]] +name = "lupa" +version = "2.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/a6/0f869fbb07c393f15473b1eefefb7b5bec162fb7481803d040ed4dc46002/lupa-2.8.tar.gz", hash = "sha256:d8022641b9ec8ecf2c5ecbe9f47e5a70e0b87c4b5ae921b92cb02a638e0acd08", size = 6156370, upload-time = "2026-04-15T20:08:30.534Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/09/21/9be4516ddd22f8eadba336d9ba065d17d79108465ae1b7f71424ab99b9d0/lupa-2.8-cp310-abi3-win32.whl", hash = "sha256:c2a5fd15dc62374e1661a55f01744c9ec1c56f291ba4a0749d3af2174556e78f", size = 1594887, upload-time = "2026-04-15T20:05:23.377Z" }, + { url = "https://files.pythonhosted.org/packages/2d/99/1557c9685d7034d9ce8dd2b54c40a26d6deb7c67c1fdb5c801abd1a02c3f/lupa-2.8-cp310-abi3-win_arm64.whl", hash = "sha256:9e304fb1c50cf23fd8882afbe1aa87525ef8a72667bcab3b37b2bbb2bc542269", size = 1371742, upload-time = "2026-04-15T20:05:27.417Z" }, + { url = "https://files.pythonhosted.org/packages/ad/0b/368f2f0bc750b25c69d4563e44f677925ab5dd3d2887f9b0c15465d21a2a/lupa-2.8-cp312-abi3-macosx_10_13_x86_64.whl", hash = "sha256:f4342f4de76ae7ce2ab0672d36003bdb7e1a33252f293b569298ddd792e70e33", size = 1194056, upload-time = "2026-04-15T20:05:55.794Z" }, + { url = "https://files.pythonhosted.org/packages/5b/0f/c89eb8dd36fdea4e50ae3f7f5275bea3b0cc5d4057b8ee7b3bbc78010422/lupa-2.8-cp312-abi3-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:4203fa1659315e939a5304e75001b8cc14234fb3cbb3ed86c049b0cc5d90fcee", size = 1434278, upload-time = "2026-04-15T20:05:57.94Z" }, + { url = "https://files.pythonhosted.org/packages/47/30/c3b4d2cd8733621b404b8a4214e5f852955c4ba632546dc84123bea9ee89/lupa-2.8-cp312-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:81f2d843ce668b653146c007467570210ae44be51dac6926666c51d49536f307", size = 1150068, upload-time = "2026-04-15T20:06:01.04Z" }, + { url = "https://files.pythonhosted.org/packages/8d/d2/bac12c398519efafc6af84be1974edd0d7a4895fb4735b5c8d615d298595/lupa-2.8-cp312-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d3d0cde2c77588d1c60875a4f34f059513476c6e1775351897195b51e0f3df08", size = 1409532, upload-time = "2026-04-15T20:06:03.592Z" }, + { url = "https://files.pythonhosted.org/packages/9c/6a/18b52e11962014026e07813530b0b108ee8bc0a2a13ef0eaea5d41dce023/lupa-2.8-cp312-abi3-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9e0d11b8f3a8dac6413f704fef7161d048bb10c58bdac6cbffa5e60efa56e9a3", size = 1242687, upload-time = "2026-04-15T20:06:06.863Z" }, + { url = "https://files.pythonhosted.org/packages/b3/8e/7fd4eb049875f61429b96780d2eae4700f0e78fe0a52db8edb231b1cd09f/lupa-2.8-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:54cff414f21f8cd8c6be4aae52541f3b9cd39602b59e3a3db9b5c9f9f674ff18", size = 1856038, upload-time = "2026-04-15T20:06:09.358Z" }, + { url = "https://files.pythonhosted.org/packages/e9/f9/37ad9d2773d30f2931890d310a4bdce28d45484206e6f48bc18b0325eabd/lupa-2.8-cp312-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:24b4d8af5558e549b70daf1547f5c1c1d664ecea9fc790f83efe5d75e9a93797", size = 1128982, upload-time = "2026-04-15T20:06:12.312Z" }, + { url = "https://files.pythonhosted.org/packages/57/31/c0fd7984c24844ea79caa45c0235f61a06b38fd69a839f6c62770f8d684a/lupa-2.8-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:ce86dff1ee7f7cf45f5622065ae991949dd7bb1703581cbc58a630137bb7ccf9", size = 1457594, upload-time = "2026-04-15T20:06:15.881Z" }, + { url = "https://files.pythonhosted.org/packages/11/f5/a28e411be30ec1bf0db1eb0c087eebc73be9e7a1adcfe6ac209861ccc446/lupa-2.8-cp312-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:f4d01b2a08c70bbb883a9e082b6b36b89121ed5910b710f1ba11c73295ff4fba", size = 1425721, upload-time = "2026-04-15T20:06:18.009Z" }, + { url = "https://files.pythonhosted.org/packages/ed/c1/359f767c4ae024be30d909fe8a9f0e9af266bad47ce2bd2ed248fb986fcf/lupa-2.8-cp312-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:7f210d5a8353e510ea1199c42cf3cbdd630553bf2bc8fb4c00fea06fdec7c798", size = 1253258, upload-time = "2026-04-15T20:06:21.17Z" }, + { url = "https://files.pythonhosted.org/packages/17/52/473f11790c261fd02bbf318a546fe040e9ec9f677181272fa78d3b4112a4/lupa-2.8-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4f81a02806e7c7ad26d8c6fa222c8bef1b0c1b124347c879be880b41339d41e4", size = 2395272, upload-time = "2026-04-15T20:06:24.137Z" }, + { url = "https://files.pythonhosted.org/packages/94/bf/75c8795655a8836eab6a11a630352c4b7c5dc5c54d075077bc9bffdeee45/lupa-2.8-cp312-abi3-win32.whl", hash = "sha256:360056453a7a4eaa4ac5a204c31a5a014b1eb2ee5490603234d2ba831684f1f2", size = 1606136, upload-time = "2026-04-15T20:06:27.815Z" }, + { url = "https://files.pythonhosted.org/packages/d8/29/11a2cdd612b6f55e506292dfb6ba343216e80a693e7fe3f876ef204ce9c6/lupa-2.8-cp312-abi3-win_arm64.whl", hash = "sha256:1628371c6592a6d5650497a9e31fb2bb3a7e9883c1f301d1111265e484045af9", size = 1364495, upload-time = "2026-04-15T20:06:30.254Z" }, + { url = "https://files.pythonhosted.org/packages/4d/17/fa834b6b09ad17e7df5d0f7715d64877a125a3776ada689751a1f9dc2959/lupa-2.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:450650f91c48c2415b0d59ab3abfcfda3b6efb5b858205f4d4bda8ad141fa529", size = 1190111, upload-time = "2026-04-15T20:06:32.84Z" }, + { url = "https://files.pythonhosted.org/packages/ab/43/45589901b7d1a0e3a9d91d19a311fb6a56924e8571536c3f2212160fd953/lupa-2.8-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27044f3363047f946b3d3aab9157cbd172b3538ada9ec1baef43432bf7d03a78", size = 1812999, upload-time = "2026-04-15T20:06:35.664Z" }, + { url = "https://files.pythonhosted.org/packages/a1/ac/4ade7d15ff5c61758d7943ac6f0a496bf1cc65b6c09f842b52a0702e664c/lupa-2.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8cf4f064a0e5531afce2d7d750120c10c10f9529139af6ca6150d13151034398", size = 2368731, upload-time = "2026-04-15T20:06:37.959Z" }, + { url = "https://files.pythonhosted.org/packages/0c/27/05f950d15b8ab120b39c43588b438ff3ace70c1b1b0225a960393a497483/lupa-2.8-cp312-cp312-win_amd64.whl", hash = "sha256:281bedc5deb92d31e649a3552edd662449365a635904fa4d5cb4509c7245e34e", size = 1941809, upload-time = "2026-04-15T20:06:40.302Z" }, + { url = "https://files.pythonhosted.org/packages/a6/3f/19f83c3a0c84dc8bea8a58e7416dca6a3ede662c33c8d1ec758e5afc754a/lupa-2.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45fc9da0145ecb0083ef5ff9975116cc784bd0258bdc2bd131ba15483ce18398", size = 1201203, upload-time = "2026-04-15T20:06:42.169Z" }, + { url = "https://files.pythonhosted.org/packages/89/0f/a14f0073f09610158038582e230618a48c14da6bd88185289461aa4cb854/lupa-2.8-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:58e18afed57955b41130e269c78f53d4123ab86e236b53816f4cbffa25cb5d30", size = 1806210, upload-time = "2026-04-15T20:06:45.486Z" }, + { url = "https://files.pythonhosted.org/packages/2f/14/48fff156c63a136001a7620878af7d31aa07e66b495ed621e3eddd73c294/lupa-2.8-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fc47f536ac13a79cef47d29a2b205576a22841f042a2bcec1676b95806e7706a", size = 2359005, upload-time = "2026-04-15T20:06:47.819Z" }, + { url = "https://files.pythonhosted.org/packages/fe/18/3ac638ec90edf178242b8a2b2f00f8adae694248c03a26341ef941bb746e/lupa-2.8-cp313-cp313-win_amd64.whl", hash = "sha256:ce9404c661dbac65cc9bed351ad45e797af93d30d70be309a3fa8209ac86d93b", size = 1936754, upload-time = "2026-04-15T20:06:50.448Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ef/5ee5fed6ea7459a671196359ce04bfeeaf26be1dac8ff24bf28e5c7a6e81/lupa-2.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:348c3f8ecabb6324dcbc05c2740d762ef8fcec7b06c79e45262ab97a217684e3", size = 1209388, upload-time = "2026-04-15T20:06:53.022Z" }, + { url = "https://files.pythonhosted.org/packages/6e/b1/67a940d5542cb0384b443fe951b5a83ea9340d1333a733a258fdd1c619ba/lupa-2.8-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:951496471056061598a7d1729a6cdf48d662fec777a9f2d8aa5a1e62fd30e5a5", size = 1826821, upload-time = "2026-04-15T20:06:55.699Z" }, + { url = "https://files.pythonhosted.org/packages/a1/a2/b354e5ba3b911ec50686003dc8897e892b9e8c5c036b33219b03d54c4daf/lupa-2.8-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a591b9947ca347b41a63370e121d6e2b1458fe6dde9ae065029ec10a37f25ff4", size = 2366893, upload-time = "2026-04-15T20:06:58.9Z" }, + { url = "https://files.pythonhosted.org/packages/8e/52/d76066401f29539df5352f70ecded66576f32933b6045cd0bfc56cb770b9/lupa-2.8-cp314-cp314-win_amd64.whl", hash = "sha256:3903c9cf628dae2f56405503247b77a61a3a61bd2dda470e336950c74776d55d", size = 1994716, upload-time = "2026-04-15T20:07:19.194Z" }, + { url = "https://files.pythonhosted.org/packages/c3/bd/3efc437a4361c16d25e66478c50357c9a8e8ecfb718fe749eb9ca3176ef6/lupa-2.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f711a8ab0486b9ac6fdda94a22ddcfbc9f0d4a27e3a8cf1bf79c6e48b33017c1", size = 1251217, upload-time = "2026-04-15T20:07:01.64Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f4/2e9f8ecbaca854bfdf14af8a9b505ec0cbc640377b3b218921594b7563cd/lupa-2.8-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc51250e76367a3e27fcd01dc769b9bfcbbc34f48df48dde53d6af6e75b7eaa5", size = 1814701, upload-time = "2026-04-15T20:07:04.149Z" }, + { url = "https://files.pythonhosted.org/packages/ba/53/4000b1acaa8b1f3827fcff0cfcdff44d3befddda42cab7e685a49689b5a1/lupa-2.8-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f8a22088a552828958603323f0a5c4b3e11e03b75d0bf4c965ef879de9b60a8d", size = 2348414, upload-time = "2026-04-15T20:07:07.285Z" }, + { url = "https://files.pythonhosted.org/packages/d5/78/26ee48d3890cddf03cefb65f433e3492759c0b3c0582180755bddbaab7bd/lupa-2.8-cp314-cp314t-win32.whl", hash = "sha256:4f7c553c1d8cfffbe85d81daef730d12cae4b6002d457542914da0ac8a1145b3", size = 1831611, upload-time = "2026-04-15T20:07:09.752Z" }, + { url = "https://files.pythonhosted.org/packages/3c/d1/4a5cc64a3cad22821ae4c3f7a90456a08ca19457d8354f4abf46ad03c7e8/lupa-2.8-cp314-cp314t-win_amd64.whl", hash = "sha256:d8766aff03a78c80ad2d188a8bdb216de5ec838359cd87e05bbdfa56394a6105", size = 2209250, upload-time = "2026-04-15T20:07:11.906Z" }, + { url = "https://files.pythonhosted.org/packages/37/7c/cdcb654daf668192aaf36b0aeb94f2281dad092aaa5003688691131736ea/lupa-2.8-cp314-cp314t-win_arm64.whl", hash = "sha256:91d622777febda3ab1bed1d45295f2f32a4680c7b3d7caf8c669998ed5c44118", size = 1126735, upload-time = "2026-04-15T20:07:15.434Z" }, + { url = "https://files.pythonhosted.org/packages/1d/44/de1961ad38e17cd326a53c246c7e3b91178ed578f4cf22ffcd5e7e11b041/lupa-2.8-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b036738282a5acd2e71fdddb317c9df8b87c1673aa57f403d05fcc2be8abc4ba", size = 1186020, upload-time = "2026-04-15T20:07:35.017Z" }, + { url = "https://files.pythonhosted.org/packages/13/c2/276f0b9dc8bcc5a8a58af5316dfa0e6f56be3613dd6dbcc8d3d2cb6559ba/lupa-2.8-cp39-abi3-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:ac6b6e8d0e617e26a98cbb44880bcd75de5d32b3ad7b3b3793583909292b47ed", size = 1468944, upload-time = "2026-04-15T20:07:37.782Z" }, + { url = "https://files.pythonhosted.org/packages/63/38/52934e52a5180dc6425d20284d004fe4b27a4f9171a82dc99fb67af250bf/lupa-2.8-cp39-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ba3a7dd839f90c3d2e53bebe3c192b1f3f9fd720a6781256405123211fd0dce6", size = 1172998, upload-time = "2026-04-15T20:07:40.812Z" }, + { url = "https://files.pythonhosted.org/packages/c7/82/76b3809bd0839d9b3b4ec58d06591e08f17337b6d9576877cb9d48b34e94/lupa-2.8-cp39-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d7edb13a7a5250b5c6c22d1495d9e842b5c9fc5081c8fe6b5efe2112fe3e41f9", size = 1449975, upload-time = "2026-04-15T20:07:44.262Z" }, + { url = "https://files.pythonhosted.org/packages/16/07/2f89d54f747c67c23b4b9ae4aa8c8dd06bb409155dedcf406157f2736b66/lupa-2.8-cp39-abi3-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:891f72e0bffbed1e4175f975aeb2a083956586a100066525e1be485f617f7b25", size = 1281944, upload-time = "2026-04-15T20:07:46.458Z" }, + { url = "https://files.pythonhosted.org/packages/e7/bd/7375d2b0fcae79d806baf52a76f26c96964593f58e1372d13ae5ac09c676/lupa-2.8-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a295f87b5b7ebbfd5191932e8cb0e51df3c7769101ac6b6c7d7c9fb27bfd1307", size = 1910455, upload-time = "2026-04-15T20:07:49.75Z" }, + { url = "https://files.pythonhosted.org/packages/8b/0c/8abb3bc0e08b311fc01db05b6e9f9ff31a8f65e4fc3f0aeb05cfef75c8ac/lupa-2.8-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:4fe5d7a810b64ea8511eb885fc8cdde042ee5ff7b7d08ae78f32449756acb177", size = 1155548, upload-time = "2026-04-15T20:07:52.657Z" }, + { url = "https://files.pythonhosted.org/packages/80/2e/9eeecd3f493099721c1d3f31beeca23a4237db1a54223684df4dc96aa1bd/lupa-2.8-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:bfc470012ef66ad064c7bd77416af03a3452ef630b04b9012595ea13f2e54518", size = 1489232, upload-time = "2026-04-15T20:07:54.92Z" }, + { url = "https://files.pythonhosted.org/packages/c3/13/731c99dc2e7652ae818a6de45bdf0142049f7cb566049061c898355f1891/lupa-2.8-cp39-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:250e035fdaffe8c87093e3ebc206ac29a26131b1568ea711d780c26001ce96e7", size = 1466321, upload-time = "2026-04-15T20:07:57.627Z" }, + { url = "https://files.pythonhosted.org/packages/de/71/3ad8cc4fc05a77dc0d3f7079348bd1cad4675a0d14c24f8e6a3ce5f008f7/lupa-2.8-cp39-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:b9bddb09acfffb4f828f790f444b11dc0cca591afea1a244d9329eea2d20c003", size = 1288577, upload-time = "2026-04-15T20:07:59.913Z" }, + { url = "https://files.pythonhosted.org/packages/d8/b2/1175f6d0aa7b68627fbe2f58bd1e8bea36a89d10dfd67671d2b024c96162/lupa-2.8-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2e64acbbd47e9b82a64405a39e0d2b36a5a7dad8ab41c0f3437f572f7d282ba3", size = 2444866, upload-time = "2026-04-15T20:08:02.753Z" }, +] + [[package]] name = "markdown-it-py" version = "4.0.0" @@ -1937,7 +2075,7 @@ wheels = [ [[package]] name = "mcp" -version = "1.13.0" +version = "1.27.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -1946,15 +2084,18 @@ dependencies = [ { name = "jsonschema" }, { name = "pydantic" }, { name = "pydantic-settings" }, + { name = "pyjwt", extra = ["crypto"] }, { name = "python-multipart" }, { name = "pywin32", marker = "sys_platform == 'win32'" }, { name = "sse-starlette" }, { name = "starlette" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/a8/564c094de5d6199f727f5d9f5672dbec3b00dfafd0f67bf52d995eaa5951/mcp-1.13.0.tar.gz", hash = "sha256:70452f56f74662a94eb72ac5feb93997b35995e389b3a3a574e078bed2aa9ab3", size = 434709, upload-time = "2025-08-14T15:03:58.58Z" } +sdist = { url = "https://files.pythonhosted.org/packages/27/3c/347cf965d313f5d41764e7d46bea6ffe7d9ef13b983cc429b0340962a082/mcp-1.27.2.tar.gz", hash = "sha256:8e02db104096d1c25b28e64bde29a5c32b31bc241710213e12fd4d84985bdfef", size = 621116, upload-time = "2026-05-29T17:16:04.039Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/6b/46b8bcefc2ee9e2d2e8d2bd25f1c2512f5a879fac4619d716b194d6e7ccc/mcp-1.13.0-py3-none-any.whl", hash = "sha256:8b1a002ebe6e17e894ec74d1943cc09aa9d23cb931bf58d49ab2e9fa6bb17e4b", size = 160226, upload-time = "2025-08-14T15:03:56.641Z" }, + { url = "https://files.pythonhosted.org/packages/c9/11/252c6f971dc4f16af1d98a1c469d8ba523aab00d1bb76b4d3bc1ff32eacc/mcp-1.27.2-py3-none-any.whl", hash = "sha256:d6ff5160c6ca65d93013626efb3fc249de683c30b2d8570755ceddd490344de5", size = 220498, upload-time = "2026-05-29T17:16:02.442Z" }, ] [[package]] @@ -2411,26 +2552,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ff/de/b02f27f8a022804e64b09dfa1f0ca477546dbb3b83257a5961fa2013d1ac/openai_guardrails-0.2.1-py3-none-any.whl", hash = "sha256:d2d27735ec0d8a801c2398b600ecb484ae063f93502ead146be2ad36436eba36", size = 160382, upload-time = "2025-12-15T23:54:27.684Z" }, ] -[[package]] -name = "openapi-core" -version = "0.19.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "isodate" }, - { name = "jsonschema" }, - { name = "jsonschema-path" }, - { name = "more-itertools" }, - { name = "openapi-schema-validator" }, - { name = "openapi-spec-validator" }, - { name = "parse" }, - { name = "typing-extensions" }, - { name = "werkzeug" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b1/35/1acaa5f2fcc6e54eded34a2ec74b479439c4e469fc4e8d0e803fda0234db/openapi_core-0.19.5.tar.gz", hash = "sha256:421e753da56c391704454e66afe4803a290108590ac8fa6f4a4487f4ec11f2d3", size = 103264, upload-time = "2025-03-20T20:17:28.193Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/27/6f/83ead0e2e30a90445ee4fc0135f43741aebc30cca5b43f20968b603e30b6/openapi_core-0.19.5-py3-none-any.whl", hash = "sha256:ef7210e83a59394f46ce282639d8d26ad6fc8094aa904c9c16eb1bac8908911f", size = 106595, upload-time = "2025-03-20T20:17:26.77Z" }, -] - [[package]] name = "openapi-pydantic" version = "0.5.1" @@ -2443,35 +2564,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/12/cf/03675d8bd8ecbf4445504d8071adab19f5f993676795708e36402ab38263/openapi_pydantic-0.5.1-py3-none-any.whl", hash = "sha256:a3a09ef4586f5bd760a8df7f43028b60cafb6d9f61de2acba9574766255ab146", size = 96381, upload-time = "2025-01-08T19:29:25.275Z" }, ] -[[package]] -name = "openapi-schema-validator" -version = "0.6.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jsonschema" }, - { name = "jsonschema-specifications" }, - { name = "rfc3339-validator" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8b/f3/5507ad3325169347cd8ced61c232ff3df70e2b250c49f0fe140edb4973c6/openapi_schema_validator-0.6.3.tar.gz", hash = "sha256:f37bace4fc2a5d96692f4f8b31dc0f8d7400fd04f3a937798eaf880d425de6ee", size = 11550, upload-time = "2025-01-10T18:08:22.268Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/21/c6/ad0fba32775ae749016829dace42ed80f4407b171da41313d1a3a5f102e4/openapi_schema_validator-0.6.3-py3-none-any.whl", hash = "sha256:f3b9870f4e556b5a62a1c39da72a6b4b16f3ad9c73dc80084b1b11e74ba148a3", size = 8755, upload-time = "2025-01-10T18:08:19.758Z" }, -] - -[[package]] -name = "openapi-spec-validator" -version = "0.7.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jsonschema" }, - { name = "jsonschema-path" }, - { name = "lazy-object-proxy" }, - { name = "openapi-schema-validator" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/82/af/fe2d7618d6eae6fb3a82766a44ed87cd8d6d82b4564ed1c7cfb0f6378e91/openapi_spec_validator-0.7.2.tar.gz", hash = "sha256:cc029309b5c5dbc7859df0372d55e9d1ff43e96d678b9ba087f7c56fc586f734", size = 36855, upload-time = "2025-06-07T14:48:56.299Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/27/dd/b3fd642260cb17532f66cc1e8250f3507d1e580483e209dc1e9d13bd980d/openapi_spec_validator-0.7.2-py3-none-any.whl", hash = "sha256:4bbdc0894ec85f1d1bea1d6d9c8b2c3c8d7ccaa13577ef40da9c006c9fd0eb60", size = 39713, upload-time = "2025-06-07T14:48:54.077Z" }, -] - [[package]] name = "openinference-instrumentation" version = "0.1.42" @@ -2829,15 +2921,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/70/44/5191d2e4026f86a2a109053e194d3ba7a31a2d10a9c2348368c63ed4e85a/pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87", size = 13202175, upload-time = "2025-09-29T23:31:59.173Z" }, ] -[[package]] -name = "parse" -version = "1.20.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4f/78/d9b09ba24bb36ef8b83b71be547e118d46214735b6dfb39e4bfde0e9b9dd/parse-1.20.2.tar.gz", hash = "sha256:b41d604d16503c79d81af5165155c0b20f6c8d6c559efa66b4b695c3e5a0a0ce", size = 29391, upload-time = "2024-06-11T04:41:57.34Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/31/ba45bf0b2aa7898d81cbbfac0e88c267befb59ad91a19e36e1bc5578ddb1/parse-1.20.2-py2.py3-none-any.whl", hash = "sha256:967095588cb802add9177d0c0b6133b5ba33b1ea9007ca800e526f42a85af558", size = 20126, upload-time = "2024-06-11T04:41:55.057Z" }, -] - [[package]] name = "parso" version = "0.8.5" @@ -2865,6 +2948,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, ] +[[package]] +name = "pathvalidate" +version = "3.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/2a/52a8da6fe965dea6192eb716b357558e103aea0a1e9a8352ad575a8406ca/pathvalidate-3.3.1.tar.gz", hash = "sha256:b18c07212bfead624345bb8e1d6141cdcf15a39736994ea0b94035ad2b1ba177", size = 63262, upload-time = "2025-06-15T09:07:20.736Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/70/875f4a23bfc4731703a5835487d0d2fb999031bd415e7d17c0ae615c18b7/pathvalidate-3.3.1-py3-none-any.whl", hash = "sha256:5263baab691f8e1af96092fa5137ee17df5bdfbd6cff1fcac4d6ef4bc2e1735f", size = 24305, upload-time = "2025-06-15T09:07:19.117Z" }, +] + [[package]] name = "pdfminer-six" version = "20250506" @@ -3109,6 +3201,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/dd/df0d00ff44977868878f391b08feb65b8269904262d0f1387de827c13c7d/presidio_analyzer-2.2.360-py3-none-any.whl", hash = "sha256:aa6e83779b8f23587000ccfa3ef47aa34356a60b51284742e0d2a96b33205c4e", size = 128661, upload-time = "2025-09-09T09:24:20.75Z" }, ] +[[package]] +name = "prometheus-client" +version = "0.25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/fb/d9aa83ffe43ce1f19e557c0971d04b90561b0cfd50762aafb01968285553/prometheus_client-0.25.0.tar.gz", hash = "sha256:5e373b75c31afb3c86f1a52fa1ad470c9aace18082d39ec0d2f918d11cc9ba28", size = 86035, upload-time = "2026-04-09T19:53:42.359Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/9b/d4b1e644385499c8346fa9b622a3f030dce14cd6ef8a1871c221a17a67e7/prometheus_client-0.25.0-py3-none-any.whl", hash = "sha256:d5aec89e349a6ec230805d0df882f3807f74fd6c1a2fa86864e3c2279059fed1", size = 64154, upload-time = "2026-04-09T19:53:41.324Z" }, +] + [[package]] name = "prompt-toolkit" version = "3.0.52" @@ -3210,6 +3311,47 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, ] +[[package]] +name = "py-key-value-aio" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beartype" }, + { name = "py-key-value-shared" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/93/ce/3136b771dddf5ac905cc193b461eb67967cf3979688c6696e1f2cdcde7ea/py_key_value_aio-0.3.0.tar.gz", hash = "sha256:858e852fcf6d696d231266da66042d3355a7f9871650415feef9fca7a6cd4155", size = 50801, upload-time = "2025-11-17T16:50:04.711Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/10/72f6f213b8f0bce36eff21fda0a13271834e9eeff7f9609b01afdc253c79/py_key_value_aio-0.3.0-py3-none-any.whl", hash = "sha256:1c781915766078bfd608daa769fefb97e65d1d73746a3dfb640460e322071b64", size = 96342, upload-time = "2025-11-17T16:50:03.801Z" }, +] + +[package.optional-dependencies] +disk = [ + { name = "diskcache" }, + { name = "pathvalidate" }, +] +keyring = [ + { name = "keyring" }, +] +memory = [ + { name = "cachetools" }, +] +redis = [ + { name = "redis" }, +] + +[[package]] +name = "py-key-value-shared" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beartype" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7b/e4/1971dfc4620a3a15b4579fe99e024f5edd6e0967a71154771a059daff4db/py_key_value_shared-0.3.0.tar.gz", hash = "sha256:8fdd786cf96c3e900102945f92aa1473138ebe960ef49da1c833790160c28a4b", size = 11666, upload-time = "2025-11-17T16:50:06.849Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/e4/b8b0a03ece72f47dce2307d36e1c34725b7223d209fc679315ffe6a4e2c3/py_key_value_shared-0.3.0-py3-none-any.whl", hash = "sha256:5b0efba7ebca08bb158b1e93afc2f07d30b8f40c2fc12ce24a4c0d84f42f9298", size = 19560, upload-time = "2025-11-17T16:50:05.954Z" }, +] + [[package]] name = "pyarrow" version = "22.0.0" @@ -3499,6 +3641,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/58/f0/427018098906416f580e3cf1366d3b1abfb408a0652e9f31600c24a1903c/pydantic_settings-2.10.1-py3-none-any.whl", hash = "sha256:a60952460b99cf661dc25c29c0ef171721f98bfcb52ef8d9ea4c943d7c8cc796", size = 45235, upload-time = "2025-06-24T13:26:45.485Z" }, ] +[[package]] +name = "pydocket" +version = "0.21.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "burner-redis" }, + { name = "cloudpickle" }, + { name = "cronsim" }, + { name = "opentelemetry-api" }, + { name = "prometheus-client" }, + { name = "py-key-value-aio", extra = ["memory", "redis"] }, + { name = "python-json-logger" }, + { name = "redis" }, + { name = "rich" }, + { name = "typer" }, + { name = "typing-extensions" }, + { name = "tzdata", marker = "sys_platform == 'win32'" }, + { name = "uncalled-for" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/88/5f/2f68c38ac3fdbff3cdec3ccfe31303ae5972f3e3f1c365d0ec71573dfd2e/pydocket-0.21.1.tar.gz", hash = "sha256:79d19d5f3be29caa23eba95226a516f8b2aed3ba1ad7830095fdce59f49b51f7", size = 399652, upload-time = "2026-05-29T21:04:10.855Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/ca/6bd186338cf4abb318825d9e8d2d2cf069432f9217d17f78f61204eaba70/pydocket-0.21.1-py3-none-any.whl", hash = "sha256:7f3b12c307488efbfde96f76dac533babf5dff4ce72dc0a108a49de03ab39564", size = 117222, upload-time = "2026-05-29T21:04:09.375Z" }, +] + [[package]] name = "pygments" version = "2.19.2" @@ -3517,6 +3683,11 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" }, ] +[package.optional-dependencies] +crypto = [ + { name = "cryptography" }, +] + [[package]] name = "pypdfium2" version = "4.30.0" @@ -3672,6 +3843,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, ] +[[package]] +name = "python-json-logger" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f7/ff/3cc9165fd44106973cd7ac9facb674a65ed853494592541d339bdc9a30eb/python_json_logger-4.1.0.tar.gz", hash = "sha256:b396b9e3ed782b09ff9d6e4f1683d46c83ad0d35d2e407c09a9ebbf038f88195", size = 17573, upload-time = "2026-03-29T04:39:56.805Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/be/0631a861af4d1c875f096c07d34e9a63639560a717130e7a87cbc82b7e3f/python_json_logger-4.1.0-py3-none-any.whl", hash = "sha256:132994765cf75bf44554be9aa49b06ef2345d23661a96720262716438141b6b2", size = 15021, upload-time = "2026-03-29T04:39:55.266Z" }, +] + [[package]] name = "python-multipart" version = "0.0.20" @@ -3729,6 +3909,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, ] +[[package]] +name = "pywin32-ctypes" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471, upload-time = "2024-08-14T10:15:34.626Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756, upload-time = "2024-08-14T10:15:33.187Z" }, +] + [[package]] name = "pyyaml" version = "6.0.2" @@ -3848,6 +4037,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c1/f4/dfc7b8c46b1044a47f7ca55deceb5965985cff3193906cb32913121e6652/rapidfuzz-3.14.1-cp314-cp314t-win_arm64.whl", hash = "sha256:7cd312c380d3ce9d35c3ec9726b75eee9da50e8a38e89e229a03db2262d3d96b", size = 853771, upload-time = "2025-09-08T21:08:00.816Z" }, ] +[[package]] +name = "redis" +version = "8.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/ae/ed461cca5780b5fc8b9fe8ca0ed98d89508645fb9d880c24cc42c087678f/redis-8.0.0.tar.gz", hash = "sha256:a00c5355432051ac14e593b8b197fc76c887ee12d55a0984f69328a1115fdc49", size = 5101591, upload-time = "2026-05-28T12:45:13.5Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/e3/b519734372d305bd547534a9f32e4ce9f98552af753dce72cf3483a0ff0b/redis-8.0.0-py3-none-any.whl", hash = "sha256:c938c18338585009f0bc310f4c7e4e4b4d37639356c4ac072cedf3af570c8dc7", size = 499870, upload-time = "2026-05-28T12:45:11.697Z" }, +] + [[package]] name = "referencing" version = "0.36.2" @@ -3964,18 +4162,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" }, ] -[[package]] -name = "rfc3339-validator" -version = "0.1.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "six" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/28/ea/a9387748e2d111c3c2b275ba970b735e04e15cdb1eb30693b6b5708c4dbd/rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", size = 5513, upload-time = "2021-05-12T16:37:54.178Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa", size = 3490, upload-time = "2021-05-12T16:37:52.536Z" }, -] - [[package]] name = "rich" version = "14.1.0" @@ -4178,6 +4364,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/64/47/a494741db7280eae6dc033510c319e34d42dd41b7ac0c7ead39354d1a2b5/scipy-1.16.3-cp314-cp314t-win_arm64.whl", hash = "sha256:21d9d6b197227a12dcbf9633320a4e34c6b0e51c57268df255a0942983bac562", size = 26464127, upload-time = "2025-10-28T17:38:11.34Z" }, ] +[[package]] +name = "secretstorage" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "jeepney" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884, upload-time = "2025-11-23T19:02:53.191Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", size = 15554, upload-time = "2025-11-23T19:02:51.545Z" }, +] + [[package]] name = "setuptools" version = "80.9.0" @@ -4235,6 +4434,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, ] +[[package]] +name = "sortedcontainers" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" }, +] + [[package]] name = "spacy" version = "3.8.11" @@ -4675,11 +4883,11 @@ wheels = [ [[package]] name = "typing-extensions" -version = "4.14.1" +version = "4.15.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" }, + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, ] [[package]] @@ -4716,6 +4924,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" }, ] +[[package]] +name = "uncalled-for" +version = "0.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/82/345cc927f7fbdae6065e7768759932fcc827fc20b29b45dfbafa2f1f7da4/uncalled_for-0.3.2.tar.gz", hash = "sha256:89f5dbcd71e2b8f47c030b1fa302e6cce2ec795d1ac565eeb6525c5fe55cb8a2", size = 50032, upload-time = "2026-05-06T13:38:25.204Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/25/2c87754f3a9e692315f7b811244090e68f362979fc8886b3fbd2985a1d8c/uncalled_for-0.3.2-py3-none-any.whl", hash = "sha256:0ff60b142c7d1f8070bde9d42afaa70aedc77dcc10998c227687e9c15713418e", size = 11444, upload-time = "2026-05-06T13:38:24.025Z" }, +] + [[package]] name = "urllib3" version = "2.5.0" @@ -4979,18 +5196,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, ] -[[package]] -name = "werkzeug" -version = "3.1.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markupsafe" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/32/af/d4502dc713b4ccea7175d764718d5183caf8d0867a4f0190d5d4a45cea49/werkzeug-3.1.1.tar.gz", hash = "sha256:8cd39dfbdfc1e051965f156163e2974e52c210f130810e9ad36858f0fd3edad4", size = 806453, upload-time = "2024-11-01T16:40:45.462Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/ea/c67e1dee1ba208ed22c06d1d547ae5e293374bfc43e0eb0ef5e262b68561/werkzeug-3.1.1-py3-none-any.whl", hash = "sha256:a71124d1ef06008baafa3d266c02f56e1836a5984afd6dd6c9230669d60d9fb5", size = 224371, upload-time = "2024-11-01T16:40:43.994Z" }, -] - [[package]] name = "wrapt" version = "1.17.3" diff --git a/examples/api/messenger/uv.lock b/examples/api/messenger/uv.lock index 6b2a22b46..c225992ad 100644 --- a/examples/api/messenger/uv.lock +++ b/examples/api/messenger/uv.lock @@ -9,7 +9,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -18,9 +18,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -206,11 +206,11 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "agentkernel", extras = ["api", "openai", "messenger"], specifier = ">=0.4.0" }] +requires-dist = [{ name = "agentkernel", extras = ["api", "openai", "messenger"], specifier = ">=0.5.1" }] [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "isort", specifier = ">=5.0.0" }, { name = "mypy", specifier = ">=1.0.0" }, diff --git a/examples/api/multimodal/adk/uv.lock b/examples/api/multimodal/adk/uv.lock index 8cb110c82..185b8716c 100644 --- a/examples/api/multimodal/adk/uv.lock +++ b/examples/api/multimodal/adk/uv.lock @@ -15,7 +15,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -24,9 +24,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -40,9 +40,6 @@ api = [ { name = "gunicorn" }, { name = "uvicorn" }, ] -multimodal = [ - { name = "litellm" }, -] test = [ { name = "datasets" }, { name = "litellm" }, @@ -222,7 +219,7 @@ name = "api-adk-multimodal-example" version = "0.1.0" source = { virtual = "." } dependencies = [ - { name = "agentkernel", extra = ["adk", "api", "multimodal"] }, + { name = "agentkernel", extra = ["adk", "api"] }, ] [package.dev-dependencies] @@ -234,11 +231,11 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "agentkernel", extras = ["adk", "api", "multimodal"], specifier = ">=0.4.0" }] +requires-dist = [{ name = "agentkernel", extras = ["adk", "api", "multimodal"], specifier = ">=0.5.1" }] [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "isort", specifier = ">=5.0.0" }, { name = "mypy", specifier = ">=1.0.0" }, diff --git a/examples/api/multimodal/crewai/uv.lock b/examples/api/multimodal/crewai/uv.lock index d8f372a06..cd798b041 100644 --- a/examples/api/multimodal/crewai/uv.lock +++ b/examples/api/multimodal/crewai/uv.lock @@ -15,7 +15,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -26,9 +26,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -42,9 +42,6 @@ crewai = [ { name = "openinference-instrumentation-crewai" }, { name = "openinference-instrumentation-litellm" }, ] -multimodal = [ - { name = "litellm" }, -] test = [ { name = "datasets" }, { name = "litellm" }, @@ -201,7 +198,7 @@ name = "api-crewai-multimodal-example" version = "0.1.0" source = { virtual = "." } dependencies = [ - { name = "agentkernel", extra = ["api", "crewai", "multimodal"] }, + { name = "agentkernel", extra = ["api", "crewai"] }, { name = "crewai" }, ] @@ -218,13 +215,13 @@ dev = [ [package.metadata] requires-dist = [ - { name = "agentkernel", extras = ["api", "crewai", "multimodal"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["api", "crewai", "multimodal"], specifier = ">=0.5.1" }, { name = "crewai", specifier = "==0.165.1" }, ] [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "httpx", specifier = ">=0.27.0" }, { name = "isort", specifier = ">=5.0.0" }, diff --git a/examples/api/multimodal/dynamodb/uv.lock b/examples/api/multimodal/dynamodb/uv.lock index 7a6e96818..e26fddcde 100644 --- a/examples/api/multimodal/dynamodb/uv.lock +++ b/examples/api/multimodal/dynamodb/uv.lock @@ -15,7 +15,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -24,18 +24,15 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] aws = [ { name = "boto3" }, ] -multimodal = [ - { name = "litellm" }, -] openai = [ { name = "openai-agents" }, { name = "openai-guardrails" }, @@ -1733,7 +1730,7 @@ name = "multimodal-dynamodb-serverless" version = "0.1.0" source = { virtual = "." } dependencies = [ - { name = "agentkernel", extra = ["aws", "multimodal", "openai"] }, + { name = "agentkernel", extra = ["aws", "openai"] }, ] [package.dev-dependencies] @@ -1745,11 +1742,11 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "agentkernel", extras = ["openai", "aws", "multimodal"], specifier = ">=0.4.0" }] +requires-dist = [{ name = "agentkernel", extras = ["openai", "aws", "multimodal"], specifier = ">=0.5.1" }] [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "isort", specifier = ">=5.0.0" }, { name = "mypy", specifier = ">=1.0.0" }, diff --git a/examples/api/multimodal/langgraph/uv.lock b/examples/api/multimodal/langgraph/uv.lock index a45a5d3a8..e193eb9d8 100644 --- a/examples/api/multimodal/langgraph/uv.lock +++ b/examples/api/multimodal/langgraph/uv.lock @@ -15,7 +15,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -24,9 +24,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -44,9 +44,6 @@ langgraph = [ { name = "langgraph" }, { name = "litellm" }, ] -multimodal = [ - { name = "litellm" }, -] test = [ { name = "datasets" }, { name = "litellm" }, @@ -203,7 +200,7 @@ name = "api-langgraph-multimodal-example" version = "0.1.0" source = { virtual = "." } dependencies = [ - { name = "agentkernel", extra = ["api", "auth", "langgraph", "multimodal"] }, + { name = "agentkernel", extra = ["api", "auth", "langgraph"] }, { name = "langchain-openai" }, { name = "python-multipart" }, ] @@ -220,14 +217,14 @@ dev = [ [package.metadata] requires-dist = [ - { name = "agentkernel", extras = ["api", "auth", "langgraph", "multimodal"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["api", "auth", "langgraph", "multimodal"], specifier = ">=0.5.1" }, { name = "langchain-openai", specifier = ">=0.1.0" }, { name = "python-multipart", specifier = ">=0.0.12" }, ] [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "httpx", specifier = ">=0.27.0" }, { name = "isort", specifier = ">=5.0.0" }, diff --git a/examples/api/multimodal/openai/uv.lock b/examples/api/multimodal/openai/uv.lock index 20d22cd55..ba965764f 100644 --- a/examples/api/multimodal/openai/uv.lock +++ b/examples/api/multimodal/openai/uv.lock @@ -15,7 +15,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -24,9 +24,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -35,9 +35,6 @@ api = [ { name = "gunicorn" }, { name = "uvicorn" }, ] -multimodal = [ - { name = "litellm" }, -] openai = [ { name = "openai-agents" }, { name = "openai-guardrails" }, @@ -199,7 +196,7 @@ name = "api-openai-multimodal-example" version = "0.1.0" source = { virtual = "." } dependencies = [ - { name = "agentkernel", extra = ["api", "multimodal", "openai"] }, + { name = "agentkernel", extra = ["api", "openai"] }, ] [package.dev-dependencies] @@ -213,11 +210,11 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "agentkernel", extras = ["api", "openai", "multimodal"], specifier = ">=0.4.0" }] +requires-dist = [{ name = "agentkernel", extras = ["api", "openai", "multimodal"], specifier = ">=0.5.1" }] [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "httpx", specifier = ">=0.27.0" }, { name = "isort", specifier = ">=5.0.0" }, diff --git a/examples/api/multimodal/redis/uv.lock b/examples/api/multimodal/redis/uv.lock index aa1d55c91..cda538829 100644 --- a/examples/api/multimodal/redis/uv.lock +++ b/examples/api/multimodal/redis/uv.lock @@ -15,7 +15,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -24,18 +24,15 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] aws = [ { name = "boto3" }, ] -multimodal = [ - { name = "litellm" }, -] openai = [ { name = "openai-agents" }, { name = "openai-guardrails" }, @@ -1736,7 +1733,7 @@ name = "multimodal-redis-serverless" version = "0.1.0" source = { virtual = "." } dependencies = [ - { name = "agentkernel", extra = ["aws", "multimodal", "openai", "redis"] }, + { name = "agentkernel", extra = ["aws", "openai", "redis"] }, ] [package.dev-dependencies] @@ -1748,11 +1745,11 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "agentkernel", extras = ["openai", "redis", "aws", "multimodal"], specifier = ">=0.4.0" }] +requires-dist = [{ name = "agentkernel", extras = ["openai", "redis", "aws", "multimodal"], specifier = ">=0.5.1" }] [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "isort", specifier = ">=5.0.0" }, { name = "mypy", specifier = ">=1.0.0" }, diff --git a/examples/api/multimodal/smolagents/uv.lock b/examples/api/multimodal/smolagents/uv.lock index 946e728cd..e959d391e 100644 --- a/examples/api/multimodal/smolagents/uv.lock +++ b/examples/api/multimodal/smolagents/uv.lock @@ -15,7 +15,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -24,9 +24,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -35,8 +35,8 @@ api = [ { name = "gunicorn" }, { name = "uvicorn" }, ] -multimodal = [ - { name = "litellm" }, +smolagents = [ + { name = "smolagents" }, ] test = [ { name = "datasets" }, @@ -194,14 +194,14 @@ name = "api-smolagents-multimodal-example" version = "0.1.0" source = { virtual = "." } dependencies = [ - { name = "agentkernel", extra = ["api", "multimodal"] }, + { name = "agentkernel", extra = ["api", "smolagents"] }, { name = "python-multipart" }, { name = "smolagents" }, ] [package.dev-dependencies] dev = [ - { name = "agentkernel", extra = ["test"] }, + { name = "agentkernel", extra = ["smolagents", "test"] }, { name = "black" }, { name = "httpx" }, { name = "isort" }, @@ -211,14 +211,14 @@ dev = [ [package.metadata] requires-dist = [ - { name = "agentkernel", extras = ["api", "smolagents", "multimodal"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["api", "smolagents", "multimodal"], specifier = ">=0.5.1" }, { name = "python-multipart", specifier = ">=0.0.12" }, { name = "smolagents" }, ] [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test", "smolagents"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["test", "smolagents"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "httpx", specifier = ">=0.27.0" }, { name = "isort", specifier = ">=5.0.0" }, @@ -727,7 +727,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ea/ab/1608e5a7578e62113506740b88066bf09888322a311cff602105e619bd87/greenlet-3.3.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:ac8d61d4343b799d1e526db579833d72f23759c71e07181c2d2944e429eb09cd", size = 280358, upload-time = "2026-02-20T20:17:43.971Z" }, { url = "https://files.pythonhosted.org/packages/a5/23/0eae412a4ade4e6623ff7626e38998cb9b11e9ff1ebacaa021e4e108ec15/greenlet-3.3.2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ceec72030dae6ac0c8ed7591b96b70410a8be370b6a477b1dbc072856ad02bd", size = 601217, upload-time = "2026-02-20T20:47:31.462Z" }, { url = "https://files.pythonhosted.org/packages/f8/16/5b1678a9c07098ecb9ab2dd159fafaf12e963293e61ee8d10ecb55273e5e/greenlet-3.3.2-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a2a5be83a45ce6188c045bcc44b0ee037d6a518978de9a5d97438548b953a1ac", size = 611792, upload-time = "2026-02-20T20:55:58.423Z" }, - { url = "https://files.pythonhosted.org/packages/5c/c5/cc09412a29e43406eba18d61c70baa936e299bc27e074e2be3806ed29098/greenlet-3.3.2-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ae9e21c84035c490506c17002f5c8ab25f980205c3e61ddb3a2a2a2e6c411fcb", size = 626250, upload-time = "2026-02-20T21:02:46.596Z" }, { url = "https://files.pythonhosted.org/packages/50/1f/5155f55bd71cabd03765a4aac9ac446be129895271f73872c36ebd4b04b6/greenlet-3.3.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43e99d1749147ac21dde49b99c9abffcbc1e2d55c67501465ef0930d6e78e070", size = 613875, upload-time = "2026-02-20T20:21:01.102Z" }, { url = "https://files.pythonhosted.org/packages/fc/dd/845f249c3fcd69e32df80cdab059b4be8b766ef5830a3d0aa9d6cad55beb/greenlet-3.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4c956a19350e2c37f2c48b336a3afb4bff120b36076d9d7fb68cb44e05d95b79", size = 1571467, upload-time = "2026-02-20T20:49:33.495Z" }, { url = "https://files.pythonhosted.org/packages/2a/50/2649fe21fcc2b56659a452868e695634722a6655ba245d9f77f5656010bf/greenlet-3.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6c6f8ba97d17a1e7d664151284cb3315fc5f8353e75221ed4324f84eb162b395", size = 1640001, upload-time = "2026-02-20T20:21:09.154Z" }, @@ -736,7 +735,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ac/48/f8b875fa7dea7dd9b33245e37f065af59df6a25af2f9561efa8d822fde51/greenlet-3.3.2-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:aa6ac98bdfd716a749b84d4034486863fd81c3abde9aa3cf8eff9127981a4ae4", size = 279120, upload-time = "2026-02-20T20:19:01.9Z" }, { url = "https://files.pythonhosted.org/packages/49/8d/9771d03e7a8b1ee456511961e1b97a6d77ae1dea4a34a5b98eee706689d3/greenlet-3.3.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab0c7e7901a00bc0a7284907273dc165b32e0d109a6713babd04471327ff7986", size = 603238, upload-time = "2026-02-20T20:47:32.873Z" }, { url = "https://files.pythonhosted.org/packages/59/0e/4223c2bbb63cd5c97f28ffb2a8aee71bdfb30b323c35d409450f51b91e3e/greenlet-3.3.2-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d248d8c23c67d2291ffd47af766e2a3aa9fa1c6703155c099feb11f526c63a92", size = 614219, upload-time = "2026-02-20T20:55:59.817Z" }, - { url = "https://files.pythonhosted.org/packages/94/2b/4d012a69759ac9d77210b8bfb128bc621125f5b20fc398bce3940d036b1c/greenlet-3.3.2-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ccd21bb86944ca9be6d967cf7691e658e43417782bce90b5d2faeda0ff78a7dd", size = 628268, upload-time = "2026-02-20T21:02:48.024Z" }, { url = "https://files.pythonhosted.org/packages/7a/34/259b28ea7a2a0c904b11cd36c79b8cef8019b26ee5dbe24e73b469dea347/greenlet-3.3.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6997d360a4e6a4e936c0f9625b1c20416b8a0ea18a8e19cabbefc712e7397ab", size = 616774, upload-time = "2026-02-20T20:21:02.454Z" }, { url = "https://files.pythonhosted.org/packages/0a/03/996c2d1689d486a6e199cb0f1cf9e4aa940c500e01bdf201299d7d61fa69/greenlet-3.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:64970c33a50551c7c50491671265d8954046cb6e8e2999aacdd60e439b70418a", size = 1571277, upload-time = "2026-02-20T20:49:34.795Z" }, { url = "https://files.pythonhosted.org/packages/d9/c4/2570fc07f34a39f2caf0bf9f24b0a1a0a47bc2e8e465b2c2424821389dfc/greenlet-3.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1a9172f5bf6bd88e6ba5a84e0a68afeac9dc7b6b412b245dd64f52d83c81e55b", size = 1640455, upload-time = "2026-02-20T20:21:10.261Z" }, @@ -745,7 +743,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3f/ae/8bffcbd373b57a5992cd077cbe8858fff39110480a9d50697091faea6f39/greenlet-3.3.2-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:8d1658d7291f9859beed69a776c10822a0a799bc4bfe1bd4272bb60e62507dab", size = 279650, upload-time = "2026-02-20T20:18:00.783Z" }, { url = "https://files.pythonhosted.org/packages/d1/c0/45f93f348fa49abf32ac8439938726c480bd96b2a3c6f4d949ec0124b69f/greenlet-3.3.2-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18cb1b7337bca281915b3c5d5ae19f4e76d35e1df80f4ad3c1a7be91fadf1082", size = 650295, upload-time = "2026-02-20T20:47:34.036Z" }, { url = "https://files.pythonhosted.org/packages/b3/de/dd7589b3f2b8372069ab3e4763ea5329940fc7ad9dcd3e272a37516d7c9b/greenlet-3.3.2-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c2e47408e8ce1c6f1ceea0dffcdf6ebb85cc09e55c7af407c99f1112016e45e9", size = 662163, upload-time = "2026-02-20T20:56:01.295Z" }, - { url = "https://files.pythonhosted.org/packages/cd/ac/85804f74f1ccea31ba518dcc8ee6f14c79f73fe36fa1beba38930806df09/greenlet-3.3.2-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e3cb43ce200f59483eb82949bf1835a99cf43d7571e900d7c8d5c62cdf25d2f9", size = 675371, upload-time = "2026-02-20T21:02:49.664Z" }, { url = "https://files.pythonhosted.org/packages/d2/d8/09bfa816572a4d83bccd6750df1926f79158b1c36c5f73786e26dbe4ee38/greenlet-3.3.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63d10328839d1973e5ba35e98cccbca71b232b14051fd957b6f8b6e8e80d0506", size = 664160, upload-time = "2026-02-20T20:21:04.015Z" }, { url = "https://files.pythonhosted.org/packages/48/cf/56832f0c8255d27f6c35d41b5ec91168d74ec721d85f01a12131eec6b93c/greenlet-3.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8e4ab3cfb02993c8cc248ea73d7dae6cec0253e9afa311c9b37e603ca9fad2ce", size = 1619181, upload-time = "2026-02-20T20:49:36.052Z" }, { url = "https://files.pythonhosted.org/packages/0a/23/b90b60a4aabb4cec0796e55f25ffbfb579a907c3898cd2905c8918acaa16/greenlet-3.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94ad81f0fd3c0c0681a018a976e5c2bd2ca2d9d94895f23e7bb1af4e8af4e2d5", size = 1687713, upload-time = "2026-02-20T20:21:11.684Z" }, @@ -754,7 +751,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/98/6d/8f2ef704e614bcf58ed43cfb8d87afa1c285e98194ab2cfad351bf04f81e/greenlet-3.3.2-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:e26e72bec7ab387ac80caa7496e0f908ff954f31065b0ffc1f8ecb1338b11b54", size = 286617, upload-time = "2026-02-20T20:19:29.856Z" }, { url = "https://files.pythonhosted.org/packages/5e/0d/93894161d307c6ea237a43988f27eba0947b360b99ac5239ad3fe09f0b47/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b466dff7a4ffda6ca975979bab80bdadde979e29fc947ac3be4451428d8b0e4", size = 655189, upload-time = "2026-02-20T20:47:35.742Z" }, { url = "https://files.pythonhosted.org/packages/f5/2c/d2d506ebd8abcb57386ec4f7ba20f4030cbe56eae541bc6fd6ef399c0b41/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b8bddc5b73c9720bea487b3bffdb1840fe4e3656fba3bd40aa1489e9f37877ff", size = 658225, upload-time = "2026-02-20T20:56:02.527Z" }, - { url = "https://files.pythonhosted.org/packages/d1/67/8197b7e7e602150938049d8e7f30de1660cfb87e4c8ee349b42b67bdb2e1/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:59b3e2c40f6706b05a9cd299c836c6aa2378cabe25d021acd80f13abf81181cf", size = 666581, upload-time = "2026-02-20T21:02:51.526Z" }, { url = "https://files.pythonhosted.org/packages/8e/30/3a09155fbf728673a1dea713572d2d31159f824a37c22da82127056c44e4/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b26b0f4428b871a751968285a1ac9648944cea09807177ac639b030bddebcea4", size = 657907, upload-time = "2026-02-20T20:21:05.259Z" }, { url = "https://files.pythonhosted.org/packages/f3/fd/d05a4b7acd0154ed758797f0a43b4c0962a843bedfe980115e842c5b2d08/greenlet-3.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1fb39a11ee2e4d94be9a76671482be9398560955c9e568550de0224e41104727", size = 1618857, upload-time = "2026-02-20T20:49:37.309Z" }, { url = "https://files.pythonhosted.org/packages/6f/e1/50ee92a5db521de8f35075b5eff060dd43d39ebd46c2181a2042f7070385/greenlet-3.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:20154044d9085151bc309e7689d6f7ba10027f8f5a8c0676ad398b951913d89e", size = 1680010, upload-time = "2026-02-20T20:21:13.427Z" }, diff --git a/examples/api/openai/uv.lock b/examples/api/openai/uv.lock index 977277552..fc65f1a87 100644 --- a/examples/api/openai/uv.lock +++ b/examples/api/openai/uv.lock @@ -9,7 +9,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -18,9 +18,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -381,11 +381,11 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "agentkernel", extras = ["openai", "api"], specifier = ">=0.4.0" }] +requires-dist = [{ name = "agentkernel", extras = ["openai", "api"], specifier = ">=0.5.1" }] [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "isort", specifier = ">=5.0.0" }, { name = "mypy", specifier = ">=1.0.0" }, diff --git a/examples/api/slack/uv.lock b/examples/api/slack/uv.lock index 331b55a63..0cf925385 100644 --- a/examples/api/slack/uv.lock +++ b/examples/api/slack/uv.lock @@ -9,7 +9,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -18,9 +18,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -209,13 +209,13 @@ dev = [ [package.metadata] requires-dist = [ - { name = "agentkernel", extras = ["api", "openai", "slack"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["api", "openai", "slack"], specifier = ">=0.5.1" }, { name = "aiohttp", specifier = ">=3.13.2" }, ] [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "isort", specifier = ">=5.0.0" }, { name = "mypy", specifier = ">=1.0.0" }, diff --git a/examples/api/teams/uv.lock b/examples/api/teams/uv.lock index 790103d88..1f26cea09 100644 --- a/examples/api/teams/uv.lock +++ b/examples/api/teams/uv.lock @@ -15,7 +15,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -24,9 +24,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -214,11 +214,11 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "agentkernel", extras = ["api", "openai", "teams"], specifier = ">=0.4.0" }] +requires-dist = [{ name = "agentkernel", extras = ["api", "openai", "teams"], specifier = ">=0.5.1" }] [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "isort", specifier = ">=5.0.0" }, { name = "mypy", specifier = ">=1.0.0" }, diff --git a/examples/api/telegram/uv.lock b/examples/api/telegram/uv.lock index 86016a786..ec56cb49a 100644 --- a/examples/api/telegram/uv.lock +++ b/examples/api/telegram/uv.lock @@ -9,7 +9,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -18,9 +18,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -3572,11 +3572,11 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "agentkernel", extras = ["api", "openai", "telegram"], specifier = ">=0.4.0" }] +requires-dist = [{ name = "agentkernel", extras = ["api", "openai", "telegram"], specifier = ">=0.5.1" }] [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "isort", specifier = ">=5.0.0" }, { name = "mypy", specifier = ">=1.0.0" }, diff --git a/examples/api/whatsapp/uv.lock b/examples/api/whatsapp/uv.lock index f2a82480b..6f3326f1b 100644 --- a/examples/api/whatsapp/uv.lock +++ b/examples/api/whatsapp/uv.lock @@ -9,7 +9,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -18,9 +18,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -206,11 +206,11 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "agentkernel", extras = ["api", "openai", "whatsapp"], specifier = ">=0.4.0" }] +requires-dist = [{ name = "agentkernel", extras = ["api", "openai", "whatsapp"], specifier = ">=0.5.1" }] [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "isort", specifier = ">=5.0.0" }, { name = "mypy", specifier = ">=1.0.0" }, diff --git a/examples/aws-containerized/adk/uv.lock b/examples/aws-containerized/adk/uv.lock index 64ddb0858..e15c94a60 100644 --- a/examples/aws-containerized/adk/uv.lock +++ b/examples/aws-containerized/adk/uv.lock @@ -24,7 +24,7 @@ wheels = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -33,9 +33,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -231,11 +231,11 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "agentkernel", extras = ["adk", "api", "redis"], specifier = ">=0.4.0" }] +requires-dist = [{ name = "agentkernel", extras = ["adk", "api", "redis"], specifier = ">=0.5.1" }] [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "isort", specifier = ">=5.0.0" }, { name = "mypy", specifier = ">=1.0.0" }, diff --git a/examples/aws-containerized/crewai-auth/uv.lock b/examples/aws-containerized/crewai-auth/uv.lock index 10a584951..ff71f6317 100644 --- a/examples/aws-containerized/crewai-auth/uv.lock +++ b/examples/aws-containerized/crewai-auth/uv.lock @@ -8,7 +8,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -17,9 +17,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -223,7 +223,7 @@ dev = [ [package.metadata] requires-dist = [ - { name = "agentkernel", extras = ["crewai", "api", "redis", "auth"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["crewai", "api", "redis", "auth"], specifier = ">=0.5.1" }, { name = "crewai", specifier = "==0.165.1" }, ] diff --git a/examples/aws-containerized/crewai/uv.lock b/examples/aws-containerized/crewai/uv.lock index d653006e2..12b92a2ee 100644 --- a/examples/aws-containerized/crewai/uv.lock +++ b/examples/aws-containerized/crewai/uv.lock @@ -18,7 +18,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -27,9 +27,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -243,13 +243,13 @@ dev = [ [package.metadata] requires-dist = [ - { name = "agentkernel", extras = ["crewai", "api", "redis"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["crewai", "api", "redis"], specifier = ">=0.5.1" }, { name = "crewai", specifier = "==0.165.1" }, ] [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "isort", specifier = ">=5.0.0" }, { name = "mypy", specifier = ">=1.0.0" }, diff --git a/examples/aws-containerized/mcp/multi/uv.lock b/examples/aws-containerized/mcp/multi/uv.lock index 3a02cbd0d..55442e7cb 100644 --- a/examples/aws-containerized/mcp/multi/uv.lock +++ b/examples/aws-containerized/mcp/multi/uv.lock @@ -9,7 +9,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -18,9 +18,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -210,11 +210,11 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "agentkernel", extras = ["api", "crewai", "openai", "mcp"], specifier = ">=0.4.0" }] +requires-dist = [{ name = "agentkernel", extras = ["api", "crewai", "openai", "mcp"], specifier = ">=0.5.1" }] [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "isort", specifier = ">=5.0.0" }, { name = "mypy", specifier = ">=1.0.0" }, @@ -412,6 +412,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-py3-none-any.whl", hash = "sha256:7145f0b5061ba90a1500d60bd1b13ca0a8a4cebdd0cc16ed8adf1c0e739f43b4", size = 23382, upload-time = "2025-08-01T21:27:07.844Z" }, ] +[[package]] +name = "burner-redis" +version = "0.1.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/89/54706febafc135095b2a9d797cfbd4eed2ab1ad7819808b99b587020471b/burner_redis-0.1.7.tar.gz", hash = "sha256:7474ff092669fd11ef765411572cdafcc3d89b8054aef4ca0617be6d6be4c680", size = 638644, upload-time = "2026-05-08T15:01:42.961Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/5d/198bd1d22e504b3034353430703afbdb3efe6e25cb90bf52d896e1d266a7/burner_redis-0.1.7-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:f80c866996e0455d584eb3c0f3b067e411c632fb0519eab454e0968edf01e62c", size = 1288888, upload-time = "2026-05-08T15:01:26.103Z" }, + { url = "https://files.pythonhosted.org/packages/2f/4e/ce5c91b884ac37fcd380756402536f8810964014097950900517ce8bd30c/burner_redis-0.1.7-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:a3d9569a376b690fb5876d454e4904443332dc3ad5c0057e149fc2ad220bf599", size = 1234282, upload-time = "2026-05-08T15:01:28.286Z" }, + { url = "https://files.pythonhosted.org/packages/6f/c0/31c25cc88143eac2dddcc394151a0db627923d44c94376a83768552c9f13/burner_redis-0.1.7-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20eba1917e3bca9eea5957d5700ff8defcb5a209e57a7841d005549aa0151f44", size = 1337341, upload-time = "2026-05-08T15:01:30.397Z" }, + { url = "https://files.pythonhosted.org/packages/e1/32/95cfa1833316ca2b6b2e58150a4900bc1ad256043cdd36198f1887618ccc/burner_redis-0.1.7-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39111467059b8a28f15ea061d2414ec25c3e57c65759983f90f4d358e7d6a72d", size = 1366800, upload-time = "2026-05-08T15:01:32.891Z" }, + { url = "https://files.pythonhosted.org/packages/34/ad/93c3916f053f89b7b5760da5bf855cd78b7885d480f9cfcc64f3732c1dc2/burner_redis-0.1.7-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9b5adfe99aeb8407f468078f3769b2a63e9168fea12f7709df5d2a3b152706e4", size = 1538160, upload-time = "2026-05-08T15:01:34.667Z" }, + { url = "https://files.pythonhosted.org/packages/5c/b9/19bae42cb124932d71168bc8e5bcb1da33aa62b908e5e632b3d298d7cb15/burner_redis-0.1.7-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:591a9d20685f9d6d22bf0c863b50b12dfcf328b06111b3f62c33cd3185d48ce0", size = 1591491, upload-time = "2026-05-08T15:01:36.708Z" }, + { url = "https://files.pythonhosted.org/packages/f5/30/207f47f406619a5b564355d2946c3171f84231a28b800709b5645b06a5ae/burner_redis-0.1.7-cp310-abi3-win_amd64.whl", hash = "sha256:f6cf4ac666766b32fd63940aad0c120847905fd3102c17e5b6b305f91a21d079", size = 1117564, upload-time = "2026-05-08T15:01:39.221Z" }, + { url = "https://files.pythonhosted.org/packages/76/6f/e9beaf46c5e9fd10dfcdb889ebf7d3aa85142c650c0ab17ab284194f58e1/burner_redis-0.1.7-cp310-abi3-win_arm64.whl", hash = "sha256:458f88feeddfb40a586cc3fcbd8e9384bbdfd2a4512a695af4900e06052570d4", size = 1040407, upload-time = "2026-05-08T15:01:41.235Z" }, +] + [[package]] name = "cachetools" version = "6.2.4" @@ -778,6 +794,14 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/06/87/8ab9924b79025165ed7f1b04a90f9b80137d18ceae9b8e34445a8495320c/crewai-1.6.1-py3-none-any.whl", hash = "sha256:8cec403ab89183bda28b830c722b6bc22457a2151a6aa46f07730e6fe7ab2723", size = 642861, upload-time = "2025-11-29T01:58:23.232Z" }, ] +[[package]] +name = "cronsim" +version = "2.7" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/1a/02f105147f7f2e06ed4f734ff5a6439590bb275a53dd91fc73df6312298a/cronsim-2.7-py3-none-any.whl", hash = "sha256:1e1431fa08c51dc7f72e67e571c7c7a09af26420169b607badd4ca9677ffad1e", size = 14213, upload-time = "2025-10-21T16:38:20.431Z" }, +] + [[package]] name = "cryptography" version = "46.0.3" @@ -1088,16 +1112,19 @@ wheels = [ [[package]] name = "fastmcp" -version = "2.14.1" +version = "2.14.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "authlib" }, { name = "cyclopts" }, { name = "exceptiongroup" }, + { name = "fakeredis", extra = ["lua"] }, { name = "httpx" }, + { name = "jsonref" }, { name = "jsonschema-path" }, { name = "mcp" }, { name = "openapi-pydantic" }, + { name = "packaging" }, { name = "platformdirs" }, { name = "py-key-value-aio", extra = ["disk", "keyring", "memory"] }, { name = "pydantic", extra = ["email"] }, @@ -1108,9 +1135,9 @@ dependencies = [ { name = "uvicorn" }, { name = "websockets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9e/50/d38e4371bdc34e709f4731b1e882cb7bc50e51c1a224859d4cd381b3a79b/fastmcp-2.14.1.tar.gz", hash = "sha256:132725cbf77b68fa3c3d165eff0cfa47e40c1479457419e6a2cfda65bd84c8d6", size = 8263331, upload-time = "2025-12-15T02:26:27.102Z" } +sdist = { url = "https://files.pythonhosted.org/packages/41/c7/6b5202e427b87b14a34ef8b65eba0f5c60a8fbc71919e98a13d646b4a641/fastmcp-2.14.7.tar.gz", hash = "sha256:0d2e83b9d1c5fe0af32036979de3bb99a38f05dc1476d60f1b3259168e139cd0", size = 8296810, upload-time = "2026-04-13T01:12:53.098Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/82/72401d09dc27c27fdf72ad6c2fe331e553e3c3646e01b5ff16473191033d/fastmcp-2.14.1-py3-none-any.whl", hash = "sha256:fb3e365cc1d52573ab89caeba9944dd4b056149097be169bce428e011f0a57e5", size = 412176, upload-time = "2025-12-15T02:26:25.356Z" }, + { url = "https://files.pythonhosted.org/packages/a6/f1/56310847b0bdd5b14c2af8f0a39082af078deff60d0dc43efef4e366a83e/fastmcp-2.14.7-py3-none-any.whl", hash = "sha256:e081a5222a6d302a148871d89fd714b13130cee6594f53a3e1afd7518f0096c0", size = 418162, upload-time = "2026-04-13T01:12:51.325Z" }, ] [[package]] @@ -2973,20 +3000,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/95/f1/b27d3e2e003cd9a3592c43d099d2ed8d0a947c15281bf8463a256db0b46c/opentelemetry_exporter_otlp_proto_http-1.39.1-py3-none-any.whl", hash = "sha256:d9f5207183dd752a412c4cd564ca8875ececba13be6e9c6c370ffb752fd59985", size = 19641, upload-time = "2025-12-11T13:32:22.248Z" }, ] -[[package]] -name = "opentelemetry-exporter-prometheus" -version = "0.60b1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "opentelemetry-api" }, - { name = "opentelemetry-sdk" }, - { name = "prometheus-client" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/14/39/7dafa6fff210737267bed35a8855b6ac7399b9e582b8cf1f25f842517012/opentelemetry_exporter_prometheus-0.60b1.tar.gz", hash = "sha256:a4011b46906323f71724649d301b4dc188aaa068852e814f4df38cc76eac616b", size = 14976, upload-time = "2025-12-11T13:32:42.944Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/0d/4be6bf5477a3eb3d917d2f17d3c0b6720cd6cb97898444a61d43cc983f5c/opentelemetry_exporter_prometheus-0.60b1-py3-none-any.whl", hash = "sha256:49f59178de4f4590e3cef0b8b95cf6e071aae70e1f060566df5546fad773b8fd", size = 13019, upload-time = "2025-12-11T13:32:23.974Z" }, -] - [[package]] name = "opentelemetry-instrumentation" version = "0.60b1" @@ -3906,14 +3919,13 @@ wheels = [ [[package]] name = "pydocket" -version = "0.16.1" +version = "0.21.1" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "burner-redis" }, { name = "cloudpickle" }, - { name = "fakeredis", extra = ["lua"] }, + { name = "cronsim" }, { name = "opentelemetry-api" }, - { name = "opentelemetry-exporter-prometheus" }, - { name = "opentelemetry-instrumentation" }, { name = "prometheus-client" }, { name = "py-key-value-aio", extra = ["memory", "redis"] }, { name = "python-json-logger" }, @@ -3921,10 +3933,12 @@ dependencies = [ { name = "rich" }, { name = "typer" }, { name = "typing-extensions" }, + { name = "tzdata", marker = "sys_platform == 'win32'" }, + { name = "uncalled-for" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2b/ff/87e931e4abc7efb1e8c16adaa55327452931e8c5f460f2ba089447673226/pydocket-0.16.1.tar.gz", hash = "sha256:8663cb6dc801d8b8d703541fb665f4099c84f4d10d8f3fd441e208b080aa4826", size = 289028, upload-time = "2025-12-19T19:43:48.773Z" } +sdist = { url = "https://files.pythonhosted.org/packages/88/5f/2f68c38ac3fdbff3cdec3ccfe31303ae5972f3e3f1c365d0ec71573dfd2e/pydocket-0.21.1.tar.gz", hash = "sha256:79d19d5f3be29caa23eba95226a516f8b2aed3ba1ad7830095fdce59f49b51f7", size = 399652, upload-time = "2026-05-29T21:04:10.855Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ad/ab/0da7d0397112546309709f464bdf65de4e1697e3caba07556751fc4d8bcd/pydocket-0.16.1-py3-none-any.whl", hash = "sha256:bc6ccf7e91164761def854b4014101abf23c3cc2fb7d0fa2c4e07ea3bf6a1826", size = 63208, upload-time = "2025-12-19T19:43:47.309Z" }, + { url = "https://files.pythonhosted.org/packages/e5/ca/6bd186338cf4abb318825d9e8d2d2cf069432f9217d17f78f61204eaba70/pydocket-0.21.1-py3-none-any.whl", hash = "sha256:7f3b12c307488efbfde96f76dac533babf5dff4ce72dc0a108a49de03ab39564", size = 117222, upload-time = "2026-05-29T21:04:09.375Z" }, ] [[package]] @@ -5242,6 +5256,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" }, ] +[[package]] +name = "uncalled-for" +version = "0.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/82/345cc927f7fbdae6065e7768759932fcc827fc20b29b45dfbafa2f1f7da4/uncalled_for-0.3.2.tar.gz", hash = "sha256:89f5dbcd71e2b8f47c030b1fa302e6cce2ec795d1ac565eeb6525c5fe55cb8a2", size = 50032, upload-time = "2026-05-06T13:38:25.204Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/25/2c87754f3a9e692315f7b811244090e68f362979fc8886b3fbd2985a1d8c/uncalled_for-0.3.2-py3-none-any.whl", hash = "sha256:0ff60b142c7d1f8070bde9d42afaa70aedc77dcc10998c227687e9c15713418e", size = 11444, upload-time = "2026-05-06T13:38:24.025Z" }, +] + [[package]] name = "urllib3" version = "2.3.0" diff --git a/examples/aws-containerized/openai-dynamodb-scalable/app_agent_runner.py b/examples/aws-containerized/openai-dynamodb-scalable/app_agent_runner.py index aa6306ff3..1450b2316 100644 --- a/examples/aws-containerized/openai-dynamodb-scalable/app_agent_runner.py +++ b/examples/aws-containerized/openai-dynamodb-scalable/app_agent_runner.py @@ -24,4 +24,4 @@ def shutdown(*args): logging.info("Agent Runner heartbeat") logging.info("Agent Runner stopping") -sys.exit(0) \ No newline at end of file +sys.exit(0) diff --git a/examples/aws-containerized/openai-dynamodb/uv.lock b/examples/aws-containerized/openai-dynamodb/uv.lock index 94718135e..66f542065 100644 --- a/examples/aws-containerized/openai-dynamodb/uv.lock +++ b/examples/aws-containerized/openai-dynamodb/uv.lock @@ -15,7 +15,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -24,9 +24,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -230,11 +230,11 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "agentkernel", extras = ["openai", "api", "aws"], specifier = ">=0.4.0" }] +requires-dist = [{ name = "agentkernel", extras = ["openai", "api", "aws"], specifier = ">=0.5.1" }] [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "isort", specifier = ">=5.0.0" }, { name = "mypy", specifier = ">=1.0.0" }, diff --git a/examples/aws-serverless/adk/uv.lock b/examples/aws-serverless/adk/uv.lock index c2ed7d48a..5351d947f 100644 --- a/examples/aws-serverless/adk/uv.lock +++ b/examples/aws-serverless/adk/uv.lock @@ -24,7 +24,7 @@ wheels = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -33,9 +33,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -226,11 +226,11 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "agentkernel", extras = ["adk", "redis"], specifier = ">=0.4.0" }] +requires-dist = [{ name = "agentkernel", extras = ["adk", "redis"], specifier = ">=0.5.1" }] [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "isort", specifier = ">=5.0.0" }, { name = "mypy", specifier = ">=1.0.0" }, diff --git a/examples/aws-serverless/crewai/uv.lock b/examples/aws-serverless/crewai/uv.lock index 6462174e2..4b138f56d 100644 --- a/examples/aws-serverless/crewai/uv.lock +++ b/examples/aws-serverless/crewai/uv.lock @@ -15,7 +15,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -24,9 +24,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -193,13 +193,13 @@ dev = [ [package.metadata] requires-dist = [ - { name = "agentkernel", extras = ["crewai", "redis"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["crewai", "redis"], specifier = ">=0.5.1" }, { name = "crewai", specifier = "==0.165.1" }, ] [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "isort", specifier = ">=5.0.0" }, { name = "mypy", specifier = ">=1.0.0" }, diff --git a/examples/aws-serverless/langgraph/uv.lock b/examples/aws-serverless/langgraph/uv.lock index 4589a1fba..ac687d8a0 100644 --- a/examples/aws-serverless/langgraph/uv.lock +++ b/examples/aws-serverless/langgraph/uv.lock @@ -15,7 +15,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -24,9 +24,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -197,7 +197,7 @@ dev = [ [package.metadata] requires-dist = [ - { name = "agentkernel", extras = ["langgraph", "redis"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["langgraph", "redis"], specifier = ">=0.5.1" }, { name = "langchain-litellm", specifier = ">=0.2.2" }, { name = "langchain-openai", specifier = ">=0.3.28" }, { name = "langgraph-supervisor", specifier = ">=0.0.29" }, @@ -206,7 +206,7 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "isort", specifier = ">=5.0.0" }, { name = "mypy", specifier = ">=1.0.0" }, diff --git a/examples/aws-serverless/openai-auth/uv.lock b/examples/aws-serverless/openai-auth/uv.lock index e1c3d2aff..16a8cacfc 100644 --- a/examples/aws-serverless/openai-auth/uv.lock +++ b/examples/aws-serverless/openai-auth/uv.lock @@ -4,7 +4,7 @@ requires-python = ">=3.12" [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -13,9 +13,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -94,11 +94,11 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "agentkernel", extras = ["openai", "redis", "auth"], specifier = ">=0.4.0" }] +requires-dist = [{ name = "agentkernel", extras = ["openai", "redis", "auth"], specifier = ">=0.5.1" }] [package.metadata.requires-dev] auth = [ - { name = "agentkernel", specifier = ">=0.4.0" }, + { name = "agentkernel", specifier = ">=0.5.1" }, { name = "annotated-types", specifier = "==0.7.0" }, { name = "cffi", specifier = "==2.0.0" }, { name = "cryptography", specifier = "==46.0.4" }, diff --git a/examples/aws-serverless/openai/uv.lock b/examples/aws-serverless/openai/uv.lock index 7ba65c426..e9732c0c2 100644 --- a/examples/aws-serverless/openai/uv.lock +++ b/examples/aws-serverless/openai/uv.lock @@ -15,7 +15,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -24,9 +24,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -224,11 +224,11 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "agentkernel", extras = ["openai", "redis"], specifier = ">=0.4.0" }] +requires-dist = [{ name = "agentkernel", extras = ["openai", "redis"], specifier = ">=0.5.1" }] [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "isort", specifier = ">=5.0.0" }, { name = "mypy", specifier = ">=1.0.0" }, diff --git a/examples/aws-serverless/scalable-openai/uv.lock b/examples/aws-serverless/scalable-openai/uv.lock index b95cc6d94..4735e3273 100644 --- a/examples/aws-serverless/scalable-openai/uv.lock +++ b/examples/aws-serverless/scalable-openai/uv.lock @@ -15,7 +15,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -24,9 +24,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -224,11 +224,11 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "agentkernel", extras = ["aws", "openai"], specifier = ">=0.4.0" }] +requires-dist = [{ name = "agentkernel", extras = ["aws", "openai"], specifier = ">=0.5.1" }] [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "isort", specifier = ">=5.0.0" }, { name = "mypy", specifier = ">=1.0.0" }, diff --git a/examples/aws-serverless/websocket-openai/uv.lock b/examples/aws-serverless/websocket-openai/uv.lock index d2d39b1a5..f6c9c4c30 100644 --- a/examples/aws-serverless/websocket-openai/uv.lock +++ b/examples/aws-serverless/websocket-openai/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.12" resolution-markers = [ "python_full_version >= '3.15' and sys_platform == 'win32'", @@ -18,7 +18,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -27,9 +27,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -340,11 +340,11 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "agentkernel", extras = ["aws", "openai", "redis"], specifier = ">=0.4.0" }] +requires-dist = [{ name = "agentkernel", extras = ["aws", "openai", "redis"], specifier = ">=0.5.1" }] [package.metadata.requires-dev] auth = [ - { name = "agentkernel", specifier = ">=0.4.0" }, + { name = "agentkernel", specifier = ">=0.5.1" }, { name = "annotated-types", specifier = "==0.7.0" }, { name = "cffi", specifier = "==2.0.0" }, { name = "cryptography", specifier = "==46.0.4" }, @@ -360,7 +360,7 @@ auth = [ { name = "typing-inspection", specifier = "==0.4.2" }, ] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "isort", specifier = ">=5.0.0" }, { name = "mypy", specifier = ">=1.0.0" }, @@ -1105,9 +1105,7 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ef/32/f2ce6d4cac3e55bc6173f92dbe627e782e1850f89d986c3606feb63aafa7/greenlet-3.5.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:db2910d3c809444e0a20147361f343fe2798e106af8d9d8506f5305302655a9f", size = 286228, upload-time = "2026-04-27T12:20:34.421Z" }, { url = "https://files.pythonhosted.org/packages/b7/aa/caed9e5adf742315fc7be2a84196373aab4816e540e38ba0d76cb7584d68/greenlet-3.5.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ec9ea74e7268ace7f9aab1b1a4e730193fc661b39a993cd91c606c32d4a3628", size = 601775, upload-time = "2026-04-27T12:52:41.045Z" }, { url = "https://files.pythonhosted.org/packages/c7/af/90ae08497400a941595d12774447f752d3dfe0fbb012e35b76bc5c0ff37e/greenlet-3.5.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54d243512da35485fc7a6bf3c178fdda6327a9d6506fcdd62b1abd1e41b2927b", size = 614436, upload-time = "2026-04-27T12:59:41.595Z" }, - { url = "https://files.pythonhosted.org/packages/3f/e9/4eeadf8cb3403ac274245ba75f07844abc7fa5f6787583fc9156ba741e0f/greenlet-3.5.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:41353ec2ecedf7aa8f682753a41919f8718031a6edac46b8d3dc7ed9e1ceb136", size = 620610, upload-time = "2026-04-27T13:02:39.194Z" }, { url = "https://files.pythonhosted.org/packages/2b/e0/2e13df68f367e2f9960616927d60857dd7e56aaadd59a47c644216b2f920/greenlet-3.5.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d280a7f5c331622c69f97eb167f33577ff2d1df282c41cd15907fc0a3ca198c", size = 611388, upload-time = "2026-04-27T12:25:28.008Z" }, - { url = "https://files.pythonhosted.org/packages/ee/ef/f913b3c0eb7d26d86a2401c5e1546c9d46b657efee724b06f6f4ac5d8824/greenlet-3.5.0-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:58c1c374fe2b3d852f9b6b11a7dff4c85404e51b9a596fd9e89cf904eb09866d", size = 422775, upload-time = "2026-04-27T13:05:14.261Z" }, { url = "https://files.pythonhosted.org/packages/82/f7/393c64055132ac0d488ef6be549253b7e6274194863967ddc0bc8f5b87b8/greenlet-3.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1eb67d5adefb5bd2e182d42678a328979a209e4e82eb93575708185d31d1f588", size = 1570768, upload-time = "2026-04-27T12:53:28.099Z" }, { url = "https://files.pythonhosted.org/packages/b8/4b/eaf7735253522cf56d1b74d672a58f54fc114702ceaf05def59aae72f6e1/greenlet-3.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2628d6c86f6cb0cb45e0c3c54058bbec559f57eaae699447748cb3928150577e", size = 1635983, upload-time = "2026-04-27T12:25:26.903Z" }, { url = "https://files.pythonhosted.org/packages/4c/fe/4fb3a0805bd5165da5ebf858da7cc01cce8061674106d2cf5bdab32cbfde/greenlet-3.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:d4d9f0624c775f2dfc56ba54d515a8c771044346852a918b405914f6b19d7fd8", size = 238840, upload-time = "2026-04-27T12:23:54.806Z" }, @@ -1115,9 +1113,7 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0c/58/fc576f99037ce19c5aa16628e4c3226b6d1419f72a62c79f5f40576e6eb3/greenlet-3.5.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:5a5ed18de6a0f6cc7087f1563f6bd93fc7df1c19165ca01e9bde5a5dc281d106", size = 285066, upload-time = "2026-04-27T12:23:05.033Z" }, { url = "https://files.pythonhosted.org/packages/4a/ba/b28ddbe6bfad6a8ac196ef0e8cff37bc65b79735995b9e410923fffeeb70/greenlet-3.5.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a717fbc46d8a354fa675f7c1e813485b6ba3885f9bef0cd56e5ba27d758ff5b", size = 604414, upload-time = "2026-04-27T12:52:42.358Z" }, { url = "https://files.pythonhosted.org/packages/09/06/4b69f8f0b67603a8be2790e55107a190b376f2627fe0eaf5695d85ffb3cd/greenlet-3.5.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ddc090c5c1792b10246a78e8c2163ebbe04cf877f9d785c230a7b27b39ad038e", size = 617349, upload-time = "2026-04-27T12:59:43.32Z" }, - { url = "https://files.pythonhosted.org/packages/6a/15/a643b4ecd09969e30b8a150d5919960caae0abe4f5af75ab040b1ab85e78/greenlet-3.5.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4964101b8585c144cbda5532b1aa644255126c08a265dae90c16e7a0e63aaa9d", size = 623234, upload-time = "2026-04-27T13:02:40.611Z" }, { url = "https://files.pythonhosted.org/packages/8a/17/a3918541fd0ddefe024a69de6d16aa7b46d36ac19562adaa63c7fa180eff/greenlet-3.5.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2094acd54b272cb6eae8c03dd87b3fa1820a4cef18d6889c378d503500a1dc13", size = 613927, upload-time = "2026-04-27T12:25:30.28Z" }, - { url = "https://files.pythonhosted.org/packages/77/18/3b13d5ef1275b0ffaf933b05efa21408ac4ca95823c7411d79682e4fdcff/greenlet-3.5.0-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:7022615368890680e67b9965d33f5773aade330d5343bbe25560135aaa849eae", size = 425243, upload-time = "2026-04-27T13:05:15.689Z" }, { url = "https://files.pythonhosted.org/packages/ee/e1/bd0af6213c7dd33175d8a462d4c1fe1175124ebed4855bc1475a5b5242c2/greenlet-3.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5e05ba267789ea87b5a155cf0e810b1ab88bf18e9e8740813945ceb8ee4350ba", size = 1570893, upload-time = "2026-04-27T12:53:29.483Z" }, { url = "https://files.pythonhosted.org/packages/9b/2a/0789702f864f5382cb476b93d7a9c823c10472658102ccd65f415747d2e2/greenlet-3.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0ecec963079cd58cbd14723582384f11f166fd58883c15dcbfb342e0bc9b5846", size = 1636060, upload-time = "2026-04-27T12:25:28.845Z" }, { url = "https://files.pythonhosted.org/packages/b2/8f/22bf9df92bbff0eb07842b60f7e63bf7675a9742df628437a9f02d09137f/greenlet-3.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:728d9667d8f2f586644b748dbd9bb67e50d6a9381767d1357714ea6825bb3bf5", size = 238740, upload-time = "2026-04-27T12:24:01.341Z" }, @@ -1125,9 +1121,7 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/94/5e/a70f31e3e8d961c4ce589c15b28e4225d63704e431a23932a3808cbcc867/greenlet-3.5.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:f35807464c4c58c55f0d31dfa83c541a5615d825c2fe3d2b95360cf7c4e3c0a8", size = 285564, upload-time = "2026-04-27T12:23:08.555Z" }, { url = "https://files.pythonhosted.org/packages/af/a6/046c0a28e21833e4086918218cfb3d8bed51c075a1b700f20b9d7861c0f4/greenlet-3.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55fa7ea52771be44af0de27d8b80c02cd18c2c3cddde6c847ecebdf72418b6a1", size = 651166, upload-time = "2026-04-27T12:52:43.644Z" }, { url = "https://files.pythonhosted.org/packages/47/f8/4af27f71c5ff32a7fbc516adb46370d9c4ae2bc7bd3dc7d066ac542b4b15/greenlet-3.5.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a97e4821aa710603f94de0da25f25096454d78ffdace5dc77f3a006bc01abba3", size = 663792, upload-time = "2026-04-27T12:59:44.93Z" }, - { url = "https://files.pythonhosted.org/packages/fb/89/2dadb89793c37ee8b4c237857188293e9060dc085f19845c292e00f8e091/greenlet-3.5.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bf2d8a80bec89ab46221ae45c5373d5ba0bd36c19aa8508e85c6cd7e5106cd37", size = 668086, upload-time = "2026-04-27T13:02:42.314Z" }, { url = "https://files.pythonhosted.org/packages/a3/59/1bd6d7428d6ed9106efbb8c52310c60fd04f6672490f452aeaa3829aa436/greenlet-3.5.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f52a464e4ed91780bdfbbdd2b97197f3accaa629b98c200f4dffada759f3ae7", size = 660933, upload-time = "2026-04-27T12:25:33.276Z" }, - { url = "https://files.pythonhosted.org/packages/82/35/75722be7e26a2af4cbd2dc35b0ed382dacf9394b7e75551f76ed1abe87f2/greenlet-3.5.0-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:1bae92a1dd94c5f9d9493c3a212dd874c202442047cf96446412c862feca83a2", size = 470799, upload-time = "2026-04-27T13:05:17.094Z" }, { url = "https://files.pythonhosted.org/packages/83/e4/b903e5a5fae1e8a28cdd32a0cfbfd560b668c25b692f67768822ddc5f40f/greenlet-3.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:762612baf1161ccb8437c0161c668a688223cba28e1bf038f4eb47b13e39ccdf", size = 1618401, upload-time = "2026-04-27T12:53:31.062Z" }, { url = "https://files.pythonhosted.org/packages/0e/e3/5ec408a329acb854fb607a122e1ee5fb3ff649f9a97952948a90803c0d8e/greenlet-3.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:57a43c6079a89713522bc4bcb9f75070ecf5d3dbad7792bfe42239362cbf2a16", size = 1682038, upload-time = "2026-04-27T12:25:31.838Z" }, { url = "https://files.pythonhosted.org/packages/91/20/6b165108058767ee643c55c5c4904d591a830ee2b3c7dbd359828fbc829f/greenlet-3.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:3bc59be3945ae9750b9e7d45067d01ae3fe90ea5f9ade99239dabdd6e28a5033", size = 239835, upload-time = "2026-04-27T12:24:54.136Z" }, @@ -1135,9 +1129,7 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/78/a8/4522939255bb5409af4e87132f915446bf3622c2c292d14d3c38d128ae82/greenlet-3.5.0-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:a10a732421ab4fec934783ce3e54763470d0181db6e3468f9103a275c3ed1853", size = 293614, upload-time = "2026-04-27T12:24:12.874Z" }, { url = "https://files.pythonhosted.org/packages/15/5e/8744c52e2c027b5a8772a01561934c8835f869733e101f62075c60430340/greenlet-3.5.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7fc391b1566f2907d17aaebe78f8855dc45675159a775fcf9e61f8ee0078e87f", size = 650723, upload-time = "2026-04-27T12:52:45.412Z" }, { url = "https://files.pythonhosted.org/packages/00/ef/7b4c39c03cf46ceca512c5d3f914afd85aa30b2cc9a93015b0dd73e4be6c/greenlet-3.5.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:680bd0e7ad5e8daa8a4aa89f68fd6adc834b8a8036dc256533f7e08f4a4b01f7", size = 656529, upload-time = "2026-04-27T12:59:46.295Z" }, - { url = "https://files.pythonhosted.org/packages/5f/5c/0602239503b124b70e39355cbdb39361ecfe65b87a5f2f63752c32f5286f/greenlet-3.5.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1aa4ce8debcd4ea7fb2e150f3036588c41493d1d52c43538924ae1819003f4ce", size = 657015, upload-time = "2026-04-27T13:02:43.973Z" }, { url = "https://files.pythonhosted.org/packages/0b/b5/c7768f352f5c010f92064d0063f987e7dc0cd290a6d92a34109015ce4aa1/greenlet-3.5.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddb36c7d6c9c0a65f18c7258634e0c416c6ab59caac8c987b96f80c2ebda0112", size = 654364, upload-time = "2026-04-27T12:25:35.64Z" }, - { url = "https://files.pythonhosted.org/packages/38/51/8699f865f125dc952384cb432b0f7138aa4d8f2969a7d12d0df5b94d054d/greenlet-3.5.0-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:728a73687e39ae9ca34e4694cbf2f049d3fbc7174639468d0f67200a97d8f9e2", size = 488275, upload-time = "2026-04-27T13:05:18.28Z" }, { url = "https://files.pythonhosted.org/packages/ef/d0/079ebe12e4b1fc758857ce5be1a5e73f06870f2101e52611d1e71925ce54/greenlet-3.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e5ddf316ced87539144621453c3aef229575825fe60c604e62bedc4003f372b2", size = 1614204, upload-time = "2026-04-27T12:53:32.618Z" }, { url = "https://files.pythonhosted.org/packages/6d/89/6c2fb63df3596552d20e58fb4d96669243388cf680cff222758812c7bfaa/greenlet-3.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4a448128607be0de65342dc9b31be7f948ef4cc0bc8832069350abefd310a8f2", size = 1675480, upload-time = "2026-04-27T12:25:34.168Z" }, { url = "https://files.pythonhosted.org/packages/15/32/77ee8a6c1564fc345a491a4e85b3bf360e4cf26eac98c4532d2fdb96e01f/greenlet-3.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d60097128cb0a1cab9ea541186ea13cd7b847b8449a7787c2e2350da0cb82d86", size = 245324, upload-time = "2026-04-27T12:24:40.295Z" }, diff --git a/examples/azure-containerized/openai-cosmos/uv.lock b/examples/azure-containerized/openai-cosmos/uv.lock index d61ec6587..458c5426a 100644 --- a/examples/azure-containerized/openai-cosmos/uv.lock +++ b/examples/azure-containerized/openai-cosmos/uv.lock @@ -15,7 +15,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -24,9 +24,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -231,7 +231,7 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "agentkernel", extras = ["openai", "api", "azure", "test"], specifier = ">=0.4.0" }] +requires-dist = [{ name = "agentkernel", extras = ["openai", "api", "azure", "test"], specifier = ">=0.5.1" }] [package.metadata.requires-dev] dev = [ diff --git a/examples/azure-serverless/openai/uv.lock b/examples/azure-serverless/openai/uv.lock index 29a060183..2043574c7 100644 --- a/examples/azure-serverless/openai/uv.lock +++ b/examples/azure-serverless/openai/uv.lock @@ -15,7 +15,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -24,9 +24,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -270,11 +270,11 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "agentkernel", extras = ["openai", "redis", "azure"], specifier = ">=0.4.0" }] +requires-dist = [{ name = "agentkernel", extras = ["openai", "redis", "azure"], specifier = ">=0.5.1" }] [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "isort", specifier = ">=5.0.0" }, { name = "mypy", specifier = ">=1.0.0" }, diff --git a/examples/cli/adk/uv.lock b/examples/cli/adk/uv.lock index c9404d97e..8a7b0ea8c 100644 --- a/examples/cli/adk/uv.lock +++ b/examples/cli/adk/uv.lock @@ -17,7 +17,7 @@ wheels = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -26,9 +26,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -358,11 +358,11 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "agentkernel", extras = ["cli", "adk"], specifier = ">=0.4.0" }] +requires-dist = [{ name = "agentkernel", extras = ["cli", "adk"], specifier = ">=0.5.1" }] [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "isort", specifier = ">=5.0.0" }, { name = "mypy", specifier = ">=1.0.0" }, diff --git a/examples/cli/crewai/uv.lock b/examples/cli/crewai/uv.lock index cbc0218c5..0900cd3a0 100644 --- a/examples/cli/crewai/uv.lock +++ b/examples/cli/crewai/uv.lock @@ -18,7 +18,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -27,9 +27,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -555,13 +555,13 @@ dev = [ [package.metadata] requires-dist = [ - { name = "agentkernel", extras = ["cli", "crewai"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["cli", "crewai"], specifier = ">=0.5.1" }, { name = "crewai", specifier = "==0.165.1" }, ] [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "isort", specifier = ">=5.0.0" }, { name = "mypy", specifier = ">=1.0.0" }, diff --git a/examples/cli/guardrail/bedrock/uv.lock b/examples/cli/guardrail/bedrock/uv.lock index f66eaf63d..4445c7ef9 100644 --- a/examples/cli/guardrail/bedrock/uv.lock +++ b/examples/cli/guardrail/bedrock/uv.lock @@ -10,7 +10,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -19,9 +19,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -628,7 +628,7 @@ dev = [ [package.metadata] requires-dist = [ - { name = "agentkernel", extras = ["cli", "langgraph", "aws"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["cli", "langgraph", "aws"], specifier = ">=0.5.1" }, { name = "langchain-litellm", specifier = ">=0.2.2" }, { name = "langchain-openai", specifier = ">=0.3.28" }, { name = "langgraph-supervisor", specifier = ">=0.0.29" }, @@ -637,7 +637,7 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "isort", specifier = ">=5.0.0" }, { name = "mypy", specifier = ">=1.0.0" }, diff --git a/examples/cli/guardrail/openai/uv.lock b/examples/cli/guardrail/openai/uv.lock index 017a67681..0f95a7167 100644 --- a/examples/cli/guardrail/openai/uv.lock +++ b/examples/cli/guardrail/openai/uv.lock @@ -10,7 +10,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -19,9 +19,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -824,7 +824,7 @@ dev = [ [package.metadata] requires-dist = [ - { name = "agentkernel", extras = ["cli", "langgraph", "openai"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["cli", "langgraph", "openai"], specifier = ">=0.5.1" }, { name = "langchain-litellm", specifier = ">=0.2.2" }, { name = "langchain-openai", specifier = ">=0.3.28" }, { name = "langgraph-supervisor", specifier = ">=0.0.29" }, @@ -833,7 +833,7 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "isort", specifier = ">=5.0.0" }, { name = "mypy", specifier = ">=1.0.0" }, diff --git a/examples/cli/guardrail/walledai/uv.lock b/examples/cli/guardrail/walledai/uv.lock index ca5052e7e..7464a543c 100644 --- a/examples/cli/guardrail/walledai/uv.lock +++ b/examples/cli/guardrail/walledai/uv.lock @@ -10,7 +10,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -19,9 +19,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -600,7 +600,7 @@ dev = [ [package.metadata] requires-dist = [ - { name = "agentkernel", extras = ["cli", "langgraph", "walledai"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["cli", "langgraph", "walledai"], specifier = ">=0.5.1" }, { name = "langchain-litellm", specifier = ">=0.2.2" }, { name = "langchain-openai", specifier = ">=0.3.28" }, { name = "langgraph-supervisor", specifier = ">=0.0.29" }, @@ -609,7 +609,7 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "isort", specifier = ">=5.0.0" }, { name = "mypy", specifier = ">=1.0.0" }, diff --git a/examples/cli/langgraph/uv.lock b/examples/cli/langgraph/uv.lock index 983ed48b6..c52a7416e 100644 --- a/examples/cli/langgraph/uv.lock +++ b/examples/cli/langgraph/uv.lock @@ -8,7 +8,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -17,9 +17,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -296,7 +296,7 @@ dev = [ [package.metadata] requires-dist = [ - { name = "agentkernel", extras = ["cli", "langgraph"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["cli", "langgraph"], specifier = ">=0.5.1" }, { name = "langchain-litellm", specifier = ">=0.2.2" }, { name = "langchain-openai", specifier = ">=0.3.28" }, { name = "langgraph-supervisor", specifier = ">=0.0.29" }, @@ -305,7 +305,7 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "isort", specifier = ">=5.0.0" }, { name = "mypy", specifier = ">=1.0.0" }, diff --git a/examples/cli/multi/uv.lock b/examples/cli/multi/uv.lock index aaa305ccc..87042e3f5 100644 --- a/examples/cli/multi/uv.lock +++ b/examples/cli/multi/uv.lock @@ -8,7 +8,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -17,9 +17,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -502,11 +502,11 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "agentkernel", extras = ["cli", "crewai", "openai"], specifier = ">=0.4.0" }] +requires-dist = [{ name = "agentkernel", extras = ["cli", "crewai", "openai"], specifier = ">=0.5.1" }] [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "isort", specifier = ">=5.0.0" }, { name = "mypy", specifier = ">=1.0.0" }, diff --git a/examples/cli/openai-dynamic/uv.lock b/examples/cli/openai-dynamic/uv.lock index cb195d906..92f8b45a3 100644 --- a/examples/cli/openai-dynamic/uv.lock +++ b/examples/cli/openai-dynamic/uv.lock @@ -9,7 +9,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -18,9 +18,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -348,11 +348,11 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "agentkernel", extras = ["cli", "openai"], specifier = ">=0.4.0" }] +requires-dist = [{ name = "agentkernel", extras = ["cli", "openai"], specifier = ">=0.5.1" }] [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "isort", specifier = ">=5.0.0" }, { name = "mypy", specifier = ">=1.0.0" }, diff --git a/examples/cli/openai/uv.lock b/examples/cli/openai/uv.lock index d10721b1e..1f4935424 100644 --- a/examples/cli/openai/uv.lock +++ b/examples/cli/openai/uv.lock @@ -9,7 +9,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -18,9 +18,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -348,11 +348,11 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "agentkernel", extras = ["cli", "openai"], specifier = ">=0.4.0" }] +requires-dist = [{ name = "agentkernel", extras = ["cli", "openai"], specifier = ">=0.5.1" }] [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "isort", specifier = ">=5.0.0" }, { name = "mypy", specifier = ">=1.0.0" }, diff --git a/examples/cli/smolagents/uv.lock b/examples/cli/smolagents/uv.lock index 59a582485..79eb714a1 100644 --- a/examples/cli/smolagents/uv.lock +++ b/examples/cli/smolagents/uv.lock @@ -15,7 +15,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -24,12 +24,15 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] +smolagents = [ + { name = "smolagents" }, +] test = [ { name = "datasets" }, { name = "litellm" }, @@ -318,13 +321,13 @@ name = "cli-smolagents" version = "0.1.0" source = { virtual = "." } dependencies = [ - { name = "agentkernel" }, + { name = "agentkernel", extra = ["smolagents"] }, { name = "smolagents" }, ] [package.dev-dependencies] dev = [ - { name = "agentkernel", extra = ["test"] }, + { name = "agentkernel", extra = ["smolagents", "test"] }, { name = "black" }, { name = "isort" }, { name = "mypy" }, @@ -332,13 +335,13 @@ dev = [ [package.metadata] requires-dist = [ - { name = "agentkernel", extras = ["cli", "smolagents"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["cli", "smolagents"], specifier = ">=0.5.1" }, { name = "smolagents" }, ] [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test", "smolagents"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["test", "smolagents"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "isort", specifier = ">=5.0.0" }, { name = "mypy", specifier = ">=1.0.0" }, @@ -697,7 +700,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ea/ab/1608e5a7578e62113506740b88066bf09888322a311cff602105e619bd87/greenlet-3.3.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:ac8d61d4343b799d1e526db579833d72f23759c71e07181c2d2944e429eb09cd", size = 280358, upload-time = "2026-02-20T20:17:43.971Z" }, { url = "https://files.pythonhosted.org/packages/a5/23/0eae412a4ade4e6623ff7626e38998cb9b11e9ff1ebacaa021e4e108ec15/greenlet-3.3.2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ceec72030dae6ac0c8ed7591b96b70410a8be370b6a477b1dbc072856ad02bd", size = 601217, upload-time = "2026-02-20T20:47:31.462Z" }, { url = "https://files.pythonhosted.org/packages/f8/16/5b1678a9c07098ecb9ab2dd159fafaf12e963293e61ee8d10ecb55273e5e/greenlet-3.3.2-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a2a5be83a45ce6188c045bcc44b0ee037d6a518978de9a5d97438548b953a1ac", size = 611792, upload-time = "2026-02-20T20:55:58.423Z" }, - { url = "https://files.pythonhosted.org/packages/5c/c5/cc09412a29e43406eba18d61c70baa936e299bc27e074e2be3806ed29098/greenlet-3.3.2-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ae9e21c84035c490506c17002f5c8ab25f980205c3e61ddb3a2a2a2e6c411fcb", size = 626250, upload-time = "2026-02-20T21:02:46.596Z" }, { url = "https://files.pythonhosted.org/packages/50/1f/5155f55bd71cabd03765a4aac9ac446be129895271f73872c36ebd4b04b6/greenlet-3.3.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43e99d1749147ac21dde49b99c9abffcbc1e2d55c67501465ef0930d6e78e070", size = 613875, upload-time = "2026-02-20T20:21:01.102Z" }, { url = "https://files.pythonhosted.org/packages/fc/dd/845f249c3fcd69e32df80cdab059b4be8b766ef5830a3d0aa9d6cad55beb/greenlet-3.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4c956a19350e2c37f2c48b336a3afb4bff120b36076d9d7fb68cb44e05d95b79", size = 1571467, upload-time = "2026-02-20T20:49:33.495Z" }, { url = "https://files.pythonhosted.org/packages/2a/50/2649fe21fcc2b56659a452868e695634722a6655ba245d9f77f5656010bf/greenlet-3.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6c6f8ba97d17a1e7d664151284cb3315fc5f8353e75221ed4324f84eb162b395", size = 1640001, upload-time = "2026-02-20T20:21:09.154Z" }, @@ -706,7 +708,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ac/48/f8b875fa7dea7dd9b33245e37f065af59df6a25af2f9561efa8d822fde51/greenlet-3.3.2-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:aa6ac98bdfd716a749b84d4034486863fd81c3abde9aa3cf8eff9127981a4ae4", size = 279120, upload-time = "2026-02-20T20:19:01.9Z" }, { url = "https://files.pythonhosted.org/packages/49/8d/9771d03e7a8b1ee456511961e1b97a6d77ae1dea4a34a5b98eee706689d3/greenlet-3.3.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab0c7e7901a00bc0a7284907273dc165b32e0d109a6713babd04471327ff7986", size = 603238, upload-time = "2026-02-20T20:47:32.873Z" }, { url = "https://files.pythonhosted.org/packages/59/0e/4223c2bbb63cd5c97f28ffb2a8aee71bdfb30b323c35d409450f51b91e3e/greenlet-3.3.2-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d248d8c23c67d2291ffd47af766e2a3aa9fa1c6703155c099feb11f526c63a92", size = 614219, upload-time = "2026-02-20T20:55:59.817Z" }, - { url = "https://files.pythonhosted.org/packages/94/2b/4d012a69759ac9d77210b8bfb128bc621125f5b20fc398bce3940d036b1c/greenlet-3.3.2-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ccd21bb86944ca9be6d967cf7691e658e43417782bce90b5d2faeda0ff78a7dd", size = 628268, upload-time = "2026-02-20T21:02:48.024Z" }, { url = "https://files.pythonhosted.org/packages/7a/34/259b28ea7a2a0c904b11cd36c79b8cef8019b26ee5dbe24e73b469dea347/greenlet-3.3.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6997d360a4e6a4e936c0f9625b1c20416b8a0ea18a8e19cabbefc712e7397ab", size = 616774, upload-time = "2026-02-20T20:21:02.454Z" }, { url = "https://files.pythonhosted.org/packages/0a/03/996c2d1689d486a6e199cb0f1cf9e4aa940c500e01bdf201299d7d61fa69/greenlet-3.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:64970c33a50551c7c50491671265d8954046cb6e8e2999aacdd60e439b70418a", size = 1571277, upload-time = "2026-02-20T20:49:34.795Z" }, { url = "https://files.pythonhosted.org/packages/d9/c4/2570fc07f34a39f2caf0bf9f24b0a1a0a47bc2e8e465b2c2424821389dfc/greenlet-3.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1a9172f5bf6bd88e6ba5a84e0a68afeac9dc7b6b412b245dd64f52d83c81e55b", size = 1640455, upload-time = "2026-02-20T20:21:10.261Z" }, @@ -715,7 +716,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3f/ae/8bffcbd373b57a5992cd077cbe8858fff39110480a9d50697091faea6f39/greenlet-3.3.2-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:8d1658d7291f9859beed69a776c10822a0a799bc4bfe1bd4272bb60e62507dab", size = 279650, upload-time = "2026-02-20T20:18:00.783Z" }, { url = "https://files.pythonhosted.org/packages/d1/c0/45f93f348fa49abf32ac8439938726c480bd96b2a3c6f4d949ec0124b69f/greenlet-3.3.2-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18cb1b7337bca281915b3c5d5ae19f4e76d35e1df80f4ad3c1a7be91fadf1082", size = 650295, upload-time = "2026-02-20T20:47:34.036Z" }, { url = "https://files.pythonhosted.org/packages/b3/de/dd7589b3f2b8372069ab3e4763ea5329940fc7ad9dcd3e272a37516d7c9b/greenlet-3.3.2-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c2e47408e8ce1c6f1ceea0dffcdf6ebb85cc09e55c7af407c99f1112016e45e9", size = 662163, upload-time = "2026-02-20T20:56:01.295Z" }, - { url = "https://files.pythonhosted.org/packages/cd/ac/85804f74f1ccea31ba518dcc8ee6f14c79f73fe36fa1beba38930806df09/greenlet-3.3.2-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e3cb43ce200f59483eb82949bf1835a99cf43d7571e900d7c8d5c62cdf25d2f9", size = 675371, upload-time = "2026-02-20T21:02:49.664Z" }, { url = "https://files.pythonhosted.org/packages/d2/d8/09bfa816572a4d83bccd6750df1926f79158b1c36c5f73786e26dbe4ee38/greenlet-3.3.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63d10328839d1973e5ba35e98cccbca71b232b14051fd957b6f8b6e8e80d0506", size = 664160, upload-time = "2026-02-20T20:21:04.015Z" }, { url = "https://files.pythonhosted.org/packages/48/cf/56832f0c8255d27f6c35d41b5ec91168d74ec721d85f01a12131eec6b93c/greenlet-3.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8e4ab3cfb02993c8cc248ea73d7dae6cec0253e9afa311c9b37e603ca9fad2ce", size = 1619181, upload-time = "2026-02-20T20:49:36.052Z" }, { url = "https://files.pythonhosted.org/packages/0a/23/b90b60a4aabb4cec0796e55f25ffbfb579a907c3898cd2905c8918acaa16/greenlet-3.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94ad81f0fd3c0c0681a018a976e5c2bd2ca2d9d94895f23e7bb1af4e8af4e2d5", size = 1687713, upload-time = "2026-02-20T20:21:11.684Z" }, @@ -724,7 +724,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/98/6d/8f2ef704e614bcf58ed43cfb8d87afa1c285e98194ab2cfad351bf04f81e/greenlet-3.3.2-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:e26e72bec7ab387ac80caa7496e0f908ff954f31065b0ffc1f8ecb1338b11b54", size = 286617, upload-time = "2026-02-20T20:19:29.856Z" }, { url = "https://files.pythonhosted.org/packages/5e/0d/93894161d307c6ea237a43988f27eba0947b360b99ac5239ad3fe09f0b47/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b466dff7a4ffda6ca975979bab80bdadde979e29fc947ac3be4451428d8b0e4", size = 655189, upload-time = "2026-02-20T20:47:35.742Z" }, { url = "https://files.pythonhosted.org/packages/f5/2c/d2d506ebd8abcb57386ec4f7ba20f4030cbe56eae541bc6fd6ef399c0b41/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b8bddc5b73c9720bea487b3bffdb1840fe4e3656fba3bd40aa1489e9f37877ff", size = 658225, upload-time = "2026-02-20T20:56:02.527Z" }, - { url = "https://files.pythonhosted.org/packages/d1/67/8197b7e7e602150938049d8e7f30de1660cfb87e4c8ee349b42b67bdb2e1/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:59b3e2c40f6706b05a9cd299c836c6aa2378cabe25d021acd80f13abf81181cf", size = 666581, upload-time = "2026-02-20T21:02:51.526Z" }, { url = "https://files.pythonhosted.org/packages/8e/30/3a09155fbf728673a1dea713572d2d31159f824a37c22da82127056c44e4/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b26b0f4428b871a751968285a1ac9648944cea09807177ac639b030bddebcea4", size = 657907, upload-time = "2026-02-20T20:21:05.259Z" }, { url = "https://files.pythonhosted.org/packages/f3/fd/d05a4b7acd0154ed758797f0a43b4c0962a843bedfe980115e842c5b2d08/greenlet-3.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1fb39a11ee2e4d94be9a76671482be9398560955c9e568550de0224e41104727", size = 1618857, upload-time = "2026-02-20T20:49:37.309Z" }, { url = "https://files.pythonhosted.org/packages/6f/e1/50ee92a5db521de8f35075b5eff060dd43d39ebd46c2181a2042f7070385/greenlet-3.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:20154044d9085151bc309e7689d6f7ba10027f8f5a8c0676ad398b951913d89e", size = 1680010, upload-time = "2026-02-20T20:21:13.427Z" }, diff --git a/examples/containerized/openai/uv.lock b/examples/containerized/openai/uv.lock index 977277552..fc65f1a87 100644 --- a/examples/containerized/openai/uv.lock +++ b/examples/containerized/openai/uv.lock @@ -9,7 +9,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -18,9 +18,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -381,11 +381,11 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "agentkernel", extras = ["openai", "api"], specifier = ">=0.4.0" }] +requires-dist = [{ name = "agentkernel", extras = ["openai", "api"], specifier = ">=0.5.1" }] [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "isort", specifier = ">=5.0.0" }, { name = "mypy", specifier = ">=1.0.0" }, diff --git a/examples/gcp-containerized/openai-auth/uv.lock b/examples/gcp-containerized/openai-auth/uv.lock index 0ea080fcb..2d53dec47 100644 --- a/examples/gcp-containerized/openai-auth/uv.lock +++ b/examples/gcp-containerized/openai-auth/uv.lock @@ -15,7 +15,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.3.1" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -24,9 +24,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f0/67/ff874c93084b8aea7d611e3ba664ac96a51dcf86bf2fb613aa7a7c27d1dd/agentkernel-0.3.1.tar.gz", hash = "sha256:e40af5a67132470e1abe5b8c86ed8aa53b7e389681fa4d4dc3f13cfbda6aad8e", size = 167000, upload-time = "2026-04-11T05:51:44.765Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/97/23/1580f9c86e22bd6648d01e2dbc7cf2796ed13292f9c0d482f7f2a5509fde/agentkernel-0.3.1-py3-none-any.whl", hash = "sha256:dd8cb586b9f821fbb25ffb57744e7348649357d4876c69460c41ad57d02ef6d8", size = 238346, upload-time = "2026-04-11T05:51:42.68Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -920,11 +920,11 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "agentkernel", extras = ["openai", "api", "redis"], specifier = ">=0.2.14" }] +requires-dist = [{ name = "agentkernel", extras = ["openai", "api", "redis"], specifier = ">=0.5.1" }] [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.2.14" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "isort", specifier = ">=5.0.0" }, { name = "mypy", specifier = ">=1.0.0" }, diff --git a/examples/gcp-containerized/openai/uv.lock b/examples/gcp-containerized/openai/uv.lock index 929cd73dd..13ea04e6f 100644 --- a/examples/gcp-containerized/openai/uv.lock +++ b/examples/gcp-containerized/openai/uv.lock @@ -15,7 +15,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.2.14" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -24,9 +24,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/16/ba/76928c19495fe1d70410e7c92e5e3961096d28056ed5db11ed9401cfff3e/agentkernel-0.2.14.tar.gz", hash = "sha256:3f1d1179016d177ad51bd781b7c2b649d7ade0920926b8925f0fb45b62ab985e", size = 124556, upload-time = "2026-03-11T06:50:25.882Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/cd/a1f856dc5e0c724e59aeab6238d5cc796d182c13c9fa12c10d0ea0108530/agentkernel-0.2.14-py3-none-any.whl", hash = "sha256:d4b4fbb1a390dcd623098b65fa4c0d4483eea8db31f34b11f14ea0ae49766f8d", size = 189365, upload-time = "2026-03-11T06:50:23.55Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -35,6 +35,9 @@ api = [ { name = "gunicorn" }, { name = "uvicorn" }, ] +gcp = [ + { name = "google-cloud-firestore" }, +] openai = [ { name = "openai-agents" }, { name = "openai-guardrails" }, @@ -899,7 +902,7 @@ name = "gcp-containerized-openai" version = "0.1.0" source = { virtual = "." } dependencies = [ - { name = "agentkernel", extra = ["api", "openai", "redis"] }, + { name = "agentkernel", extra = ["api", "gcp", "openai", "redis"] }, ] [package.dev-dependencies] @@ -911,16 +914,93 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "agentkernel", extras = ["openai", "api", "redis", "gcp"], specifier = ">=0.2.14" }] +requires-dist = [{ name = "agentkernel", extras = ["openai", "api", "redis", "gcp"], specifier = ">=0.5.1" }] [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.2.14" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "isort", specifier = ">=5.0.0" }, { name = "mypy", specifier = ">=1.0.0" }, ] +[[package]] +name = "google-api-core" +version = "2.30.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-auth" }, + { name = "googleapis-common-protos" }, + { name = "proto-plus" }, + { name = "protobuf" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/16/ce/502a57fb0ec752026d24df1280b162294b22a0afb98a326084f9a979138b/google_api_core-2.30.3.tar.gz", hash = "sha256:e601a37f148585319b26db36e219df68c5d07b6382cff2d580e83404e44d641b", size = 177001, upload-time = "2026-04-10T00:41:28.035Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/15/e56f351cf6ef1cfea58e6ac226a7318ed1deb2218c4b3cc9bd9e4b786c5a/google_api_core-2.30.3-py3-none-any.whl", hash = "sha256:a85761ba72c444dad5d611c2220633480b2b6be2521eca69cca2dbb3ffd6bfe8", size = 173274, upload-time = "2026-04-09T22:57:16.198Z" }, +] + +[package.optional-dependencies] +grpc = [ + { name = "grpcio" }, + { name = "grpcio-status" }, +] + +[[package]] +name = "google-auth" +version = "2.54.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "pyasn1-modules" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/15/f6/494e18317546d7def90c957b71d68b025d24f0e22e486c2606bc57765c48/google_auth-2.54.0.tar.gz", hash = "sha256:130f6fd5e3f497fdad897a23ed9489973437edf561238c4b92a4d02c435f8af9", size = 343161, upload-time = "2026-06-12T18:03:17.606Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/c5/d53bddd2c0949833fcb4ea06f9d5dd1c40575a1a4214cd1021eff57ba301/google_auth-2.54.0-py3-none-any.whl", hash = "sha256:784e9837f92244141250470d47c893df50cbab485ce491aca5e9deb558ad2b48", size = 249878, upload-time = "2026-06-12T18:02:57.58Z" }, +] + +[[package]] +name = "google-cloud-core" +version = "2.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core" }, + { name = "google-auth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/dd/1eef226e470369b26824a505c34482c0b493bc35fe8e0c6b003b5feca21a/google_cloud_core-2.6.0.tar.gz", hash = "sha256:e76149739f90fac1fc6757c09f47eaccb3145b54adbd7759b0f7c4b235f46c83", size = 36001, upload-time = "2026-05-07T08:04:04.124Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/4a/98da8930ab109c73d9a5d13782a9ebb81ea8c111f6d534a567b71d23e52b/google_cloud_core-2.6.0-py3-none-any.whl", hash = "sha256:6d63ac8e5eca6d9e4319d0a1e2265fadcd7f1049904378caecfa01cf52dd869e", size = 29390, upload-time = "2026-05-07T08:02:34.672Z" }, +] + +[[package]] +name = "google-cloud-firestore" +version = "2.27.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core", extra = ["grpc"] }, + { name = "google-auth" }, + { name = "google-cloud-core" }, + { name = "grpcio" }, + { name = "proto-plus" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4c/83/bdfd4387fadc5f44de1d0f97b456c648b6f87ec0b1818a9f7f477e6e6eab/google_cloud_firestore-2.27.0.tar.gz", hash = "sha256:5633cb164ef56ca6c73a807822191a56a98f6f10e76978c4f2eb197ae03383d2", size = 649244, upload-time = "2026-04-13T22:55:43.931Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/db/fc72c4279887cc95011f16e70200d021030f0261e6391fa52e3adfaaea25/google_cloud_firestore-2.27.0-py3-none-any.whl", hash = "sha256:cc2ea78bc2d4dcc928016d56802deacfda3c9bbda0a7d691ee73b41a2f1a80d7", size = 429806, upload-time = "2026-04-13T22:55:41.726Z" }, +] + +[[package]] +name = "googleapis-common-protos" +version = "1.75.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b5/c8/f439cffde755cffa462bfbb156278fa6f9d09119719af9814b858fd4f81f/googleapis_common_protos-1.75.0.tar.gz", hash = "sha256:53a062ff3c32552fbd62c11fe23768b78e4ddf0494d5e5fd97d3f4689c75fbbd", size = 151035, upload-time = "2026-05-07T08:04:49.423Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/c8/e2645aa8ed02fd4c7a2f59d68783b65b1f3cbdfe39a6308e156509d1fee8/googleapis_common_protos-1.75.0-py3-none-any.whl", hash = "sha256:961ed60399c457ceb0ee8f285a84c870aabc9c6a832b9d37bb281b5bebde43ed", size = 300631, upload-time = "2026-05-07T08:03:30.345Z" }, +] + [[package]] name = "greenlet" version = "3.5.1" @@ -988,6 +1068,61 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9c/83/3b1d03d36f224edded98e9affd0467630fc09d766c0e56fb1498cbb04a9b/griffe-1.15.0-py3-none-any.whl", hash = "sha256:6f6762661949411031f5fcda9593f586e6ce8340f0ba88921a0f2ef7a81eb9a3", size = 150705, upload-time = "2025-11-10T15:03:13.549Z" }, ] +[[package]] +name = "grpcio" +version = "1.81.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b0/b5/1ff353970a87eda4c98251e34d2dfd214abd4982dc89119c9252a2a482d2/grpcio-1.81.1.tar.gz", hash = "sha256:6fa10a767143a5e82e8eaab53918af0cd8909a57a27f8cb2288b80a613ac671b", size = 13026582, upload-time = "2026-06-11T12:46:51.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/07/9a979c81738863a738dc23d65177056e71fbb2db817740ed870b33434e7a/grpcio-1.81.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:8b39472beafc0bdcafc4c8c73ad082ebfdb449d566897a61e7acb4fa88089115", size = 6053264, upload-time = "2026-06-11T12:45:21.017Z" }, + { url = "https://files.pythonhosted.org/packages/75/95/539706ca0d3bd40dbad583dc56fd883da941f37556b629132da5762781b9/grpcio-1.81.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:12b7524c88d4026d3dcb7b0ebe16b6714f3b4af402ddd0f0639ab064a00c87c3", size = 12052560, upload-time = "2026-06-11T12:45:23.652Z" }, + { url = "https://files.pythonhosted.org/packages/e0/44/f257b7e0bd69c93b06c6cb8ac8d1b901ccb42bedabd83c1a4c77a71f8810/grpcio-1.81.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1e123f9b37edb8375fd74130d1f69c944bbf0a7b06761ae7211154b8759e94d2", size = 6595983, upload-time = "2026-06-11T12:45:26.963Z" }, + { url = "https://files.pythonhosted.org/packages/b9/f3/19782aa04c960968bef8c5539329d8e3bbc3364e2e46d19eb5e5cc5e43b7/grpcio-1.81.1-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:2c2e2ae6867c2966b8daccc836d54a13218e0007e9a490aeb81dd05be64d22d7", size = 7303455, upload-time = "2026-06-11T12:45:29.707Z" }, + { url = "https://files.pythonhosted.org/packages/eb/8c/dea020b6d91508cd84463917a63149ec196ee7db505d032ae43fcb3303b9/grpcio-1.81.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:766bc7c9a9c340342f4c864ccbda8e78111e4751f13b895812b9c148fb79e9d0", size = 6809167, upload-time = "2026-06-11T12:45:32.52Z" }, + { url = "https://files.pythonhosted.org/packages/1c/c7/3030dd940408083bd32cd95d634777a71605ade4887154d93e8a89244946/grpcio-1.81.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b259a04a737cb3496be0901328eb8b7552ed8df4865d8c8f1cf1bffcfc0776a3", size = 7412536, upload-time = "2026-06-11T12:45:35.403Z" }, + { url = "https://files.pythonhosted.org/packages/e0/dd/1172a9e42b168edcafefad6115346ef619a3fc02158bb170e66ced24bcdd/grpcio-1.81.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:85b10a45b8993d195c4f3ff57025b8d1e11834909ee475c403bfa60cb4caefaf", size = 8408276, upload-time = "2026-06-11T12:45:37.78Z" }, + { url = "https://files.pythonhosted.org/packages/25/7a/71437c7f3596e5246155c515852795a85a1a8d228190212432b13b97a95d/grpcio-1.81.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8ea1936c26b99999b27479853039a7f34713f56c49375ad52b38535ec93a796c", size = 7849660, upload-time = "2026-06-11T12:45:40.627Z" }, + { url = "https://files.pythonhosted.org/packages/65/40/7debc0da45d2efebafb82da75644be347497fe4ee250514b8cd3b86ae8bf/grpcio-1.81.1-cp312-cp312-win32.whl", hash = "sha256:a185a04039df6cae8648bc8ab6d6fde7bf94f7188ecf7828e76ac52eef1e41d6", size = 4185819, upload-time = "2026-06-11T12:45:43.027Z" }, + { url = "https://files.pythonhosted.org/packages/2e/b9/8fe3ba5ed462067774ebc1f9c7f26aa7ebcc280ddd476be107153de1339e/grpcio-1.81.1-cp312-cp312-win_amd64.whl", hash = "sha256:3ad74f8bb1a18963914c5452d289422830b39459e8776ebbcd207be1fbfb1d94", size = 4930461, upload-time = "2026-06-11T12:45:45.775Z" }, + { url = "https://files.pythonhosted.org/packages/7a/42/dcc2e4b600538ef18327c0839d56b7d3c3812337c5d710df5877dbb39b1e/grpcio-1.81.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:b10e1ff4756ed27d5a29d7fc79cfce7ef1ff56ad20025b89bac7cf79e09abbbe", size = 6054466, upload-time = "2026-06-11T12:45:48.43Z" }, + { url = "https://files.pythonhosted.org/packages/7b/4a/a36e03210183a8a7d4c80c3936acee679f4bd77d5861f369db47b2cc5f05/grpcio-1.81.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:819edbdcb42ab8598b494bcf0222684bbb7a3c772bd1b1f0be7e029a6063c28e", size = 12048795, upload-time = "2026-06-11T12:45:54.011Z" }, + { url = "https://files.pythonhosted.org/packages/b0/d5/d68e30b29098f63beab6fe501100fe82674ff142b32c672532da86a99b3a/grpcio-1.81.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c5bf2dc311127d91230cc79b92188c082634a06cf66c5234db49a43b910183b0", size = 6599094, upload-time = "2026-06-11T12:45:57.799Z" }, + { url = "https://files.pythonhosted.org/packages/3d/b3/e837954d279754f638a11cca5dcf6b24a005efb398984cefaf7735945a54/grpcio-1.81.1-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:e8ca6a1fcdb2943c9cbc1804a1baf3acb6071d72a471591678ded84218006e14", size = 7307182, upload-time = "2026-06-11T12:46:00.568Z" }, + { url = "https://files.pythonhosted.org/packages/0d/1e/b47957057e729adc6cdf519a47f8be2562b7140e280f1418443eb4022192/grpcio-1.81.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e64dd101d380a115cc5a0c7856788adb535f1a4e21fc543775602f8be95180ae", size = 6810962, upload-time = "2026-06-11T12:46:03.312Z" }, + { url = "https://files.pythonhosted.org/packages/40/26/569868e364e05b19ec8f969da53d230bcd89c962cd198f7c29943155c4d3/grpcio-1.81.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:98a07f9bf591e3a8919797bee1c53f026ba4acd587e5a4404c8e57c9ec36b2a5", size = 7415698, upload-time = "2026-06-11T12:46:06.005Z" }, + { url = "https://files.pythonhosted.org/packages/36/0c/5440a0582cb5653fc42a6e262eeb22700943313f8076f9dc927491b20a59/grpcio-1.81.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c261d74b1a945cf895a9d6eccd1685a8e837531beaab782da4d630a8d12deffb", size = 8407779, upload-time = "2026-06-11T12:46:08.84Z" }, + { url = "https://files.pythonhosted.org/packages/ff/aa/66fe9f39871d766987d869a03ee0842a026f499c7b1e62decb9e78a8088e/grpcio-1.81.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:58ad1131c300d3c9b933802b3cc4dc69d380822935ba50b28703156ea826fbf7", size = 7844521, upload-time = "2026-06-11T12:46:12.171Z" }, + { url = "https://files.pythonhosted.org/packages/f0/9e/69bb7194861bcd28fb3193261d4f9c3831b4446993f002cf59068943e7ab/grpcio-1.81.1-cp313-cp313-win32.whl", hash = "sha256:78e29211f26da2fdd0e9c6d2b79f489476140cf7029b6a64808ade7ca4156a42", size = 4182786, upload-time = "2026-06-11T12:46:15.192Z" }, + { url = "https://files.pythonhosted.org/packages/0d/20/3da8bb0d637feccdc3e1e419bb511ce93651ce7d54164f95de22cc0b8b34/grpcio-1.81.1-cp313-cp313-win_amd64.whl", hash = "sha256:edb59506291b647a30884b1d51a599d605f40b20af4a7dc3d33786a47a31de60", size = 4928648, upload-time = "2026-06-11T12:46:17.823Z" }, + { url = "https://files.pythonhosted.org/packages/b6/58/19414622b1bf6981bc9c05a365bd548e71876c89000083b3af489251e9c0/grpcio-1.81.1-cp314-cp314-linux_armv7l.whl", hash = "sha256:506f48f2f9c29b143fca3dad7b0d518c188b6c9648c75a2ae6e2d9f2c13a060b", size = 6055336, upload-time = "2026-06-11T12:46:20.557Z" }, + { url = "https://files.pythonhosted.org/packages/32/f1/2ec88adb92b0eba970dd0e0e7dd086341daa3c75eba4f735f9e44bf684b0/grpcio-1.81.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d865db4a6318e1c1bea83292e0ed231090538fc4ca45425b0f0480eb338bbc6e", size = 12056279, upload-time = "2026-06-11T12:46:24.255Z" }, + { url = "https://files.pythonhosted.org/packages/41/36/e8c5f8c6ec71de73733695ebc809e98b178b534ec6d8eaa31a7ebab4ad4c/grpcio-1.81.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e2aa72e3ce1770317ef534f63d397b55e130725f5149bd36077c3b539019db27", size = 6608225, upload-time = "2026-06-11T12:46:27.601Z" }, + { url = "https://files.pythonhosted.org/packages/30/22/96fc577a845ab093326d9ab1adb874bd4936c8cf98ac8ed2f3db13a0a2fb/grpcio-1.81.1-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0490c30c261eded63f3f354979f9dc4502a9fb944cccb60cd9dc85f5a7349854", size = 7306576, upload-time = "2026-06-11T12:46:30.514Z" }, + { url = "https://files.pythonhosted.org/packages/76/7b/61dab5d5969f28d97fb1009cead1df0a5cd987d3315e1b37f18a4449f8bc/grpcio-1.81.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:410482da976329fe5f4067270401b12cf2bd552ff8020f054ecfaddb5475f9d6", size = 6812165, upload-time = "2026-06-11T12:46:33.699Z" }, + { url = "https://files.pythonhosted.org/packages/82/78/6e501929d4f5f96462fd82fd9f0f06e5f9612207582b862868d68757b27d/grpcio-1.81.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e3657301562ac3cb8018d30d0d3ebfa39932239f7b5703422057ef14b69949f5", size = 7422962, upload-time = "2026-06-11T12:46:36.511Z" }, + { url = "https://files.pythonhosted.org/packages/2a/7e/f2157589e66daa78ebb3165942d05a08bdea93b9d11c2bc1e172aef89685/grpcio-1.81.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:24c8e57504c8f45b237e40b99262d181071e5099a07053695b75d97bb53053a0", size = 8408176, upload-time = "2026-06-11T12:46:39.803Z" }, + { url = "https://files.pythonhosted.org/packages/da/df/c6717fef716e00d235ffb96123baf6dce76d6004f6233fa767c502861460/grpcio-1.81.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b427c19380991a4eaab2f6144b64b99b412043314c6bf4ab544f97bb31ee4190", size = 7846681, upload-time = "2026-06-11T12:46:43.013Z" }, + { url = "https://files.pythonhosted.org/packages/36/84/3502e9f210a6a5c4438c8aca3f88edd2e04f6a27f3d41b26cf0a0024b096/grpcio-1.81.1-cp314-cp314-win32.whl", hash = "sha256:61233fe8951e5c85dff81c2458b6528624760166946b5b47ea150a589168411f", size = 4264615, upload-time = "2026-06-11T12:46:45.741Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b0/4af731ff7492c68a96e4c71bfd0f4590acde92b31c6fe4894e6465c10ff6/grpcio-1.81.1-cp314-cp314-win_amd64.whl", hash = "sha256:3768a5ff1b2125e6f552e561b6b2dca0e64982d8949689b4df145cf8b98d7821", size = 5070275, upload-time = "2026-06-11T12:46:48.486Z" }, +] + +[[package]] +name = "grpcio-status" +version = "1.81.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "googleapis-common-protos" }, + { name = "grpcio" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/32/26/0aa9168c87882381fd810d140c279a2490ed6aee655f0515d6f56c5ca404/grpcio_status-1.81.1.tar.gz", hash = "sha256:9389a03e746017b10f0630c064289201458f3ce01f5d7ef4b0bebc1ef6cf82ad", size = 13923, upload-time = "2026-06-11T12:58:48.636Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/5e/5abfec5f7e89d3b7993d57cfb025ca5f968a2c18656d7fcda2b6919440b9/grpcio_status-1.81.1-py3-none-any.whl", hash = "sha256:08072fa9995f4a95c647fc6f4f85e2411573d00087bcabdf30f260114338f232", size = 14638, upload-time = "2026-06-11T12:58:31.982Z" }, +] + [[package]] name = "gunicorn" version = "25.1.0" @@ -2524,6 +2659,33 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3a/ed/1cdcab6ba3d6ab7feca11fc14f0eeea80755bb53ef4e892079f31b10a25f/propcache-0.5.2-py3-none-any.whl", hash = "sha256:be1ddfcbb376e3de5d2e2db1d58d6d67463e6b4f9f040c000de8e300295465fe", size = 14036, upload-time = "2026-05-08T21:02:10.673Z" }, ] +[[package]] +name = "proto-plus" +version = "1.28.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/56/e647b0c675392d2da368da7b6f158f7368b18542fd6f7d7400a2f39de000/proto_plus-1.28.0.tar.gz", hash = "sha256:38e5696342835b08fc116f30a25665b29531cda9d5d5643e9b81fc312385abd9", size = 57221, upload-time = "2026-05-07T08:04:50.811Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/20/b122d4626976acb81132036d2ad1bb35a1a8775fceb837ec30964622516a/proto_plus-1.28.0-py3-none-any.whl", hash = "sha256:a630604310899e73c59ec302e5765c058d412b2f090b9c79c8822589f14955b8", size = 50410, upload-time = "2026-05-07T08:03:31.962Z" }, +] + +[[package]] +name = "protobuf" +version = "7.35.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/01/9ef0afd7999eb9badb3a768b4aedd78c86d4c65cfaf1958ab276199e76b4/protobuf-7.35.1.tar.gz", hash = "sha256:ce115a26fe0c39a2c29973d914d327e516a6455464489fe3cd1e51a1b354f81a", size = 458717, upload-time = "2026-06-11T21:55:40.257Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/03/8aeeb7458d22546bf64b5250ca1daeb5ff757d900e8e4a7476c6f0db843e/protobuf-7.35.1-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:24f857477359a85c0c235261b8ba905fd51b2562f4a64ca1df5473f29850cbf6", size = 433226, upload-time = "2026-06-11T21:55:31.719Z" }, + { url = "https://files.pythonhosted.org/packages/37/4b/dfb89eb0e652a1ff073c39a59fb5e3a83cfe9b57a2c83fa6d78270101767/protobuf-7.35.1-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:11d6b0ec246892d85215b0a13ca6e0233cf5284b68f0ac02646427f4ff88a799", size = 328847, upload-time = "2026-06-11T21:55:34.035Z" }, + { url = "https://files.pythonhosted.org/packages/0f/58/dc12f2cd484951524af6e3382c785869b9b3fb5e52ee95ae23add53ee8f9/protobuf-7.35.1-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:b73f9489a4b8b1c9cb1f8ed951c736392592edb24b9d6819f36d2e10b171d5b4", size = 344030, upload-time = "2026-06-11T21:55:34.941Z" }, + { url = "https://files.pythonhosted.org/packages/e4/be/5b3cfe508bfab6761414ff944e3366eb13be4fd71efcd69450f89ba39f43/protobuf-7.35.1-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:74758715c53d7158fb76caf4f0cfdacc5329a4b1bb994f865d6cf302d413a1c4", size = 327130, upload-time = "2026-06-11T21:55:35.921Z" }, + { url = "https://files.pythonhosted.org/packages/d8/bc/6d6c7ba8709c85f8f2c390b2b118d6fb08a783676a572271851bf45a7d22/protobuf-7.35.1-cp310-abi3-win32.whl", hash = "sha256:353652e4efd0bca5b5fc2656abf8307ef351f0cf938c9eba09f0e09c20a25c30", size = 428945, upload-time = "2026-06-11T21:55:37.034Z" }, + { url = "https://files.pythonhosted.org/packages/0a/19/8d0cb6f20a1ef7b18f1c8986ad5783f22f84cce39c6ce9a6e645ea55192e/protobuf-7.35.1-cp310-abi3-win_amd64.whl", hash = "sha256:230a75ddfc2de4806e56696ce9640c1cdfdb6543b7cfce98d42a4c0a0e7bdb87", size = 439996, upload-time = "2026-06-11T21:55:38.123Z" }, + { url = "https://files.pythonhosted.org/packages/19/c7/5f7c636ec43e0c545e28d1f1db71990108306f7bdcb89f069ba97e428e7f/protobuf-7.35.1-py3-none-any.whl", hash = "sha256:4bc97768d8fe4ad6743c8a19403e314511ed9f6d13205b687e52421c023ac1b9", size = 171659, upload-time = "2026-06-11T21:55:39.155Z" }, +] + [[package]] name = "pyarrow" version = "24.0.0" @@ -2567,6 +2729,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/51/be/6f79d55816d5c22557cf27533543d5d70dfe692adfbee4b99f2760674f38/pyarrow-24.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:c91d00057f23b8d353039520dc3a6c09d8608164c692e9f59a175a42b2ae0c19", size = 28131282, upload-time = "2026-04-21T10:51:16.815Z" }, ] +[[package]] +name = "pyasn1" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/5f/6583902b6f79b399c9c40674ac384fd9cd77805f9e6205075f828ef11fb2/pyasn1-0.6.3.tar.gz", hash = "sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf", size = 148685, upload-time = "2026-03-17T01:06:53.382Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/a0/7d793dce3fa811fe047d6ae2431c672364b462850c6235ae306c0efd025f/pyasn1-0.6.3-py3-none-any.whl", hash = "sha256:a80184d120f0864a52a073acc6fc642847d0be408e7c7252f31390c0f4eadcde", size = 83997, upload-time = "2026-03-17T01:06:52.036Z" }, +] + +[[package]] +name = "pyasn1-modules" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892, upload-time = "2025-03-28T02:41:22.17Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" }, +] + [[package]] name = "pycparser" version = "3.0" diff --git a/examples/gcp-serverless/openai-auth/uv.lock b/examples/gcp-serverless/openai-auth/uv.lock index f84b9e0b1..7f8bbd73f 100644 --- a/examples/gcp-serverless/openai-auth/uv.lock +++ b/examples/gcp-serverless/openai-auth/uv.lock @@ -15,7 +15,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.3.1" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -24,9 +24,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f0/67/ff874c93084b8aea7d611e3ba664ac96a51dcf86bf2fb613aa7a7c27d1dd/agentkernel-0.3.1.tar.gz", hash = "sha256:e40af5a67132470e1abe5b8c86ed8aa53b7e389681fa4d4dc3f13cfbda6aad8e", size = 167000, upload-time = "2026-04-11T05:51:44.765Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/97/23/1580f9c86e22bd6648d01e2dbc7cf2796ed13292f9c0d482f7f2a5509fde/agentkernel-0.3.1-py3-none-any.whl", hash = "sha256:dd8cb586b9f821fbb25ffb57744e7348649357d4876c69460c41ad57d02ef6d8", size = 238346, upload-time = "2026-04-11T05:51:42.68Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -920,11 +920,11 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "agentkernel", extras = ["openai", "api", "redis"], specifier = ">=0.2.14" }] +requires-dist = [{ name = "agentkernel", extras = ["openai", "api", "redis"], specifier = ">=0.5.1" }] [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.2.14" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "isort", specifier = ">=5.0.0" }, { name = "mypy", specifier = ">=1.0.0" }, diff --git a/examples/gcp-serverless/openai-firestore/uv.lock b/examples/gcp-serverless/openai-firestore/uv.lock index 792c4172d..a00b562e0 100644 --- a/examples/gcp-serverless/openai-firestore/uv.lock +++ b/examples/gcp-serverless/openai-firestore/uv.lock @@ -18,7 +18,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -27,9 +27,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -38,6 +38,9 @@ api = [ { name = "gunicorn" }, { name = "uvicorn" }, ] +gcp = [ + { name = "google-cloud-firestore" }, +] openai = [ { name = "openai-agents" }, { name = "openai-guardrails" }, @@ -948,7 +951,7 @@ name = "gcp-serverless-openai-firestore" version = "0.1.0" source = { virtual = "." } dependencies = [ - { name = "agentkernel", extra = ["api", "openai"] }, + { name = "agentkernel", extra = ["api", "gcp", "openai"] }, ] [package.dev-dependencies] @@ -960,16 +963,93 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "agentkernel", extras = ["openai", "api", "gcp"], specifier = ">=0.2.14" }] +requires-dist = [{ name = "agentkernel", extras = ["openai", "api", "gcp"], specifier = ">=0.5.1" }] [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.2.14" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "isort", specifier = ">=5.0.0" }, { name = "mypy", specifier = ">=1.0.0" }, ] +[[package]] +name = "google-api-core" +version = "2.31.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-auth" }, + { name = "googleapis-common-protos" }, + { name = "proto-plus" }, + { name = "protobuf" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c6/22/155cadf1d49272a9cf48f3168c0f3874fa13397297e611a5ea00cd093880/google_api_core-2.31.0.tar.gz", hash = "sha256:2be84ee0f584c48e6bde1b36766e23348b361fb7e55e56135fc76ce1c397f9c2", size = 176492, upload-time = "2026-06-03T14:52:17.257Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/40/9bdbb60b03a332bd45acb8703da08bbc27d991d35286b62e42acc86d243a/google_api_core-2.31.0-py3-none-any.whl", hash = "sha256:ef79fb3784c71cbac89cbd03301ba0c8fb8ad2aa95d7f9204dd9628f7adf59ab", size = 173102, upload-time = "2026-06-03T14:51:26.729Z" }, +] + +[package.optional-dependencies] +grpc = [ + { name = "grpcio" }, + { name = "grpcio-status" }, +] + +[[package]] +name = "google-auth" +version = "2.54.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "pyasn1-modules" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/15/f6/494e18317546d7def90c957b71d68b025d24f0e22e486c2606bc57765c48/google_auth-2.54.0.tar.gz", hash = "sha256:130f6fd5e3f497fdad897a23ed9489973437edf561238c4b92a4d02c435f8af9", size = 343161, upload-time = "2026-06-12T18:03:17.606Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/c5/d53bddd2c0949833fcb4ea06f9d5dd1c40575a1a4214cd1021eff57ba301/google_auth-2.54.0-py3-none-any.whl", hash = "sha256:784e9837f92244141250470d47c893df50cbab485ce491aca5e9deb558ad2b48", size = 249878, upload-time = "2026-06-12T18:02:57.58Z" }, +] + +[[package]] +name = "google-cloud-core" +version = "2.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core" }, + { name = "google-auth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/dd/1eef226e470369b26824a505c34482c0b493bc35fe8e0c6b003b5feca21a/google_cloud_core-2.6.0.tar.gz", hash = "sha256:e76149739f90fac1fc6757c09f47eaccb3145b54adbd7759b0f7c4b235f46c83", size = 36001, upload-time = "2026-05-07T08:04:04.124Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/4a/98da8930ab109c73d9a5d13782a9ebb81ea8c111f6d534a567b71d23e52b/google_cloud_core-2.6.0-py3-none-any.whl", hash = "sha256:6d63ac8e5eca6d9e4319d0a1e2265fadcd7f1049904378caecfa01cf52dd869e", size = 29390, upload-time = "2026-05-07T08:02:34.672Z" }, +] + +[[package]] +name = "google-cloud-firestore" +version = "2.27.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core", extra = ["grpc"] }, + { name = "google-auth" }, + { name = "google-cloud-core" }, + { name = "grpcio" }, + { name = "proto-plus" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4c/83/bdfd4387fadc5f44de1d0f97b456c648b6f87ec0b1818a9f7f477e6e6eab/google_cloud_firestore-2.27.0.tar.gz", hash = "sha256:5633cb164ef56ca6c73a807822191a56a98f6f10e76978c4f2eb197ae03383d2", size = 649244, upload-time = "2026-04-13T22:55:43.931Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/db/fc72c4279887cc95011f16e70200d021030f0261e6391fa52e3adfaaea25/google_cloud_firestore-2.27.0-py3-none-any.whl", hash = "sha256:cc2ea78bc2d4dcc928016d56802deacfda3c9bbda0a7d691ee73b41a2f1a80d7", size = 429806, upload-time = "2026-04-13T22:55:41.726Z" }, +] + +[[package]] +name = "googleapis-common-protos" +version = "1.75.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b5/c8/f439cffde755cffa462bfbb156278fa6f9d09119719af9814b858fd4f81f/googleapis_common_protos-1.75.0.tar.gz", hash = "sha256:53a062ff3c32552fbd62c11fe23768b78e4ddf0494d5e5fd97d3f4689c75fbbd", size = 151035, upload-time = "2026-05-07T08:04:49.423Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/c8/e2645aa8ed02fd4c7a2f59d68783b65b1f3cbdfe39a6308e156509d1fee8/googleapis_common_protos-1.75.0-py3-none-any.whl", hash = "sha256:961ed60399c457ceb0ee8f285a84c870aabc9c6a832b9d37bb281b5bebde43ed", size = 300631, upload-time = "2026-05-07T08:03:30.345Z" }, +] + [[package]] name = "greenlet" version = "3.5.0" @@ -1018,6 +1098,61 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/11/8c/c9138d881c79aa0ea9ed83cbd58d5ca75624378b38cee225dcf5c42cc91f/griffelib-2.0.2-py3-none-any.whl", hash = "sha256:925c857658fb1ba40c0772c37acbc2ab650bd794d9c1b9726922e36ea4117ea1", size = 142357, upload-time = "2026-03-27T11:34:46.275Z" }, ] +[[package]] +name = "grpcio" +version = "1.81.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b0/b5/1ff353970a87eda4c98251e34d2dfd214abd4982dc89119c9252a2a482d2/grpcio-1.81.1.tar.gz", hash = "sha256:6fa10a767143a5e82e8eaab53918af0cd8909a57a27f8cb2288b80a613ac671b", size = 13026582, upload-time = "2026-06-11T12:46:51.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/07/9a979c81738863a738dc23d65177056e71fbb2db817740ed870b33434e7a/grpcio-1.81.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:8b39472beafc0bdcafc4c8c73ad082ebfdb449d566897a61e7acb4fa88089115", size = 6053264, upload-time = "2026-06-11T12:45:21.017Z" }, + { url = "https://files.pythonhosted.org/packages/75/95/539706ca0d3bd40dbad583dc56fd883da941f37556b629132da5762781b9/grpcio-1.81.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:12b7524c88d4026d3dcb7b0ebe16b6714f3b4af402ddd0f0639ab064a00c87c3", size = 12052560, upload-time = "2026-06-11T12:45:23.652Z" }, + { url = "https://files.pythonhosted.org/packages/e0/44/f257b7e0bd69c93b06c6cb8ac8d1b901ccb42bedabd83c1a4c77a71f8810/grpcio-1.81.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1e123f9b37edb8375fd74130d1f69c944bbf0a7b06761ae7211154b8759e94d2", size = 6595983, upload-time = "2026-06-11T12:45:26.963Z" }, + { url = "https://files.pythonhosted.org/packages/b9/f3/19782aa04c960968bef8c5539329d8e3bbc3364e2e46d19eb5e5cc5e43b7/grpcio-1.81.1-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:2c2e2ae6867c2966b8daccc836d54a13218e0007e9a490aeb81dd05be64d22d7", size = 7303455, upload-time = "2026-06-11T12:45:29.707Z" }, + { url = "https://files.pythonhosted.org/packages/eb/8c/dea020b6d91508cd84463917a63149ec196ee7db505d032ae43fcb3303b9/grpcio-1.81.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:766bc7c9a9c340342f4c864ccbda8e78111e4751f13b895812b9c148fb79e9d0", size = 6809167, upload-time = "2026-06-11T12:45:32.52Z" }, + { url = "https://files.pythonhosted.org/packages/1c/c7/3030dd940408083bd32cd95d634777a71605ade4887154d93e8a89244946/grpcio-1.81.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b259a04a737cb3496be0901328eb8b7552ed8df4865d8c8f1cf1bffcfc0776a3", size = 7412536, upload-time = "2026-06-11T12:45:35.403Z" }, + { url = "https://files.pythonhosted.org/packages/e0/dd/1172a9e42b168edcafefad6115346ef619a3fc02158bb170e66ced24bcdd/grpcio-1.81.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:85b10a45b8993d195c4f3ff57025b8d1e11834909ee475c403bfa60cb4caefaf", size = 8408276, upload-time = "2026-06-11T12:45:37.78Z" }, + { url = "https://files.pythonhosted.org/packages/25/7a/71437c7f3596e5246155c515852795a85a1a8d228190212432b13b97a95d/grpcio-1.81.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8ea1936c26b99999b27479853039a7f34713f56c49375ad52b38535ec93a796c", size = 7849660, upload-time = "2026-06-11T12:45:40.627Z" }, + { url = "https://files.pythonhosted.org/packages/65/40/7debc0da45d2efebafb82da75644be347497fe4ee250514b8cd3b86ae8bf/grpcio-1.81.1-cp312-cp312-win32.whl", hash = "sha256:a185a04039df6cae8648bc8ab6d6fde7bf94f7188ecf7828e76ac52eef1e41d6", size = 4185819, upload-time = "2026-06-11T12:45:43.027Z" }, + { url = "https://files.pythonhosted.org/packages/2e/b9/8fe3ba5ed462067774ebc1f9c7f26aa7ebcc280ddd476be107153de1339e/grpcio-1.81.1-cp312-cp312-win_amd64.whl", hash = "sha256:3ad74f8bb1a18963914c5452d289422830b39459e8776ebbcd207be1fbfb1d94", size = 4930461, upload-time = "2026-06-11T12:45:45.775Z" }, + { url = "https://files.pythonhosted.org/packages/7a/42/dcc2e4b600538ef18327c0839d56b7d3c3812337c5d710df5877dbb39b1e/grpcio-1.81.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:b10e1ff4756ed27d5a29d7fc79cfce7ef1ff56ad20025b89bac7cf79e09abbbe", size = 6054466, upload-time = "2026-06-11T12:45:48.43Z" }, + { url = "https://files.pythonhosted.org/packages/7b/4a/a36e03210183a8a7d4c80c3936acee679f4bd77d5861f369db47b2cc5f05/grpcio-1.81.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:819edbdcb42ab8598b494bcf0222684bbb7a3c772bd1b1f0be7e029a6063c28e", size = 12048795, upload-time = "2026-06-11T12:45:54.011Z" }, + { url = "https://files.pythonhosted.org/packages/b0/d5/d68e30b29098f63beab6fe501100fe82674ff142b32c672532da86a99b3a/grpcio-1.81.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c5bf2dc311127d91230cc79b92188c082634a06cf66c5234db49a43b910183b0", size = 6599094, upload-time = "2026-06-11T12:45:57.799Z" }, + { url = "https://files.pythonhosted.org/packages/3d/b3/e837954d279754f638a11cca5dcf6b24a005efb398984cefaf7735945a54/grpcio-1.81.1-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:e8ca6a1fcdb2943c9cbc1804a1baf3acb6071d72a471591678ded84218006e14", size = 7307182, upload-time = "2026-06-11T12:46:00.568Z" }, + { url = "https://files.pythonhosted.org/packages/0d/1e/b47957057e729adc6cdf519a47f8be2562b7140e280f1418443eb4022192/grpcio-1.81.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e64dd101d380a115cc5a0c7856788adb535f1a4e21fc543775602f8be95180ae", size = 6810962, upload-time = "2026-06-11T12:46:03.312Z" }, + { url = "https://files.pythonhosted.org/packages/40/26/569868e364e05b19ec8f969da53d230bcd89c962cd198f7c29943155c4d3/grpcio-1.81.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:98a07f9bf591e3a8919797bee1c53f026ba4acd587e5a4404c8e57c9ec36b2a5", size = 7415698, upload-time = "2026-06-11T12:46:06.005Z" }, + { url = "https://files.pythonhosted.org/packages/36/0c/5440a0582cb5653fc42a6e262eeb22700943313f8076f9dc927491b20a59/grpcio-1.81.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c261d74b1a945cf895a9d6eccd1685a8e837531beaab782da4d630a8d12deffb", size = 8407779, upload-time = "2026-06-11T12:46:08.84Z" }, + { url = "https://files.pythonhosted.org/packages/ff/aa/66fe9f39871d766987d869a03ee0842a026f499c7b1e62decb9e78a8088e/grpcio-1.81.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:58ad1131c300d3c9b933802b3cc4dc69d380822935ba50b28703156ea826fbf7", size = 7844521, upload-time = "2026-06-11T12:46:12.171Z" }, + { url = "https://files.pythonhosted.org/packages/f0/9e/69bb7194861bcd28fb3193261d4f9c3831b4446993f002cf59068943e7ab/grpcio-1.81.1-cp313-cp313-win32.whl", hash = "sha256:78e29211f26da2fdd0e9c6d2b79f489476140cf7029b6a64808ade7ca4156a42", size = 4182786, upload-time = "2026-06-11T12:46:15.192Z" }, + { url = "https://files.pythonhosted.org/packages/0d/20/3da8bb0d637feccdc3e1e419bb511ce93651ce7d54164f95de22cc0b8b34/grpcio-1.81.1-cp313-cp313-win_amd64.whl", hash = "sha256:edb59506291b647a30884b1d51a599d605f40b20af4a7dc3d33786a47a31de60", size = 4928648, upload-time = "2026-06-11T12:46:17.823Z" }, + { url = "https://files.pythonhosted.org/packages/b6/58/19414622b1bf6981bc9c05a365bd548e71876c89000083b3af489251e9c0/grpcio-1.81.1-cp314-cp314-linux_armv7l.whl", hash = "sha256:506f48f2f9c29b143fca3dad7b0d518c188b6c9648c75a2ae6e2d9f2c13a060b", size = 6055336, upload-time = "2026-06-11T12:46:20.557Z" }, + { url = "https://files.pythonhosted.org/packages/32/f1/2ec88adb92b0eba970dd0e0e7dd086341daa3c75eba4f735f9e44bf684b0/grpcio-1.81.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d865db4a6318e1c1bea83292e0ed231090538fc4ca45425b0f0480eb338bbc6e", size = 12056279, upload-time = "2026-06-11T12:46:24.255Z" }, + { url = "https://files.pythonhosted.org/packages/41/36/e8c5f8c6ec71de73733695ebc809e98b178b534ec6d8eaa31a7ebab4ad4c/grpcio-1.81.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e2aa72e3ce1770317ef534f63d397b55e130725f5149bd36077c3b539019db27", size = 6608225, upload-time = "2026-06-11T12:46:27.601Z" }, + { url = "https://files.pythonhosted.org/packages/30/22/96fc577a845ab093326d9ab1adb874bd4936c8cf98ac8ed2f3db13a0a2fb/grpcio-1.81.1-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0490c30c261eded63f3f354979f9dc4502a9fb944cccb60cd9dc85f5a7349854", size = 7306576, upload-time = "2026-06-11T12:46:30.514Z" }, + { url = "https://files.pythonhosted.org/packages/76/7b/61dab5d5969f28d97fb1009cead1df0a5cd987d3315e1b37f18a4449f8bc/grpcio-1.81.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:410482da976329fe5f4067270401b12cf2bd552ff8020f054ecfaddb5475f9d6", size = 6812165, upload-time = "2026-06-11T12:46:33.699Z" }, + { url = "https://files.pythonhosted.org/packages/82/78/6e501929d4f5f96462fd82fd9f0f06e5f9612207582b862868d68757b27d/grpcio-1.81.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e3657301562ac3cb8018d30d0d3ebfa39932239f7b5703422057ef14b69949f5", size = 7422962, upload-time = "2026-06-11T12:46:36.511Z" }, + { url = "https://files.pythonhosted.org/packages/2a/7e/f2157589e66daa78ebb3165942d05a08bdea93b9d11c2bc1e172aef89685/grpcio-1.81.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:24c8e57504c8f45b237e40b99262d181071e5099a07053695b75d97bb53053a0", size = 8408176, upload-time = "2026-06-11T12:46:39.803Z" }, + { url = "https://files.pythonhosted.org/packages/da/df/c6717fef716e00d235ffb96123baf6dce76d6004f6233fa767c502861460/grpcio-1.81.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b427c19380991a4eaab2f6144b64b99b412043314c6bf4ab544f97bb31ee4190", size = 7846681, upload-time = "2026-06-11T12:46:43.013Z" }, + { url = "https://files.pythonhosted.org/packages/36/84/3502e9f210a6a5c4438c8aca3f88edd2e04f6a27f3d41b26cf0a0024b096/grpcio-1.81.1-cp314-cp314-win32.whl", hash = "sha256:61233fe8951e5c85dff81c2458b6528624760166946b5b47ea150a589168411f", size = 4264615, upload-time = "2026-06-11T12:46:45.741Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b0/4af731ff7492c68a96e4c71bfd0f4590acde92b31c6fe4894e6465c10ff6/grpcio-1.81.1-cp314-cp314-win_amd64.whl", hash = "sha256:3768a5ff1b2125e6f552e561b6b2dca0e64982d8949689b4df145cf8b98d7821", size = 5070275, upload-time = "2026-06-11T12:46:48.486Z" }, +] + +[[package]] +name = "grpcio-status" +version = "1.81.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "googleapis-common-protos" }, + { name = "grpcio" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/32/26/0aa9168c87882381fd810d140c279a2490ed6aee655f0515d6f56c5ca404/grpcio_status-1.81.1.tar.gz", hash = "sha256:9389a03e746017b10f0630c064289201458f3ce01f5d7ef4b0bebc1ef6cf82ad", size = 13923, upload-time = "2026-06-11T12:58:48.636Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/5e/5abfec5f7e89d3b7993d57cfb025ca5f968a2c18656d7fcda2b6919440b9/grpcio_status-1.81.1-py3-none-any.whl", hash = "sha256:08072fa9995f4a95c647fc6f4f85e2411573d00087bcabdf30f260114338f232", size = 14638, upload-time = "2026-06-11T12:58:31.982Z" }, +] + [[package]] name = "gunicorn" version = "26.0.0" @@ -2578,6 +2713,33 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3a/ed/1cdcab6ba3d6ab7feca11fc14f0eeea80755bb53ef4e892079f31b10a25f/propcache-0.5.2-py3-none-any.whl", hash = "sha256:be1ddfcbb376e3de5d2e2db1d58d6d67463e6b4f9f040c000de8e300295465fe", size = 14036, upload-time = "2026-05-08T21:02:10.673Z" }, ] +[[package]] +name = "proto-plus" +version = "1.28.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/56/e647b0c675392d2da368da7b6f158f7368b18542fd6f7d7400a2f39de000/proto_plus-1.28.0.tar.gz", hash = "sha256:38e5696342835b08fc116f30a25665b29531cda9d5d5643e9b81fc312385abd9", size = 57221, upload-time = "2026-05-07T08:04:50.811Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/20/b122d4626976acb81132036d2ad1bb35a1a8775fceb837ec30964622516a/proto_plus-1.28.0-py3-none-any.whl", hash = "sha256:a630604310899e73c59ec302e5765c058d412b2f090b9c79c8822589f14955b8", size = 50410, upload-time = "2026-05-07T08:03:31.962Z" }, +] + +[[package]] +name = "protobuf" +version = "7.35.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/01/9ef0afd7999eb9badb3a768b4aedd78c86d4c65cfaf1958ab276199e76b4/protobuf-7.35.1.tar.gz", hash = "sha256:ce115a26fe0c39a2c29973d914d327e516a6455464489fe3cd1e51a1b354f81a", size = 458717, upload-time = "2026-06-11T21:55:40.257Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/03/8aeeb7458d22546bf64b5250ca1daeb5ff757d900e8e4a7476c6f0db843e/protobuf-7.35.1-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:24f857477359a85c0c235261b8ba905fd51b2562f4a64ca1df5473f29850cbf6", size = 433226, upload-time = "2026-06-11T21:55:31.719Z" }, + { url = "https://files.pythonhosted.org/packages/37/4b/dfb89eb0e652a1ff073c39a59fb5e3a83cfe9b57a2c83fa6d78270101767/protobuf-7.35.1-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:11d6b0ec246892d85215b0a13ca6e0233cf5284b68f0ac02646427f4ff88a799", size = 328847, upload-time = "2026-06-11T21:55:34.035Z" }, + { url = "https://files.pythonhosted.org/packages/0f/58/dc12f2cd484951524af6e3382c785869b9b3fb5e52ee95ae23add53ee8f9/protobuf-7.35.1-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:b73f9489a4b8b1c9cb1f8ed951c736392592edb24b9d6819f36d2e10b171d5b4", size = 344030, upload-time = "2026-06-11T21:55:34.941Z" }, + { url = "https://files.pythonhosted.org/packages/e4/be/5b3cfe508bfab6761414ff944e3366eb13be4fd71efcd69450f89ba39f43/protobuf-7.35.1-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:74758715c53d7158fb76caf4f0cfdacc5329a4b1bb994f865d6cf302d413a1c4", size = 327130, upload-time = "2026-06-11T21:55:35.921Z" }, + { url = "https://files.pythonhosted.org/packages/d8/bc/6d6c7ba8709c85f8f2c390b2b118d6fb08a783676a572271851bf45a7d22/protobuf-7.35.1-cp310-abi3-win32.whl", hash = "sha256:353652e4efd0bca5b5fc2656abf8307ef351f0cf938c9eba09f0e09c20a25c30", size = 428945, upload-time = "2026-06-11T21:55:37.034Z" }, + { url = "https://files.pythonhosted.org/packages/0a/19/8d0cb6f20a1ef7b18f1c8986ad5783f22f84cce39c6ce9a6e645ea55192e/protobuf-7.35.1-cp310-abi3-win_amd64.whl", hash = "sha256:230a75ddfc2de4806e56696ce9640c1cdfdb6543b7cfce98d42a4c0a0e7bdb87", size = 439996, upload-time = "2026-06-11T21:55:38.123Z" }, + { url = "https://files.pythonhosted.org/packages/19/c7/5f7c636ec43e0c545e28d1f1db71990108306f7bdcb89f069ba97e428e7f/protobuf-7.35.1-py3-none-any.whl", hash = "sha256:4bc97768d8fe4ad6743c8a19403e314511ed9f6d13205b687e52421c023ac1b9", size = 171659, upload-time = "2026-06-11T21:55:39.155Z" }, +] + [[package]] name = "pyarrow" version = "24.0.0" @@ -2621,6 +2783,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/51/be/6f79d55816d5c22557cf27533543d5d70dfe692adfbee4b99f2760674f38/pyarrow-24.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:c91d00057f23b8d353039520dc3a6c09d8608164c692e9f59a175a42b2ae0c19", size = 28131282, upload-time = "2026-04-21T10:51:16.815Z" }, ] +[[package]] +name = "pyasn1" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/5f/6583902b6f79b399c9c40674ac384fd9cd77805f9e6205075f828ef11fb2/pyasn1-0.6.3.tar.gz", hash = "sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf", size = 148685, upload-time = "2026-03-17T01:06:53.382Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/a0/7d793dce3fa811fe047d6ae2431c672364b462850c6235ae306c0efd025f/pyasn1-0.6.3-py3-none-any.whl", hash = "sha256:a80184d120f0864a52a073acc6fc642847d0be408e7c7252f31390c0f4eadcde", size = 83997, upload-time = "2026-03-17T01:06:52.036Z" }, +] + +[[package]] +name = "pyasn1-modules" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892, upload-time = "2025-03-28T02:41:22.17Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" }, +] + [[package]] name = "pycparser" version = "3.0" diff --git a/examples/gcp-serverless/openai/uv.lock b/examples/gcp-serverless/openai/uv.lock index e9af11818..740a01516 100644 --- a/examples/gcp-serverless/openai/uv.lock +++ b/examples/gcp-serverless/openai/uv.lock @@ -15,7 +15,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.2.14" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -24,9 +24,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/16/ba/76928c19495fe1d70410e7c92e5e3961096d28056ed5db11ed9401cfff3e/agentkernel-0.2.14.tar.gz", hash = "sha256:3f1d1179016d177ad51bd781b7c2b649d7ade0920926b8925f0fb45b62ab985e", size = 124556, upload-time = "2026-03-11T06:50:25.882Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/cd/a1f856dc5e0c724e59aeab6238d5cc796d182c13c9fa12c10d0ea0108530/agentkernel-0.2.14-py3-none-any.whl", hash = "sha256:d4b4fbb1a390dcd623098b65fa4c0d4483eea8db31f34b11f14ea0ae49766f8d", size = 189365, upload-time = "2026-03-11T06:50:23.55Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -924,11 +924,11 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "agentkernel", extras = ["openai", "api", "redis"], specifier = ">=0.2.14" }] +requires-dist = [{ name = "agentkernel", extras = ["openai", "api", "redis"], specifier = ">=0.5.1" }] [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.2.14" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "isort", specifier = ">=5.0.0" }, { name = "mypy", specifier = ">=1.0.0" }, diff --git a/examples/memory/cosmos/uv.lock b/examples/memory/cosmos/uv.lock index 633786647..1ab7e2ad6 100644 --- a/examples/memory/cosmos/uv.lock +++ b/examples/memory/cosmos/uv.lock @@ -15,7 +15,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -24,9 +24,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -269,7 +269,7 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "agentkernel", extras = ["openai", "redis", "azure", "test"], specifier = ">=0.4.0" }] +requires-dist = [{ name = "agentkernel", extras = ["openai", "redis", "azure", "test"], specifier = ">=0.5.1" }] [package.metadata.requires-dev] dev = [ diff --git a/examples/memory/dynamodb/uv.lock b/examples/memory/dynamodb/uv.lock index 62c740d02..33198a578 100644 --- a/examples/memory/dynamodb/uv.lock +++ b/examples/memory/dynamodb/uv.lock @@ -15,7 +15,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -24,9 +24,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -228,11 +228,11 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "agentkernel", extras = ["openai", "redis", "aws"], specifier = ">=0.4.0" }] +requires-dist = [{ name = "agentkernel", extras = ["openai", "redis", "aws"], specifier = ">=0.5.1" }] [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "isort", specifier = ">=5.0.0" }, { name = "mypy", specifier = ">=1.0.0" }, diff --git a/examples/memory/key-value-cache/uv.lock b/examples/memory/key-value-cache/uv.lock index 36b7e27bc..fd6a2cb12 100644 --- a/examples/memory/key-value-cache/uv.lock +++ b/examples/memory/key-value-cache/uv.lock @@ -9,7 +9,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -18,9 +18,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -1337,11 +1337,11 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "agentkernel", extras = ["openai", "api"], specifier = ">=0.4.0" }] +requires-dist = [{ name = "agentkernel", extras = ["openai", "api"], specifier = ">=0.5.1" }] [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "isort", specifier = ">=5.0.0" }, { name = "mypy", specifier = ">=1.0.0" }, diff --git a/examples/memory/redis/uv.lock b/examples/memory/redis/uv.lock index be02c48b2..b7e8d9b50 100644 --- a/examples/memory/redis/uv.lock +++ b/examples/memory/redis/uv.lock @@ -15,7 +15,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.4.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -24,9 +24,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/87/ff7a485d0611da5ce78b67159717854dee7128a387a0cb2abbb7f28443e4/agentkernel-0.4.0.tar.gz", hash = "sha256:e628ed432374b48f4c55ac76f840c8b17390ceca320d8816b9a7e968dccaa292", size = 179480, upload-time = "2026-05-04T10:32:06.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0e/62433282db09e31b8e0413c2589c38df45a63f9c3f70a9f1c8e00ed67a8c/agentkernel-0.4.0-py3-none-any.whl", hash = "sha256:b5efbffada213aa6c6ac065c1b1caa94eaa776e0d298d7d55c20f778cfd596e0", size = 253858, upload-time = "2026-05-04T10:32:04.191Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, ] [package.optional-dependencies] @@ -225,11 +225,11 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "agentkernel", extras = ["openai", "redis"], specifier = ">=0.4.0" }] +requires-dist = [{ name = "agentkernel", extras = ["openai", "redis"], specifier = ">=0.5.1" }] [package.metadata.requires-dev] dev = [ - { name = "agentkernel", extras = ["test"], specifier = ">=0.4.0" }, + { name = "agentkernel", extras = ["test"], specifier = ">=0.5.1" }, { name = "black", specifier = ">=23.0.0" }, { name = "isort", specifier = ">=5.0.0" }, { name = "mypy", specifier = ">=1.0.0" }, From fae5c49ce47e8f047fdfb4a72699e2c13579b05f Mon Sep 17 00:00:00 2001 From: yl-nuwan Date: Thu, 18 Jun 2026 11:18:25 +0530 Subject: [PATCH 32/90] enabled the agent code after load testing --- .../app_agent_runner.py | 57 ++++++++++--------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/examples/aws-containerized/openai-dynamodb-scalable/app_agent_runner.py b/examples/aws-containerized/openai-dynamodb-scalable/app_agent_runner.py index 1450b2316..90c46effe 100644 --- a/examples/aws-containerized/openai-dynamodb-scalable/app_agent_runner.py +++ b/examples/aws-containerized/openai-dynamodb-scalable/app_agent_runner.py @@ -1,27 +1,30 @@ -import logging -import signal -import sys -import time - -logging.basicConfig(level=logging.INFO) - -running = True - - -def shutdown(*args): - global running - logging.info("Shutdown requested") - running = False - - -signal.signal(signal.SIGTERM, shutdown) -signal.signal(signal.SIGINT, shutdown) - -logging.info("Agent Runner starting") - -while running: - time.sleep(30) - logging.info("Agent Runner heartbeat") - -logging.info("Agent Runner stopping") -sys.exit(0) +from agentkernel.deployment.aws.containerized import ECSAgentRunner +from agentkernel.openai import OpenAIModule +from agents import Agent + +math_agent = Agent( + name="math", + handoff_description="Specialist agent for math questions", + instructions="You provide help with math problems. Explain your reasoning at each step and include examples. \ + If prompted for anything else you refuse to answer.", +) + +history_agent = Agent( + name="history", + handoff_description="Specialist agent for historical questions", + instructions="You provide assistance with historical queries. Explain important events and context clearly.", +) + +triage_agent = Agent( + name="triage", + instructions="You determine which agent to use based on the user's question.", + handoffs=[history_agent, math_agent], +) + +OpenAIModule([triage_agent, math_agent, history_agent]) + +# Agent Runner entrypoint - polls Input Queue, runs agent, sends to Output Queue. +handler = ECSAgentRunner.run + +if __name__ == "__main__": + handler() From 45ebaf21b7ad86aa29e4d0ba734205c792f400e4 Mon Sep 17 00:00:00 2001 From: yl-nuwan Date: Thu, 18 Jun 2026 11:24:03 +0530 Subject: [PATCH 33/90] fix: update autoscaling conditions to use local.enable_autoscaling instead of var.enable_queue_mode --- ak-deployment/ak-aws/containerized/scaling.tf | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ak-deployment/ak-aws/containerized/scaling.tf b/ak-deployment/ak-aws/containerized/scaling.tf index 3e8be0403..f55197cab 100644 --- a/ak-deployment/ak-aws/containerized/scaling.tf +++ b/ak-deployment/ak-aws/containerized/scaling.tf @@ -74,14 +74,14 @@ resource "aws_iam_policy" "backlog_metric_lambda_policy" { } resource "aws_iam_role_policy_attachment" "backlog_metric_lambda" { - count = var.enable_queue_mode ? 1 : 0 + count = local.enable_autoscaling ? 1 : 0 role = aws_iam_role.backlog_metric_lambda_role[0].name policy_arn = aws_iam_policy.backlog_metric_lambda_policy[0].arn } data "archive_file" "backlog_metric_lambda" { - count = var.enable_queue_mode ? 1 : 0 + count = local.enable_autoscaling ? 1 : 0 type = "zip" output_path = "${path.module}/.terraform/lambda/backlog_metric.zip" @@ -152,7 +152,7 @@ PYTHON } resource "aws_lambda_function" "backlog_metric" { - count = var.enable_queue_mode ? 1 : 0 + count = local.enable_autoscaling ? 1 : 0 function_name = "${local.prefix}-backlog-metric" role = aws_iam_role.backlog_metric_lambda_role[0].arn runtime = "python3.12" @@ -175,14 +175,14 @@ resource "aws_lambda_function" "backlog_metric" { } resource "aws_cloudwatch_log_group" "backlog_metric_lambda" { - count = var.enable_queue_mode ? 1 : 0 + count = local.enable_autoscaling ? 1 : 0 name = "/aws/lambda/${local.prefix}-backlog-metric" retention_in_days = 7 tags = var.tags } resource "aws_cloudwatch_event_rule" "backlog_metric_schedule" { - count = var.enable_queue_mode ? 1 : 0 + count = local.enable_autoscaling ? 1 : 0 name = "${local.prefix}-backlog-metric-schedule" description = "Trigger BacklogPerTask metric calculation every minute" schedule_expression = "rate(1 minute)" @@ -196,7 +196,7 @@ resource "aws_cloudwatch_event_target" "backlog_metric_lambda" { } resource "aws_lambda_permission" "allow_eventbridge" { - count = var.enable_queue_mode ? 1 : 0 + count = local.enable_autoscaling ? 1 : 0 statement_id = "AllowExecutionFromEventBridge" action = "lambda:InvokeFunction" function_name = aws_lambda_function.backlog_metric[0].function_name @@ -205,7 +205,7 @@ resource "aws_lambda_permission" "allow_eventbridge" { } resource "aws_appautoscaling_target" "agent_runner" { - count = var.enable_queue_mode ? 1 : 0 + count = local.enable_autoscaling ? 1 : 0 max_capacity = var.agent_runner_max_count min_capacity = var.agent_runner_min_count resource_id = "service/${module.ecs.cluster_name}/${local.prefix}-agent-runner" From 5362d289ca2d22303ce0deb641589007c185dd39 Mon Sep 17 00:00:00 2001 From: Nuwan Udara Date: Thu, 18 Jun 2026 11:37:10 +0530 Subject: [PATCH 34/90] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- docs/queue-mode-guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/queue-mode-guide.md b/docs/queue-mode-guide.md index a28644699..a63f02a6f 100644 --- a/docs/queue-mode-guide.md +++ b/docs/queue-mode-guide.md @@ -62,7 +62,7 @@ Both queues are **FIFO** with: |---------|---------| | `MessageGroupId = SessionID` | Preserves order within a session | | `MessageDeduplicationId` | Prevents the agent running the same turn twice | -| `MessageVisiibilityTimeout` | Makes undeleted messages reappear for retry | +| `MessageVisibilityTimeout` | Makes undeleted messages reappear for retry | | `MessageRetentionPeriod` | Auto-deletes stuck messages, breaks infinite loops | | DLQ (optional) | Catches messages that exceed `maxReceiveCount` | From 0db8c890f31544cde8a0485049fb427f410d49c4 Mon Sep 17 00:00:00 2001 From: Nuwan Udara Date: Thu, 18 Jun 2026 11:38:51 +0530 Subject: [PATCH 35/90] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../deployment/aws/containerized/ecs_queue_handler.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py b/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py index 15bb0a82b..741501925 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py @@ -133,10 +133,11 @@ async def poll_response(session_id: str, request_id: str = None): if self._config.execution.mode != ExecutionMode.REST_ASYNC: raise HTTPException(status_code=404, detail="GET endpoint only available in REST_ASYNC mode") - effective_request_id = request_id or session_id - if not effective_request_id: + if not request_id: raise HTTPException(status_code=400, detail={"error": "request_id is required", "session_id": session_id}) + effective_request_id = request_id + self._log.info(f"Polling for response: request_id={effective_request_id}, session_id={session_id}") response = self._get_response_store().get_message(effective_request_id) From 234b3fe4557217f4c620ead3866b750be22ed206 Mon Sep 17 00:00:00 2001 From: yl-nuwan Date: Sat, 20 Jun 2026 10:30:15 +0530 Subject: [PATCH 36/90] resolve pr review suggestions --- ak-deployment/ak-aws/containerized/README.md | 4 ++-- ak-deployment/ak-aws/containerized/ecs.tf | 2 +- ak-deployment/ak-aws/containerized/queue.tf | 6 ++++-- ak-deployment/ak-aws/containerized/variables.tf | 2 +- .../aws-containerized/openai-dynamodb-scalable/README.md | 2 +- .../openai-dynamodb-scalable/deploy/main.tf | 2 +- 6 files changed, 10 insertions(+), 8 deletions(-) diff --git a/ak-deployment/ak-aws/containerized/README.md b/ak-deployment/ak-aws/containerized/README.md index 2b0b8f139..d8f4df3b1 100644 --- a/ak-deployment/ak-aws/containerized/README.md +++ b/ak-deployment/ak-aws/containerized/README.md @@ -297,7 +297,7 @@ module "scalable_queue_app" { ecs_container_port = 8000 # Override container command (ECR module injects Lambda-style CMD) - ecs_container_command = ["python", "app_rest_service.py"] + container_entrypoint_override = ["python", "app_rest_service.py"] # Session storage create_dynamodb_memory_table = true @@ -486,7 +486,7 @@ aws application-autoscaling describe-scaling-activities \ | `agent_runner_scale_out_cooldown` | Seconds to wait before scaling out again | `number` | `30` | no | | `sqs_input_visibility_timeout` | SQS Input Queue visibility timeout (seconds) | `number` | `30` | no | | `sqs_output_visibility_timeout` | SQS Output Queue visibility timeout (seconds) | `number` | `30` | no | -| `ecs_container_command` | Command override for REST Service container | `list(string)` | `null` | no | +| `container_entrypoint_override` | Command override for REST Service container | `list(string)` | `null` | no | ## 📤 Outputs diff --git a/ak-deployment/ak-aws/containerized/ecs.tf b/ak-deployment/ak-aws/containerized/ecs.tf index 77bb339ee..cde6c9aaa 100644 --- a/ak-deployment/ak-aws/containerized/ecs.tf +++ b/ak-deployment/ak-aws/containerized/ecs.tf @@ -87,7 +87,7 @@ module "ecs" { readonlyRootFilesystem = false # Command override - if provided, replaces the Docker image's CMD - command = var.ecs_container_command + command = var.container_entrypoint_override portMappings = [ { diff --git a/ak-deployment/ak-aws/containerized/queue.tf b/ak-deployment/ak-aws/containerized/queue.tf index b8c8e3ee3..6e0d8e009 100644 --- a/ak-deployment/ak-aws/containerized/queue.tf +++ b/ak-deployment/ak-aws/containerized/queue.tf @@ -3,7 +3,8 @@ data "aws_region" "current" {} module "input_queue" { count = var.enable_queue_mode ? 1 : 0 - source = "../common/modules/sqs" + source = "yaalalabs/ak-common/aws//modules/sqs" + version = "0.5.1" product_alias = var.product_alias env_alias = var.env_alias @@ -37,7 +38,8 @@ module "input_queue" { module "output_queue" { count = var.enable_queue_mode ? 1 : 0 - source = "../common/modules/sqs" + source = "yaalalabs/ak-common/aws//modules/sqs" + version = "0.5.1" product_alias = var.product_alias env_alias = var.env_alias diff --git a/ak-deployment/ak-aws/containerized/variables.tf b/ak-deployment/ak-aws/containerized/variables.tf index 2c4476808..42dc1a136 100644 --- a/ak-deployment/ak-aws/containerized/variables.tf +++ b/ak-deployment/ak-aws/containerized/variables.tf @@ -163,7 +163,7 @@ variable "container_type" { } } -variable "ecs_container_command" { +variable "container_entrypoint_override" { type = list(string) description = "Command override for the main ECS task (REST service). If not set, uses the Docker image's CMD. Example: [\"python\", \"app_rest_service.py\"]" default = null diff --git a/examples/aws-containerized/openai-dynamodb-scalable/README.md b/examples/aws-containerized/openai-dynamodb-scalable/README.md index 1fc8d27c8..0d4349815 100644 --- a/examples/aws-containerized/openai-dynamodb-scalable/README.md +++ b/examples/aws-containerized/openai-dynamodb-scalable/README.md @@ -180,7 +180,7 @@ CMD ["python","-c","from app import runner; runner()"] ``` This is overridden at ECS task definition level using: -- `ecs_container_command = ["python", "app_rest_service.py"]` +- `container_entrypoint_override = ["python", "app_rest_service.py"]` - `agent_runner_command = ["python", "app_agent_runner.py"]` See [COMMAND_OVERRIDE.md](../../ak-deployment/ak-aws/containerized/COMMAND_OVERRIDE.md) for details. diff --git a/examples/aws-containerized/openai-dynamodb-scalable/deploy/main.tf b/examples/aws-containerized/openai-dynamodb-scalable/deploy/main.tf index 9037e1fda..f7634d332 100644 --- a/examples/aws-containerized/openai-dynamodb-scalable/deploy/main.tf +++ b/examples/aws-containerized/openai-dynamodb-scalable/deploy/main.tf @@ -63,7 +63,7 @@ module "containerized_agents" { # Override the command to use the correct entrypoint # The ECR module adds a Lambda-style CMD, so we override it here - ecs_container_command = ["python", "app_rest_service.py"] + container_entrypoint_override = ["python", "app_rest_service.py"] # ---- agent memory (session store) ---- create_dynamodb_memory_table = true From 8729ad7280a2abf35473b6157462e7e4d8ecef31 Mon Sep 17 00:00:00 2001 From: yl-nuwan Date: Sun, 21 Jun 2026 01:16:34 +0530 Subject: [PATCH 37/90] refactor(containerized): restructure infrastructure into modular components - Extract ECS, API Gateway, IAM, and DynamoDB into separate root modules - Create reusable modules for agent-runner, queues, and rest-service deployments - Split queue and rest service configurations into dedicated files (queue_mode.tf, rest_service.tf) - Replace monolithic ecs.tf and scaling.tf with modular architecture - Simplify root configuration by delegating service setup to modules - Update state management to support both Redis and DynamoDB backends - Reorganize documentation to reflect new architecture and deployment modes - Support both synchronous (direct) and asynchronous (queue-based) execution patterns - Enable independent scaling and configuration of agent-runner and rest-service components --- ak-deployment/ak-aws/containerized/README.md | 1100 +++++------------ .../ak-aws/containerized/api_gateway.tf | 22 +- .../ak-aws/containerized/api_gateway_async.tf | 24 + .../ak-aws/containerized/dynamodb.tf | 21 + ak-deployment/ak-aws/containerized/ecs.tf | 212 ---- ak-deployment/ak-aws/containerized/iam.tf | 81 ++ .../ak-aws/containerized/modules/README.md | 339 +++++ .../modules/agent-runner/main.tf | 466 +++++++ .../modules/agent-runner/outputs.tf | 24 + .../modules/agent-runner/variables.tf | 132 ++ .../containerized/modules/queues/main.tf | 72 ++ .../containerized/modules/queues/outputs.tf | 19 + .../containerized/modules/queues/variables.tf | 48 + .../modules/rest-service/main.tf | 233 ++++ .../modules/rest-service/outputs.tf | 49 + .../modules/rest-service/variables.tf | 116 ++ ak-deployment/ak-aws/containerized/outputs.tf | 18 +- ak-deployment/ak-aws/containerized/queue.tf | 430 ------- .../ak-aws/containerized/queue_mode.tf | 63 + .../ak-aws/containerized/rest_service.tf | 54 + ak-deployment/ak-aws/containerized/scaling.tf | 247 ---- ak-deployment/ak-aws/containerized/state.tf | 11 + .../ak-aws/containerized/variables.tf | 302 ++--- .../openai-dynamodb-scalable/README.md | 77 +- .../openai-dynamodb-scalable/deploy/main.tf | 149 ++- 25 files changed, 2372 insertions(+), 1937 deletions(-) create mode 100644 ak-deployment/ak-aws/containerized/api_gateway_async.tf create mode 100644 ak-deployment/ak-aws/containerized/dynamodb.tf delete mode 100644 ak-deployment/ak-aws/containerized/ecs.tf create mode 100644 ak-deployment/ak-aws/containerized/iam.tf create mode 100644 ak-deployment/ak-aws/containerized/modules/README.md create mode 100644 ak-deployment/ak-aws/containerized/modules/agent-runner/main.tf create mode 100644 ak-deployment/ak-aws/containerized/modules/agent-runner/outputs.tf create mode 100644 ak-deployment/ak-aws/containerized/modules/agent-runner/variables.tf create mode 100644 ak-deployment/ak-aws/containerized/modules/queues/main.tf create mode 100644 ak-deployment/ak-aws/containerized/modules/queues/outputs.tf create mode 100644 ak-deployment/ak-aws/containerized/modules/queues/variables.tf create mode 100644 ak-deployment/ak-aws/containerized/modules/rest-service/main.tf create mode 100644 ak-deployment/ak-aws/containerized/modules/rest-service/outputs.tf create mode 100644 ak-deployment/ak-aws/containerized/modules/rest-service/variables.tf delete mode 100644 ak-deployment/ak-aws/containerized/queue.tf create mode 100644 ak-deployment/ak-aws/containerized/queue_mode.tf create mode 100644 ak-deployment/ak-aws/containerized/rest_service.tf delete mode 100644 ak-deployment/ak-aws/containerized/scaling.tf diff --git a/ak-deployment/ak-aws/containerized/README.md b/ak-deployment/ak-aws/containerized/README.md index d8f4df3b1..9c5a76732 100644 --- a/ak-deployment/ak-aws/containerized/README.md +++ b/ak-deployment/ak-aws/containerized/README.md @@ -1,872 +1,452 @@ -# Agent Kernel - AWS Containerized Module +# AWS Containerized Deployment -A comprehensive Terraform module for deploying containerized applications on AWS using ECS Fargate, with Application Load Balancer, API Gateway, and optional Redis or DynamoDB integration. +Scalable, production-ready deployment of Agent Kernel on AWS using ECS Fargate with optional queue-based processing and auto-scaling. -## 📋 Overview +## 📋 Table of Contents -This module provides a complete containerized deployment solution on AWS: +- [Overview](#overview) +- [Architecture](#architecture) +- [Quick Start](#quick-start) +- [Configuration](#configuration) +- [Deployment Modes](#deployment-modes) +- [Auto Scaling](#auto-scaling) +- [Examples](#examples) +- [Migration](#migration) -- 🐳 **ECS Fargate**: Serverless container orchestration with auto-scaling -- 🔄 **Application Load Balancer**: Intelligent traffic distribution and health checks -- 🌐 **API Gateway HTTP API**: RESTful API endpoints with custom routing -- 🔒 **VPC Networking**: Private subnets, NAT Gateway, and security groups -- 💾 **State Management**: Optional ElastiCache Redis or DynamoDB for session/state persistence -- 📊 **CloudWatch Monitoring**: Container logs and application metrics -- 🏗️ **Automated Build**: Docker image building and ECR management +## Overview -Perfect for microservices, web applications, APIs requiring persistent connections, stateful services, and applications needing more resources than Lambda provides. +This deployment uses AWS ECS Fargate to run containerized Agent Kernel applications with: -## 📋 Requirements +- ✅ **Automatic scaling** based on queue depth +- ✅ **Async processing** with SQS queues +- ✅ **Session persistence** with Redis or DynamoDB +- ✅ **Load balancing** with ALB and API Gateway +- ✅ **Modular configuration** with config objects -| Name | Version | -|------|---------| -| Terraform | >= 1.9.5 | -| AWS Provider | >= 6.11.0 | -| Docker Provider | >= 3.6.2 | +## Architecture -## 🚀 Usage +### Non-Queue Mode (Direct Execution) -### Basic Containerized API +``` +┌─────────────┐ ┌─────┐ ┌──────────────┐ +│ API Gateway │─────▶│ ALB │─────▶│ REST Service │ +│ (HTTP) │ │ │ │ (ECS Task) │ +└─────────────┘ └─────┘ │ │ + │ • Request │ + │ • Agent │ + │ • Response │ + └──────────────┘ +``` -```hcl -module "container_app" { - source = "yaalalabs/ak-containerized/aws" +The REST service contains both request handling and agent execution in a single container. - region = "us-west-2" - product_alias = "myapp" - env_alias = "prod" - product_display_name = "My Containerized App" - - module_name = "api" - package_path = "${path.module}/src" # Directory with Dockerfile - - # ECS Configuration - ecs_cpu = 512 - ecs_memory = 1024 - ecs_desired_count = 2 - ecs_container_port = 8000 - - # Health check - ecs_health_check_endpoint = "/health" - - # Environment variables - environment_variables = { - ENVIRONMENT = "production" - LOG_LEVEL = "info" - PORT = "8000" - } - - # API Gateway - api_version = "v1" - api_base_path = "api" - agent_endpoint = "chat" - gateway_endpoints = [ - { - path = "app", - method = "GET", - overwrite_path = "/custom/task" - }, - { - path = "data", - method = "POST", - overwrite_path = "/app/library/data" - } - ] - - tags = { - Environment = "production" - Service = "api" - } -} - -output "api_url" { - value = module.container_app.agent_invoke_url -} +### Queue Mode (Async Execution) -output "alb_dns" { - value = module.container_app.alb_dns_name -} ``` - -### With Existing VPC - -```hcl -# Use existing VPC -module "container_app" { - source = "yaalalabs/ak-containerized/aws" - - region = "us-west-2" - product_alias = "myapp" - env_alias = "prod" - product_display_name = "API Service" - - module_name = "api" - package_path = "${path.module}/docker" - - # Use existing VPC - vpc_id = "vpc-0abc123def456789" - private_subnet_ids = ["subnet-111", "subnet-222"] - - ecs_cpu = 1024 - ecs_memory = 2048 - ecs_desired_count = 3 - - environment_variables = { - DB_HOST = "database.example.com" - CACHE = "enabled" - } - - api_version = "v2" - agent_endpoint = "process" -} +┌─────────────┐ ┌─────┐ ┌──────────────┐ +│ API Gateway │─────▶│ ALB │─────▶│ REST Service │ +│ (HTTP) │ │ │ │ (ECS Task) │ +└─────────────┘ └─────┘ │ │ + │ • Thread 1 │ + │ (Request) │ + │ • Thread 2 │ + │ (Response) │ + └──────┬───────┘ + │ + ┌────────────────────┴────────────────────┐ + │ │ + ▼ ▼ + ┌─────────────┐ ┌─────────────┐ + │ Input Queue │ │Output Queue │ + │ (SQS) │ │ (SQS) │ + └──────┬──────┘ └──────▲──────┘ + │ │ + │ ┌──────────────┐ │ + └─────────▶│Agent Runner │───────────────┘ + │ (ECS Task) │ + │ │ + │ • Poll Input │ + │ • Run Agent │ + │ • Send Output│ + └──────┬───────┘ + │ + ▼ + ┌──────────────────┐ + │ Auto Scaling │ + │ │ + │ Scales 0-N tasks │ + │ based on queue │ + │ backlog │ + └──────────────────┘ + + ┌──────────────────┐ + │ Response Store │ + │ (DynamoDB) │ + │ │ + │ Maps request IDs │ + │ to responses │ + └──────────────────┘ ``` -### With Redis and Custom VPC - -```hcl -module "container_app_redis" { - source = "yaalalabs/ak-containerized/aws" +The REST service handles HTTP requests while Agent Runner processes the actual agent logic from queues. - region = "us-west-2" - product_alias = "myapp" - env_alias = "prod" - product_display_name = "Stateful API" - - module_name = "stateful-api" - package_path = "${path.module}/app" - - # Create new VPC with custom CIDR - vpc_cidr = "10.1.0.0/16" - public_subnet_cidrs = ["10.1.1.0/24", "10.1.2.0/24"] - private_subnet_cidrs = ["10.1.10.0/23", "10.1.12.0/23"] - - # Enable Redis - create_redis_cluster = true - - # ECS Configuration - ecs_cpu = 2048 - ecs_memory = 4096 - ecs_desired_count = 4 - ecs_container_port = 3000 - - environment_variables = { - NODE_ENV = "production" - WORKERS = "4" - # Redis URL automatically injected as AK_REDIS_URL - } - - api_version = "v1" - agent_endpoint = "session" -} -``` +## Quick Start -### With DynamoDB for Session Storage +### Basic Deployment ```hcl -module "container_app_dynamodb" { - source = "yaalalabs/ak-containerized/aws" - - region = "us-west-2" - product_alias = "myapp" - env_alias = "prod" - product_display_name = "Serverless State API" - - module_name = "serverless-api" - package_path = "${path.module}/app" - - # Create new VPC - vpc_cidr = "10.2.0.0/16" - public_subnet_cidrs = ["10.2.1.0/24", "10.2.2.0/24"] - private_subnet_cidrs = ["10.2.10.0/23", "10.2.12.0/23"] - - # Enable DynamoDB for session storage - create_dynamodb_memory_table = true - - # ECS Configuration - ecs_cpu = 1024 - ecs_memory = 2048 - ecs_desired_count = 2 - ecs_container_port = 8000 - - environment_variables = { - NODE_ENV = "production" - # DynamoDB table name automatically injected as AK_SESSION__DYNAMODB__TABLE_NAME +module "containerized_agents" { + source = "yaalalabs/ak-containerized/aws" + version = "0.5.1" + + product_alias = "my-agent" + env_alias = "dev" + module_name = "chatbot" + region = "us-east-1" + + # Docker image path + package_path = "./dist" + + # REST Service configuration + rest_service = { + cpu = 256 + memory = 512 + environment_variables = { + OPENAI_API_KEY = var.api_key + } } - api_version = "v1" - agent_endpoint = "chat" + # Session storage + create_redis_cluster = true } ``` -### High-Availability Production Setup +### Scalable Queue-Based Deployment ```hcl -module "production_app" { - source = "yaalalabs/ak-containerized/aws" - - region = "us-west-2" - product_alias = "enterprise" - env_alias = "prod" - product_display_name = "Enterprise Application" - - module_name = "core-api" - package_path = "${path.module}/docker" - - # High-performance VPC - vpc_cidr = "10.0.0.0/16" - - # Redis for low-latency session management (or use DynamoDB with create_dynamodb_memory_table = true) - create_redis_cluster = true - - # High-performance ECS - ecs_cpu = 4096 # 4 vCPU - ecs_memory = 8192 # 8 GB - ecs_desired_count = 6 # High availability - - ecs_container_port = 8080 - ecs_health_check_endpoint = "/api/health" - - environment_variables = { - ENVIRONMENT = "production" - LOG_LEVEL = "warn" - MAX_CONNECTIONS = "1000" - ENABLE_METRICS = "true" - GRACEFUL_SHUTDOWN_MS = "30000" +module "containerized_agents" { + source = "yaalalabs/ak-containerized/aws" + version = "0.5.1" + + product_alias = "my-agent" + env_alias = "prod" + module_name = "assistant" + region = "us-east-1" + + package_path = "./dist-rest-service" + + # REST Service (handles HTTP requests) + rest_service = { + cpu = 512 + memory = 1024 + desired_count = 2 + command = ["python", "app_rest_service.py"] + environment_variables = { + OPENAI_API_KEY = var.api_key + } } - api_version = "v1" - agent_endpoint = "enterprise" + # Enable queue mode + enable_queue_mode = true + queue_mode_type = "async" # or "sync" + + # Queue configuration + queue_config = { + input_queue_visibility_timeout = 120 + output_queue_visibility_timeout = 60 + input_queue_create_dlq = true + output_queue_create_dlq = true + } - tags = { - Environment = "production" - Compliance = "SOC2" - CostCenter = "Engineering" - Criticality = "high" + # Agent Runner (processes from queue) + agent_runner = { + cpu = 1024 + memory = 2048 + desired_count = 1 + package_path = "../dist-agent-runner" # Build separate image + command = ["python", "app_agent_runner.py"] + environment_variables = { + OPENAI_API_KEY = var.api_key + } } -} - -# CloudWatch alarms for production -resource "aws_cloudwatch_metric_alarm" "high_cpu" { - alarm_name = "enterprise-prod-high-cpu" - comparison_operator = "GreaterThanThreshold" - evaluation_periods = 2 - metric_name = "CPUUtilization" - namespace = "AWS/ECS" - period = 300 - statistic = "Average" - threshold = 80 - dimensions = { - ClusterName = module.production_app.cluster_arn + # Auto scaling + scaling_config = { + enabled = true + min_count = 1 + max_count = 10 + backlog_target = 5 + scale_in_cooldown = 180 + scale_out_cooldown = 60 } + + create_dynamodb_memory_table = true } ``` -### Scalable Queue Mode with SQS +## Configuration -For high-throughput workloads with asynchronous processing: +### REST Service Object ```hcl -module "agent_runner_image" { - source = "yaalalabs/ak-common/aws//modules/ecr" - - env_alias = var.env_alias - module_name = "${var.module_name}-runner" - product_alias = var.product_alias - source_path = "${path.module}/dist-agent-runner" +rest_service = { + cpu = 256 # Fargate CPU units (256, 512, 1024, etc.) + memory = 512 # Fargate memory in MiB + desired_count = 1 # Number of tasks + container_port = 8000 # Container port + health_check_endpoint = "/health" # Health check path + image_uri = null # Docker image (defaults to package_path) + command = null # Override Docker CMD + environment_variables = {} # Service-specific env vars } +``` -module "scalable_queue_app" { - source = "yaalalabs/ak-containerized/aws" +### Agent Runner Object - region = "us-west-2" - product_alias = "myapp" - env_alias = "prod" - product_display_name = "Scalable Agent Service" - - module_name = "scalable" - package_path = "${path.module}/dist-rest-service" - - vpc_id = var.vpc_id - private_subnet_ids = var.private_subnet_ids - - # ECS Configuration for REST Service - ecs_cpu = 1024 - ecs_memory = 2048 - ecs_desired_count = 2 - ecs_container_port = 8000 - - # Override container command (ECR module injects Lambda-style CMD) - container_entrypoint_override = ["python", "app_rest_service.py"] - - # Session storage - create_dynamodb_memory_table = true - - # Enable Queue Mode - enable_queue_mode = true - queue_mode_type = "sync" # or "async" for polling mode - - # Enable Autoscaling (optional) - enable_agent_runner_autoscaling = true - agent_runner_min_count = 1 - agent_runner_max_count = 10 - agent_runner_backlog_target = 10 - - # Agent Runner Configuration - agent_runner_image_uri = module.agent_runner_image.docker_image_uri - agent_runner_command = ["python", "app_agent_runner.py"] - agent_runner_cpu = 1024 - agent_runner_memory = 2048 - agent_runner_desired_count = 2 - - # SQS Configuration - sqs_input_visibility_timeout = 120 # Should exceed agent processing time - sqs_output_visibility_timeout = 60 - - environment_variables = { - OPENAI_API_KEY = var.openai_api_key - } - - api_version = "v1" - agent_endpoint = "chat" +```hcl +agent_runner = { + cpu = 512 # Fargate CPU units + memory = 1024 # Fargate memory in MiB + desired_count = 1 # Initial task count + package_path = null # Path to build agent runner Docker image + image_uri = null # Or provide pre-built image URI + command = null # Override Docker CMD + environment_variables = {} # Service-specific env vars } ``` -**Queue Mode Architecture:** -- **REST Service** (Thread 1): Accepts HTTP requests, enqueues to Input Queue -- **REST Service** (Thread 2): Polls Output Queue, writes to DynamoDB Response Store -- **Agent Runner**: Polls Input Queue, executes agents, sends to Output Queue -- **REST_SYNC**: Client blocks until response ready in DynamoDB -- **REST_ASYNC**: Client receives `request_id`, polls GET endpoint for result +**Image Resolution Priority:** +1. If `package_path` is provided → Build Docker image from path +2. Else if `image_uri` is provided → Use specified image +3. Else → Use REST service image (from `package_path`) -**Use Cases:** -- Long-running agent workflows (>30s) -- Workloads requiring independent scaling of API and processing -- High-throughput scenarios with bursty traffic -- Decoupling request handling from agent execution +### Queue Config Object -See [examples/aws-containerized/openai-dynamodb-scalable](../../../examples/aws-containerized/openai-dynamodb-scalable/) for complete implementation. - -### Agent Runner Autoscaling - -When `enable_agent_runner_autoscaling = true`, the Agent Runner automatically scales based on queue backlog. - -#### How It Works +```hcl +queue_config = { + # Queue names (optional customization) + input_queue_name = "input-queue" # Queue name suffix + output_queue_name = "output-queue" # Queue name suffix + + # Shared settings + sqs_managed_sse_enabled = true # Enable SSE for queues + max_message_size = 262144 # 256 KB max message size + receive_wait_time_seconds = 0 # Long polling wait time + + # Input queue (requests → agent runner) + input_queue_visibility_timeout = 60 # Should be >= processing time + input_queue_message_retention_seconds = 1800 # 30 minutes + input_queue_max_receive_count = 5 # Before DLQ + input_queue_create_dlq = false # Create dead letter queue + input_queue_dlq_message_retention_seconds = 1800 + + # Output queue (agent runner → REST service) + output_queue_visibility_timeout = 60 + output_queue_message_retention_seconds = 1800 + output_queue_max_receive_count = 5 + output_queue_create_dlq = false + output_queue_dlq_message_retention_seconds = 1800 +} +``` -A Lambda function runs every **1 minute** and calculates: +### Scaling Config Object -``` -BacklogPerTask = ApproximateNumberOfMessages / max(RunningTasks, 1) +```hcl +scaling_config = { + enabled = false # Enable auto scaling + min_count = 0 # Minimum tasks (0 to scale to zero) + max_count = 10 # Maximum tasks + backlog_target = 10 # Target messages per task + scale_in_cooldown = 120 # Seconds before scaling in again + scale_out_cooldown = 30 # Seconds before scaling out again +} ``` -AWS Target Tracking monitors this metric and scales using: +## Deployment Modes -``` -New Capacity = Current Capacity × (Current Metric / Target) -``` +### Non-Queue Mode (Default) -- **Scale-out**: Aggressive and fast (~3-4 minutes) -- **Scale-in**: Conservative and gradual (~15-30 minutes, multiple cycles) +Direct synchronous execution. The REST service contains both request handling and agent logic. -#### Configuration +**Use when:** +- Simple, low-volume workloads +- Quick response times required +- No need for background processing +**Configuration:** ```hcl -enable_agent_runner_autoscaling = true -agent_runner_min_count = 1 # Minimum tasks (0 = scale to zero) -agent_runner_max_count = 10 # Maximum tasks -agent_runner_backlog_target = 10 # Target messages per task -agent_runner_scale_in_cooldown = 120 # Seconds between scale-in -agent_runner_scale_out_cooldown = 30 # Seconds between scale-out +enable_queue_mode = false # This is the default ``` -**Choosing Target Value:** -- **Lower target** (e.g., 5): Scales out sooner, more tasks, faster processing, higher cost -- **Higher target** (e.g., 20): Scales out later, fewer tasks, slower processing, lower cost -- Start with default (10), monitor queue depth and task count, then tune +### Queue Mode - Sync -#### Scaling Examples +Requests use queues but client connection stays open until response is ready. -**Scale-Out:** -``` -Queue=100, Tasks=1, BacklogPerTask=100, Target=10 -→ Scales to ~10 tasks quickly (2-3 minutes) -``` +**Use when:** +- Need queue benefits (reliability, retry) +- Can tolerate slightly longer response times +- Want to keep simple client code -**Scale-In (gradual over multiple cycles):** +**Configuration:** +```hcl +enable_queue_mode = true +queue_mode_type = "sync" ``` -Queue=20, Tasks=10, BacklogPerTask=2, Target=10 -Cycle 1: Remove 3 tasks → 7 tasks -Wait 2min (cooldown) -Cycle 2: Remove 2 tasks → 5 tasks -...continues until stable -``` +### Queue Mode - Async -#### Monitoring +Requests return immediately with a request ID. Client polls for results. -**CloudWatch Metrics:** -- `Custom/ECS/BacklogPerTask` - Custom metric -- `RunningTaskCount` - ECS service -- `ApproximateNumberOfMessages` - SQS queue +**Use when:** +- Long-running agent tasks +- High-volume concurrent requests +- Need maximum scalability -**View Lambda Logs:** -```bash -aws logs tail /aws/lambda/-backlog-metric --follow +**Configuration:** +```hcl +enable_queue_mode = true +queue_mode_type = "async" ``` -**View Scaling Activity:** +**Client flow:** ```bash -aws application-autoscaling describe-scaling-activities \ - --service-namespace ecs \ - --resource-id service// -``` +# 1. Submit request +curl -X POST .../chat -d '{"prompt":"..."}' +# Returns: {"status":"ACCEPTED","request_id":"..."} -#### Troubleshooting - -- **Scaling too aggressively**: Increase `backlog_target` or cooldown periods -- **Not scaling fast enough**: Decrease `backlog_target`, verify Lambda is running -- **Stuck at min/max**: Check capacity limits, verify messages are being processed -- **Scale to zero not working**: Set `min_count = 0`, wait 30+ minutes for gradual scale-in - -## 📥 Inputs - -| Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| -| `region` | AWS region for deployment | `string` | n/a | yes | -| `product_alias` | Short identifier for the product | `string` | n/a | yes | -| `env_alias` | Environment identifier (dev, staging, prod) | `string` | n/a | yes | -| `product_display_name` | Human-readable product name | `string` | `"An Agent Kernel deployment"` | no | -| `module_name` | Module name for resource identification | `string` | n/a | yes | -| `package_path` | Path to Docker source directory (with Dockerfile) | `string` | n/a | yes | -| `environment_variables` | Environment variables for container | `map(string)` | `{}` | no | -| `api_version` | API version for endpoint path | `string` | `"v1"` | no | -| `agent_endpoint` | API endpoint name | `string` | `"chat"` | no | -| `enable_mcp_server` | Enable MCP server and expose MCP API endpoint | `bool` | `false` | no | -| `tags` | Additional tags for resources | `map(string)` | `{}` | no | -| **API Gateway CORS** | -| `enable_cors` | Enable CORS on the HTTP API | `bool` | `true` | no | -| `cors_allow_origins` | CORS allowed origins | `list(string)` | `["*"]` | no | -| `cors_allow_methods` | CORS allowed methods | `list(string)` | `["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"]` | no | -| `cors_allow_headers` | CORS allowed headers | `list(string)` | `["*"]` | no | -| `cors_expose_headers` | CORS exposed headers | `list(string)` | `[]` | no | -| `cors_max_age` | CORS max age in seconds | `number` | `600` | no | -| `cors_allow_credentials` | Whether to allow credentials for CORS | `bool` | `false` | no | -| **API Gateway Throttling** | -| `throttling_rate_limit` | Steady-state RPS limit for default route (set `null` to disable) | `number` | `null` | no | -| `throttling_burst_limit` | Burst token bucket size for default route (set `null` to disable) | `number` | `null` | no | -| **VPC Configuration** | -| `vpc_cidr` | CIDR block for new VPC (if not using existing) | `string` | `"10.0.0.0/16"` | no | -| `public_subnet_cidrs` | Public subnet CIDRs | `list(string)` | `["10.0.1.0/24", "10.0.2.0/24"]` | no | -| `private_subnet_cidrs` | Private subnet CIDRs | `list(string)` | `["10.0.3.0/24", "10.0.4.0/24"]` | no | -| `vpc_id` | Existing VPC ID (if not creating new) | `string` | `null` | no | -| `private_subnet_ids` | Existing private subnet IDs | `list(string)` | `null` | no | -| **ECS Configuration** | -| `ecs_cpu` | Fargate CPU units (256, 512, 1024, 2048, 4096) | `number` | `256` | no | -| `ecs_memory` | Fargate memory in MiB | `number` | `512` | no | -| `ecs_desired_count` | Number of ECS tasks to run | `number` | `1` | no | -| `ecs_container_port` | Container port exposed by service | `number` | `8000` | no | -| `ecs_health_check_endpoint` | Health check path for ALB | `string` | `"/health"` | no | -| `container_type` | Container orchestration type (ecs or eks) | `string` | `"ecs"` | no | -| **State Management** | -| `create_redis_cluster` | Enable Redis ElastiCache cluster | `bool` | `false` | no | -| `create_dynamodb_memory_table` | Enable DynamoDB table for session storage | `bool` | `false` | no | -| **Queue Mode (Scalable Architecture)** | -| `enable_queue_mode` | Enable SQS queue mode with separate Agent Runner service | `bool` | `false` | no | -| `enable_agent_runner_autoscaling` | Enable SQS-based autoscaling for Agent Runner (requires `enable_queue_mode=true`) | `bool` | `false` | no | -| `queue_mode_type` | Queue mode type: `sync` (client blocks) or `async` (client polls) | `string` | `"sync"` | no | -| `agent_runner_image_uri` | Docker image URI for Agent Runner (required when `enable_queue_mode=true`) | `string` | `null` | conditional | -| `agent_runner_command` | Command override for Agent Runner container | `list(string)` | `null` | no | -| `agent_runner_cpu` | Fargate CPU units for Agent Runner | `number` | `1024` | no | -| `agent_runner_memory` | Fargate memory in MiB for Agent Runner | `number` | `2048` | no | -| `agent_runner_desired_count` | Number of Agent Runner tasks | `number` | `1` | no | -| `agent_runner_min_count` | Minimum tasks when autoscaling enabled | `number` | `0` | no | -| `agent_runner_max_count` | Maximum tasks when autoscaling enabled | `number` | `10` | no | -| `agent_runner_backlog_target` | Target messages per task for autoscaling (lower=more aggressive) | `number` | `10` | no | -| `agent_runner_scale_in_cooldown` | Seconds to wait before scaling in again | `number` | `120` | no | -| `agent_runner_scale_out_cooldown` | Seconds to wait before scaling out again | `number` | `30` | no | -| `sqs_input_visibility_timeout` | SQS Input Queue visibility timeout (seconds) | `number` | `30` | no | -| `sqs_output_visibility_timeout` | SQS Output Queue visibility timeout (seconds) | `number` | `30` | no | -| `container_entrypoint_override` | Command override for REST Service container | `list(string)` | `null` | no | - -## 📤 Outputs - -| Name | Description | Example | -|------|-------------|---------| -| `alb_dns_name` | DNS name of Application Load Balancer | `myapp-prod-alb-123456789.us-west-2.elb.amazonaws.com` | -| `cluster_arn` | ARN of the ECS cluster | `arn:aws:ecs:us-west-2:123456789012:cluster/myapp-prod-api` | -| `api_gateway_id` | API Gateway HTTP API ID | `abc123defg` | -| `api_gateway_stage` | API Gateway stage name | `default` | -| `agent_invoke_url` | Full HTTPS URL to invoke API | `https://abc123.execute-api.us-west-2.amazonaws.com/api/v1/chat` | -| `vpc_id` | VPC ID (created or used) | `vpc-0abc123def456789` | -| `redis_url` | Redis connection URL (if enabled) | `redis://myapp-prod-cache.abc123.cache.amazonaws.com:6379` | -| `dynamodb_memory_table_arn` | DynamoDB table ARN (if enabled) | `arn:aws:dynamodb:us-west-2:123456789012:table/myapp-prod-api-session_store` | -| `dynamodb_memory_table_name` | DynamoDB table name (if enabled) | `myapp-prod-api-session_store` | - -## ✨ Features - -### 🐳 ECS Fargate Configuration - -**Serverless Containers**: -- No server management required -- Automatic scaling capabilities -- Pay only for resources used -- Multiple task sizes available - -**CPU/Memory Combinations**: -| CPU (vCPU) | Memory Options (GB) | -|------------|-------------------| -| 0.25 | 0.5, 1, 2 | -| 0.5 | 1, 2, 3, 4 | -| 1 | 2, 3, 4, 5, 6, 7, 8 | -| 2 | 4-16 (1 GB increments) | -| 4 | 8-30 (1 GB increments) | - -### 🔄 Load Balancer Features - -- **Application Load Balancer**: Layer 7 HTTP/HTTPS routing -- **Health Checks**: Automatic unhealthy task replacement -- **Multiple AZs**: High availability across availability zones -- **Target Groups**: Efficient request distribution -- **CloudWatch Integration**: Detailed metrics and logging - -### 🌐 API Gateway Integration - -**HTTP API Gateway**: +# 2. Poll for result +curl -X GET .../chat/{session_id}?request_id=... +# Returns: {"result":"..."} ``` -https://{api-id}.execute-api.{region}.amazonaws.com/api/{version}/{endpoint} -``` - -**Features**: -- Low-latency API access -- Cost-effective compared to REST API -- VPC Link integration with ALB -- Custom domain support compatible - -### 🔒 Network Security -**Private Subnet Deployment**: -- Containers run in private subnets -- Internet access via NAT Gateway -- No direct public exposure +## Auto Scaling -**Security Groups**: -- ALB security group: Allows inbound HTTP/HTTPS -- ECS security group: Allows traffic from ALB only -- Redis security group: VPC-only access +### How It Works -### 💾 Redis Integration +The agent runner automatically scales based on queue backlog: -**Optional ElastiCache**: -- Automatic provisioning when enabled -- Connection URL injected as `AK_REDIS_URL` -- Deployed in private subnets -- Secure VPC-only access - -## 🏗️ Architecture +1. **Lambda function** runs every minute calculating: + ``` + BacklogPerTask = QueueDepth / max(RunningTasks, 1) + ``` -``` - ┌─────────────────────┐ - │ API Gateway │ - │ (HTTP API) │ - └──────────┬──────────┘ - │ HTTPS - ▼ - ┌─────────────────────┐ - │ VPC Link │ - └──────────┬──────────┘ - │ -┌──────────────────────────────────┼───────────────────────────────┐ -│ VPC │ │ -│ ▼ │ -│ ┌──────────────────────────┐ │ -│ │ Application Load │ │ -│ │ Balancer (Public) │ │ -│ └────────────┬─────────────┘ │ -│ │ │ -│ ┌──────────────────────────────┼───────────────────────────┐ │ -│ │ Private Subnets │ │ │ -│ │ ▼ │ │ -│ │ ┌────────────────────────────┐ │ │ -│ │ │ ECS Fargate Service │ │ │ -│ │ │ │ │ │ -│ │ │ ┌──────┐ ┌──────┐ │ │ │ -│ │ │ │Task 1│ │Task 2│ ... │ │ │ -│ │ │ └───┬──┘ └───┬──┘ │ │ │ -│ │ └──────┼─────────┼──────────┘ │ │ -│ │ │ │ │ │ -│ │ └────┬────┘ │ │ -│ │ │ │ │ -│ │ ▼ │ │ -│ │ ┌────────────────────────┐ │ │ -│ │ │ ElastiCache Redis │ │ │ -│ │ │ (Optional) │ │ │ -│ │ └────────────────────────┘ │ │ -│ └───────────────────────────────────────────────────────┘ │ -│ │ -│ ┌───────────────────────────────────────────────────────┐ │ -│ │ Public Subnets │ │ -│ │ │ │ -│ │ ┌────────────────────────┐ │ │ -│ │ │ NAT Gateway │ │ │ -│ │ │ (Outbound Internet) │ │ │ -│ │ └────────────────────────┘ │ │ -│ └───────────────────────────────────────────────────────┘ │ -└──────────────────────────────────────────────────────────────┘ -``` +2. **CloudWatch metric** published: + - Namespace: `Custom/ECS` + - Metric: `BacklogPerTask` + - Dimensions: `ClusterName`, `ServiceName` -## 🎯 Best Practices +3. **Target Tracking** adjusts task count to maintain target: + - `BacklogPerTask > backlog_target` → Scale out + - `BacklogPerTask < backlog_target` → Scale in -### 1. Right-Size Your Containers +### Configuration Example ```hcl -# Development: Minimal resources -ecs_cpu = 256 -ecs_memory = 512 -ecs_desired_count = 1 - -# Production: High availability -ecs_cpu = 1024 -ecs_memory = 2048 -ecs_desired_count = 3 # Spread across AZs +scaling_config = { + enabled = true + min_count = 1 # Never scale below 1 + max_count = 20 # Never scale above 20 + backlog_target = 5 # Target 5 messages per task + scale_in_cooldown = 300 # Wait 5min before scaling in + scale_out_cooldown = 60 # Wait 1min before scaling out +} ``` -### 2. Implement Health Checks +### Choosing `backlog_target` -```dockerfile -# In your Dockerfile, add health check endpoint -HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \ - CMD curl -f http://localhost:8000/health || exit 1 -``` +The `backlog_target` determines how aggressively to scale: -```python -# In your application -@app.get("/health") -async def health_check(): - return {"status": "healthy", "timestamp": time.time()} -``` +| Target | Behavior | Use Case | +|--------|----------|----------| +| 5-10 | Aggressive scaling | Cost-sensitive, can tolerate queue buildup | +| 2-5 | Balanced | General purpose | +| 1 | Very aggressive | Low-latency, cost is less important | -### 3. Use Environment-Specific Configs +**Example:** +- Queue has 100 messages +- Target is 10 messages per task +- System scales to 10 tasks +- When queue drains to 20 messages +- System scales down to 2 tasks -```hcl -locals { - env_config = { - dev = { - cpu = 256 - memory = 512 - desired_count = 1 - redis_enabled = false - } - staging = { - cpu = 512 - memory = 1024 - desired_count = 2 - redis_enabled = true - } - prod = { - cpu = 1024 - memory = 2048 - desired_count = 4 - redis_enabled = true - } - } - config = local.env_config[var.env_alias] -} +### Cost Optimization -module "app" { - # ... - ecs_cpu = local.config.cpu - ecs_memory = local.config.memory - ecs_desired_count = local.config.desired_count - create_redis_cluster = local.config.redis_enabled +**Scale to zero:** +```hcl +scaling_config = { + enabled = true + min_count = 0 # Scale to zero when idle } ``` -### 4. Optimize Docker Images - -```dockerfile -# Use multi-stage builds -FROM python:3.11-slim AS builder -WORKDIR /app -COPY requirements.txt . -RUN pip install --user -r requirements.txt - -FROM python:3.11-slim -WORKDIR /app -COPY --from=builder /root/.local /root/.local -COPY . . -ENV PATH=/root/.local/bin:$PATH -CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] -``` - -## 💰 Cost Optimization - -### Monthly Cost Estimate (US-West-2) - -**ECS Fargate Costs**: -| Config | vCPU | Memory (GB) | Per Hour | Per Month (730h) | -|--------|------|-------------|----------|------------------| -| Small | 0.25 | 0.5 | $0.01229 | ~$9 | -| Medium | 0.5 | 1 | $0.02458 | ~$18 | -| Large | 1 | 2 | $0.04916 | ~$36 | -| X-Large | 2 | 4 | $0.09832 | ~$72 | - -**Additional Costs**: -- ALB: ~$16/month + $0.008 per LCU-hour -- NAT Gateway: ~$32/month + $0.045/GB processed -- Redis (cache.t3.micro): ~$12/month -- Data Transfer: Varies - -**Cost Saving Tips**: -1. Use Spot capacity for non-critical workloads -2. Right-size CPU/memory based on metrics -3. Use VPC endpoints for AWS services -4. Enable container insights selectively - -## 🔍 Troubleshooting - -### Container Won't Start - -**Issue**: Tasks fail to start or immediately stop - -**Solutions**: -1. Check CloudWatch Logs: - ```bash - aws logs tail /ecs/{product}-{env}-{module} --follow - ``` - -2. Verify Dockerfile builds locally: - ```bash - docker build -t test . - docker run -p 8000:8000 test - ``` - -3. Check health check endpoint: - ```bash - curl http://localhost:8000/health - ``` - -### ALB Health Check Failures - -**Issue**: Tasks marked unhealthy and replaced - -**Solutions**: -1. Verify health check path returns 200: - ```hcl - ecs_health_check_endpoint = "/health" # Must return 200 OK - ``` - -2. Increase health check grace period -3. Check security group allows ALB → ECS traffic -4. Review application startup time - -### High Memory Usage - -**Issue**: Tasks being killed due to OOM - -**Solutions**: +**Gradual scale-in:** ```hcl -# Increase memory allocation -ecs_memory = 2048 # Double the memory - -# Or optimize application -environment_variables = { - NODE_OPTIONS = "--max-old-space-size=1536" # Node.js - WORKERS = "2" # Reduce workers +scaling_config = { + scale_in_cooldown = 300 # Wait 5min before scaling in } ``` -### Cannot Connect to Redis - -**Issue**: Application can't reach Redis - -**Solutions**: -1. Verify Redis is enabled: - ```hcl - create_redis_cluster = true - ``` - -2. Check Redis URL in logs (injected as `AK_REDIS_URL`) -3. Verify security groups allow ECS → Redis traffic -4. Ensure both in same VPC +### Monitoring -## 📊 Monitoring and Observability +**CloudWatch Metrics:** +- `Custom/ECS/BacklogPerTask` - Custom metric +- `AWS/SQS/ApproximateNumberOfMessagesVisible` - Queue depth +- `AWS/ECS/CPUUtilization` - Task CPU usage +- `AWS/ECS/MemoryUtilization` - Task memory usage -### CloudWatch Metrics +**CloudWatch Logs:** +- `/aws/lambda/{prefix}-backlog-metric` - Scaling Lambda logs +- `/ecs/{prefix}-agent-runner` - Agent runner logs -```hcl -# CPU Utilization Alarm -resource "aws_cloudwatch_metric_alarm" "ecs_cpu" { - alarm_name = "${var.product_alias}-${var.env_alias}-cpu-high" - comparison_operator = "GreaterThanThreshold" - evaluation_periods = 2 - metric_name = "CPUUtilization" - namespace = "AWS/ECS" - period = 300 - statistic = "Average" - threshold = 80 - - dimensions = { - ClusterName = module.app.cluster_arn - ServiceName = "${var.product_alias}-${var.env_alias}-${var.module_name}" - } -} +## Examples -# Memory Utilization Alarm -resource "aws_cloudwatch_metric_alarm" "ecs_memory" { - alarm_name = "${var.product_alias}-${var.env_alias}-memory-high" - comparison_operator = "GreaterThanThreshold" - evaluation_periods = 2 - metric_name = "MemoryUtilization" - namespace = "AWS/ECS" - period = 300 - statistic = "Average" - threshold = 80 - - dimensions = { - ClusterName = module.app.cluster_arn - ServiceName = "${var.product_alias}-${var.env_alias}-${var.module_name}" - } -} -``` +See [examples/aws-containerized/](../../examples/aws-containerized/) for complete examples: -### Application Logs +- **openai-dynamodb-scalable** - Production-ready OpenAI agent with auto-scaling -Access logs via CloudWatch: -```bash -# Stream logs -aws logs tail /ecs/{product}-{env}-{module} --follow +## Migration -# Filter logs -aws logs filter-log-events \ - --log-group-name /ecs/{product}-{env}-{module} \ - --filter-pattern "ERROR" -``` +Migrating from version 0.4.0 or earlier? See: -## 📚 Additional Resources +- [MIGRATION_GUIDE.md](./MIGRATION_GUIDE.md) - Step-by-step migration guide +- [REFACTORING_SUMMARY.md](./REFACTORING_SUMMARY.md) - Detailed changes overview +- [CHANGELOG.md](./CHANGELOG.md) - Version history -- [AWS ECS Documentation](https://docs.aws.amazon.com/ecs/) -- [Fargate Pricing](https://aws.amazon.com/fargate/pricing/) -- [ECS Best Practices](https://docs.aws.amazon.com/AmazonECS/latest/bestpracticesguide/) -- [Application Load Balancer](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/) +## Module Documentation -## 🔗 Related Modules +For detailed module documentation, see: -- [ECR Module](../common/modules/ecr/) - For building and storing Docker images -- [VPC Module](../common/modules/vpc/) - For custom VPC configurations -- [Redis Module](../common/modules/redis/) - For standalone Redis clusters +- [modules/README.md](./modules/README.md) - Module architecture and usage +- [modules/queues/](./modules/queues/) - Queue module +- [modules/rest-service/](./modules/rest-service/) - REST service module +- [modules/agent-runner/](./modules/agent-runner/) - Agent runner module ---- +## Outputs -**Note**: This module automatically provisions VPC, subnets, NAT Gateway, Load Balancer, ECS cluster, and optionally Redis. Ensure your AWS credentials have permissions to create these resources. +The module provides these outputs: -## License +```hcl +output "agent_invoke_url" # API Gateway endpoint URL +output "alb_dns_name" # ALB DNS name +output "cluster_arn" # ECS cluster ARN +output "rest_service_name" # ECS REST service name +output "agent_runner_service_name" # ECS agent runner service name (queue mode) +output "input_queue_url" # Input queue URL (queue mode) +output "output_queue_url" # Output queue URL (queue mode) +output "vpc_id" # VPC ID +output "private_subnet_ids" # Private subnet IDs +``` -Unless otherwise specified, all content, including all source code files and documentation files in this repository are: +## Requirements -Copyright (c) 2025-2026 Yaala Labs. +- **Terraform**: >= 1.9.5 +- **AWS Provider**: >= 6.11.0 +- **Docker Provider**: 3.6.2 -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 +## Support -http://www.apache.org/licenses/LICENSE-2.0 +For issues, questions, or contributions: -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. +- 📖 [Documentation](./modules/README.md) +- 🐛 [Report Issues](https://github.com/your-org/agent-kernel/issues) +- 💬 [Discussions](https://github.com/your-org/agent-kernel/discussions) diff --git a/ak-deployment/ak-aws/containerized/api_gateway.tf b/ak-deployment/ak-aws/containerized/api_gateway.tf index 1f8eccdd9..2d2657a62 100644 --- a/ak-deployment/ak-aws/containerized/api_gateway.tf +++ b/ak-deployment/ak-aws/containerized/api_gateway.tf @@ -18,20 +18,20 @@ resource "aws_apigatewayv2_api" "http_api" { } resource "aws_apigatewayv2_vpc_link" "ecs_alb" { - name = "${var.product_alias}-${var.env_alias}-httpapi-vpclink" - security_group_ids = [aws_security_group.ecs_alb.id] - subnet_ids = local.subnet_ids - tags = var.tags + name = "${var.product_alias}-${var.env_alias}-httpapi-vpclink" + security_group_ids = [module.rest_service.alb_security_group_id] + subnet_ids = local.subnet_ids + tags = var.tags } resource "aws_apigatewayv2_integration" "alb_proxy" { - for_each = local.gateway_endpoints_map - api_id = aws_apigatewayv2_api.http_api.id - integration_type = "HTTP_PROXY" - integration_method = "ANY" - integration_uri = aws_lb_listener.http.arn - connection_type = "VPC_LINK" - connection_id = aws_apigatewayv2_vpc_link.ecs_alb.id + for_each = local.gateway_endpoints_map + api_id = aws_apigatewayv2_api.http_api.id + integration_type = "HTTP_PROXY" + integration_method = "ANY" + integration_uri = module.rest_service.alb_listener_arn + connection_type = "VPC_LINK" + connection_id = aws_apigatewayv2_vpc_link.ecs_alb.id passthrough_behavior = "WHEN_NO_MATCH" request_parameters = try(each.value["overwrite_path"], null) != null ? { "overwrite:path" = each.value["overwrite_path"] diff --git a/ak-deployment/ak-aws/containerized/api_gateway_async.tf b/ak-deployment/ak-aws/containerized/api_gateway_async.tf new file mode 100644 index 000000000..6768e2cab --- /dev/null +++ b/ak-deployment/ak-aws/containerized/api_gateway_async.tf @@ -0,0 +1,24 @@ +# ---------- API Gateway Async Mode Integration ---------- +# Only needed for rest_async mode — adds GET /api/{version}/{endpoint}/{sessionId} + +resource "aws_apigatewayv2_integration" "async_get" { + count = var.enable_queue_mode && var.queue_mode_type == "async" ? 1 : 0 + api_id = aws_apigatewayv2_api.http_api.id + integration_type = "HTTP_PROXY" + integration_method = "ANY" + integration_uri = module.rest_service.alb_listener_arn + connection_type = "VPC_LINK" + connection_id = aws_apigatewayv2_vpc_link.ecs_alb.id + passthrough_behavior = "WHEN_NO_MATCH" + + request_parameters = { + "overwrite:path" = "/api/v1/chat/$request.path.sessionId" + } +} + +resource "aws_apigatewayv2_route" "async_get" { + count = var.enable_queue_mode && var.queue_mode_type == "async" ? 1 : 0 + api_id = aws_apigatewayv2_api.http_api.id + route_key = "GET ${local.api_base_segment_with_version}/${var.agent_endpoint}/{sessionId}" + target = "integrations/${aws_apigatewayv2_integration.async_get[0].id}" +} diff --git a/ak-deployment/ak-aws/containerized/dynamodb.tf b/ak-deployment/ak-aws/containerized/dynamodb.tf new file mode 100644 index 000000000..71ab9229f --- /dev/null +++ b/ak-deployment/ak-aws/containerized/dynamodb.tf @@ -0,0 +1,21 @@ +# ---------- DynamoDB Response Store ---------- + +resource "aws_dynamodb_table" "response_store" { + count = var.enable_queue_mode ? 1 : 0 + + name = "${local.prefix}-response-store" + billing_mode = "PAY_PER_REQUEST" + hash_key = "request_id" + + attribute { + name = "request_id" + type = "S" + } + + ttl { + attribute_name = "expiry_time" + enabled = true + } + + tags = merge(var.tags, { Type = "ResponseStore" }) +} diff --git a/ak-deployment/ak-aws/containerized/ecs.tf b/ak-deployment/ak-aws/containerized/ecs.tf deleted file mode 100644 index cde6c9aaa..000000000 --- a/ak-deployment/ak-aws/containerized/ecs.tf +++ /dev/null @@ -1,212 +0,0 @@ -resource "aws_service_discovery_http_namespace" "this" { - name = "${var.product_alias}-${var.env_alias}-${var.module_name}" - description = "CloudMap namespace for ${var.product_alias}-${var.env_alias}-${var.module_name}" - tags = var.tags -} - -resource "aws_iam_policy" "dynamodb_policy" { - count = var.create_dynamodb_memory_table == true ? 1 : 0 - name = "${var.product_alias}-${var.env_alias}-${var.module_name}-dynamodb-policy" - description = "Policy for DynamoDB access" - - policy = jsonencode({ - Version = "2012-10-17" - Statement = [ - { - Effect = "Allow" - Action = [ - "dynamodb:DescribeTable", - "dynamodb:GetItem", - "dynamodb:PutItem", - "dynamodb:UpdateItem", - "dynamodb:DeleteItem", - "dynamodb:Query", - "dynamodb:Scan" - ] - Resource = [ - local.dynamodb_memory_table_arn, - "${local.dynamodb_memory_table_arn}/index/*" - ] - } - ] - }) - - tags = var.tags -} -module "ecs" { - source = "terraform-aws-modules/ecs/aws" - version = "6.10.0" - - cluster_name = "${var.product_alias}-${var.env_alias}-${var.module_name}" - - services = { - (local.service_name) = { - cpu = var.ecs_cpu - memory = var.ecs_memory - desired_count = var.ecs_desired_count - enable_autoscaling = false - launch_type = "FARGATE" - platform_version = "LATEST" - subnet_ids = local.subnet_ids - security_group_ids = [aws_security_group.ecs_service.id] - - load_balancer = { - service = { - target_group_arn = aws_lb_target_group.app.arn - container_name = local.container_name - container_port = var.ecs_container_port - } - } - - service_connect_configuration = { - namespace = aws_service_discovery_http_namespace.this.arn - service = [ - { - client_alias = { - port = var.ecs_container_port - dns_name = local.container_name - } - port_name = local.container_name - discovery_name = local.container_name - } - ] - } - - # Attach DynamoDB access to the task role if a memory table exists - create_tasks_iam_role = true - tasks_iam_role_policies = var.create_dynamodb_memory_table ? { - DynamoDB = var.create_dynamodb_memory_table ? aws_iam_policy.dynamodb_policy[0].arn : null - } : {} - - container_definitions = { - (local.container_name) = { - cpu = var.ecs_cpu - memory = var.ecs_memory - image = module.docker_image[0].docker_image_uri - essential = true - readonlyRootFilesystem = false - - # Command override - if provided, replaces the Docker image's CMD - command = var.container_entrypoint_override - - portMappings = [ - { - name = local.container_name, - containerPort = var.ecs_container_port, - hostPort = var.ecs_container_port, - protocol = "tcp", - } - ] - enable_cloudwatch_logging = true - environment = [ - for k, v in merge( - var.environment_variables, - local.redis_url != null ? { - AK_SESSION__REDIS__URL = local.redis_url - } : {}, - local.dynamodb_memory_table_arn != null ? { - AK_SESSION__DYNAMODB__TABLE_NAME = local.dynamodb_memory_table_name - } : {}, - # Queue mode — inject queue URLs and response store table name - var.enable_queue_mode ? { - AK_EXECUTION__QUEUES__INPUT__URL = module.input_queue[0].queue_url - AK_EXECUTION__QUEUES__OUTPUT__URL = module.output_queue[0].queue_url - AK_EXECUTION__RESPONSE_STORE__DYNAMODB__TABLE_NAME = aws_dynamodb_table.response_store[0].name - } : {} - ) : { - name = k - value = v - } - ] - - health_check = { - command = ["CMD-SHELL", "curl -sf http://localhost:${var.ecs_container_port}${var.ecs_health_check_endpoint} || exit 1"], - interval = 30, - timeout = 5, - retries = 3, - startPeriod = 10 - } - log_configuration = { - logDriver = "awslogs", - options = { - awslogs-group = "/ecs/${var.product_alias}-${var.env_alias}-${var.module_name}", - awslogs-region = var.region, - awslogs-stream-prefix = "ecs" - } - } - } - } - } - } -} - -resource "aws_security_group" "ecs_alb" { - name = "${var.product_alias}-${var.env_alias}-ecs-alb-sg" - description = "ALB SG for ECS" - vpc_id = local.vpc_id - ingress { - from_port = 80 - to_port = 80 - protocol = "tcp" - cidr_blocks = [local.vpc_cidr] - } - egress { - from_port = 0 - to_port = 0 - protocol = "-1" - cidr_blocks = ["0.0.0.0/0"] - } -} - -resource "aws_security_group" "ecs_service" { - name = "${var.product_alias}-${var.env_alias}-ecs-svc-sg" - description = "ECS service SG" - vpc_id = local.vpc_id - ingress { - from_port = var.ecs_container_port - to_port = var.ecs_container_port - protocol = "tcp" - security_groups = [aws_security_group.ecs_alb.id] - } - egress { - from_port = 0 - to_port = 0 - protocol = "-1" - cidr_blocks = ["0.0.0.0/0"] - } -} - -resource "aws_lb" "app" { - name = "${var.product_alias}-${var.env_alias}-${var.module_name}-alb" - internal = true - load_balancer_type = "application" - subnets = local.subnet_ids - security_groups = [aws_security_group.ecs_alb.id] -} - -resource "aws_lb_target_group" "app" { - name = "${var.product_alias}-${var.env_alias}-tg" - port = var.ecs_container_port - protocol = "HTTP" - vpc_id = local.vpc_id - target_type = "ip" - health_check { - path = var.ecs_health_check_endpoint - healthy_threshold = 2 - unhealthy_threshold = 2 - timeout = 5 - interval = 30 - matcher = "200-399" - } -} - - -resource "aws_lb_listener" "http" { - load_balancer_arn = aws_lb.app.arn - port = 80 - protocol = "HTTP" - default_action { - type = "forward" - target_group_arn = aws_lb_target_group.app.arn - } -} \ No newline at end of file diff --git a/ak-deployment/ak-aws/containerized/iam.tf b/ak-deployment/ak-aws/containerized/iam.tf new file mode 100644 index 000000000..55e4b05d2 --- /dev/null +++ b/ak-deployment/ak-aws/containerized/iam.tf @@ -0,0 +1,81 @@ +# ---------- REST Service IAM Policies ---------- + +resource "aws_iam_policy" "rest_service_sqs_policy" { + count = var.enable_queue_mode ? 1 : 0 + + name = "${local.prefix}-rest-svc-sqs" + description = "Allow REST Service ECS task to send to Input Queue and consume from Output Queue" + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Sid = "SendToInputQueue" + Effect = "Allow" + Action = [ + "sqs:SendMessage", + "sqs:GetQueueAttributes", + "sqs:GetQueueUrl" + ] + Resource = module.queues[0].input_queue_arn + }, + { + Sid = "ConsumeOutputQueue" + Effect = "Allow" + Action = [ + "sqs:ReceiveMessage", + "sqs:DeleteMessage", + "sqs:ChangeMessageVisibility", + "sqs:GetQueueAttributes", + "sqs:GetQueueUrl" + ] + Resource = module.queues[0].output_queue_arn + } + ] + }) + + tags = var.tags +} + +resource "aws_iam_policy" "rest_service_response_store_policy" { + count = var.enable_queue_mode ? 1 : 0 + + name = "${local.prefix}-rest-svc-response-store" + description = "Allow REST Service ECS task to read/write the DynamoDB response store" + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Action = [ + "dynamodb:DescribeTable", + "dynamodb:GetItem", + "dynamodb:PutItem", + "dynamodb:UpdateItem", + "dynamodb:DeleteItem", + "dynamodb:Query", + "dynamodb:Scan" + ] + Resource = [ + aws_dynamodb_table.response_store[0].arn, + "${aws_dynamodb_table.response_store[0].arn}/index/*" + ] + } + ] + }) + + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "rest_service_sqs_attachment" { + count = var.enable_queue_mode ? 1 : 0 + role = module.rest_service.task_role_name + policy_arn = aws_iam_policy.rest_service_sqs_policy[0].arn +} + +resource "aws_iam_role_policy_attachment" "rest_service_response_store_attachment" { + count = var.enable_queue_mode ? 1 : 0 + role = module.rest_service.task_role_name + policy_arn = aws_iam_policy.rest_service_response_store_policy[0].arn +} diff --git a/ak-deployment/ak-aws/containerized/modules/README.md b/ak-deployment/ak-aws/containerized/modules/README.md new file mode 100644 index 000000000..e82b92638 --- /dev/null +++ b/ak-deployment/ak-aws/containerized/modules/README.md @@ -0,0 +1,339 @@ +# Containerized Deployment Modules + +This directory contains reusable Terraform modules for the containerized Agent Kernel deployment, following the same pattern as the serverless deployment. + +## Architecture Overview + +### Non-Queue Mode (Direct Execution) +``` +┌─────────────┐ ┌─────┐ ┌──────────────┐ +│ API Gateway │─────▶│ ALB │─────▶│ REST Service │ +│ (HTTP) │ │ │ │ (ECS Task) │ +└─────────────┘ └─────┘ │ │ + │ • Request │ + │ • Agent │ + │ • Response │ + └──────────────┘ +``` + +### Queue Mode (Async Execution) +``` +┌─────────────┐ ┌─────┐ ┌──────────────┐ +│ API Gateway │─────▶│ ALB │─────▶│ REST Service │ +│ (HTTP) │ │ │ │ (ECS Task) │ +└─────────────┘ └─────┘ │ │ + │ • Thread 1 │ + │ (Request) │ + │ • Thread 2 │ + │ (Response) │ + └──────┬───────┘ + │ + ┌────────────────────┴────────────────────┐ + │ │ + ▼ ▼ + ┌─────────────┐ ┌─────────────┐ + │ Input Queue │ │Output Queue │ + │ (SQS) │ │ (SQS) │ + └──────┬──────┘ └──────▲──────┘ + │ │ + │ ┌──────────────┐ │ + └─────────▶│Agent Runner │───────────────┘ + │ (ECS Task) │ + │ │ + │ • Poll Input │ + │ • Run Agent │ + │ • Send Output│ + └──────────────┘ + + ┌──────────────────┐ + │ Response Store │ + │ (DynamoDB) │ + │ │ + │ Maps request IDs │ + │ to responses │ + └──────────────────┘ +``` + +## Modules + +### 1. `queues/` + +**Purpose**: Manages SQS queues for queue-based execution mode + +**Resources Created**: +- Input Queue (FIFO SQS) +- Output Queue (FIFO SQS) +- Dead Letter Queues (optional) + +**Input Variables**: +```hcl +queue_config = { + # Shared settings + sqs_managed_sse_enabled = true + max_message_size = 262144 + receive_wait_time_seconds = 0 + + # Input queue + input_queue_visibility_timeout = 60 + input_queue_message_retention_seconds = 1800 + input_queue_max_receive_count = 5 + input_queue_create_dlq = false + input_queue_dlq_message_retention_seconds = 1800 + + # Output queue + output_queue_visibility_timeout = 60 + output_queue_message_retention_seconds = 1800 + output_queue_max_receive_count = 5 + output_queue_create_dlq = false + output_queue_dlq_message_retention_seconds = 1800 +} +``` + +**Outputs**: +- `input_queue_arn` +- `input_queue_url` +- `output_queue_arn` +- `output_queue_url` + +### 2. `rest-service/` + +**Purpose**: Manages the main ECS service that handles HTTP requests + +**Resources Created**: +- ECS Task Definition and Service +- Application Load Balancer (ALB) +- Target Group and Listener +- Security Groups (ALB and ECS) +- Service Discovery Namespace +- IAM Policy for DynamoDB memory access + +**Input Variables**: +```hcl +rest_service = { + cpu = 256 + memory = 512 + desired_count = 1 + container_port = 8000 + health_check_endpoint = "/health" + image_uri = "..." + command = ["python", "app.py"] + environment_variables = {} +} +``` + +**Outputs**: +- `service_arn` +- `service_name` +- `task_role_name` +- `task_role_arn` +- `alb_arn` +- `alb_dns_name` +- `alb_listener_arn` +- `target_group_arn` +- `security_group_id` +- `alb_security_group_id` + +**Environment Variables Injected**: +- `AK_SESSION__REDIS__URL` (if Redis enabled) +- `AK_SESSION__DYNAMODB__TABLE_NAME` (if DynamoDB memory table enabled) +- `AK_EXECUTION__QUEUES__INPUT__URL` (if queue mode enabled) +- `AK_EXECUTION__QUEUES__OUTPUT__URL` (if queue mode enabled) +- `AK_EXECUTION__RESPONSE_STORE__DYNAMODB__TABLE_NAME` (if queue mode enabled) + +### 3. `agent-runner/` + +**Purpose**: Manages the Agent Runner ECS service that processes messages from the input queue + +**Resources Created**: +- ECS Task Definition and Service +- IAM Execution Role +- IAM Task Role +- IAM Policies (Logs, SQS, DynamoDB) +- Security Group +- CloudWatch Log Group + +**Optional Auto Scaling Resources** (when `scaling_config.enabled = true`): +- Lambda Function (BacklogPerTask metric calculator) +- IAM Role and Policy for Lambda +- CloudWatch Log Group for Lambda +- EventBridge Rule (1-minute schedule) +- EventBridge Target +- Lambda Permission +- Application Auto Scaling Target +- Application Auto Scaling Policy (Target Tracking) + +**Input Variables**: +```hcl +agent_runner = { + cpu = 512 + memory = 1024 + desired_count = 1 + image_uri = null # Defaults to rest_service image + command = null + environment_variables = {} +} + +scaling_config = { + enabled = false + min_count = 0 + max_count = 10 + backlog_target = 10 + scale_in_cooldown = 120 + scale_out_cooldown = 30 +} +``` + +**Outputs**: +- `service_name` +- `service_arn` +- `task_role_arn` +- `execution_role_arn` +- `security_group_id` + +**Environment Variables Injected**: +- `AK_EXECUTION__QUEUES__INPUT__URL` +- `AK_EXECUTION__QUEUES__OUTPUT__URL` +- `AK_EXECUTION__QUEUES__INPUT__MAX_RECEIVE_COUNT` +- `AK_SESSION__REDIS__URL` (if Redis enabled) +- `AK_SESSION__DYNAMODB__TABLE_NAME` (if DynamoDB memory table enabled) + +**Auto Scaling Behavior**: +When `scaling_config.enabled = true`: + +1. **Lambda Function** runs every minute to calculate: + ``` + BacklogPerTask = ApproximateNumberOfMessages / max(runningCount, 1) + ``` + +2. **Custom CloudWatch Metric** published: + - Namespace: `Custom/ECS` + - Metric: `BacklogPerTask` + - Dimensions: `ClusterName`, `ServiceName` + +3. **Target Tracking Scaling** adjusts task count to maintain `backlog_target`: + - If `BacklogPerTask > backlog_target`: Scale out + - If `BacklogPerTask < backlog_target`: Scale in + - Cooldown periods prevent rapid scaling + +## Usage Examples + +### Basic (Non-Queue Mode) + +```hcl +module "containerized_agents" { + source = "yaalalabs/ak-containerized/aws" + + product_alias = "my-agent" + env_alias = "dev" + module_name = "chatbot" + region = "us-east-1" + + package_path = "./dist" + + rest_service = { + cpu = 256 + memory = 512 + environment_variables = { + OPENAI_API_KEY = var.api_key + } + } + + create_redis_cluster = true +} +``` + +### With Queue Mode and Auto Scaling + +```hcl +module "containerized_agents" { + source = "yaalalabs/ak-containerized/aws" + + product_alias = "my-agent" + env_alias = "prod" + module_name = "assistant" + region = "us-east-1" + + package_path = "./dist-rest-service" + + # REST Service handles HTTP requests + rest_service = { + cpu = 512 + memory = 1024 + desired_count = 2 + command = ["python", "app_rest_service.py"] + environment_variables = { + OPENAI_API_KEY = var.api_key + } + } + + # Queue mode for async processing + enable_queue_mode = true + queue_mode_type = "async" + + queue_config = { + input_queue_visibility_timeout = 120 + output_queue_visibility_timeout = 60 + input_queue_create_dlq = true + output_queue_create_dlq = true + } + + # Agent Runner processes queue messages + agent_runner = { + cpu = 1024 + memory = 2048 + desired_count = 1 + image_uri = "123456789.dkr.ecr.us-east-1.amazonaws.com/agent-runner:latest" + command = ["python", "app_agent_runner.py"] + environment_variables = { + OPENAI_API_KEY = var.api_key + } + } + + # Auto scaling based on queue depth + scaling_config = { + enabled = true + min_count = 1 + max_count = 10 + backlog_target = 5 # Scale when >5 messages per task + scale_in_cooldown = 180 + scale_out_cooldown = 60 + } + + create_dynamodb_memory_table = true +} +``` + +## Design Principles + +1. **Config Objects Over Flat Variables**: Group related settings into logical objects +2. **Sensible Defaults**: Provide reasonable defaults for all optional parameters +3. **Environment Variable Injection**: Automatically inject required environment variables +4. **Conditional Resources**: Only create resources when needed (e.g., scaling, DLQ) +5. **Module Independence**: Each module is self-contained and reusable +6. **Consistent Pattern**: Follows the same structure as serverless deployment + +## Benefits + +✅ **Clarity**: Config objects make it clear what settings belong together +✅ **Consistency**: Same pattern as serverless deployment +✅ **Maintainability**: Modules can be updated independently +✅ **Flexibility**: Easy to enable/disable features (scaling, DLQ, etc.) +✅ **Reusability**: Modules can be used in other projects +✅ **Type Safety**: Terraform validates object structure + +## Comparison with Serverless + +| Feature | Serverless | Containerized | +|---------|-----------|---------------| +| **Request Handler** | Lambda Function | ECS Task (REST Service) | +| **Agent Runner** | Lambda Function | ECS Task | +| **Compute** | AWS Lambda | AWS Fargate | +| **Scaling** | Lambda auto-scales | Custom ECS auto-scaling | +| **Config Pattern** | `request_handler` object | `rest_service` object | +| **Queue Module** | ✅ | ✅ | +| **Scaling Config** | Built-in | `scaling_config` object | +| **Load Balancer** | API Gateway direct | ALB + API Gateway | + +## Migration from Old Structure + +See [REFACTORING_SUMMARY.md](../REFACTORING_SUMMARY.md) for detailed migration guide. diff --git a/ak-deployment/ak-aws/containerized/modules/agent-runner/main.tf b/ak-deployment/ak-aws/containerized/modules/agent-runner/main.tf new file mode 100644 index 000000000..7f848afee --- /dev/null +++ b/ak-deployment/ak-aws/containerized/modules/agent-runner/main.tf @@ -0,0 +1,466 @@ +locals { + agent_runner_image = var.agent_runner.image_uri != null ? var.agent_runner.image_uri : var.default_image_uri + + agent_runner_environment = merge( + var.agent_runner.environment_variables, + { + AK_EXECUTION__QUEUES__INPUT__URL = var.input_queue_url + AK_EXECUTION__QUEUES__OUTPUT__URL = var.output_queue_url + AK_EXECUTION__QUEUES__INPUT__MAX_RECEIVE_COUNT = tostring(max(1, var.queue_config.input_queue_max_receive_count - 1)) + }, + var.redis_url != null ? { AK_SESSION__REDIS__URL = var.redis_url } : {}, + var.dynamodb_memory_table_arn != null ? { + AK_SESSION__DYNAMODB__TABLE_NAME = var.dynamodb_memory_table_name + } : {} + ) +} + +# ---------- IAM Roles ---------- + +resource "aws_iam_role" "agent_runner_execution_role" { + name = "${var.prefix}-agent-runner-exec-role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + Effect = "Allow" + Action = "sts:AssumeRole" + Principal = { Service = "ecs-tasks.amazonaws.com" } + }] + }) + + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "agent_runner_execution_policy" { + role = aws_iam_role.agent_runner_execution_role.name + policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" +} + +resource "aws_iam_role" "agent_runner_task_role" { + name = "${var.prefix}-agent-runner-task-role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + Effect = "Allow" + Action = "sts:AssumeRole" + Principal = { Service = "ecs-tasks.amazonaws.com" } + }] + }) + + tags = var.tags +} + +# ---------- IAM Policies ---------- + +resource "aws_iam_policy" "agent_runner_logs_policy" { + name = "${var.prefix}-agent-runner-logs" + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + Effect = "Allow" + Action = [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ] + Resource = "arn:aws:logs:${var.region}:*:log-group:/ecs/${var.prefix}-agent-runner:*" + }] + }) + + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "agent_runner_logs_attachment" { + role = aws_iam_role.agent_runner_task_role.name + policy_arn = aws_iam_policy.agent_runner_logs_policy.arn +} + +resource "aws_iam_policy" "agent_runner_sqs_policy" { + name = "${var.prefix}-agent-runner-sqs" + description = "Allow Agent Runner ECS task to consume Input Queue and produce to Output Queue" + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Sid = "ConsumeInputQueue" + Effect = "Allow" + Action = [ + "sqs:ReceiveMessage", + "sqs:DeleteMessage", + "sqs:ChangeMessageVisibility", + "sqs:GetQueueAttributes", + "sqs:GetQueueUrl" + ] + Resource = var.input_queue_arn + }, + { + Sid = "SendToOutputQueue" + Effect = "Allow" + Action = [ + "sqs:SendMessage", + "sqs:GetQueueAttributes", + "sqs:GetQueueUrl" + ] + Resource = var.output_queue_arn + } + ] + }) + + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "agent_runner_sqs_attachment" { + role = aws_iam_role.agent_runner_task_role.name + policy_arn = aws_iam_policy.agent_runner_sqs_policy.arn +} + +resource "aws_iam_policy" "agent_runner_dynamodb_memory_policy" { + count = var.dynamodb_memory_table_arn != null ? 1 : 0 + name = "${var.prefix}-agent-runner-dynamodb-memory" + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + Effect = "Allow" + Action = [ + "dynamodb:DescribeTable", + "dynamodb:GetItem", + "dynamodb:PutItem", + "dynamodb:UpdateItem", + "dynamodb:DeleteItem", + "dynamodb:Query", + "dynamodb:Scan" + ] + Resource = [ + var.dynamodb_memory_table_arn, + "${var.dynamodb_memory_table_arn}/index/*" + ] + }] + }) + + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "agent_runner_dynamodb_memory_attachment" { + count = var.dynamodb_memory_table_arn != null ? 1 : 0 + role = aws_iam_role.agent_runner_task_role.name + policy_arn = aws_iam_policy.agent_runner_dynamodb_memory_policy[0].arn +} + +# ---------- ECS Resources ---------- + +resource "aws_security_group" "agent_runner" { + name = "${var.prefix}-agent-runner-sg" + description = "Agent Runner ECS service SG - egress only (queue-polling)" + vpc_id = var.vpc_id + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = var.tags +} + +resource "aws_cloudwatch_log_group" "agent_runner" { + name = "/ecs/${var.prefix}-agent-runner" + retention_in_days = 90 + tags = var.tags +} + +resource "aws_ecs_task_definition" "agent_runner" { + family = "${var.prefix}-agent-runner" + requires_compatibilities = ["FARGATE"] + network_mode = "awsvpc" + cpu = var.agent_runner.cpu + memory = var.agent_runner.memory + execution_role_arn = aws_iam_role.agent_runner_execution_role.arn + task_role_arn = aws_iam_role.agent_runner_task_role.arn + + container_definitions = jsonencode([ + { + name = "${var.prefix}-agent-runner" + image = local.agent_runner_image + essential = true + + # Command override - if provided, replaces the Docker image's CMD + command = var.agent_runner.command + + environment = [ + for k, v in local.agent_runner_environment : { name = k, value = v } + ] + + logConfiguration = { + logDriver = "awslogs" + options = { + "awslogs-group" = "/ecs/${var.prefix}-agent-runner" + "awslogs-region" = var.region + "awslogs-stream-prefix" = "ecs" + } + } + } + ]) + + tags = var.tags +} + +resource "aws_ecs_service" "agent_runner" { + name = "${var.prefix}-agent-runner" + cluster = var.ecs_cluster_arn + task_definition = aws_ecs_task_definition.agent_runner.arn + desired_count = var.agent_runner.desired_count + launch_type = "FARGATE" + + network_configuration { + subnets = var.subnet_ids + security_groups = [aws_security_group.agent_runner.id] + assign_public_ip = false + } + + tags = var.tags + + lifecycle { + ignore_changes = [desired_count] + } +} + +# ---------- Auto Scaling ---------- + +resource "aws_iam_role" "backlog_metric_lambda_role" { + count = var.scaling_config.enabled ? 1 : 0 + name = "${var.prefix}-backlog-metric-lambda-role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + Effect = "Allow" + Action = "sts:AssumeRole" + Principal = { Service = "lambda.amazonaws.com" } + }] + }) + + tags = var.tags +} + +resource "aws_iam_policy" "backlog_metric_lambda_policy" { + count = var.scaling_config.enabled ? 1 : 0 + name = "${var.prefix}-backlog-metric-lambda-policy" + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Sid = "GetQueueDepth" + Effect = "Allow" + Action = [ + "sqs:GetQueueAttributes" + ] + Resource = var.input_queue_arn + }, + { + Sid = "GetRunningTaskCount" + Effect = "Allow" + Action = [ + "ecs:DescribeServices" + ] + Resource = "arn:aws:ecs:${var.region}:${var.account_id}:service/${var.ecs_cluster_name}/${var.prefix}-agent-runner" + }, + { + Sid = "PutCustomMetric" + Effect = "Allow" + Action = "cloudwatch:PutMetricData" + Resource = "*" + }, + { + Sid = "LambdaLogging" + Effect = "Allow" + Action = [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ] + Resource = "arn:aws:logs:${var.region}:${var.account_id}:log-group:/aws/lambda/${var.prefix}-backlog-metric:*" + } + ] + }) + + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "backlog_metric_lambda" { + count = var.scaling_config.enabled ? 1 : 0 + role = aws_iam_role.backlog_metric_lambda_role[0].name + policy_arn = aws_iam_policy.backlog_metric_lambda_policy[0].arn +} + +data "archive_file" "backlog_metric_lambda" { + count = var.scaling_config.enabled ? 1 : 0 + type = "zip" + output_path = "${path.module}/.terraform/lambda/backlog_metric.zip" + + source { + content = <<-PYTHON +import boto3 +import os +import json + +def handler(event, context): + """ + Calculate BacklogPerTask metric for ECS agent runner autoscaling. + + BacklogPerTask = ApproximateNumberOfMessages / max(runningCount, 1) + + This metric is used by Target Tracking Scaling to adjust the number + of agent runner tasks based on queue depth. + """ + sqs = boto3.client("sqs") + ecs = boto3.client("ecs") + cw = boto3.client("cloudwatch") + + queue_url = os.environ["QUEUE_URL"] + cluster_name = os.environ["CLUSTER_NAME"] + service_name = os.environ["SERVICE_NAME"] + namespace = os.environ.get("METRIC_NAMESPACE", "Custom/ECS") + metric_name = os.environ.get("METRIC_NAME", "BacklogPerTask") + + # --- Get queue depth --- + attrs = sqs.get_queue_attributes( + QueueUrl=queue_url, + AttributeNames=["ApproximateNumberOfMessages"] + ) + queue_depth = int(attrs["Attributes"]["ApproximateNumberOfMessages"]) + + # --- Get running task count --- + resp = ecs.describe_services(cluster=cluster_name, services=[service_name]) + running = resp["services"][0]["runningCount"] + + # Avoid division by zero; treat 0 running tasks as 1 for the metric + divisor = max(running, 1) + backlog_per_task = queue_depth / divisor + + # --- Publish custom metric --- + cw.put_metric_data( + Namespace=namespace, + MetricData=[{ + "MetricName": metric_name, + "Value": backlog_per_task, + "Unit": "Count", + "Dimensions": [ + {"Name": "ClusterName", "Value": cluster_name}, + {"Name": "ServiceName", "Value": service_name}, + ] + }] + ) + + print(f"Published metric: {metric_name}={backlog_per_task} (queue_depth={queue_depth}, running={running})") + + return { + "backlog_per_task": backlog_per_task, + "queue_depth": queue_depth, + "running": running + } +PYTHON + filename = "index.py" + } +} + +resource "aws_lambda_function" "backlog_metric" { + count = var.scaling_config.enabled ? 1 : 0 + function_name = "${var.prefix}-backlog-metric" + role = aws_iam_role.backlog_metric_lambda_role[0].arn + runtime = "python3.12" + handler = "index.handler" + filename = data.archive_file.backlog_metric_lambda[0].output_path + source_code_hash = data.archive_file.backlog_metric_lambda[0].output_base64sha256 + timeout = 30 + + environment { + variables = { + QUEUE_URL = var.input_queue_url + CLUSTER_NAME = var.ecs_cluster_name + SERVICE_NAME = "${var.prefix}-agent-runner" + METRIC_NAMESPACE = "Custom/ECS" + METRIC_NAME = "BacklogPerTask" + } + } + + tags = var.tags +} + +resource "aws_cloudwatch_log_group" "backlog_metric_lambda" { + count = var.scaling_config.enabled ? 1 : 0 + name = "/aws/lambda/${var.prefix}-backlog-metric" + retention_in_days = 7 + tags = var.tags +} + +resource "aws_cloudwatch_event_rule" "backlog_metric_schedule" { + count = var.scaling_config.enabled ? 1 : 0 + name = "${var.prefix}-backlog-metric-schedule" + description = "Trigger BacklogPerTask metric calculation every minute" + schedule_expression = "rate(1 minute)" + tags = var.tags +} + +resource "aws_cloudwatch_event_target" "backlog_metric_lambda" { + count = var.scaling_config.enabled ? 1 : 0 + rule = aws_cloudwatch_event_rule.backlog_metric_schedule[0].name + arn = aws_lambda_function.backlog_metric[0].arn +} + +resource "aws_lambda_permission" "allow_eventbridge" { + count = var.scaling_config.enabled ? 1 : 0 + statement_id = "AllowExecutionFromEventBridge" + action = "lambda:InvokeFunction" + function_name = aws_lambda_function.backlog_metric[0].function_name + principal = "events.amazonaws.com" + source_arn = aws_cloudwatch_event_rule.backlog_metric_schedule[0].arn +} + +resource "aws_appautoscaling_target" "agent_runner" { + count = var.scaling_config.enabled ? 1 : 0 + max_capacity = var.scaling_config.max_count + min_capacity = var.scaling_config.min_count + resource_id = "service/${var.ecs_cluster_name}/${var.prefix}-agent-runner" + scalable_dimension = "ecs:service:DesiredCount" + service_namespace = "ecs" + + depends_on = [aws_ecs_service.agent_runner] +} + +resource "aws_appautoscaling_policy" "agent_runner_backlog" { + count = var.scaling_config.enabled ? 1 : 0 + name = "${var.prefix}-agent-runner-backlog-tracking" + policy_type = "TargetTrackingScaling" + resource_id = aws_appautoscaling_target.agent_runner[0].resource_id + scalable_dimension = aws_appautoscaling_target.agent_runner[0].scalable_dimension + service_namespace = aws_appautoscaling_target.agent_runner[0].service_namespace + + target_tracking_scaling_policy_configuration { + target_value = var.scaling_config.backlog_target + scale_in_cooldown = var.scaling_config.scale_in_cooldown + scale_out_cooldown = var.scaling_config.scale_out_cooldown + + customized_metric_specification { + namespace = "Custom/ECS" + metric_name = "BacklogPerTask" + statistic = "Average" + + dimensions { + name = "ClusterName" + value = var.ecs_cluster_name + } + + dimensions { + name = "ServiceName" + value = "${var.prefix}-agent-runner" + } + } + } +} diff --git a/ak-deployment/ak-aws/containerized/modules/agent-runner/outputs.tf b/ak-deployment/ak-aws/containerized/modules/agent-runner/outputs.tf new file mode 100644 index 000000000..751c7c139 --- /dev/null +++ b/ak-deployment/ak-aws/containerized/modules/agent-runner/outputs.tf @@ -0,0 +1,24 @@ +output "service_name" { + description = "Agent Runner ECS service name" + value = aws_ecs_service.agent_runner.name +} + +output "service_arn" { + description = "Agent Runner ECS service ARN" + value = aws_ecs_service.agent_runner.arn +} + +output "task_role_arn" { + description = "Agent Runner task role ARN" + value = aws_iam_role.agent_runner_task_role.arn +} + +output "execution_role_arn" { + description = "Agent Runner execution role ARN" + value = aws_iam_role.agent_runner_execution_role.arn +} + +output "security_group_id" { + description = "Agent Runner security group ID" + value = aws_security_group.agent_runner.id +} diff --git a/ak-deployment/ak-aws/containerized/modules/agent-runner/variables.tf b/ak-deployment/ak-aws/containerized/modules/agent-runner/variables.tf new file mode 100644 index 000000000..7d570d640 --- /dev/null +++ b/ak-deployment/ak-aws/containerized/modules/agent-runner/variables.tf @@ -0,0 +1,132 @@ +variable "product_alias" { + type = string + description = "Product alias for resource naming" +} + +variable "env_alias" { + type = string + description = "Environment alias for resource naming" +} + +variable "module_name" { + type = string + description = "Module name for resource naming" +} + +variable "region" { + type = string + description = "AWS region" +} + +variable "prefix" { + type = string + description = "Resource name prefix" +} + +variable "vpc_id" { + type = string + description = "VPC ID" +} + +variable "subnet_ids" { + type = list(string) + description = "Private subnet IDs for ECS tasks" +} + +variable "ecs_cluster_arn" { + type = string + description = "ECS cluster ARN" +} + +variable "ecs_cluster_name" { + type = string + description = "ECS cluster name" +} + +variable "input_queue_url" { + type = string + description = "Input queue URL" +} + +variable "input_queue_arn" { + type = string + description = "Input queue ARN" +} + +variable "output_queue_url" { + type = string + description = "Output queue URL" +} + +variable "output_queue_arn" { + type = string + description = "Output queue ARN" +} + +variable "redis_url" { + type = string + description = "Redis URL for session storage" + default = null +} + +variable "dynamodb_memory_table_arn" { + type = string + description = "DynamoDB memory table ARN" + default = null +} + +variable "dynamodb_memory_table_name" { + type = string + description = "DynamoDB memory table name" + default = null +} + +variable "agent_runner" { + description = "Agent runner configuration object" + type = object({ + cpu = optional(number, 512) + memory = optional(number, 1024) + desired_count = optional(number, 1) + image_uri = optional(string, null) + command = optional(list(string), null) + environment_variables = optional(map(string), {}) + }) +} + +variable "queue_config" { + description = "Queue configuration for input max_receive_count" + type = object({ + input_queue_max_receive_count = number + }) +} + +variable "scaling_config" { + description = "Auto scaling configuration object" + type = object({ + enabled = optional(bool, false) + min_count = optional(number, 0) + max_count = optional(number, 10) + backlog_target = optional(number, 10) + scale_in_cooldown = optional(number, 120) + scale_out_cooldown = optional(number, 30) + }) + default = { + enabled = false + } +} + +variable "default_image_uri" { + type = string + description = "Default Docker image URI (from main service)" +} + +variable "tags" { + type = map(string) + description = "Resource tags" + default = {} +} + +variable "account_id" { + type = string + description = "AWS Account ID" +} diff --git a/ak-deployment/ak-aws/containerized/modules/queues/main.tf b/ak-deployment/ak-aws/containerized/modules/queues/main.tf new file mode 100644 index 000000000..25479e51b --- /dev/null +++ b/ak-deployment/ak-aws/containerized/modules/queues/main.tf @@ -0,0 +1,72 @@ +data "aws_region" "current" {} + +# ---------- SQS Input Queue ---------- + +module "input_queue" { + source = "yaalalabs/ak-common/aws//modules/sqs" + version = "0.5.1" + + product_alias = var.product_alias + env_alias = var.env_alias + module_name = var.module_name + queue_name = var.queue_config.input_queue_name + region = data.aws_region.current.name + product_display_name = var.product_alias + is_production = var.env_alias == "prod" + + fifo_queue = true + content_based_deduplication = false + deduplication_scope = "messageGroup" + fifo_throughput_limit = "perMessageGroupId" + + visibility_timeout_seconds = var.queue_config.input_queue_visibility_timeout + message_retention_seconds = var.queue_config.input_queue_message_retention_seconds + max_message_size = var.queue_config.max_message_size + receive_wait_time_seconds = var.queue_config.receive_wait_time_seconds + max_receive_count = var.queue_config.input_queue_max_receive_count + create_dlq = var.queue_config.input_queue_create_dlq + dlq_message_retention_seconds = var.queue_config.input_queue_dlq_message_retention_seconds + + sqs_managed_sse_enabled = var.queue_config.sqs_managed_sse_enabled + + # IAM access is managed via ECS task role policies + enable_producer_access = false + enable_consumer_access = false + + tags = merge(var.tags, { Type = "InputQueue" }) +} + +# ---------- SQS Output Queue ---------- + +module "output_queue" { + source = "yaalalabs/ak-common/aws//modules/sqs" + version = "0.5.1" + + product_alias = var.product_alias + env_alias = var.env_alias + module_name = var.module_name + queue_name = var.queue_config.output_queue_name + region = data.aws_region.current.name + product_display_name = var.product_alias + is_production = var.env_alias == "prod" + + fifo_queue = true + content_based_deduplication = false + deduplication_scope = "messageGroup" + fifo_throughput_limit = "perMessageGroupId" + + visibility_timeout_seconds = var.queue_config.output_queue_visibility_timeout + message_retention_seconds = var.queue_config.output_queue_message_retention_seconds + max_message_size = var.queue_config.max_message_size + receive_wait_time_seconds = var.queue_config.receive_wait_time_seconds + max_receive_count = var.queue_config.output_queue_max_receive_count + create_dlq = var.queue_config.output_queue_create_dlq + dlq_message_retention_seconds = var.queue_config.output_queue_dlq_message_retention_seconds + + sqs_managed_sse_enabled = var.queue_config.sqs_managed_sse_enabled + + enable_producer_access = false + enable_consumer_access = false + + tags = merge(var.tags, { Type = "OutputQueue" }) +} diff --git a/ak-deployment/ak-aws/containerized/modules/queues/outputs.tf b/ak-deployment/ak-aws/containerized/modules/queues/outputs.tf new file mode 100644 index 000000000..770c835f0 --- /dev/null +++ b/ak-deployment/ak-aws/containerized/modules/queues/outputs.tf @@ -0,0 +1,19 @@ +output "input_queue_arn" { + description = "ARN of the input queue" + value = module.input_queue.queue_arn +} + +output "input_queue_url" { + description = "URL of the input queue" + value = module.input_queue.queue_url +} + +output "output_queue_arn" { + description = "ARN of the output queue" + value = module.output_queue.queue_arn +} + +output "output_queue_url" { + description = "URL of the output queue" + value = module.output_queue.queue_url +} diff --git a/ak-deployment/ak-aws/containerized/modules/queues/variables.tf b/ak-deployment/ak-aws/containerized/modules/queues/variables.tf new file mode 100644 index 000000000..944399076 --- /dev/null +++ b/ak-deployment/ak-aws/containerized/modules/queues/variables.tf @@ -0,0 +1,48 @@ +variable "product_alias" { + type = string + description = "Product alias for resource naming" +} + +variable "env_alias" { + type = string + description = "Environment alias for resource naming" +} + +variable "module_name" { + type = string + description = "Module name for resource naming" +} + +variable "queue_config" { + description = "Queue configuration object" + type = object({ + # Queue names + input_queue_name = optional(string, "input-queue") + output_queue_name = optional(string, "output-queue") + + # Shared settings + sqs_managed_sse_enabled = optional(bool, true) + max_message_size = optional(number, 262144) + receive_wait_time_seconds = optional(number, 0) + + # Input queue settings + input_queue_visibility_timeout = optional(number, 60) + input_queue_message_retention_seconds = optional(number, 1800) + input_queue_max_receive_count = optional(number, 5) + input_queue_create_dlq = optional(bool, false) + input_queue_dlq_message_retention_seconds = optional(number, 1800) + + # Output queue settings + output_queue_visibility_timeout = optional(number, 60) + output_queue_message_retention_seconds = optional(number, 1800) + output_queue_max_receive_count = optional(number, 5) + output_queue_create_dlq = optional(bool, false) + output_queue_dlq_message_retention_seconds = optional(number, 1800) + }) +} + +variable "tags" { + type = map(string) + description = "Resource tags" + default = {} +} diff --git a/ak-deployment/ak-aws/containerized/modules/rest-service/main.tf b/ak-deployment/ak-aws/containerized/modules/rest-service/main.tf new file mode 100644 index 000000000..ad5b529c5 --- /dev/null +++ b/ak-deployment/ak-aws/containerized/modules/rest-service/main.tf @@ -0,0 +1,233 @@ +locals { + rest_service_environment = merge( + var.rest_service.environment_variables, + var.redis_url != null ? { + AK_SESSION__REDIS__URL = var.redis_url + } : {}, + var.dynamodb_memory_table_arn != null ? { + AK_SESSION__DYNAMODB__TABLE_NAME = var.dynamodb_memory_table_name + } : {}, + # Queue mode — inject queue URLs and response store table name + var.enable_queue_mode ? { + AK_EXECUTION__QUEUES__INPUT__URL = var.input_queue_url + AK_EXECUTION__QUEUES__OUTPUT__URL = var.output_queue_url + AK_EXECUTION__RESPONSE_STORE__DYNAMODB__TABLE_NAME = var.response_store_table_name + } : {} + ) +} + +# ---------- Service Discovery ---------- + +resource "aws_service_discovery_http_namespace" "this" { + name = "${var.product_alias}-${var.env_alias}-${var.module_name}" + description = "CloudMap namespace for ${var.product_alias}-${var.env_alias}-${var.module_name}" + tags = var.tags +} + +# ---------- IAM Policies ---------- + +resource "aws_iam_policy" "dynamodb_policy" { + count = var.dynamodb_memory_table_arn != null ? 1 : 0 + name = "${var.product_alias}-${var.env_alias}-${var.module_name}-dynamodb-policy" + description = "Policy for DynamoDB access" + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Action = [ + "dynamodb:DescribeTable", + "dynamodb:GetItem", + "dynamodb:PutItem", + "dynamodb:UpdateItem", + "dynamodb:DeleteItem", + "dynamodb:Query", + "dynamodb:Scan" + ] + Resource = [ + var.dynamodb_memory_table_arn, + "${var.dynamodb_memory_table_arn}/index/*" + ] + } + ] + }) + + tags = var.tags +} + +# ---------- Security Groups ---------- + +resource "aws_security_group" "ecs_alb" { + name = "${var.product_alias}-${var.env_alias}-ecs-alb-sg" + description = "ALB SG for ECS" + vpc_id = var.vpc_id + ingress { + from_port = 80 + to_port = 80 + protocol = "tcp" + cidr_blocks = [var.vpc_cidr] + } + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = var.tags +} + +resource "aws_security_group" "ecs_service" { + name = "${var.product_alias}-${var.env_alias}-ecs-svc-sg" + description = "ECS service SG" + vpc_id = var.vpc_id + ingress { + from_port = var.rest_service.container_port + to_port = var.rest_service.container_port + protocol = "tcp" + security_groups = [aws_security_group.ecs_alb.id] + } + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = var.tags +} + +# ---------- Load Balancer ---------- + +resource "aws_lb" "app" { + name = "${var.product_alias}-${var.env_alias}-${var.module_name}-alb" + internal = true + load_balancer_type = "application" + subnets = var.subnet_ids + security_groups = [aws_security_group.ecs_alb.id] + + tags = var.tags +} + +resource "aws_lb_target_group" "app" { + name = "${var.product_alias}-${var.env_alias}-tg" + port = var.rest_service.container_port + protocol = "HTTP" + vpc_id = var.vpc_id + target_type = "ip" + health_check { + path = var.rest_service.health_check_endpoint + healthy_threshold = 2 + unhealthy_threshold = 2 + timeout = 5 + interval = 30 + matcher = "200-399" + } + + tags = var.tags +} + +resource "aws_lb_listener" "http" { + load_balancer_arn = aws_lb.app.arn + port = 80 + protocol = "HTTP" + default_action { + type = "forward" + target_group_arn = aws_lb_target_group.app.arn + } + + tags = var.tags +} + +# ---------- ECS Service ---------- + +module "ecs_service" { + source = "terraform-aws-modules/ecs/aws//modules/service" + version = "6.10.0" + + name = var.service_name + cluster_arn = var.ecs_cluster_arn + cpu = var.rest_service.cpu + memory = var.rest_service.memory + desired_count = var.rest_service.desired_count + launch_type = "FARGATE" + platform_version = "LATEST" + subnet_ids = var.subnet_ids + security_group_ids = [aws_security_group.ecs_service.id] + + load_balancer = { + service = { + target_group_arn = aws_lb_target_group.app.arn + container_name = var.container_name + container_port = var.rest_service.container_port + } + } + + service_connect_configuration = { + namespace = aws_service_discovery_http_namespace.this.arn + service = [ + { + client_alias = { + port = var.rest_service.container_port + dns_name = var.container_name + } + port_name = var.container_name + discovery_name = var.container_name + } + ] + } + + # Attach DynamoDB access to the task role if a memory table exists + create_tasks_iam_role = true + tasks_iam_role_policies = var.dynamodb_memory_table_arn != null ? { + DynamoDB = aws_iam_policy.dynamodb_policy[0].arn + } : {} + + container_definitions = { + (var.container_name) = { + cpu = var.rest_service.cpu + memory = var.rest_service.memory + image = var.rest_service.image_uri + essential = true + readonlyRootFilesystem = false + + # Command override - if provided, replaces the Docker image's CMD + command = var.rest_service.command + + portMappings = [ + { + name = var.container_name, + containerPort = var.rest_service.container_port, + hostPort = var.rest_service.container_port, + protocol = "tcp", + } + ] + enable_cloudwatch_logging = true + environment = [ + for k, v in local.rest_service_environment : { + name = k + value = v + } + ] + + health_check = { + command = ["CMD-SHELL", "curl -sf http://localhost:${var.rest_service.container_port}${var.rest_service.health_check_endpoint} || exit 1"], + interval = 30, + timeout = 5, + retries = 3, + startPeriod = 10 + } + log_configuration = { + logDriver = "awslogs", + options = { + awslogs-group = "/ecs/${var.product_alias}-${var.env_alias}-${var.module_name}", + awslogs-region = var.region, + awslogs-stream-prefix = "ecs" + } + } + } + } + + tags = var.tags +} diff --git a/ak-deployment/ak-aws/containerized/modules/rest-service/outputs.tf b/ak-deployment/ak-aws/containerized/modules/rest-service/outputs.tf new file mode 100644 index 000000000..ab7f3eff2 --- /dev/null +++ b/ak-deployment/ak-aws/containerized/modules/rest-service/outputs.tf @@ -0,0 +1,49 @@ +output "service_arn" { + description = "ECS service ARN" + value = module.ecs_service.services[var.service_name].service_arn +} + +output "service_name" { + description = "ECS service name" + value = module.ecs_service.services[var.service_name].service_name +} + +output "task_role_name" { + description = "ECS task role name" + value = module.ecs_service.services[var.service_name].tasks_iam_role_name +} + +output "task_role_arn" { + description = "ECS task role ARN" + value = module.ecs_service.services[var.service_name].tasks_iam_role_arn +} + +output "alb_arn" { + description = "Application Load Balancer ARN" + value = aws_lb.app.arn +} + +output "alb_dns_name" { + description = "Application Load Balancer DNS name" + value = aws_lb.app.dns_name +} + +output "alb_listener_arn" { + description = "ALB HTTP listener ARN" + value = aws_lb_listener.http.arn +} + +output "target_group_arn" { + description = "ALB target group ARN" + value = aws_lb_target_group.app.arn +} + +output "security_group_id" { + description = "ECS service security group ID" + value = aws_security_group.ecs_service.id +} + +output "alb_security_group_id" { + description = "ALB security group ID" + value = aws_security_group.ecs_alb.id +} diff --git a/ak-deployment/ak-aws/containerized/modules/rest-service/variables.tf b/ak-deployment/ak-aws/containerized/modules/rest-service/variables.tf new file mode 100644 index 000000000..5f74845d8 --- /dev/null +++ b/ak-deployment/ak-aws/containerized/modules/rest-service/variables.tf @@ -0,0 +1,116 @@ +variable "product_alias" { + type = string + description = "Product alias for resource naming" +} + +variable "env_alias" { + type = string + description = "Environment alias for resource naming" +} + +variable "module_name" { + type = string + description = "Module name for resource naming" +} + +variable "region" { + type = string + description = "AWS region" +} + +variable "prefix" { + type = string + description = "Resource name prefix" +} + +variable "vpc_id" { + type = string + description = "VPC ID" +} + +variable "vpc_cidr" { + type = string + description = "VPC CIDR block" +} + +variable "subnet_ids" { + type = list(string) + description = "Private subnet IDs for ECS tasks" +} + +variable "ecs_cluster_arn" { + type = string + description = "ECS cluster ARN" +} + +variable "service_name" { + type = string + description = "ECS service name" +} + +variable "container_name" { + type = string + description = "Container name" +} + +variable "redis_url" { + type = string + description = "Redis URL for session storage" + default = null +} + +variable "dynamodb_memory_table_arn" { + type = string + description = "DynamoDB memory table ARN" + default = null +} + +variable "dynamodb_memory_table_name" { + type = string + description = "DynamoDB memory table name" + default = null +} + +variable "rest_service" { + description = "REST service configuration object" + type = object({ + cpu = optional(number, 256) + memory = optional(number, 512) + desired_count = optional(number, 1) + container_port = optional(number, 8000) + health_check_endpoint = optional(string, "/health") + image_uri = string + command = optional(list(string), null) + environment_variables = optional(map(string), {}) + }) +} + +variable "enable_queue_mode" { + type = bool + description = "Whether queue mode is enabled" + default = false +} + +variable "input_queue_url" { + type = string + description = "Input queue URL (for queue mode)" + default = null +} + +variable "output_queue_url" { + type = string + description = "Output queue URL (for queue mode)" + default = null +} + +variable "response_store_table_name" { + type = string + description = "Response store DynamoDB table name (for queue mode)" + default = null +} + +variable "tags" { + type = map(string) + description = "Resource tags" + default = {} +} diff --git a/ak-deployment/ak-aws/containerized/outputs.tf b/ak-deployment/ak-aws/containerized/outputs.tf index ce28c7cfb..1d6d4547a 100644 --- a/ak-deployment/ak-aws/containerized/outputs.tf +++ b/ak-deployment/ak-aws/containerized/outputs.tf @@ -1,5 +1,5 @@ output "alb_dns_name" { - value = aws_lb.app.dns_name + value = module.rest_service.alb_dns_name } output "cluster_arn" { @@ -30,12 +30,12 @@ output "private_subnet_ids" { output "input_queue_url" { description = "URL of the SQS Input Queue (queue mode only)" - value = var.enable_queue_mode ? module.input_queue[0].queue_url : null + value = var.enable_queue_mode ? module.queues[0].input_queue_url : null } output "output_queue_url" { description = "URL of the SQS Output Queue (queue mode only)" - value = var.enable_queue_mode ? module.output_queue[0].queue_url : null + value = var.enable_queue_mode ? module.queues[0].output_queue_url : null } output "response_store_table_name" { @@ -45,10 +45,20 @@ output "response_store_table_name" { output "agent_runner_service_name" { description = "ECS Agent Runner service name (queue mode only)" - value = var.enable_queue_mode ? aws_ecs_service.agent_runner[0].name : null + value = var.enable_queue_mode ? module.agent_runner[0].service_name : null } output "rest_service_image_uri" { description = "Docker image URI used by the REST Service ECS task" value = module.docker_image[0].docker_image_uri } + +output "rest_service_name" { + description = "ECS REST service name" + value = module.rest_service.service_name +} + +output "rest_service_task_role_arn" { + description = "ECS REST service task role ARN" + value = module.rest_service.task_role_arn +} diff --git a/ak-deployment/ak-aws/containerized/queue.tf b/ak-deployment/ak-aws/containerized/queue.tf deleted file mode 100644 index 6e0d8e009..000000000 --- a/ak-deployment/ak-aws/containerized/queue.tf +++ /dev/null @@ -1,430 +0,0 @@ -data "aws_region" "current" {} - - -module "input_queue" { - count = var.enable_queue_mode ? 1 : 0 - source = "yaalalabs/ak-common/aws//modules/sqs" - version = "0.5.1" - - product_alias = var.product_alias - env_alias = var.env_alias - module_name = var.module_name - queue_name = "input-queue" - region = data.aws_region.current.name - product_display_name = var.product_alias - is_production = var.env_alias == "prod" - - fifo_queue = true - content_based_deduplication = false - deduplication_scope = "messageGroup" - fifo_throughput_limit = "perMessageGroupId" - - visibility_timeout_seconds = var.sqs_input_visibility_timeout - message_retention_seconds = var.sqs_input_message_retention_seconds - max_message_size = var.sqs_max_message_size - receive_wait_time_seconds = var.sqs_receive_wait_time_seconds - max_receive_count = var.sqs_input_max_receive_count - create_dlq = var.sqs_input_create_dlq - dlq_message_retention_seconds = var.sqs_input_dlq_message_retention_seconds - - sqs_managed_sse_enabled = var.sqs_managed_sse_enabled - - # IAM access is managed via ECS task role policies below - enable_producer_access = false - enable_consumer_access = false - - tags = merge(var.tags, { Type = "InputQueue" }) -} - -module "output_queue" { - count = var.enable_queue_mode ? 1 : 0 - source = "yaalalabs/ak-common/aws//modules/sqs" - version = "0.5.1" - - product_alias = var.product_alias - env_alias = var.env_alias - module_name = var.module_name - queue_name = "output-queue" - region = data.aws_region.current.name - product_display_name = var.product_alias - is_production = var.env_alias == "prod" - - fifo_queue = true - content_based_deduplication = false - deduplication_scope = "messageGroup" - fifo_throughput_limit = "perMessageGroupId" - - visibility_timeout_seconds = var.sqs_output_visibility_timeout - message_retention_seconds = var.sqs_output_message_retention_seconds - max_message_size = var.sqs_max_message_size - receive_wait_time_seconds = var.sqs_receive_wait_time_seconds - max_receive_count = var.sqs_output_max_receive_count - create_dlq = var.sqs_output_create_dlq - dlq_message_retention_seconds = var.sqs_output_dlq_message_retention_seconds - - sqs_managed_sse_enabled = var.sqs_managed_sse_enabled - - enable_producer_access = false - enable_consumer_access = false - - tags = merge(var.tags, { Type = "OutputQueue" }) -} - -resource "aws_dynamodb_table" "response_store" { - count = var.enable_queue_mode ? 1 : 0 - - name = "${local.prefix}-response-store" - billing_mode = "PAY_PER_REQUEST" - hash_key = "request_id" - - attribute { - name = "request_id" - type = "S" - } - - ttl { - attribute_name = "expiry_time" - enabled = true - } - - tags = merge(var.tags, { Type = "ResponseStore" }) -} - - -resource "aws_iam_policy" "rest_service_sqs_policy" { - count = var.enable_queue_mode ? 1 : 0 - - name = "${local.prefix}-rest-svc-sqs" - description = "Allow REST Service ECS task to send to Input Queue and consume from Output Queue" - - policy = jsonencode({ - Version = "2012-10-17" - Statement = [ - { - Sid = "SendToInputQueue" - Effect = "Allow" - Action = [ - "sqs:SendMessage", - "sqs:GetQueueAttributes", - "sqs:GetQueueUrl" - ] - Resource = module.input_queue[0].queue_arn - }, - { - Sid = "ConsumeOutputQueue" - Effect = "Allow" - Action = [ - "sqs:ReceiveMessage", - "sqs:DeleteMessage", - "sqs:ChangeMessageVisibility", - "sqs:GetQueueAttributes", - "sqs:GetQueueUrl" - ] - Resource = module.output_queue[0].queue_arn - } - ] - }) - - tags = var.tags -} - -resource "aws_iam_policy" "rest_service_response_store_policy" { - count = var.enable_queue_mode ? 1 : 0 - - name = "${local.prefix}-rest-svc-response-store" - description = "Allow REST Service ECS task to read/write the DynamoDB response store" - - policy = jsonencode({ - Version = "2012-10-17" - Statement = [ - { - Effect = "Allow" - Action = [ - "dynamodb:DescribeTable", - "dynamodb:GetItem", - "dynamodb:PutItem", - "dynamodb:UpdateItem", - "dynamodb:DeleteItem", - "dynamodb:Query", - "dynamodb:Scan" - ] - Resource = [ - aws_dynamodb_table.response_store[0].arn, - "${aws_dynamodb_table.response_store[0].arn}/index/*" - ] - } - ] - }) - - tags = var.tags -} - -resource "aws_iam_role_policy_attachment" "rest_service_sqs_attachment" { - count = var.enable_queue_mode ? 1 : 0 - role = module.ecs.services[local.service_name].tasks_iam_role_name - policy_arn = aws_iam_policy.rest_service_sqs_policy[0].arn -} - -resource "aws_iam_role_policy_attachment" "rest_service_response_store_attachment" { - count = var.enable_queue_mode ? 1 : 0 - role = module.ecs.services[local.service_name].tasks_iam_role_name - policy_arn = aws_iam_policy.rest_service_response_store_policy[0].arn -} - - -resource "aws_iam_role" "agent_runner_execution_role" { - count = var.enable_queue_mode ? 1 : 0 - - name = "${local.prefix}-agent-runner-exec-role" - - assume_role_policy = jsonencode({ - Version = "2012-10-17" - Statement = [{ - Effect = "Allow" - Action = "sts:AssumeRole" - Principal = { Service = "ecs-tasks.amazonaws.com" } - }] - }) - - tags = var.tags -} - -resource "aws_iam_role_policy_attachment" "agent_runner_execution_policy" { - count = var.enable_queue_mode ? 1 : 0 - role = aws_iam_role.agent_runner_execution_role[0].name - policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" -} - -resource "aws_iam_role" "agent_runner_task_role" { - count = var.enable_queue_mode ? 1 : 0 - - name = "${local.prefix}-agent-runner-task-role" - - assume_role_policy = jsonencode({ - Version = "2012-10-17" - Statement = [{ - Effect = "Allow" - Action = "sts:AssumeRole" - Principal = { Service = "ecs-tasks.amazonaws.com" } - }] - }) - - tags = var.tags -} - - -resource "aws_iam_policy" "agent_runner_logs_policy" { - count = var.enable_queue_mode ? 1 : 0 - name = "${local.prefix}-agent-runner-logs" - - policy = jsonencode({ - Version = "2012-10-17" - Statement = [{ - Effect = "Allow" - Action = [ - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:PutLogEvents" - ] - Resource = "arn:aws:logs:${var.region}:*:log-group:/ecs/${local.prefix}-agent-runner:*" - }] - }) -} - -resource "aws_iam_role_policy_attachment" "agent_runner_logs_attachment" { - count = var.enable_queue_mode ? 1 : 0 - role = aws_iam_role.agent_runner_task_role[0].name - policy_arn = aws_iam_policy.agent_runner_logs_policy[0].arn -} - -resource "aws_iam_policy" "agent_runner_sqs_policy" { - count = var.enable_queue_mode ? 1 : 0 - - name = "${local.prefix}-agent-runner-sqs" - description = "Allow Agent Runner ECS task to consume Input Queue and produce to Output Queue" - - policy = jsonencode({ - Version = "2012-10-17" - Statement = [ - { - Sid = "ConsumeInputQueue" - Effect = "Allow" - Action = [ - "sqs:ReceiveMessage", - "sqs:DeleteMessage", - "sqs:ChangeMessageVisibility", - "sqs:GetQueueAttributes", - "sqs:GetQueueUrl" - ] - Resource = module.input_queue[0].queue_arn - }, - { - Sid = "SendToOutputQueue" - Effect = "Allow" - Action = [ - "sqs:SendMessage", - "sqs:GetQueueAttributes", - "sqs:GetQueueUrl" - ] - Resource = module.output_queue[0].queue_arn - } - ] - }) - - tags = var.tags -} - -resource "aws_iam_role_policy_attachment" "agent_runner_sqs_attachment" { - count = var.enable_queue_mode ? 1 : 0 - role = aws_iam_role.agent_runner_task_role[0].name - policy_arn = aws_iam_policy.agent_runner_sqs_policy[0].arn -} - -# DynamoDB session store access for agent runner (same table as REST Service) -resource "aws_iam_policy" "agent_runner_dynamodb_memory_policy" { - count = var.enable_queue_mode && var.create_dynamodb_memory_table ? 1 : 0 - name = "${local.prefix}-agent-runner-dynamodb-memory" - - policy = jsonencode({ - Version = "2012-10-17" - Statement = [{ - Effect = "Allow" - Action = [ - "dynamodb:DescribeTable", - "dynamodb:GetItem", - "dynamodb:PutItem", - "dynamodb:UpdateItem", - "dynamodb:DeleteItem", - "dynamodb:Query", - "dynamodb:Scan" - ] - Resource = [ - local.dynamodb_memory_table_arn, - "${local.dynamodb_memory_table_arn}/index/*" - ] - }] - }) -} - -resource "aws_iam_role_policy_attachment" "agent_runner_dynamodb_memory_attachment" { - count = var.enable_queue_mode && var.create_dynamodb_memory_table ? 1 : 0 - role = aws_iam_role.agent_runner_task_role[0].name - policy_arn = aws_iam_policy.agent_runner_dynamodb_memory_policy[0].arn -} - -# ---------- ECS Agent Runner Service ---------- - -resource "aws_security_group" "agent_runner" { - count = var.enable_queue_mode ? 1 : 0 - - name = "${local.prefix}-agent-runner-sg" - description = "Agent Runner ECS service SG - egress only (queue-polling)" - vpc_id = local.vpc_id - - egress { - from_port = 0 - to_port = 0 - protocol = "-1" - cidr_blocks = ["0.0.0.0/0"] - } - - tags = var.tags -} - -resource "aws_cloudwatch_log_group" "agent_runner" { - count = var.enable_queue_mode ? 1 : 0 - name = "/ecs/${local.prefix}-agent-runner" - retention_in_days = 90 - tags = var.tags -} - -resource "aws_ecs_task_definition" "agent_runner" { - count = var.enable_queue_mode ? 1 : 0 - - family = "${local.prefix}-agent-runner" - requires_compatibilities = ["FARGATE"] - network_mode = "awsvpc" - cpu = var.agent_runner_cpu - memory = var.agent_runner_memory - execution_role_arn = aws_iam_role.agent_runner_execution_role[0].arn - task_role_arn = aws_iam_role.agent_runner_task_role[0].arn - - container_definitions = jsonencode([ - { - name = "${local.prefix}-agent-runner" - image = var.agent_runner_image_uri != null ? var.agent_runner_image_uri : module.docker_image[0].docker_image_uri - essential = true - - # Command override - if provided, replaces the Docker image's CMD - command = var.agent_runner_command - - environment = [ - for k, v in merge( - var.environment_variables, - { - AK_EXECUTION__QUEUES__INPUT__URL = module.input_queue[0].queue_url - AK_EXECUTION__QUEUES__OUTPUT__URL = module.output_queue[0].queue_url - AK_EXECUTION__QUEUES__INPUT__MAX_RECEIVE_COUNT = tostring(max(1, var.sqs_input_max_receive_count - 1)) - }, - local.redis_url != null ? { AK_SESSION__REDIS__URL = local.redis_url } : {}, - local.dynamodb_memory_table_arn != null ? { - AK_SESSION__DYNAMODB__TABLE_NAME = local.dynamodb_memory_table_name - } : {} - ) : { name = k, value = v } - ] - - logConfiguration = { - logDriver = "awslogs" - options = { - "awslogs-group" = "/ecs/${local.prefix}-agent-runner" - "awslogs-region" = var.region - "awslogs-stream-prefix" = "ecs" - } - } - } - ]) - - tags = var.tags -} - -resource "aws_ecs_service" "agent_runner" { - count = var.enable_queue_mode ? 1 : 0 - - name = "${local.prefix}-agent-runner" - cluster = module.ecs.cluster_arn - task_definition = aws_ecs_task_definition.agent_runner[0].arn - desired_count = var.agent_runner_desired_count - launch_type = "FARGATE" - - network_configuration { - subnets = local.subnet_ids - security_groups = [aws_security_group.agent_runner[0].id] - assign_public_ip = false - } - - tags = var.tags -} - -# Only needed for rest_async mode — adds GET /api/{version}/{endpoint}/{sessionId} - -resource "aws_apigatewayv2_integration" "async_get" { - count = var.enable_queue_mode && var.queue_mode_type == "async" ? 1 : 0 - api_id = aws_apigatewayv2_api.http_api.id - integration_type = "HTTP_PROXY" - integration_method = "ANY" - integration_uri = aws_lb_listener.http.arn - connection_type = "VPC_LINK" - connection_id = aws_apigatewayv2_vpc_link.ecs_alb.id - passthrough_behavior = "WHEN_NO_MATCH" - - request_parameters = { - "overwrite:path" = "/api/v1/chat/$request.path.sessionId" - } -} - -resource "aws_apigatewayv2_route" "async_get" { - count = var.enable_queue_mode && var.queue_mode_type == "async" ? 1 : 0 - api_id = aws_apigatewayv2_api.http_api.id - route_key = "GET ${local.api_base_segment_with_version}/${var.agent_endpoint}/{sessionId}" - target = "integrations/${aws_apigatewayv2_integration.async_get[0].id}" -} diff --git a/ak-deployment/ak-aws/containerized/queue_mode.tf b/ak-deployment/ak-aws/containerized/queue_mode.tf new file mode 100644 index 000000000..d506e3aa1 --- /dev/null +++ b/ak-deployment/ak-aws/containerized/queue_mode.tf @@ -0,0 +1,63 @@ +# ---------- Queue Mode Resources ---------- +# This file orchestrates SQS queues, agent runner, and related resources for queue-based execution + +# ---------- SQS Queues Module ---------- + +module "queues" { + count = var.enable_queue_mode ? 1 : 0 + source = "./modules/queues" + + product_alias = var.product_alias + env_alias = var.env_alias + module_name = var.module_name + + queue_config = var.queue_config + + tags = var.tags +} + +# ---------- Agent Runner Module ---------- + +module "agent_runner" { + count = var.enable_queue_mode ? 1 : 0 + source = "./modules/agent-runner" + + product_alias = var.product_alias + env_alias = var.env_alias + module_name = var.module_name + region = var.region + prefix = local.prefix + + vpc_id = local.vpc_id + subnet_ids = local.subnet_ids + + ecs_cluster_arn = module.ecs.cluster_arn + ecs_cluster_name = module.ecs.cluster_name + + input_queue_url = module.queues[0].input_queue_url + input_queue_arn = module.queues[0].input_queue_arn + output_queue_url = module.queues[0].output_queue_url + output_queue_arn = module.queues[0].output_queue_arn + + redis_url = local.redis_url + dynamodb_memory_table_arn = local.dynamodb_memory_table_arn + dynamodb_memory_table_name = local.dynamodb_memory_table_name + + agent_runner = { + cpu = var.agent_runner.cpu + memory = var.agent_runner.memory + desired_count = var.agent_runner.desired_count + # Use agent_runner image if package_path provided, else use image_uri, else fallback to rest_service image + image_uri = var.agent_runner.package_path != null ? module.agent_runner_docker_image[0].docker_image_uri : (var.agent_runner.image_uri != null ? var.agent_runner.image_uri : module.docker_image[0].docker_image_uri) + command = var.agent_runner.command + environment_variables = merge(var.environment_variables, var.agent_runner.environment_variables) + } + queue_config = var.queue_config + scaling_config = var.scaling_config + + default_image_uri = module.docker_image[0].docker_image_uri + + account_id = data.aws_caller_identity.current.account_id + + tags = var.tags +} diff --git a/ak-deployment/ak-aws/containerized/rest_service.tf b/ak-deployment/ak-aws/containerized/rest_service.tf new file mode 100644 index 000000000..75d19adfa --- /dev/null +++ b/ak-deployment/ak-aws/containerized/rest_service.tf @@ -0,0 +1,54 @@ +# ---------- REST Service Module ---------- +# Manages the main ECS service that handles HTTP requests + +module "rest_service" { + source = "./modules/rest-service" + + product_alias = var.product_alias + env_alias = var.env_alias + module_name = var.module_name + region = var.region + prefix = local.prefix + + vpc_id = local.vpc_id + vpc_cidr = local.vpc_cidr + subnet_ids = local.subnet_ids + + ecs_cluster_arn = module.ecs.cluster_arn + + service_name = local.service_name + container_name = local.container_name + + redis_url = local.redis_url + dynamodb_memory_table_arn = local.dynamodb_memory_table_arn + dynamodb_memory_table_name = local.dynamodb_memory_table_name + + rest_service = { + cpu = var.rest_service.cpu + memory = var.rest_service.memory + desired_count = var.rest_service.desired_count + container_port = var.rest_service.container_port + health_check_endpoint = var.rest_service.health_check_endpoint + image_uri = var.rest_service.image_uri != null ? var.rest_service.image_uri : module.docker_image[0].docker_image_uri + command = var.rest_service.command + environment_variables = merge(var.environment_variables, var.rest_service.environment_variables) + } + + enable_queue_mode = var.enable_queue_mode + input_queue_url = var.enable_queue_mode ? module.queues[0].input_queue_url : null + output_queue_url = var.enable_queue_mode ? module.queues[0].output_queue_url : null + response_store_table_name = var.enable_queue_mode ? aws_dynamodb_table.response_store[0].name : null + + tags = var.tags +} + +# ---------- ECS Cluster ---------- + +module "ecs" { + source = "terraform-aws-modules/ecs/aws" + version = "6.10.0" + + cluster_name = "${var.product_alias}-${var.env_alias}-${var.module_name}" + + tags = var.tags +} diff --git a/ak-deployment/ak-aws/containerized/scaling.tf b/ak-deployment/ak-aws/containerized/scaling.tf deleted file mode 100644 index f55197cab..000000000 --- a/ak-deployment/ak-aws/containerized/scaling.tf +++ /dev/null @@ -1,247 +0,0 @@ -locals { - enable_autoscaling = var.enable_queue_mode && var.enable_agent_runner_autoscaling -} - -# Validation: Autoscaling requires queue mode -resource "null_resource" "validate_autoscaling_requires_queue_mode" { - count = var.enable_agent_runner_autoscaling && !var.enable_queue_mode ? 1 : 0 - - provisioner "local-exec" { - command = "echo 'ERROR: enable_agent_runner_autoscaling requires enable_queue_mode = true' && exit 1" - } -} - - -resource "aws_iam_role" "backlog_metric_lambda_role" { - count = local.enable_autoscaling ? 1 : 0 - name = "${local.prefix}-backlog-metric-lambda-role" - - assume_role_policy = jsonencode({ - Version = "2012-10-17" - Statement = [{ - Effect = "Allow" - Action = "sts:AssumeRole" - Principal = { Service = "lambda.amazonaws.com" } - }] - }) - - tags = var.tags -} - -resource "aws_iam_policy" "backlog_metric_lambda_policy" { - count = local.enable_autoscaling ? 1 : 0 - name = "${local.prefix}-backlog-metric-lambda-policy" - - policy = jsonencode({ - Version = "2012-10-17" - Statement = [ - { - Sid = "GetQueueDepth" - Effect = "Allow" - Action = [ - "sqs:GetQueueAttributes" - ] - Resource = module.input_queue[0].queue_arn - }, - { - Sid = "GetRunningTaskCount" - Effect = "Allow" - Action = [ - "ecs:DescribeServices" - ] - Resource = "arn:aws:ecs:${var.region}:${data.aws_caller_identity.current.account_id}:service/${module.ecs.cluster_name}/${local.prefix}-agent-runner" - }, - { - Sid = "PutCustomMetric" - Effect = "Allow" - Action = "cloudwatch:PutMetricData" - Resource = "*" - }, - { - Sid = "LambdaLogging" - Effect = "Allow" - Action = [ - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:PutLogEvents" - ] - Resource = "arn:aws:logs:${var.region}:${data.aws_caller_identity.current.account_id}:log-group:/aws/lambda/${local.prefix}-backlog-metric:*" - } - ] - }) - - tags = var.tags -} - -resource "aws_iam_role_policy_attachment" "backlog_metric_lambda" { - count = local.enable_autoscaling ? 1 : 0 - role = aws_iam_role.backlog_metric_lambda_role[0].name - policy_arn = aws_iam_policy.backlog_metric_lambda_policy[0].arn -} - - -data "archive_file" "backlog_metric_lambda" { - count = local.enable_autoscaling ? 1 : 0 - type = "zip" - output_path = "${path.module}/.terraform/lambda/backlog_metric.zip" - - source { - content = <<-PYTHON -import boto3 -import os -import json - -def handler(event, context): - """ - Calculate BacklogPerTask metric for ECS agent runner autoscaling. - - BacklogPerTask = ApproximateNumberOfMessages / max(runningCount, 1) - - This metric is used by Target Tracking Scaling to adjust the number - of agent runner tasks based on queue depth. - """ - sqs = boto3.client("sqs") - ecs = boto3.client("ecs") - cw = boto3.client("cloudwatch") - - queue_url = os.environ["QUEUE_URL"] - cluster_name = os.environ["CLUSTER_NAME"] - service_name = os.environ["SERVICE_NAME"] - namespace = os.environ.get("METRIC_NAMESPACE", "Custom/ECS") - metric_name = os.environ.get("METRIC_NAME", "BacklogPerTask") - - # --- Get queue depth --- - attrs = sqs.get_queue_attributes( - QueueUrl=queue_url, - AttributeNames=["ApproximateNumberOfMessages"] - ) - queue_depth = int(attrs["Attributes"]["ApproximateNumberOfMessages"]) - - # --- Get running task count --- - resp = ecs.describe_services(cluster=cluster_name, services=[service_name]) - running = resp["services"][0]["runningCount"] - - # Avoid division by zero; treat 0 running tasks as 1 for the metric - divisor = max(running, 1) - backlog_per_task = queue_depth / divisor - - # --- Publish custom metric --- - cw.put_metric_data( - Namespace=namespace, - MetricData=[{ - "MetricName": metric_name, - "Value": backlog_per_task, - "Unit": "Count", - "Dimensions": [ - {"Name": "ClusterName", "Value": cluster_name}, - {"Name": "ServiceName", "Value": service_name}, - ] - }] - ) - - print(f"Published metric: {metric_name}={backlog_per_task} (queue_depth={queue_depth}, running={running})") - - return { - "backlog_per_task": backlog_per_task, - "queue_depth": queue_depth, - "running": running - } -PYTHON - filename = "index.py" - } -} - -resource "aws_lambda_function" "backlog_metric" { - count = local.enable_autoscaling ? 1 : 0 - function_name = "${local.prefix}-backlog-metric" - role = aws_iam_role.backlog_metric_lambda_role[0].arn - runtime = "python3.12" - handler = "index.handler" - filename = data.archive_file.backlog_metric_lambda[0].output_path - source_code_hash = data.archive_file.backlog_metric_lambda[0].output_base64sha256 - timeout = 30 - - environment { - variables = { - QUEUE_URL = module.input_queue[0].queue_url - CLUSTER_NAME = module.ecs.cluster_name - SERVICE_NAME = "${local.prefix}-agent-runner" - METRIC_NAMESPACE = "Custom/ECS" - METRIC_NAME = "BacklogPerTask" - } - } - - tags = var.tags -} - -resource "aws_cloudwatch_log_group" "backlog_metric_lambda" { - count = local.enable_autoscaling ? 1 : 0 - name = "/aws/lambda/${local.prefix}-backlog-metric" - retention_in_days = 7 - tags = var.tags -} - -resource "aws_cloudwatch_event_rule" "backlog_metric_schedule" { - count = local.enable_autoscaling ? 1 : 0 - name = "${local.prefix}-backlog-metric-schedule" - description = "Trigger BacklogPerTask metric calculation every minute" - schedule_expression = "rate(1 minute)" - tags = var.tags -} - -resource "aws_cloudwatch_event_target" "backlog_metric_lambda" { - count = local.enable_autoscaling ? 1 : 0 - rule = aws_cloudwatch_event_rule.backlog_metric_schedule[0].name - arn = aws_lambda_function.backlog_metric[0].arn -} - -resource "aws_lambda_permission" "allow_eventbridge" { - count = local.enable_autoscaling ? 1 : 0 - statement_id = "AllowExecutionFromEventBridge" - action = "lambda:InvokeFunction" - function_name = aws_lambda_function.backlog_metric[0].function_name - principal = "events.amazonaws.com" - source_arn = aws_cloudwatch_event_rule.backlog_metric_schedule[0].arn -} - -resource "aws_appautoscaling_target" "agent_runner" { - count = local.enable_autoscaling ? 1 : 0 - max_capacity = var.agent_runner_max_count - min_capacity = var.agent_runner_min_count - resource_id = "service/${module.ecs.cluster_name}/${local.prefix}-agent-runner" - scalable_dimension = "ecs:service:DesiredCount" - service_namespace = "ecs" - - depends_on = [aws_ecs_service.agent_runner] -} - -resource "aws_appautoscaling_policy" "agent_runner_backlog" { - count = local.enable_autoscaling ? 1 : 0 - name = "${local.prefix}-agent-runner-backlog-tracking" - policy_type = "TargetTrackingScaling" - resource_id = aws_appautoscaling_target.agent_runner[0].resource_id - scalable_dimension = aws_appautoscaling_target.agent_runner[0].scalable_dimension - service_namespace = aws_appautoscaling_target.agent_runner[0].service_namespace - - target_tracking_scaling_policy_configuration { - target_value = var.agent_runner_backlog_target - scale_in_cooldown = var.agent_runner_scale_in_cooldown - scale_out_cooldown = var.agent_runner_scale_out_cooldown - - customized_metric_specification { - namespace = "Custom/ECS" - metric_name = "BacklogPerTask" - statistic = "Average" - - dimensions { - name = "ClusterName" - value = module.ecs.cluster_name - } - - dimensions { - name = "ServiceName" - value = "${local.prefix}-agent-runner" - } - } - } -} diff --git a/ak-deployment/ak-aws/containerized/state.tf b/ak-deployment/ak-aws/containerized/state.tf index 32b64c21c..7b612b8da 100644 --- a/ak-deployment/ak-aws/containerized/state.tf +++ b/ak-deployment/ak-aws/containerized/state.tf @@ -85,6 +85,17 @@ module "docker_image" { source_path = var.package_path } +# Agent Runner Docker Image (optional - only if package_path is provided) +module "agent_runner_docker_image" { + count = var.enable_queue_mode && var.agent_runner.package_path != null ? 1 : 0 + source = "yaalalabs/ak-common/aws//modules/ecr" + version = "0.5.1" + env_alias = var.env_alias + module_name = "${var.module_name}-runner" + product_alias = var.product_alias + source_path = var.agent_runner.package_path +} + module dynamodb_memory { source = "yaalalabs/ak-common/aws//modules/dynamodb" version = "0.5.1" diff --git a/ak-deployment/ak-aws/containerized/variables.tf b/ak-deployment/ak-aws/containerized/variables.tf index 42dc1a136..9f77f869c 100644 --- a/ak-deployment/ak-aws/containerized/variables.tf +++ b/ak-deployment/ak-aws/containerized/variables.tf @@ -122,35 +122,32 @@ variable "create_dynamodb_memory_table" { default = false } +# --------------------------------------------------------------------------- +# REST Service Configuration +# --------------------------------------------------------------------------- -variable "ecs_cpu" { - type = number - description = "Fargate CPU units" - default = 256 -} - -variable "ecs_memory" { - type = number - description = "Fargate memory in MiB" - default = 512 -} - -variable "ecs_desired_count" { - type = number - description = "Desired count for ECS service" - default = 1 -} - -variable "ecs_container_port" { - type = number - description = "Container port exposed by the ECS service" - default = 8000 -} - -variable "ecs_health_check_endpoint" { - type = string - description = "Health check path for ALB target group" - default = "/health" +variable "rest_service" { + description = "REST service configuration object" + type = object({ + cpu = optional(number, 256) + memory = optional(number, 512) + desired_count = optional(number, 1) + container_port = optional(number, 8000) + health_check_endpoint = optional(string, "/health") + image_uri = optional(string, null) # Defaults to package_path Docker image + command = optional(list(string), null) + environment_variables = optional(map(string), {}) + }) + default = { + cpu = 256 + memory = 512 + desired_count = 1 + container_port = 8000 + health_check_endpoint = "/health" + image_uri = null + command = null + environment_variables = {} + } } variable "container_type" { @@ -158,17 +155,11 @@ variable "container_type" { description = "Container type (ECS or EKS)" default = "ecs" validation { - condition = contains(["ecs", "eks"], lower(var.container_type)) + condition = contains(["ecs", "eks"], lower(var.container_type)) error_message = "Container type must be either 'ecs' or 'eks'." } } -variable "container_entrypoint_override" { - type = list(string) - description = "Command override for the main ECS task (REST service). If not set, uses the Docker image's CMD. Example: [\"python\", \"app_rest_service.py\"]" - default = null -} - # CORS configuration for HTTP API (API Gateway v2) variable "enable_cors" { type = bool @@ -236,7 +227,7 @@ data "aws_ecr_authorization_token" "token" {} data "aws_caller_identity" "current" {} # --------------------------------------------------------------------------- -# Queue Mode Variables (ECS + SQS) +# Queue Mode Configuration # --------------------------------------------------------------------------- variable "enable_queue_mode" { @@ -255,164 +246,103 @@ variable "queue_mode_type" { } } -# --- SQS shared --- - -variable "sqs_managed_sse_enabled" { - type = bool - description = "Enable SQS-managed server-side encryption for both queues." - default = true -} - -variable "sqs_max_message_size" { - type = number - description = "Maximum SQS message size in bytes (applies to both queues)." - default = 262144 # 256 KB -} - -variable "sqs_receive_wait_time_seconds" { - type = number - description = "Long-poll wait time for ReceiveMessage calls (applies to both queues)." - default = 0 -} - -# --- Input Queue --- - -variable "sqs_input_visibility_timeout" { - type = number - description = "Visibility timeout (seconds) for the Input Queue. Should be >= agent processing time." - default = 60 -} - -variable "sqs_input_message_retention_seconds" { - type = number - description = "How long messages stay in the Input Queue before being automatically deleted." - default = 1800 # 30 minutes -} - -variable "sqs_input_max_receive_count" { - type = number - description = "Number of times a message can be received before being sent to the DLQ (if enabled)." - default = 5 -} - -variable "sqs_input_create_dlq" { - type = bool - description = "Create a dead-letter queue for the Input Queue." - default = false -} - -variable "sqs_input_dlq_message_retention_seconds" { - type = number - description = "How long messages stay in the Input DLQ." - default = 1800 -} - -# --- Output Queue --- - -variable "sqs_output_visibility_timeout" { - type = number - description = "Visibility timeout (seconds) for the Output Queue." - default = 60 -} - -variable "sqs_output_message_retention_seconds" { - type = number - description = "How long messages stay in the Output Queue before being automatically deleted." - default = 1800 -} - -variable "sqs_output_max_receive_count" { - type = number - description = "Number of times a message can be received before being sent to the DLQ (if enabled)." - default = 5 -} - -variable "sqs_output_create_dlq" { - type = bool - description = "Create a dead-letter queue for the Output Queue." - default = false -} - -variable "sqs_output_dlq_message_retention_seconds" { - type = number - description = "How long messages stay in the Output DLQ." - default = 1800 -} - -# --- Agent Runner ECS Service --- - -variable "agent_runner_cpu" { - type = number - description = "Fargate CPU units for the Agent Runner ECS task." - default = 512 -} - -variable "agent_runner_memory" { - type = number - description = "Fargate memory (MiB) for the Agent Runner ECS task." - default = 1024 -} - -variable "agent_runner_desired_count" { - type = number - description = "Desired number of Agent Runner ECS tasks." - default = 1 -} - -variable "agent_runner_image_uri" { - type = string - description = "Docker image URI for the Agent Runner ECS task. Defaults to the same image as the REST Service if not set." - default = null +# --- Queue Configuration Object --- + +variable "queue_config" { + description = "Queue configuration object" + type = object({ + # Queue names + input_queue_name = optional(string, "input-queue") # Queue name suffix + output_queue_name = optional(string, "output-queue") # Queue name suffix + + # Shared settings + sqs_managed_sse_enabled = optional(bool, true) + max_message_size = optional(number, 262144) # 256 KB + receive_wait_time_seconds = optional(number, 0) + + # Input queue settings + input_queue_visibility_timeout = optional(number, 60) + input_queue_message_retention_seconds = optional(number, 1800) # 30 minutes + input_queue_max_receive_count = optional(number, 5) + input_queue_create_dlq = optional(bool, false) + input_queue_dlq_message_retention_seconds = optional(number, 1800) + + # Output queue settings + output_queue_visibility_timeout = optional(number, 60) + output_queue_message_retention_seconds = optional(number, 1800) + output_queue_max_receive_count = optional(number, 5) + output_queue_create_dlq = optional(bool, false) + output_queue_dlq_message_retention_seconds = optional(number, 1800) + }) + default = { + input_queue_name = "input-queue" + output_queue_name = "output-queue" + sqs_managed_sse_enabled = true + max_message_size = 262144 + receive_wait_time_seconds = 0 + input_queue_visibility_timeout = 60 + input_queue_message_retention_seconds = 1800 + input_queue_max_receive_count = 5 + input_queue_create_dlq = false + input_queue_dlq_message_retention_seconds = 1800 + output_queue_visibility_timeout = 60 + output_queue_message_retention_seconds = 1800 + output_queue_max_receive_count = 5 + output_queue_create_dlq = false + output_queue_dlq_message_retention_seconds = 1800 + } } -variable "agent_runner_command" { - type = list(string) - description = "Command override for the Agent Runner ECS task. If not set, uses the Docker image's CMD. Example: [\"python\", \"app_agent_runner.py\"]" - default = null +# --- Agent Runner Configuration Object --- + +variable "agent_runner" { + description = "Agent runner configuration object" + type = object({ + cpu = optional(number, 512) + memory = optional(number, 1024) + desired_count = optional(number, 1) + package_path = optional(string, null) # Path to agent runner Docker source (builds separate image) + image_uri = optional(string, null) # Or provide pre-built image URI + command = optional(list(string), null) + environment_variables = optional(map(string), {}) + }) + default = { + cpu = 512 + memory = 1024 + desired_count = 1 + package_path = null + image_uri = null + command = null + environment_variables = {} + } } -# --- Agent Runner Auto Scaling --- - -variable "enable_agent_runner_autoscaling" { - type = bool - default = false +# --- Scaling Configuration Object --- + +variable "scaling_config" { + description = "Auto scaling configuration object for agent runner" + type = object({ + enabled = optional(bool, false) + min_count = optional(number, 0) + max_count = optional(number, 10) + backlog_target = optional(number, 10) + scale_in_cooldown = optional(number, 120) + scale_out_cooldown = optional(number, 30) + }) + default = { + enabled = false + min_count = 0 + max_count = 10 + backlog_target = 10 + scale_in_cooldown = 120 + scale_out_cooldown = 30 + } validation { condition = ( - !var.enable_agent_runner_autoscaling || + !var.scaling_config.enabled || var.enable_queue_mode ) - - error_message = "enable_agent_runner_autoscaling requires enable_queue_mode = true." + error_message = "scaling_config.enabled requires enable_queue_mode = true." } } - -variable "agent_runner_min_count" { - type = number - description = "Minimum number of agent runner tasks when autoscaling is enabled." - default = 0 -} - -variable "agent_runner_max_count" { - type = number - description = "Maximum number of agent runner tasks when autoscaling is enabled." - default = 10 -} - -variable "agent_runner_backlog_target" { - type = number - description = "Target BacklogPerTask value that triggers scaling. Lower values scale up more aggressively. Recommended: 5-20 messages per task." - default = 10 -} - -variable "agent_runner_scale_in_cooldown" { - type = number - description = "Seconds to wait after a scale-in before allowing another scale-in. Prevents flapping." - default = 120 -} - -variable "agent_runner_scale_out_cooldown" { - type = number - description = "Seconds to wait after a scale-out before allowing another scale-out. Allows new tasks to start processing before scaling again." - default = 30 -} diff --git a/examples/aws-containerized/openai-dynamodb-scalable/README.md b/examples/aws-containerized/openai-dynamodb-scalable/README.md index 0d4349815..5f6d6bac2 100644 --- a/examples/aws-containerized/openai-dynamodb-scalable/README.md +++ b/examples/aws-containerized/openai-dynamodb-scalable/README.md @@ -151,10 +151,30 @@ Response (when complete): ### Two-Image Pattern -| Image | Source | Role | -|-------|--------|------| -| REST Service | `app_rest_service.py` | Thread 1: FastAPI API server
Thread 2: Output queue poller → DynamoDB | -| Agent Runner | `app_agent_runner.py` | Input queue consumer → Agent executor → Output queue | +This example uses separate images for REST service and Agent Runner: + +| Image | Source | Configuration | Role | +|-------|--------|---------------|------| +| REST Service | `dist-rest-service/` | `rest_service` object | Thread 1: FastAPI API server
Thread 2: Output queue poller → DynamoDB | +| Agent Runner | `dist-agent-runner/` | `agent_runner` object | Input queue consumer → Agent executor → Output queue | + +**Key Configuration in `deploy/main.tf`:** +```hcl +# REST Service image from package_path +package_path = "../dist-rest-service" + +rest_service = { + command = ["python", "app_rest_service.py"] + # ... +} + +# Agent Runner uses dedicated image +agent_runner = { + image_uri = module.agent_runner_image.docker_image_uri + command = ["python", "app_agent_runner.py"] + # ... +} +``` ### Request Flow @@ -174,31 +194,44 @@ Response (when complete): ### CMD Override -The ECR module (`terraform-aws-modules/lambda/aws//modules/docker-build`) is Lambda-oriented and automatically injects: -```dockerfile -CMD ["python","-c","from app import runner; runner()"] -``` +The ECR module (`yaalalabs/ak-common/aws//modules/ecr`) is Lambda-oriented and automatically injects a Lambda-style CMD. This is overridden at ECS task definition level using the new config objects: -This is overridden at ECS task definition level using: -- `container_entrypoint_override = ["python", "app_rest_service.py"]` -- `agent_runner_command = ["python", "app_agent_runner.py"]` +```hcl +rest_service = { + command = ["python", "app_rest_service.py"] + # ... other settings +} -See [COMMAND_OVERRIDE.md](../../ak-deployment/ak-aws/containerized/COMMAND_OVERRIDE.md) for details. +agent_runner = { + command = ["python", "app_agent_runner.py"] + # ... other settings +} +``` + +This ensures the correct Python script runs for each container. ## Monitoring and Scaling The architecture automatically scales based on: -- **REST Service**: Scales with ALB target group metrics -- **Agent Runner**: Scales based on SQS queue depth using custom BacklogPerTask metric +- **REST Service**: Can be manually scaled by adjusting `rest_service.desired_count` +- **Agent Runner**: Scales automatically based on SQS queue depth using custom BacklogPerTask metric ### Agent Runner Auto Scaling -The Agent Runner can automatically scale based on queue backlog per task. +The Agent Runner automatically scales based on queue backlog per task. -**Enable autoscaling:** +**Enable autoscaling in `deploy/main.tf`:** ```hcl enable_queue_mode = true -enable_agent_runner_autoscaling = true + +scaling_config = { + enabled = true + min_count = 1 # Minimum tasks (0 to scale to zero) + max_count = 10 # Maximum tasks + backlog_target = 10 # Target messages per task + scale_in_cooldown = 120 # Wait 2min before scaling in + scale_out_cooldown = 30 # Wait 30s before scaling out +} ``` **How it works:** @@ -206,16 +239,6 @@ enable_agent_runner_autoscaling = true 2. Publishes custom CloudWatch metric: `Custom/ECS/BacklogPerTask` 3. Target tracking policy scales agent_runner up/down to maintain target BacklogPerTask -**Configuration:** -```hcl -enable_agent_runner_autoscaling = true # Must also enable_queue_mode = true -agent_runner_min_count = 1 # Minimum tasks (0 to scale to zero) -agent_runner_max_count = 10 # Maximum tasks -agent_runner_backlog_target = 10 # Target messages per task -agent_runner_scale_in_cooldown = 120 # Wait before scaling in -agent_runner_scale_out_cooldown = 30 # Wait before scaling out -``` - **Example scaling behavior:** - Queue has 100 messages, 2 running tasks → BacklogPerTask = 50 - Target is 10 → Scales up to 10 tasks diff --git a/examples/aws-containerized/openai-dynamodb-scalable/deploy/main.tf b/examples/aws-containerized/openai-dynamodb-scalable/deploy/main.tf index f7634d332..d61b1b14c 100644 --- a/examples/aws-containerized/openai-dynamodb-scalable/deploy/main.tf +++ b/examples/aws-containerized/openai-dynamodb-scalable/deploy/main.tf @@ -28,24 +28,12 @@ provider "docker" { } # --------------------------------------------------------------------------- -# Agent Runner image - built from dist-agent-runner/ +# Containerized Agents Deployment # --------------------------------------------------------------------------- -module "agent_runner_image" { - # Points to the local ECR module. - # Switch to registry once published: - source = "yaalalabs/ak-common/aws//modules/ecr" - version = "0.4.0" - - - env_alias = var.env_alias - module_name = "${var.module_name}-runner" - product_alias = var.product_alias - source_path = "../dist-agent-runner" -} - module "containerized_agents" { - source = "yaalalabs/ak-containerized/aws" - version = "0.4.0" + # When using from registry: + source = "yaalalabs/ak-containerized/aws" + version = "0.5.1" product_alias = var.product_alias env_alias = var.env_alias @@ -56,55 +44,96 @@ module "containerized_agents" { vpc_id = var.vpc_id private_subnet_ids = var.private_subnet_ids - # ---- REST Service image (Thread 1 + Thread 2) ---- - package_path = "../dist-rest-service" - container_type = "ecs" - ecs_container_port = 8000 - - # Override the command to use the correct entrypoint - # The ECR module adds a Lambda-style CMD, so we override it here - container_entrypoint_override = ["python", "app_rest_service.py"] + # ---- REST Service Docker Image ---- + # This image contains Thread 1 (request handler) and Thread 2 (response handler) + package_path = "../dist-rest-service" + + # ---- REST Service Configuration ---- + # In queue mode, this service handles HTTP requests and manages the queue interaction + rest_service = { + cpu = 256 + memory = 512 + desired_count = 1 + container_port = 8000 + health_check_endpoint = "/health" + # Override the Docker CMD to specify the correct entrypoint + command = ["python", "app_rest_service.py"] + environment_variables = { + OPENAI_API_KEY = var.openai_api_key + } + } - # ---- agent memory (session store) ---- + # ---- Agent Memory (Session Store) ---- + # DynamoDB table to store agent conversation history and state create_dynamodb_memory_table = true - # ---- queue mode ---- - enable_queue_mode = true - queue_mode_type = "async" - - # Enable autoscaling for Agent Runner (optional) - enable_agent_runner_autoscaling = true - - - # Agent Runner uses its own image (different CMD) - agent_runner_image_uri = module.agent_runner_image.docker_image_uri - - # Override the command to use the correct entrypoint - # The ECR module adds a Lambda-style CMD, so we override it here - agent_runner_command = ["python", "app_agent_runner.py"] - - # SQS visibility timeout should exceed agent processing time - sqs_input_visibility_timeout = 120 - sqs_output_visibility_timeout = 60 - - # Agent Runner Fargate sizing - agent_runner_cpu = 1024 - agent_runner_memory = 2048 - agent_runner_desired_count = 1 - - # Agent Runner Auto Scaling (optional) - agent_runner_min_count = 1 # Minimum tasks (can be 0 to scale to zero) - agent_runner_max_count = 10 # Maximum tasks - agent_runner_backlog_target = 10 # Target messages per task (scale up when exceeded) - agent_runner_scale_in_cooldown = 120 # Wait 2min before scaling in again - agent_runner_scale_out_cooldown = 30 # Wait 30s before scaling out again - - # Environment variables for both containers - environment_variables = { - OPENAI_API_KEY = var.openai_api_key + # ---- Queue Mode ---- + # Enable queue-based execution for scalable, async processing + enable_queue_mode = true + queue_mode_type = "async" # "async" | "sync" + + # ---- Queue Configuration ---- + # SQS queues for request/response handling + queue_config = { + # Optional: customize queue names + input_queue_name = "input-queue" # Default + output_queue_name = "output-queue" # Default + + # Input queue settings (requests from REST service to agent runner) + input_queue_visibility_timeout = 120 # Should be >= agent processing time + input_queue_message_retention_seconds = 1800 + input_queue_max_receive_count = 3 + input_queue_create_dlq = true + + # Output queue settings (responses from agent runner to REST service) + output_queue_visibility_timeout = 60 + output_queue_message_retention_seconds = 1800 + output_queue_max_receive_count = 3 + output_queue_create_dlq = true + + # Shared settings + sqs_managed_sse_enabled = true + max_message_size = 262144 # 256 KB + receive_wait_time_seconds = 0 # Long polling disabled + } + + # ---- Agent Runner Configuration ---- + # Separate ECS service that processes messages from the input queue + # This runs the actual agent logic + agent_runner = { + cpu = 1024 + memory = 2048 + desired_count = 1 + # Provide package_path to build a separate Docker image for agent runner + package_path = "../dist-agent-runner" + # Override the Docker CMD to specify the correct entrypoint + command = ["python", "app_agent_runner.py"] + environment_variables = { + OPENAI_API_KEY = var.openai_api_key + } + } + + # ---- Agent Runner Auto Scaling ---- + # Scale based on queue depth (BacklogPerTask metric) + scaling_config = { + enabled = true + + # Scaling limits + min_count = 1 # Minimum tasks (can be 0 to scale to zero) + max_count = 10 # Maximum tasks + + # Scaling behavior + backlog_target = 10 # Target messages per task (scale up when exceeded) + # Lower values = more aggressive scaling + # Example: backlog_target = 5 means scale up when >5 msgs per task + + # Cooldown periods (prevent flapping) + scale_in_cooldown = 120 # Wait 2min before scaling in again + scale_out_cooldown = 30 # Wait 30s before scaling out again } tags = { - Example = "openai-dynamodb-scalable" + Example = "openai-dynamodb-scalable" + Environment = var.env_alias } } From 1ba020733fef2eb413e4c0f2364d061a8eada3ba Mon Sep 17 00:00:00 2001 From: yl-nuwan Date: Sun, 21 Jun 2026 09:04:02 +0530 Subject: [PATCH 38/90] reduced test scope --- .github/integration-test-config.yaml | 116 +++++++++++++-------------- 1 file changed, 58 insertions(+), 58 deletions(-) diff --git a/.github/integration-test-config.yaml b/.github/integration-test-config.yaml index 6ad1c68e2..72e2b5017 100644 --- a/.github/integration-test-config.yaml +++ b/.github/integration-test-config.yaml @@ -37,74 +37,74 @@ weekly: # then each project is deployed and tested tests: #AWS Containerized - - type: aws-containerized - path: examples/aws-containerized/adk - deploy_dir: deploy + # - type: aws-containerized + # path: examples/aws-containerized/adk + # deploy_dir: deploy - type: aws-containerized path: examples/aws-containerized/openai-dynamodb deploy_dir: deploy - - type: aws-containerized - path: examples/aws-containerized/crewai - deploy_dir: deploy - - type: aws-containerized - path: examples/aws-containerized/mcp/multi - deploy_dir: deploy + # - type: aws-containerized + # path: examples/aws-containerized/crewai + # deploy_dir: deploy + # - type: aws-containerized + # path: examples/aws-containerized/mcp/multi + # deploy_dir: deploy - type: aws-containerized path: examples/aws-containerized/openai-dynamodb-scalable deploy_dir: deploy - # AWS Serverless - - type: aws-serverless - path: examples/aws-serverless/adk - deploy_dir: deploy - - type: aws-serverless - path: examples/aws-serverless/crewai - deploy_dir: deploy - - type: aws-serverless - path: examples/aws-serverless/langgraph - deploy_dir: deploy - - type: aws-serverless - path: examples/aws-serverless/openai-auth - deploy_dir: deploy - - type: aws-serverless - path: examples/aws-serverless/scalable-openai - deploy_dir: deploy + # # AWS Serverless + # - type: aws-serverless + # path: examples/aws-serverless/adk + # deploy_dir: deploy + # - type: aws-serverless + # path: examples/aws-serverless/crewai + # deploy_dir: deploy + # - type: aws-serverless + # path: examples/aws-serverless/langgraph + # deploy_dir: deploy + # - type: aws-serverless + # path: examples/aws-serverless/openai-auth + # deploy_dir: deploy + # - type: aws-serverless + # path: examples/aws-serverless/scalable-openai + # deploy_dir: deploy - # Memory options - - type: aws-serverless - path: examples/memory/redis - - type: aws-serverless - path: examples/memory/dynamodb - - type: azure-serverless - path: examples/memory/cosmos - deploy_dir: deploy + # # # Memory options + # - type: aws-serverless + # path: examples/memory/redis + # - type: aws-serverless + # path: examples/memory/dynamodb + # - type: azure-serverless + # path: examples/memory/cosmos + # deploy_dir: deploy - # Azure Serverless - - type: azure-serverless - path: examples/azure-serverless/openai - deploy_dir: deploy + # # Azure Serverless + # - type: azure-serverless + # path: examples/azure-serverless/openai + # deploy_dir: deploy - # Azure Containerized - - type: azure-containerized - path: examples/azure-containerized/openai-cosmos - deploy_dir: deploy + # # Azure Containerized + # - type: azure-containerized + # path: examples/azure-containerized/openai-cosmos + # deploy_dir: deploy - # GCP Serverless - - type: gcp-serverless - path: examples/gcp-serverless/openai-firestore - deploy_dir: deploy - - type: gcp-serverless - path: examples/gcp-serverless/openai - deploy_dir: deploy - - type: gcp-serverless - path: examples/gcp-serverless/openai-auth - deploy_dir: deploy + # # GCP Serverless + # - type: gcp-serverless + # path: examples/gcp-serverless/openai-firestore + # deploy_dir: deploy + # - type: gcp-serverless + # path: examples/gcp-serverless/openai + # deploy_dir: deploy + # - type: gcp-serverless + # path: examples/gcp-serverless/openai-auth + # deploy_dir: deploy - # GCP Containerized - - type: gcp-containerized - path: examples/gcp-containerized/openai - deploy_dir: deploy - - type: gcp-containerized - path: examples/gcp-containerized/openai-auth - deploy_dir: deploy + # # GCP Containerized + # - type: gcp-containerized + # path: examples/gcp-containerized/openai + # deploy_dir: deploy + # - type: gcp-containerized + # path: examples/gcp-containerized/openai-auth + # deploy_dir: deploy From b475aaf64429a2ec7db0f72977d73552d27f7d11 Mon Sep 17 00:00:00 2001 From: yl-nuwan Date: Sun, 21 Jun 2026 09:31:13 +0530 Subject: [PATCH 39/90] refactor: update containerized module configurations for consistency and clarity --- .../modules/rest-service/outputs.tf | 8 +-- examples/aws-containerized/adk/deploy/main.tf | 36 ++++++++----- .../crewai-auth/deploy/main.tf | 51 ++++++++++++------- .../aws-containerized/crewai/deploy/main.tf | 49 +++++++++++------- .../mcp/multi/deploy/main.tf | 42 ++++++++++----- .../openai-dynamodb/deploy/main.tf | 46 +++++++++++------ 6 files changed, 152 insertions(+), 80 deletions(-) diff --git a/ak-deployment/ak-aws/containerized/modules/rest-service/outputs.tf b/ak-deployment/ak-aws/containerized/modules/rest-service/outputs.tf index ab7f3eff2..24e673fc9 100644 --- a/ak-deployment/ak-aws/containerized/modules/rest-service/outputs.tf +++ b/ak-deployment/ak-aws/containerized/modules/rest-service/outputs.tf @@ -1,21 +1,21 @@ output "service_arn" { description = "ECS service ARN" - value = module.ecs_service.services[var.service_name].service_arn + value = module.ecs_service.id } output "service_name" { description = "ECS service name" - value = module.ecs_service.services[var.service_name].service_name + value = module.ecs_service.name } output "task_role_name" { description = "ECS task role name" - value = module.ecs_service.services[var.service_name].tasks_iam_role_name + value = module.ecs_service.tasks_iam_role_name } output "task_role_arn" { description = "ECS task role ARN" - value = module.ecs_service.services[var.service_name].tasks_iam_role_arn + value = module.ecs_service.tasks_iam_role_arn } output "alb_arn" { diff --git a/examples/aws-containerized/adk/deploy/main.tf b/examples/aws-containerized/adk/deploy/main.tf index 3ec071c26..266a6d368 100644 --- a/examples/aws-containerized/adk/deploy/main.tf +++ b/examples/aws-containerized/adk/deploy/main.tf @@ -1,22 +1,34 @@ -# Containered module configuration for deploying Google ADK Agent in ECS +# Containerized module configuration for deploying Google ADK Agent in ECS module "containered_agents" { - source = "yaalalabs/ak-containerized/aws" + source = "yaalalabs/ak-containerized/aws" version = "0.5.1" - # Basic ECS configuration + # Basic configuration product_alias = var.product_alias env_alias = var.env_alias module_name = var.module_name - package_path = "../dist" - container_type = "ecs" region = var.region - vpc_id = var.vpc_id - private_subnet_ids = var.private_subnet_ids - product_display_name = "AK Google ADK Containered Example" - ecs_container_port = 8000 + product_display_name = "AK Google ADK Containerized Example" - # Environment variables passed to container - environment_variables = { - OPENAI_API_KEY = var.openai_api_key + vpc_id = var.vpc_id + private_subnet_ids = var.private_subnet_ids + + # Docker image path + package_path = "../dist" + + # REST Service configuration + rest_service = { + cpu = 256 + memory = 512 + desired_count = 1 + container_port = 8000 + health_check_endpoint = "/health" + environment_variables = { + OPENAI_API_KEY = var.openai_api_key + } + } + + tags = { + Example = "adk" } } diff --git a/examples/aws-containerized/crewai-auth/deploy/main.tf b/examples/aws-containerized/crewai-auth/deploy/main.tf index f3631dd14..9bb3f2800 100644 --- a/examples/aws-containerized/crewai-auth/deploy/main.tf +++ b/examples/aws-containerized/crewai-auth/deploy/main.tf @@ -4,38 +4,53 @@ # /api/v1/app - Custom endpoint created via a direct route addition # /api/v1/app_info - Custom endpoint created by a custom handler module "containered_agents" { - source = "yaalalabs/ak-containerized/aws" + source = "yaalalabs/ak-containerized/aws" version = "0.5.1" - # Basic ECS configuration + # Basic configuration product_alias = var.product_alias env_alias = var.env_alias module_name = var.module_name - package_path = "../dist" - container_type = "ecs" region = var.region - vpc_id = "vpc-09033229d67314c1c" - private_subnet_ids = ["subnet-00e888e445f16d1b1", "subnet-0ab5240262cd77119"] product_display_name = "AK CrewAI Auth Containerized Example" - ecs_container_port = 8000 + + vpc_id = "vpc-09033229d67314c1c" + private_subnet_ids = ["subnet-00e888e445f16d1b1", "subnet-0ab5240262cd77119"] + + # Docker image path + package_path = "../dist" + + # REST Service configuration + rest_service = { + cpu = 256 + memory = 512 + desired_count = 1 + container_port = 8000 + health_check_endpoint = "/health" + environment_variables = { + OPENAI_API_KEY = var.openai_api_key + CREWAI_STORAGE_DIR = "/tmp/crewai" + EMBEDCHAIN_DB_PATH = "/tmp/crewai/embedchain.db" + HOME = "/tmp" + SOME_OTHER_KEY = "Some Other Value" + } + } + + # Custom API endpoints gateway_endpoints = [ { - path = "app", - method = "GET", + path = "app" + method = "GET" overwrite_path = "/custom/version" # The default `/custom` prefix should be added for routes added via direct custom route capability }, { - path = "app_info", - method = "GET", + path = "app_info" + method = "GET" overwrite_path = "/whoami" } ] - # Environment variables passed to container - environment_variables = { - OPENAI_API_KEY = var.openai_api_key, - CREWAI_STORAGE_DIR = "/tmp/crewai", - EMBEDCHAIN_DB_PATH = "/tmp/crewai/embedchain.db", - HOME = "/tmp", - SOME_OTHER_KEY = "Some Other Value" + + tags = { + Example = "crewai-auth" } } diff --git a/examples/aws-containerized/crewai/deploy/main.tf b/examples/aws-containerized/crewai/deploy/main.tf index 4d2a53d30..28c767992 100644 --- a/examples/aws-containerized/crewai/deploy/main.tf +++ b/examples/aws-containerized/crewai/deploy/main.tf @@ -4,37 +4,52 @@ # /api/v1/app - Custom endpoint created via a direct route addition # /api/v1/app_info - Custom endpoint created by a custom handler module "containered_agents" { - source = "yaalalabs/ak-containerized/aws" + source = "yaalalabs/ak-containerized/aws" version = "0.5.1" - # Basic ECS configuration + # Basic configuration product_alias = var.product_alias env_alias = var.env_alias module_name = var.module_name - package_path = "../dist" - container_type = "ecs" region = var.region - vpc_id = var.vpc_id - private_subnet_ids = var.private_subnet_ids product_display_name = "AK CrewAI Containerized Example" - ecs_container_port = 8000 + + vpc_id = var.vpc_id + private_subnet_ids = var.private_subnet_ids + + # Docker image path + package_path = "../dist" + + # REST Service configuration + rest_service = { + cpu = 256 + memory = 512 + desired_count = 1 + container_port = 8000 + health_check_endpoint = "/health" + environment_variables = { + OPENAI_API_KEY = var.openai_api_key + CREWAI_STORAGE_DIR = "/tmp/crewai" + EMBEDCHAIN_DB_PATH = "/tmp/crewai/embedchain.db" + HOME = "/tmp" + } + } + + # Custom API endpoints gateway_endpoints = [ { - path = "app", - method = "GET", + path = "app" + method = "GET" overwrite_path = "/custom/version" # The default `/custom` prefix should be added for routes added via direct custom route capability }, { - path = "app_info", - method = "GET", + path = "app_info" + method = "GET" overwrite_path = "/whoami" } ] - # Environment variables passed to container - environment_variables = { - OPENAI_API_KEY = var.openai_api_key, - CREWAI_STORAGE_DIR = "/tmp/crewai", - EMBEDCHAIN_DB_PATH = "/tmp/crewai/embedchain.db", - HOME = "/tmp" + + tags = { + Example = "crewai" } } diff --git a/examples/aws-containerized/mcp/multi/deploy/main.tf b/examples/aws-containerized/mcp/multi/deploy/main.tf index d1c267f7c..b597e14fa 100644 --- a/examples/aws-containerized/mcp/multi/deploy/main.tf +++ b/examples/aws-containerized/mcp/multi/deploy/main.tf @@ -1,24 +1,40 @@ -# Containered module configuration for deploying MCP in ECS +# Containerized module configuration for deploying MCP in ECS module "containered_agents" { - source = "yaalalabs/ak-containerized/aws" + source = "yaalalabs/ak-containerized/aws" version = "0.5.1" - # Basic ECS configuration + # Basic configuration product_alias = var.product_alias env_alias = var.env_alias module_name = var.module_name - package_path = "../dist" - container_type = "ecs" region = var.region - vpc_id = var.vpc_id - private_subnet_ids = var.private_subnet_ids - product_display_name = "MCP Containered Example" - ecs_container_port = 8000 + product_display_name = "MCP Containerized Example" + + vpc_id = var.vpc_id + private_subnet_ids = var.private_subnet_ids + + # Docker image path + package_path = "../dist" + + # REST Service configuration + rest_service = { + cpu = 256 + memory = 512 + desired_count = 1 + container_port = 8000 + health_check_endpoint = "/health" + environment_variables = { + OPENAI_API_KEY = var.openai_api_key + } + } + + # Session storage create_redis_cluster = true - enable_mcp_server = true # endpoint = ///mcp - # Environment variables passed to container - environment_variables = { - OPENAI_API_KEY = var.openai_api_key + # Enable MCP server endpoint: ///mcp + enable_mcp_server = true + + tags = { + Example = "mcp-multi" } } diff --git a/examples/aws-containerized/openai-dynamodb/deploy/main.tf b/examples/aws-containerized/openai-dynamodb/deploy/main.tf index fa52fbf1d..23aba99d5 100644 --- a/examples/aws-containerized/openai-dynamodb/deploy/main.tf +++ b/examples/aws-containerized/openai-dynamodb/deploy/main.tf @@ -1,23 +1,37 @@ -# Containered module configuration for deploying OpenAI Agent in ECS +# Containerized module configuration for deploying OpenAI Agent in ECS module "containered_agents" { - source = "yaalalabs/ak-containerized/aws" + source = "yaalalabs/ak-containerized/aws" version = "0.5.1" - # Basic ECS configuration - product_alias = var.product_alias - env_alias = var.env_alias - module_name = var.module_name - package_path = "../dist" - container_type = "ecs" - region = var.region - vpc_id = var.vpc_id + # Basic configuration + product_alias = var.product_alias + env_alias = var.env_alias + module_name = var.module_name + region = var.region + product_display_name = "OpenAI Agents" + + vpc_id = var.vpc_id + private_subnet_ids = var.private_subnet_ids + + # Docker image path + package_path = "../dist" + + # REST Service configuration + rest_service = { + cpu = 256 + memory = 512 + desired_count = 1 + container_port = 8000 + health_check_endpoint = "/health" + environment_variables = { + OPENAI_API_KEY = var.openai_api_key + } + } + + # Agent memory (session store) create_dynamodb_memory_table = true - private_subnet_ids = var.private_subnet_ids - product_display_name = "OpenAI Agents" - ecs_container_port = 8000 - # Environment variables passed to container - environment_variables = { - OPENAI_API_KEY = var.openai_api_key + tags = { + Example = "openai-dynamodb" } } From b8faff24de2b2189ad2328c3d9f75728b802fbff Mon Sep 17 00:00:00 2001 From: yl-nuwan Date: Sun, 21 Jun 2026 15:20:44 +0530 Subject: [PATCH 40/90] feat: add create_dynamodb_memory_table variable and update IAM policies for conditional creation --- .../ak-aws/containerized/modules/agent-runner/main.tf | 4 ++-- .../ak-aws/containerized/modules/agent-runner/variables.tf | 6 ++++++ .../ak-aws/containerized/modules/rest-service/main.tf | 4 ++-- .../ak-aws/containerized/modules/rest-service/variables.tf | 6 ++++++ ak-deployment/ak-aws/containerized/queue_mode.tf | 7 ++++--- ak-deployment/ak-aws/containerized/rest_service.tf | 7 ++++--- 6 files changed, 24 insertions(+), 10 deletions(-) diff --git a/ak-deployment/ak-aws/containerized/modules/agent-runner/main.tf b/ak-deployment/ak-aws/containerized/modules/agent-runner/main.tf index 7f848afee..709c57f1e 100644 --- a/ak-deployment/ak-aws/containerized/modules/agent-runner/main.tf +++ b/ak-deployment/ak-aws/containerized/modules/agent-runner/main.tf @@ -119,7 +119,7 @@ resource "aws_iam_role_policy_attachment" "agent_runner_sqs_attachment" { } resource "aws_iam_policy" "agent_runner_dynamodb_memory_policy" { - count = var.dynamodb_memory_table_arn != null ? 1 : 0 + count = var.create_dynamodb_memory_table ? 1 : 0 name = "${var.prefix}-agent-runner-dynamodb-memory" policy = jsonencode({ @@ -146,7 +146,7 @@ resource "aws_iam_policy" "agent_runner_dynamodb_memory_policy" { } resource "aws_iam_role_policy_attachment" "agent_runner_dynamodb_memory_attachment" { - count = var.dynamodb_memory_table_arn != null ? 1 : 0 + count = var.create_dynamodb_memory_table ? 1 : 0 role = aws_iam_role.agent_runner_task_role.name policy_arn = aws_iam_policy.agent_runner_dynamodb_memory_policy[0].arn } diff --git a/ak-deployment/ak-aws/containerized/modules/agent-runner/variables.tf b/ak-deployment/ak-aws/containerized/modules/agent-runner/variables.tf index 7d570d640..4222e9f07 100644 --- a/ak-deployment/ak-aws/containerized/modules/agent-runner/variables.tf +++ b/ak-deployment/ak-aws/containerized/modules/agent-runner/variables.tf @@ -69,6 +69,12 @@ variable "redis_url" { default = null } +variable "create_dynamodb_memory_table" { + type = bool + description = "Whether DynamoDB memory table is created" + default = false +} + variable "dynamodb_memory_table_arn" { type = string description = "DynamoDB memory table ARN" diff --git a/ak-deployment/ak-aws/containerized/modules/rest-service/main.tf b/ak-deployment/ak-aws/containerized/modules/rest-service/main.tf index ad5b529c5..fbd517fdd 100644 --- a/ak-deployment/ak-aws/containerized/modules/rest-service/main.tf +++ b/ak-deployment/ak-aws/containerized/modules/rest-service/main.tf @@ -27,7 +27,7 @@ resource "aws_service_discovery_http_namespace" "this" { # ---------- IAM Policies ---------- resource "aws_iam_policy" "dynamodb_policy" { - count = var.dynamodb_memory_table_arn != null ? 1 : 0 + count = var.create_dynamodb_memory_table ? 1 : 0 name = "${var.product_alias}-${var.env_alias}-${var.module_name}-dynamodb-policy" description = "Policy for DynamoDB access" @@ -180,7 +180,7 @@ module "ecs_service" { # Attach DynamoDB access to the task role if a memory table exists create_tasks_iam_role = true - tasks_iam_role_policies = var.dynamodb_memory_table_arn != null ? { + tasks_iam_role_policies = var.create_dynamodb_memory_table ? { DynamoDB = aws_iam_policy.dynamodb_policy[0].arn } : {} diff --git a/ak-deployment/ak-aws/containerized/modules/rest-service/variables.tf b/ak-deployment/ak-aws/containerized/modules/rest-service/variables.tf index 5f74845d8..697da5ad9 100644 --- a/ak-deployment/ak-aws/containerized/modules/rest-service/variables.tf +++ b/ak-deployment/ak-aws/containerized/modules/rest-service/variables.tf @@ -59,6 +59,12 @@ variable "redis_url" { default = null } +variable "create_dynamodb_memory_table" { + type = bool + description = "Whether DynamoDB memory table is created" + default = false +} + variable "dynamodb_memory_table_arn" { type = string description = "DynamoDB memory table ARN" diff --git a/ak-deployment/ak-aws/containerized/queue_mode.tf b/ak-deployment/ak-aws/containerized/queue_mode.tf index d506e3aa1..43d97af89 100644 --- a/ak-deployment/ak-aws/containerized/queue_mode.tf +++ b/ak-deployment/ak-aws/containerized/queue_mode.tf @@ -39,9 +39,10 @@ module "agent_runner" { output_queue_url = module.queues[0].output_queue_url output_queue_arn = module.queues[0].output_queue_arn - redis_url = local.redis_url - dynamodb_memory_table_arn = local.dynamodb_memory_table_arn - dynamodb_memory_table_name = local.dynamodb_memory_table_name + redis_url = local.redis_url + create_dynamodb_memory_table = var.create_dynamodb_memory_table + dynamodb_memory_table_arn = local.dynamodb_memory_table_arn + dynamodb_memory_table_name = local.dynamodb_memory_table_name agent_runner = { cpu = var.agent_runner.cpu diff --git a/ak-deployment/ak-aws/containerized/rest_service.tf b/ak-deployment/ak-aws/containerized/rest_service.tf index 75d19adfa..297df07f3 100644 --- a/ak-deployment/ak-aws/containerized/rest_service.tf +++ b/ak-deployment/ak-aws/containerized/rest_service.tf @@ -19,9 +19,10 @@ module "rest_service" { service_name = local.service_name container_name = local.container_name - redis_url = local.redis_url - dynamodb_memory_table_arn = local.dynamodb_memory_table_arn - dynamodb_memory_table_name = local.dynamodb_memory_table_name + redis_url = local.redis_url + create_dynamodb_memory_table = var.create_dynamodb_memory_table + dynamodb_memory_table_arn = local.dynamodb_memory_table_arn + dynamodb_memory_table_name = local.dynamodb_memory_table_name rest_service = { cpu = var.rest_service.cpu From c7a11d3d38aa1fe5b6e6de9f1666f6c42480e7cb Mon Sep 17 00:00:00 2001 From: yl-nuwan Date: Sun, 21 Jun 2026 17:53:16 +0530 Subject: [PATCH 41/90] refactor: update package_path references in containerized module configurations --- ak-deployment/ak-aws/containerized/state.tf | 2 +- .../ak-aws/containerized/variables.tf | 18 ++---------------- examples/aws-containerized/adk/deploy/main.tf | 4 +--- .../crewai-auth/deploy/main.tf | 4 +--- .../aws-containerized/crewai/deploy/main.tf | 4 +--- .../aws-containerized/mcp/multi/deploy/main.tf | 4 +--- .../openai-dynamodb-scalable/deploy/main.tf | 6 ++---- .../openai-dynamodb/deploy/main.tf | 4 +--- 8 files changed, 10 insertions(+), 36 deletions(-) diff --git a/ak-deployment/ak-aws/containerized/state.tf b/ak-deployment/ak-aws/containerized/state.tf index 7b612b8da..bcfa1cb71 100644 --- a/ak-deployment/ak-aws/containerized/state.tf +++ b/ak-deployment/ak-aws/containerized/state.tf @@ -82,7 +82,7 @@ module "docker_image" { env_alias = var.env_alias module_name = var.module_name product_alias = var.product_alias - source_path = var.package_path + source_path = var.rest_service.package_path } # Agent Runner Docker Image (optional - only if package_path is provided) diff --git a/ak-deployment/ak-aws/containerized/variables.tf b/ak-deployment/ak-aws/containerized/variables.tf index 9f77f869c..fed0e5597 100644 --- a/ak-deployment/ak-aws/containerized/variables.tf +++ b/ak-deployment/ak-aws/containerized/variables.tf @@ -24,11 +24,6 @@ variable "module_name" { description = "Module name" } -variable "package_path" { - type = string - description = "Docker image source path (app root)" -} - variable "environment_variables" { description = "Environment variables" type = any @@ -134,20 +129,11 @@ variable "rest_service" { desired_count = optional(number, 1) container_port = optional(number, 8000) health_check_endpoint = optional(string, "/health") - image_uri = optional(string, null) # Defaults to package_path Docker image + package_path = string # Docker image source path (required) + image_uri = optional(string, null) # Or provide pre-built image URI command = optional(list(string), null) environment_variables = optional(map(string), {}) }) - default = { - cpu = 256 - memory = 512 - desired_count = 1 - container_port = 8000 - health_check_endpoint = "/health" - image_uri = null - command = null - environment_variables = {} - } } variable "container_type" { diff --git a/examples/aws-containerized/adk/deploy/main.tf b/examples/aws-containerized/adk/deploy/main.tf index 266a6d368..ff03e7ca4 100644 --- a/examples/aws-containerized/adk/deploy/main.tf +++ b/examples/aws-containerized/adk/deploy/main.tf @@ -13,11 +13,9 @@ module "containered_agents" { vpc_id = var.vpc_id private_subnet_ids = var.private_subnet_ids - # Docker image path - package_path = "../dist" - # REST Service configuration rest_service = { + package_path = "../dist" cpu = 256 memory = 512 desired_count = 1 diff --git a/examples/aws-containerized/crewai-auth/deploy/main.tf b/examples/aws-containerized/crewai-auth/deploy/main.tf index 9bb3f2800..e1a5196e7 100644 --- a/examples/aws-containerized/crewai-auth/deploy/main.tf +++ b/examples/aws-containerized/crewai-auth/deploy/main.tf @@ -17,11 +17,9 @@ module "containered_agents" { vpc_id = "vpc-09033229d67314c1c" private_subnet_ids = ["subnet-00e888e445f16d1b1", "subnet-0ab5240262cd77119"] - # Docker image path - package_path = "../dist" - # REST Service configuration rest_service = { + package_path = "../dist" cpu = 256 memory = 512 desired_count = 1 diff --git a/examples/aws-containerized/crewai/deploy/main.tf b/examples/aws-containerized/crewai/deploy/main.tf index 28c767992..79e462824 100644 --- a/examples/aws-containerized/crewai/deploy/main.tf +++ b/examples/aws-containerized/crewai/deploy/main.tf @@ -17,11 +17,9 @@ module "containered_agents" { vpc_id = var.vpc_id private_subnet_ids = var.private_subnet_ids - # Docker image path - package_path = "../dist" - # REST Service configuration rest_service = { + package_path = "../dist" cpu = 256 memory = 512 desired_count = 1 diff --git a/examples/aws-containerized/mcp/multi/deploy/main.tf b/examples/aws-containerized/mcp/multi/deploy/main.tf index b597e14fa..985413f43 100644 --- a/examples/aws-containerized/mcp/multi/deploy/main.tf +++ b/examples/aws-containerized/mcp/multi/deploy/main.tf @@ -13,11 +13,9 @@ module "containered_agents" { vpc_id = var.vpc_id private_subnet_ids = var.private_subnet_ids - # Docker image path - package_path = "../dist" - # REST Service configuration rest_service = { + package_path = "../dist" cpu = 256 memory = 512 desired_count = 1 diff --git a/examples/aws-containerized/openai-dynamodb-scalable/deploy/main.tf b/examples/aws-containerized/openai-dynamodb-scalable/deploy/main.tf index d61b1b14c..2a027073a 100644 --- a/examples/aws-containerized/openai-dynamodb-scalable/deploy/main.tf +++ b/examples/aws-containerized/openai-dynamodb-scalable/deploy/main.tf @@ -44,13 +44,11 @@ module "containerized_agents" { vpc_id = var.vpc_id private_subnet_ids = var.private_subnet_ids - # ---- REST Service Docker Image ---- - # This image contains Thread 1 (request handler) and Thread 2 (response handler) - package_path = "../dist-rest-service" # ---- REST Service Configuration ---- # In queue mode, this service handles HTTP requests and manages the queue interaction rest_service = { + package_path = "../dist-rest-service" cpu = 256 memory = 512 desired_count = 1 @@ -70,7 +68,7 @@ module "containerized_agents" { # ---- Queue Mode ---- # Enable queue-based execution for scalable, async processing enable_queue_mode = true - queue_mode_type = "async" # "async" | "sync" + queue_mode_type = "sync" # "async" | "sync" # ---- Queue Configuration ---- # SQS queues for request/response handling diff --git a/examples/aws-containerized/openai-dynamodb/deploy/main.tf b/examples/aws-containerized/openai-dynamodb/deploy/main.tf index 23aba99d5..3d15fd404 100644 --- a/examples/aws-containerized/openai-dynamodb/deploy/main.tf +++ b/examples/aws-containerized/openai-dynamodb/deploy/main.tf @@ -13,11 +13,9 @@ module "containered_agents" { vpc_id = var.vpc_id private_subnet_ids = var.private_subnet_ids - # Docker image path - package_path = "../dist" - # REST Service configuration rest_service = { + package_path = "../dist" cpu = 256 memory = 512 desired_count = 1 From 71b42435003cbd40b8f854b1514fb9e4b161b4ca Mon Sep 17 00:00:00 2001 From: yl-nuwan Date: Mon, 22 Jun 2026 01:10:10 +0530 Subject: [PATCH 42/90] switch to sync mode for testing for the application --- examples/aws-containerized/openai-dynamodb-scalable/config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/aws-containerized/openai-dynamodb-scalable/config.yaml b/examples/aws-containerized/openai-dynamodb-scalable/config.yaml index 0cea3e1ff..35a959364 100644 --- a/examples/aws-containerized/openai-dynamodb-scalable/config.yaml +++ b/examples/aws-containerized/openai-dynamodb-scalable/config.yaml @@ -8,7 +8,7 @@ session: table_name: "ak-oai-scl-ecs-dev-scalable-session_store" execution: - mode: rest_async + mode: rest_sync queues: input: url: "" # injected by Terraform via AK_EXECUTION__QUEUES__INPUT__URL From b8cfb326a5220f6cab04c7698ee795314714e7a2 Mon Sep 17 00:00:00 2001 From: yl-nuwan Date: Mon, 22 Jun 2026 01:11:27 +0530 Subject: [PATCH 43/90] reduced test scope --- .github/integration-test-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/integration-test-config.yaml b/.github/integration-test-config.yaml index 72e2b5017..1eeb65b7c 100644 --- a/.github/integration-test-config.yaml +++ b/.github/integration-test-config.yaml @@ -40,9 +40,9 @@ weekly: # - type: aws-containerized # path: examples/aws-containerized/adk # deploy_dir: deploy - - type: aws-containerized - path: examples/aws-containerized/openai-dynamodb - deploy_dir: deploy + # - type: aws-containerized + # path: examples/aws-containerized/openai-dynamodb + # deploy_dir: deploy # - type: aws-containerized # path: examples/aws-containerized/crewai # deploy_dir: deploy From fbc2beeebe02ae4e97a403f416a9a067ef9d0a71 Mon Sep 17 00:00:00 2001 From: Lakindu Induwara Kumara Date: Fri, 26 Jun 2026 16:37:05 +0530 Subject: [PATCH 44/90] containerized code refactor --- .../agentkernel/deployment/aws/__init__.py | 3 +- .../deployment/aws/containerized/__init__.py | 3 +- .../aws/containerized/akagentrunner.py | 48 ++---- .../{akrestservice.py => akoutputconsumer.py} | 102 ++++-------- .../aws/containerized/core/__init__.py | 2 + .../aws/containerized/core/sqs_consumer.py | 149 ++++++++++++++++++ .../aws/containerized/core/thread_runner.py | 47 ++++++ .../aws/containerized/ecs_io_handler.py | 41 +++++ .../aws/containerized/ecs_queue_handler.py | 2 +- .../aws/containerized/sqs_poller.py | 113 ------------- .../app_rest_service.py | 10 +- 11 files changed, 289 insertions(+), 231 deletions(-) rename ak-py/src/agentkernel/deployment/aws/containerized/{akrestservice.py => akoutputconsumer.py} (63%) create mode 100644 ak-py/src/agentkernel/deployment/aws/containerized/core/__init__.py create mode 100644 ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py create mode 100644 ak-py/src/agentkernel/deployment/aws/containerized/core/thread_runner.py create mode 100644 ak-py/src/agentkernel/deployment/aws/containerized/ecs_io_handler.py delete mode 100644 ak-py/src/agentkernel/deployment/aws/containerized/sqs_poller.py diff --git a/ak-py/src/agentkernel/deployment/aws/__init__.py b/ak-py/src/agentkernel/deployment/aws/__init__.py index f8b254d00..4b64961e9 100644 --- a/ak-py/src/agentkernel/deployment/aws/__init__.py +++ b/ak-py/src/agentkernel/deployment/aws/__init__.py @@ -11,7 +11,8 @@ except importlib.metadata.PackageNotFoundError: __version__ = "0.1.0" -from .containerized import ECSAgentRunner, ECSRESTService +from .containerized import ECSAgentRunner, ECSIOHandler, ECSOutputConsumer from .core.sqs_handler import SQSHandler from .serverless import APIGatewayAuthorizer, Lambda, ResponseHandler, ServerlessAgentRunner, WebsocketConnectionHandler +from .containerized.core import ECSSQSConsumer from .serverless.core import LambdaSQSConsumer diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/__init__.py b/ak-py/src/agentkernel/deployment/aws/containerized/__init__.py index a8350dc8f..d745291c0 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/__init__.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/__init__.py @@ -1,3 +1,4 @@ from .akagentrunner import ECSAgentRunner -from .akrestservice import ECSRESTService +from .akoutputconsumer import ECSOutputConsumer +from .ecs_io_handler import ECSIOHandler from .ecs_queue_handler import ECSQueueRequestHandler diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/akagentrunner.py b/ak-py/src/agentkernel/deployment/aws/containerized/akagentrunner.py index 52b176c61..ac7b50ac0 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/akagentrunner.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/akagentrunner.py @@ -7,17 +7,18 @@ from ....core.config import AKConfig from ....core.model import BaseRunRequest from ..core.sqs_handler import SQSHandler -from .sqs_poller import SQSPoller +from .core import ECSSQSConsumer -class ECSAgentRunner: +class ECSAgentRunner(ECSSQSConsumer): """ ECS Agent Runner — polls the Input Queue, runs the agent, and puts the result on the Output Queue. - This is the ECS equivalent of ``ServerlessAgentRunner``. Instead of - being triggered by a Lambda Event Source Mapping, it runs a blocking - ``SQSPoller`` loop (meant to be the container's main process). + The ECS equivalent of ServerlessAgentRunner. Instead of being triggered + by a Lambda Event Source Mapping, it inherits run() from ECSSQSConsumer, + which drives a blocking long-poll loop — meant to be the container's + main process. Usage:: @@ -28,28 +29,11 @@ class ECSAgentRunner: _log = logging.getLogger("ak.ecs.agentrunner") _chat_service: ChatService | None = None _config = AKConfig.get() + max_receive_count = _config.execution.queues.input.max_receive_count @classmethod - def run(cls) -> None: - """ - Start the blocking poll loop. Call this as the container entry-point. - """ - config = cls._config - input_queue_url = config.execution.queues.input.url - max_receive_count = config.execution.queues.input.max_receive_count - - if not input_queue_url: - raise ValueError("AK_EXECUTION__QUEUES__INPUT__URL is required for ECSAgentRunner") - - cls._log.info(f"ECSAgentRunner starting — input queue: {input_queue_url}") - - poller = SQSPoller( - queue_url=input_queue_url, - process_fn=cls.process_message, - max_receive_count=max_receive_count, - on_permanent_failure_fn=cls.on_permanent_failure, - ) - poller.run() # blocks forever + def _get_queue_url(cls) -> str: + return cls._config.execution.queues.input.url @classmethod def _get_chat_service(cls) -> ChatService: @@ -96,12 +80,7 @@ def _send_to_output_queue(cls, message_body: dict, record_attributes: dict) -> N @classmethod def process_message(cls, record: dict) -> None: - """ - Process a single SQS message: run the agent and put the response - on the Output Queue. - - :param record: boto3 SQS message dict - """ + """Implements ECSSQSConsumer.process_message.""" message_id = record.get("MessageId") cls._log.info(f"[AGENT START] Processing message {message_id}") @@ -126,12 +105,7 @@ def process_message(cls, record: dict) -> None: @classmethod def on_permanent_failure(cls, record: dict) -> None: - """ - Handle a message that exceeded max retries — send an error response - to the Output Queue so the REST Service can return it to the caller. - - :param record: boto3 SQS message dict - """ + """Implements ECSSQSConsumer.on_permanent_failure. Catches own exceptions.""" cls._log.error(f"Permanent failure for message {record.get('MessageId')}") try: record_attributes = cls._get_record_attributes(raw_queue_message=record) diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/akrestservice.py b/ak-py/src/agentkernel/deployment/aws/containerized/akoutputconsumer.py similarity index 63% rename from ak-py/src/agentkernel/deployment/aws/containerized/akrestservice.py rename to ak-py/src/agentkernel/deployment/aws/containerized/akoutputconsumer.py index 37e7b7c4d..37c2f0457 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/akrestservice.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/akoutputconsumer.py @@ -2,89 +2,35 @@ import json import logging -import threading from typing import Any, Dict -from ....api.http import RESTAPI from ....core.config import AKConfig from ....core.model import ExecutionMode from ..core.response_store import ResponseDBHandler from ..core.sqs_handler import SQSHandler from ..core.websocket_service import WebSocketHandler -from .sqs_poller import SQSPoller +from .core import ECSSQSConsumer -class ECSRESTService: +class ECSOutputConsumer(ECSSQSConsumer): """ - ECS REST Service — handles all queue-based execution modes: + ECS Output Consumer — polls the Output Queue and writes results to the + DynamoDB Response Store or broadcasts via WebSocket. - - **REST Sync** (``execution.mode = rest_sync``) - Thread 2 writes output messages to the DynamoDB Response Store. - Thread 1 (FastAPI) waits on the same store and returns the response - on the original HTTP connection. - - - **REST Async** (``execution.mode = rest_async``) - Same as REST Sync for the output-queue side. Thread 1 exposes a - separate ``GET`` endpoint that the client polls. - - - **WebSocket / Async** (``execution.mode = async``) - Thread 2 pushes output messages directly to the client over the - still-open WebSocket connection via API Gateway Management API - (PostToConnection). No DynamoDB response store is used. - - Runs two threads: - - - **Thread 1** — FastAPI/uvicorn (via ``RESTAPI.run``). - - **Thread 2** — ``SQSPoller`` daemon on the Output Queue. - - Can be used standalone — just set ``execution.mode`` in ``config.yaml`` - (or via env vars) and call ``ECSRESTService.run()``. - - Usage:: - - from agentkernel.deployment.aws.containerized import ECSRESTService - - if __name__ == "__main__": - ECSRESTService.run() + Extends ECSSQSConsumer so it inherits the blocking SQS poll loop via + run(). Started as Thread 2 by ECSIOHandler. """ - _log = logging.getLogger("ak.ecs.restservice") + _log = logging.getLogger("ak.ecs.outputconsumer") _config = AKConfig.get() + max_receive_count = _config.execution.queues.output.max_receive_count - # lazily initialised, shared across Thread 2 calls _response_store = None _websocket_handler = None @classmethod - def run(cls) -> None: - """ - Start the output-queue poller (Thread 2) as a daemon, then start - the REST API (Thread 1) in the main thread. Blocks until the - REST API exits. - """ - output_queue_url = cls._config.execution.queues.output.url - max_receive_count = cls._config.execution.queues.output.max_receive_count - - if not output_queue_url: - raise ValueError("AK_EXECUTION__QUEUES__OUTPUT__URL is required for ECSRESTService") - - mode = cls._config.execution.mode - cls._log.info(f"ECSRESTService starting — mode={mode} output_queue={output_queue_url}") - - poller = SQSPoller( - queue_url=output_queue_url, - process_fn=cls.process_output_message, - max_receive_count=max_receive_count, - on_permanent_failure_fn=cls.on_permanent_failure, - ) - t2 = threading.Thread(target=poller.run, name="output-queue-poller", daemon=True) - t2.start() - cls._log.info("ECSRESTService: Thread 2 (output-queue poller) started") - - from .ecs_queue_handler import ECSQueueRequestHandler - - cls._log.info("ECSRESTService: starting REST API with queue-aware handler (Thread 1)") - RESTAPI.run(handlers=[ECSQueueRequestHandler()]) + def _get_queue_url(cls) -> str: + return cls._config.execution.queues.output.url @classmethod def _get_response_store(cls): @@ -97,7 +43,10 @@ def _get_websocket_handler(cls) -> WebSocketHandler: if cls._websocket_handler is None: ws_config = cls._config.websocket_api if not ws_config.connection_table or not ws_config.connection_table.table_name: - raise ValueError("websocket_api.connection_table.table_name is required " "for ECSRESTService in WebSocket mode") + raise ValueError( + "websocket_api.connection_table.table_name is required " + "for ECSOutputConsumer in WebSocket mode" + ) cls._websocket_handler = WebSocketHandler( conn_table_name=ws_config.connection_table.table_name, ttl=ws_config.connection_table.ttl, @@ -105,7 +54,7 @@ def _get_websocket_handler(cls) -> WebSocketHandler: return cls._websocket_handler @classmethod - def process_output_message(cls, record: Dict[str, Any]) -> None: + def process_message(cls, record: Dict[str, Any]) -> None: """ Process one message from the Output Queue. @@ -128,7 +77,10 @@ def process_output_message(cls, record: Dict[str, Any]) -> None: f"session_id={message['session_id']}, body_keys={list(message.get('body', {}).keys()) if isinstance(message.get('body'), dict) else 'N/A'}" ) cls._get_response_store().add_message(message) - cls._log.info(f"[OUTPUT DONE] Stored response — session_id={message['session_id']} " f"request_id={message['request_id']}") + cls._log.info( + f"[OUTPUT DONE] Stored response — session_id={message['session_id']} " + f"request_id={message['request_id']}" + ) @classmethod def on_permanent_failure(cls, record: Dict[str, Any]) -> None: @@ -143,7 +95,10 @@ def on_permanent_failure(cls, record: Dict[str, Any]) -> None: :param record: boto3 SQS ``receive_message`` record """ max_retries = cls._config.execution.queues.output.max_receive_count - cls._log.error(f"Permanent failure for output message {record.get('MessageId')} " f"after {max_retries} retries") + cls._log.error( + f"Permanent failure for output message {record.get('MessageId')} " + f"after {max_retries} retries" + ) try: message_attributes = SQSHandler.get_message_custom_attributes(record) @@ -163,12 +118,19 @@ def on_permanent_failure(cls, record: Dict[str, Any]) -> None: user_id=user_id, ) else: - cls._log.warning("Cannot broadcast permanent-failure error: " "endpoint_url or user_id missing") + cls._log.warning( + "Cannot broadcast permanent-failure error: " + "endpoint_url or user_id missing" + ) else: error_body = json.dumps(error_payload) message = cls._construct_message_for_store(record, body=error_body) cls._get_response_store().add_message(message) - cls._log.info(f"Stored permanent-failure error — " f"session_id={message['session_id']} " f"request_id={message['request_id']}") + cls._log.info( + f"Stored permanent-failure error — " + f"session_id={message['session_id']} " + f"request_id={message['request_id']}" + ) except Exception: cls._log.exception("Failed to handle permanent-failure output message") diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/core/__init__.py b/ak-py/src/agentkernel/deployment/aws/containerized/core/__init__.py new file mode 100644 index 000000000..50bbb8e00 --- /dev/null +++ b/ak-py/src/agentkernel/deployment/aws/containerized/core/__init__.py @@ -0,0 +1,2 @@ +from .sqs_consumer import ECSSQSConsumer +from .thread_runner import ThreadRunner diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py b/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py new file mode 100644 index 000000000..1023ada6e --- /dev/null +++ b/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py @@ -0,0 +1,149 @@ +import logging +import time +from abc import ABC, abstractmethod +from typing import Any, Dict + +import boto3 + + +class ECSSQSConsumer(ABC): + """ + Base class for ECS services that consume SQS queues via manual long-polling. + + Mirrors LambdaSQSConsumer for ECS deployments: extend this class, implement + process_message and on_permanent_failure, then call run() as the container + entry-point. + + Unlike Lambda (push-triggered), ECS actively polls SQS. The poll() template + method represents one SQS receive cycle — analogous to one Lambda invocation + calling handle(). Override poll() to customise MaxNumberOfMessages, + WaitTimeSeconds, or MessageAttributeNames; override process_message and + on_permanent_failure for business logic. + + Contract for on_permanent_failure implementations: must be internally + defensive (catch their own exceptions). If on_permanent_failure raises, the + message is NOT deleted and will re-enter the permanent-failure path on the + next visibility-timeout cycle. + """ + + max_receive_count: int = 3 # overidden by classes that inherit this + _log = logging.getLogger("ak.ecs.sqsconsumer") + + @classmethod + @abstractmethod + def _get_queue_url(cls) -> str: + """ + Return the SQS queue URL to poll. + + Required because ECS must fetch messages actively — there is no ESM to + configure the queue externally as in Lambda. + """ + raise NotImplementedError + + @classmethod + @abstractmethod + def process_message(cls, record: Dict[str, Any]) -> None: + """ + Process one SQS message. + + :param record: Raw boto3 receive_message record (PascalCase keys: + Body, MessageId, Attributes, MessageAttributes). + """ + raise NotImplementedError + + @classmethod + @abstractmethod + def on_permanent_failure(cls, record: Dict[str, Any]) -> None: + """ + Called when a message's ApproximateReceiveCount exceeds max_receive_count. + The message is deleted from the queue immediately after this returns. + + Implementations MUST catch their own exceptions. If this method raises, + the message is not deleted and will loop back to this path indefinitely. + + :param record: Raw boto3 receive_message record. + """ + pass + + @classmethod + def run(cls) -> None: + """ + Block forever, polling the queue. Call as the container entry-point. + + Analogous to the Lambda runtime invoking handle(event, context) per + batch: the runtime drives Lambda; run() drives the ECS consumer. + """ + queue_url = cls._get_queue_url() + if not queue_url: + raise ValueError(f"{cls.__name__}: queue URL is required") + + cls._log.info(f"{cls.__name__} starting — queue: {queue_url}") + client = boto3.client("sqs") + while True: + try: + cls.poll(client) + except Exception: + cls._log.exception("Unexpected error in poll loop — retrying in 5 s") + time.sleep(5) + + @classmethod + def poll(cls, client) -> None: + """ + Receive one batch of SQS messages and process each. + + Analogous to one Lambda invocation: handle() processes event.Records; + poll() processes one receive_message response. + + Override to customise MaxNumberOfMessages, WaitTimeSeconds, or + MessageAttributeNames. Overriding implementations must accept the + boto3 SQS client as the second argument. + """ + resp = client.receive_message( + QueueUrl=cls._get_queue_url(), + MaxNumberOfMessages=10, + WaitTimeSeconds=20, + AttributeNames=["All"], + MessageAttributeNames=["All"], + ) + for msg in resp.get("Messages", []): + cls._handle_message(client, msg) + + @classmethod + def _handle_message(cls, client, msg: dict) -> None: + """ + Retry-count check → dispatch to process_message or on_permanent_failure + → delete on success. + + On exception: logs and leaves the message in the queue so the + visibility timeout returns it for a retry — mirroring Lambda's + batchItemFailures behaviour. + """ + message_id = msg.get("MessageId", "") + receive_count = int(msg.get("Attributes", {}).get("ApproximateReceiveCount", "1")) + try: + if receive_count > cls.max_receive_count: + cls._log.warning( + f"Message {message_id} exceeded max_receive_count " + f"({receive_count} > {cls.max_receive_count})" + ) + cls.on_permanent_failure(msg) + cls.delete_message(client, msg) + return + + cls.process_message(msg) + cls.delete_message(client, msg) + + except Exception: + cls._log.exception( + f"Failed to process message {message_id} — " + "leaving in queue for visibility-timeout retry" + ) + # Do NOT delete — visibility timeout returns it for retry, + # mirroring how Lambda batchItemFailures re-enqueues a record. + + @classmethod + def delete_message(cls, client, msg: dict) -> None: + client.delete_message( + QueueUrl=cls._get_queue_url(), + ReceiptHandle=msg["ReceiptHandle"], + ) diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/core/thread_runner.py b/ak-py/src/agentkernel/deployment/aws/containerized/core/thread_runner.py new file mode 100644 index 000000000..1eecfc8be --- /dev/null +++ b/ak-py/src/agentkernel/deployment/aws/containerized/core/thread_runner.py @@ -0,0 +1,47 @@ +import logging +import os +from concurrent.futures import ThreadPoolExecutor, wait, FIRST_COMPLETED +from typing import Callable + +_log = logging.getLogger("ak.thread_runner") + + +class ThreadRunner: + """ + Runs multiple callables as concurrent threads. + + If a thread raises, logs the exception. When exit_on_failure=True (default), + also calls os._exit(1) so the container restarts cleanly via ECS. + If a thread returns normally (no exception), it is logged as an unexpected + exit but os._exit is NOT called regardless of exit_on_failure. + """ + + @staticmethod + def run( + *targets: Callable, + max_workers: int | None = None, + thread_names: list[str] | None = None, + exit_on_failure: bool = True, + ) -> None: + names = thread_names or [getattr(t, "__name__", f"thread-{i}") for i, t in enumerate(targets)] + + with ThreadPoolExecutor(max_workers=max_workers or len(targets)) as executor: + futures = {executor.submit(t): name for t, name in zip(targets, names)} + + done, _ = wait(futures.keys(), return_when=FIRST_COMPLETED) + + failed = False + for future in done: + name = futures[future] + exc = future.exception() + if exc is not None: + _log.exception(f"[{name}] crashed", exc_info=exc) + failed = True + else: + _log.error(f"[{name}] exited unexpectedly (no exception)") + + # os._exit must fire here, inside the `with` block, before + # executor.shutdown(wait=True) blocks indefinitely on the other + # thread (which is an infinite poll loop and never returns). + if failed and exit_on_failure: + os._exit(1) diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/ecs_io_handler.py b/ak-py/src/agentkernel/deployment/aws/containerized/ecs_io_handler.py new file mode 100644 index 000000000..820aa0049 --- /dev/null +++ b/ak-py/src/agentkernel/deployment/aws/containerized/ecs_io_handler.py @@ -0,0 +1,41 @@ +import logging + +from ....api.http import RESTAPI +from ....core.config import AKConfig +from .core import ThreadRunner +from .akoutputconsumer import ECSOutputConsumer + + +class ECSIOHandler: + """ + ECS IO Handler — starts the REST API and the output-queue consumer as + peer threads via ThreadRunner. + + Thread 1 (rest-api): RESTAPI.run — FastAPI/uvicorn, + ECSQueueRequestHandler registered. + Thread 2 (output-queue-consumer): ECSOutputConsumer.run — polls the + Output Queue, writes to DB / WebSocket. + + Usage:: + + from agentkernel.deployment.aws.containerized import ECSIOHandler + + if __name__ == "__main__": + ECSIOHandler.run() + """ + + _log = logging.getLogger("ak.ecs.iohandler") + _config = AKConfig.get() + + @classmethod + def run(cls) -> None: + from .ecs_queue_handler import ECSQueueRequestHandler + + mode = cls._config.execution.mode + cls._log.info(f"ECSIOHandler starting — mode={mode}") + + ThreadRunner.run( + lambda: RESTAPI.run(handlers=[ECSQueueRequestHandler()]), # lambda needed here to wrap the function so that it turns into a callable, because otherwise the rest api will be run here itself + ECSOutputConsumer.run, + thread_names=["rest-api", "output-queue-consumer"], + ) diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py b/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py index 741501925..2b0ed5b62 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py @@ -4,7 +4,7 @@ This handler bypasses ChatService and directly enqueues requests to SQS, similar to how the Lambda serverless DefaultEndpointsHandler works. -Used by ECSRESTService when queue mode is enabled. +Used by ECSIOHandler (Thread 1 — REST API) when queue mode is enabled. """ import logging diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/sqs_poller.py b/ak-py/src/agentkernel/deployment/aws/containerized/sqs_poller.py deleted file mode 100644 index be6e8a14f..000000000 --- a/ak-py/src/agentkernel/deployment/aws/containerized/sqs_poller.py +++ /dev/null @@ -1,113 +0,0 @@ -from __future__ import annotations - -import logging -import time -from typing import Callable - -import boto3 - -_log = logging.getLogger("ak.ecs.sqs_poller") - - -class SQSPoller: - """ - Polls an SQS queue in a tight loop and delegates each message to a - caller-supplied ``process_fn``. - - Intended to run as a background daemon thread (Thread 2 in the ECS - REST-Service, or the main loop in the Agent Runner service). - - On success the message is deleted from the queue. - On failure the message is left in the queue so that the visibility - timeout returns it for a retry — exactly mirroring the Lambda ESM - behaviour. - - :param queue_url: SQS queue URL to poll. - :param process_fn: Callable that receives a single raw boto3 SQS - message dict (same shape as a Lambda ``Records`` entry). - :param max_receive_count: Messages received more than this many times - are passed to ``on_permanent_failure_fn`` instead of - ``process_fn`` and then deleted. - :param on_permanent_failure_fn: Optional callback for permanently - failed messages. Receives the raw message dict. - :param wait_seconds: Long-poll duration (0–20 s). Defaults to 20. - :param batch_size: Number of messages to fetch per poll (1–10). - """ - - def __init__( - self, - queue_url: str, - process_fn: Callable[[dict], None], - max_receive_count: int = 3, - on_permanent_failure_fn: Callable[[dict], None] | None = None, - wait_seconds: int = 20, - batch_size: int = 10, - ) -> None: - self._queue_url = queue_url - self._process_fn = process_fn - self._max_receive_count = max_receive_count - self._on_permanent_failure_fn = on_permanent_failure_fn - self._wait_seconds = wait_seconds - self._batch_size = batch_size - self._client: object | None = None - - # ------------------------------------------------------------------ - # Public API - # ------------------------------------------------------------------ - - def run(self) -> None: - """Block forever, polling the queue. Call from a dedicated thread.""" - _log.info(f"SQSPoller starting — queue: {self._queue_url}") - client = self._get_client() - while True: - try: - self._poll_once(client) - except Exception: - _log.exception("Unexpected error in poll loop, sleeping 5 s before retry") - time.sleep(5) - - # ------------------------------------------------------------------ - # Internal - # ------------------------------------------------------------------ - - def _get_client(self): - if self._client is None: - self._client = boto3.client("sqs") - return self._client - - def _poll_once(self, client) -> None: - resp = client.receive_message( - QueueUrl=self._queue_url, - MaxNumberOfMessages=self._batch_size, - WaitTimeSeconds=self._wait_seconds, - AttributeNames=["All"], - MessageAttributeNames=["All"], - ) - for msg in resp.get("Messages", []): - self._handle_message(client, msg) - - def _handle_message(self, client, msg: dict) -> None: - message_id = msg.get("MessageId", "") - receive_count = int(msg.get("Attributes", {}).get("ApproximateReceiveCount", "1")) - - try: - if receive_count > self._max_receive_count: - _log.warning(f"Message {message_id} exceeded max receive count " f"({receive_count} > {self._max_receive_count})") - if self._on_permanent_failure_fn: - self._on_permanent_failure_fn(msg) - # Delete so it stops cycling - self._delete_message(client, msg) - return - - self._process_fn(msg) - self._delete_message(client, msg) - - except Exception: - _log.exception(f"Failed to process message {message_id} — " "leaving in queue for visibility-timeout retry") - # Do NOT delete — visibility timeout will return it for retry - - def _delete_message(self, client, msg: dict) -> None: - client.delete_message( - QueueUrl=self._queue_url, - ReceiptHandle=msg["ReceiptHandle"], - ) diff --git a/examples/aws-containerized/openai-dynamodb-scalable/app_rest_service.py b/examples/aws-containerized/openai-dynamodb-scalable/app_rest_service.py index c75538790..913de87b0 100644 --- a/examples/aws-containerized/openai-dynamodb-scalable/app_rest_service.py +++ b/examples/aws-containerized/openai-dynamodb-scalable/app_rest_service.py @@ -1,12 +1,6 @@ -from agentkernel.deployment.aws.containerized import ECSRESTService +from agentkernel.deployment.aws.containerized import ECSIOHandler -# REST Service entrypoint - NO agent definitions. -# Agents are ONLY defined in the Agent Runner (app_agent_runner.py). -# -# Thread 1: FastAPI handles POST /chat (enqueues to Input Queue, waits on DynamoDB) -# Agent validation happens in the Agent Runner, not here. -# Thread 2: Output-queue poller - writes responses to DynamoDB (sync/async) -runner = ECSRESTService.run +runner = ECSIOHandler.run if __name__ == "__main__": runner() From 8dda4402a9a52c3fb40341e4b07a74b28d98a98a Mon Sep 17 00:00:00 2001 From: Lakindu Induwara Kumara Date: Fri, 26 Jun 2026 16:47:26 +0530 Subject: [PATCH 45/90] containerized mode documentation updates --- .agents/skills/ak-dev-architecture/SKILL.md | 93 ++++++++- .../skills/ak-cloud-deploy/SKILL.md | 120 +++++++++++- docs/docs/deployment/aws-containerized.md | 89 +++++++++ docs/queue-mode-guide.md | 184 ++++++++++-------- 4 files changed, 405 insertions(+), 81 deletions(-) diff --git a/.agents/skills/ak-dev-architecture/SKILL.md b/.agents/skills/ak-dev-architecture/SKILL.md index e91e4872c..378fcb52a 100644 --- a/.agents/skills/ak-dev-architecture/SKILL.md +++ b/.agents/skills/ak-dev-architecture/SKILL.md @@ -4,7 +4,9 @@ description: > Agent Kernel architectural principles, core abstractions, and design patterns. Use this skill when you need to understand the codebase structure, how components interact, or before making changes to core functionality. Covers Session, Agent, - Runner, Module, Runtime, AgentService, AKConfig, tools, hooks, multimodal, and the adapter pattern. + Runner, Module, Runtime, AgentService, AKConfig, tools, hooks, multimodal, the adapter pattern, + and the AWS ECS containerized deployment classes (ECSIOHandler, ECSOutputConsumer, + ECSAgentRunner, ECSSQSConsumer, ThreadRunner). license: Apache-2.0 metadata: author: yaalalabs @@ -205,7 +207,17 @@ ak-py/src/agentkernel/ │ ├── a2a/ # Agent-to-Agent server │ └── mcp/ # MCP server ├── deployment/ # Cloud deployment adapters -│ ├── aws/ # Lambda handler +│ ├── aws/ +│ │ ├── serverless/ # Lambda handlers: Lambda, ResponseHandler, ServerlessAgentRunner, etc. +│ │ ├── containerized/ # ECS Fargate handlers +│ │ │ ├── core/ +│ │ │ │ ├── sqs_consumer.py # ECSSQSConsumer — ABC: SQS poll loop +│ │ │ │ └── thread_runner.py # ThreadRunner — run N callables as peer threads +│ │ │ ├── akagentrunner.py # ECSAgentRunner — polls Input Queue, runs agent +│ │ │ ├── akoutputconsumer.py # ECSOutputConsumer — polls Output Queue, writes to DB/WS +│ │ │ ├── ecs_io_handler.py # ECSIOHandler — entrypoint: wires both threads +│ │ │ └── ecs_queue_handler.py # ECSQueueRequestHandler — FastAPI routes +│ │ └── core/ # Shared: SQSHandler, WebSocketHandler, ResponseStore │ └── azure/ # Azure Functions handler ├── integration/ # Messaging integrations │ ├── slack/ @@ -241,6 +253,83 @@ ak-py/src/agentkernel/ └── session_cache.py # SessionNonVolatileCacheAttachmentStore (legacy) ``` +## AWS ECS Containerized Deployment + +The containerized deployment runs on ECS Fargate and uses a two-container architecture for scalable queue-based processing. + +### Class Hierarchy + +| Class | File | Role | +|---|---|---| +| `ECSSQSConsumer` | `containerized/core/sqs_consumer.py` | Abstract base: SQS long-poll loop, retry/DLQ logic | +| `ThreadRunner` | `containerized/core/thread_runner.py` | Runs N callables as peer threads via `ThreadPoolExecutor` | +| `ECSOutputConsumer` | `containerized/akoutputconsumer.py` | Extends `ECSSQSConsumer` — polls Output Queue, writes to DynamoDB or broadcasts via WebSocket | +| `ECSAgentRunner` | `containerized/akagentrunner.py` | Extends `ECSSQSConsumer` — polls Input Queue, runs the agent, sends to Output Queue | +| `ECSIOHandler` | `containerized/ecs_io_handler.py` | Entrypoint for the IO container: wires REST API + output consumer as peer threads | +| `ECSQueueRequestHandler` | `containerized/ecs_queue_handler.py` | FastAPI routes: `POST /api/v1/chat` enqueues; `GET /api/v1/chat/{id}` polls | + +### Two-Container Layout + +``` +Container 1 — ECSIOHandler + Thread 1 (ThreadRunner): RESTAPI.run(handlers=[ECSQueueRequestHandler()]) + — FastAPI/uvicorn, handles POST /chat and GET /chat/{id} + Thread 2 (ThreadRunner): ECSOutputConsumer.run() + — polls Output Queue, writes to DynamoDB / broadcasts via WebSocket + +Container 2 — ECSAgentRunner + Main thread: ECSSQSConsumer.run() + — polls Input Queue, runs agent, sends result to Output Queue +``` + +### ECSSQSConsumer Contract + +- **`_get_queue_url(cls) → str`** *(abstract)*: return the SQS queue URL to poll. +- **`process_message(cls, record)`** *(abstract)*: handle one message; called on every successful receive. +- **`on_permanent_failure(cls, record)`** *(abstract)*: called when `ApproximateReceiveCount > max_receive_count`; **must catch its own exceptions** — if it raises, the message is not deleted and loops back. +- **`delete_message(cls, client, msg)`** *(public)*: subclasses may call this directly when manual deletion is needed. +- **`run(cls)`**: blocking poll loop — the container entry-point. + +### ThreadRunner Contract + +`ThreadRunner.run(*targets, thread_names=..., exit_on_failure=True)` submits all callables to a `ThreadPoolExecutor` and waits for `FIRST_COMPLETED`: + +- Thread **raises** → logs exception; if `exit_on_failure=True`, calls `os._exit(1)` inside the `with` block so the container restarts cleanly via ECS (the `_exit` is placed before `executor.shutdown(wait=True)` to avoid blocking on the other infinite-loop thread). +- Thread **returns normally** (no exception) → logs unexpected exit; `os._exit` is **not** called. + +### Entry Point Pattern + +```python +# Container 1 — app_rest_service.py +from agentkernel.deployment.aws.containerized import ECSIOHandler + +runner = ECSIOHandler.run + +if __name__ == "__main__": + runner() + +# Container 2 — app_agent_runner.py +from agentkernel.deployment.aws import ECSAgentRunner +from agentkernel.openai import OpenAIModule + +OpenAIModule([...]) + +if __name__ == "__main__": + ECSAgentRunner.run() +``` + +### Public Exports + +```python +# agentkernel.deployment.aws +from agentkernel.deployment.aws import ( + ECSAgentRunner, # Container 2 entry-point + ECSIOHandler, # Container 1 entry-point + ECSOutputConsumer, # Subclass ECSSQSConsumer for custom output processing +) +from agentkernel.deployment.aws.containerized.core import ECSSQSConsumer, ThreadRunner +``` + ## Execution Flow ``` diff --git a/ak-py/src/agentkernel/skills/ak-cloud-deploy/SKILL.md b/ak-py/src/agentkernel/skills/ak-cloud-deploy/SKILL.md index 4bd9823c3..3cc7c201e 100644 --- a/ak-py/src/agentkernel/skills/ak-cloud-deploy/SKILL.md +++ b/ak-py/src/agentkernel/skills/ak-cloud-deploy/SKILL.md @@ -499,7 +499,7 @@ authorizer = { ## AWS Containerized (ECS/Fargate) -### Agent Code Pattern +### A) Basic Mode (single container, direct execution) ```python from agentkernel.api import RESTAPI @@ -511,8 +511,6 @@ if __name__ == "__main__": RESTAPI.run() ``` -### Terraform Example - ```hcl module "containerized_agents" { source = "yaalalabs/ak-containerized/aws" @@ -536,6 +534,122 @@ module "containerized_agents" { } ``` +### B) Scalable Queue Mode (two containers) + +Use this for high-throughput or long-running agents. Two separate ECS services share SQS queues: + +- **IO container** (`ECSIOHandler`) — Thread 1: FastAPI REST API; Thread 2: output queue consumer → DynamoDB / WebSocket. +- **Agent Runner container** (`ECSAgentRunner`) — polls the input queue, executes the agent, puts the result on the output queue. + +**`app_rest_service.py`** (IO container entrypoint — NO agent definitions here): + +```python +from agentkernel.deployment.aws.containerized import ECSIOHandler + +runner = ECSIOHandler.run + +if __name__ == "__main__": + runner() +``` + +**`app_agent_runner.py`** (Agent Runner container entrypoint): + +```python +from agentkernel.deployment.aws import ECSAgentRunner +from agentkernel.openai import OpenAIModule + +OpenAIModule([...]) # register agents here only + +if __name__ == "__main__": + ECSAgentRunner.run() +``` + +**`config.yaml`** (same file included in both images — queue URLs and table names are injected by Terraform): + +```yaml +execution: + response_store: + type: dynamodb + retry_count: 30 + delay: 2 +session: + type: dynamodb +``` + +**Terraform:** + +```hcl +module "containerized_agents" { + source = "yaalalabs/ak-containerized/aws" + version = "0.5.1" + + product_alias = var.product_alias + env_alias = var.env_alias + module_name = var.module_name + region = var.region + + # IO container image + package_path = "../dist-rest-service" + + rest_service = { + cpu = 512 + memory = 1024 + desired_count = 2 + command = ["python", "app_rest_service.py"] + environment_variables = { + OPENAI_API_KEY = var.openai_api_key + } + } + + enable_queue_mode = true + queue_mode_type = "sync" # or "async" + + queue_config = { + input_queue_visibility_timeout = 120 + output_queue_visibility_timeout = 60 + input_queue_create_dlq = true + output_queue_create_dlq = true + } + + # Agent Runner container — separate image with agent definitions + agent_runner = { + cpu = 1024 + memory = 2048 + desired_count = 1 + image_uri = module.agent_runner_image.docker_image_uri + command = ["python", "app_agent_runner.py"] + environment_variables = { + OPENAI_API_KEY = var.openai_api_key + } + } + + # Optional: auto-scale Agent Runner based on Input Queue depth + scaling_config = { + enabled = true + min_count = 1 + max_count = 10 + backlog_target = 5 + scale_in_cooldown = 180 + scale_out_cooldown = 60 + } + + create_dynamodb_memory_table = true +} +``` + +**Required `pyproject.toml` extras:** + +```toml +dependencies = [ + "agentkernel[openai,api,aws]>=0.5.1" +] +``` + +**Key rules:** +- Agent definitions (`OpenAIModule([...])`) go in `app_agent_runner.py` only — never in `app_rest_service.py`. +- `ECSIOHandler` starts two threads via `ThreadRunner`; if either thread crashes, the container exits (`os._exit(1)`) so ECS can restart it. +- `ECSAgentRunner` and `ECSOutputConsumer` both extend `ECSSQSConsumer` — extend either class to customise message processing. + ## Azure Serverless (Functions + APIM) ### Agent Code Pattern diff --git a/docs/docs/deployment/aws-containerized.md b/docs/docs/deployment/aws-containerized.md index 250a5a77e..35cb677ff 100644 --- a/docs/docs/deployment/aws-containerized.md +++ b/docs/docs/deployment/aws-containerized.md @@ -37,6 +37,95 @@ graph TB Refer to [example ECS implementation](https://github.com/yaalalabs/agent-kernel/tree/develop/examples/aws-containerized/crewai) which leverages Agent Kernel's [terraform module](https://registry.terraform.io/modules/yaalalabs/ak-containerized/aws) for ECS deployment. +## Scalable Queue Mode + +For high-throughput or long-running agents, use the two-container queue architecture. +The IO container and the Agent Runner are separate ECS services sharing SQS queues. + +```mermaid +graph TB + A[Client] --> B[API Gateway] + B --> C[ALB] + C --> D["IO Container
(ECSIOHandler)"] + + D --> |Thread 1 — ECSQueueRequestHandler| E[Input Queue SQS] + D --> |Thread 2 — ECSOutputConsumer| F[Output Queue SQS] + + E --> G["Agent Runner
(ECSAgentRunner)"] + G --> F + F --> H[DynamoDB Response Store] + + style D fill:#2e8555,stroke:#fff,stroke-width:2px,color:#fff + style G fill:#2e8555,stroke:#fff,stroke-width:2px,color:#fff +``` + +### Container 1 — IO container (`ECSIOHandler`) + +`ECSIOHandler.run()` starts two threads via `ThreadRunner`: + +- **Thread 1** — `RESTAPI.run(handlers=[ECSQueueRequestHandler()])`: FastAPI/uvicorn. Handles `POST /api/v1/chat` (enqueues to Input Queue, then either waits on DynamoDB or returns a `request_id`) and `GET /api/v1/chat/{session_id}` (polls DynamoDB for the result). +- **Thread 2** — `ECSOutputConsumer.run()`: polls the Output Queue and writes results to DynamoDB (REST modes) or broadcasts via WebSocket. + +If either thread crashes, `ThreadRunner` calls `os._exit(1)` so ECS restarts the task cleanly. + +**Entrypoint — `app_rest_service.py`** (no agent definitions): + +```python +from agentkernel.deployment.aws.containerized import ECSIOHandler + +runner = ECSIOHandler.run + +if __name__ == "__main__": + runner() +``` + +### Container 2 — Agent Runner (`ECSAgentRunner`) + +Extends `ECSSQSConsumer`: polls the Input Queue in a blocking loop, executes the agent, and puts the result on the Output Queue. + +**Entrypoint — `app_agent_runner.py`**: + +```python +from agentkernel.deployment.aws import ECSAgentRunner +from agentkernel.openai import OpenAIModule + +OpenAIModule([...]) # register agents here only + +if __name__ == "__main__": + ECSAgentRunner.run() +``` + +### Terraform + +Enable queue mode in the `yaalalabs/ak-containerized/aws` module: + +```hcl +enable_queue_mode = true +queue_mode_type = "sync" # or "async" + +rest_service = { + command = ["python", "app_rest_service.py"] + # ... +} + +agent_runner = { + command = ["python", "app_agent_runner.py"] + image_uri = "..." # separate image with agent definitions + # ... +} + +scaling_config = { + enabled = true + min_count = 1 + max_count = 10 + backlog_target = 5 +} +``` + +For the full example see [examples/aws-containerized/openai-dynamodb-scalable](https://github.com/yaalalabs/agent-kernel/tree/develop/examples/aws-containerized/openai-dynamodb-scalable). + +For queue mode internals see [queue-mode-guide.md](../../queue-mode-guide.md). + ## Advantages - **No cold starts** - containers always warm diff --git a/docs/queue-mode-guide.md b/docs/queue-mode-guide.md index a63f02a6f..e4b704eb7 100644 --- a/docs/queue-mode-guide.md +++ b/docs/queue-mode-guide.md @@ -1,7 +1,7 @@ -# Queue Mode — How It Works and How to Add It to ECS +# Queue Mode — How It Works in Lambda and ECS -This document explains what's already built for the Lambda (serverless) queue modes and -how to replicate the same pattern in the ECS (containerized) deployment. +This document explains the queue mode architecture for both Lambda (serverless) and +ECS (containerized) deployments. --- @@ -117,120 +117,152 @@ Located under `ak-deployment/ak-aws/serverless/modules/`: --- -## How to Add Queue Mode to ECS (Containerized) +## How Queue Mode Works in ECS (Containerized) -The ECS deployment is fundamentally the same pipeline, **except Lambda functions are -replaced by a long-running ECS service** that runs two threads: +The ECS deployment uses the same pipeline as Lambda, **except Lambda functions are +replaced by long-running ECS services**. The IO container runs two threads via +`ThreadRunner`; the Agent Runner is a separate ECS service that extends `ECSSQSConsumer`. -| Thread | Responsibility | -|--------|---------------| -| Thread 1 | Runs the FastAPI/REST service (receives requests, returns responses) | -| Thread 2 | Polls the Output Queue and writes to DynamoDB (REST modes) or sends via WebSocket | +### Python Class Hierarchy -The **Agent Runner** is already a separate ECS service that polls the Input Queue. +| Class | Container | Role | +|-------|-----------|------| +| `ECSIOHandler` | IO container | Entrypoint: starts Thread 1 + Thread 2 via `ThreadRunner` | +| `ECSQueueRequestHandler` | IO container / Thread 1 | FastAPI: `POST /api/v1/chat` enqueues; `GET /api/v1/chat/{id}` polls | +| `ECSOutputConsumer` | IO container / Thread 2 | Extends `ECSSQSConsumer` — polls Output Queue → DynamoDB / WebSocket | +| `ECSAgentRunner` | Agent Runner container | Extends `ECSSQSConsumer` — polls Input Queue, runs agent, sends to Output Queue | +| `ECSSQSConsumer` | both | Abstract base: SQS long-poll loop, retry/permanent-failure logic | +| `ThreadRunner` | IO container | Runs N callables as peer threads; calls `os._exit(1)` if any thread crashes | -### REST Sync — ECS Queue Mode +### Request Flow — REST Sync ``` Client │ ▼ -API Gateway (HTTP) — existing aws_apigatewayv2_api.http_api - │ routes to ALB via VPC Link — existing aws_apigatewayv2_integration.alb_proxy +API Gateway (HTTP) + │ routes to ALB via VPC Link ▼ -ALB → ECS REST Service (Thread 1) - │ PUT message on Input Queue - │ wait / poll DynamoDB Response Store - │ return response on same connection +ALB → ECSIOHandler container + │ + ├── Thread 1 — ECSQueueRequestHandler (FastAPI/uvicorn) + │ PUT message on Input Queue + │ poll DynamoDB Response Store + │ return response on same connection + │ + └── Thread 2 — ECSOutputConsumer.run() + poll Output Queue → write to DynamoDB Response Store │ - │ (Thread 2 running in same container) - │ poll Output Queue → write to DynamoDB Response Store ▼ Input SQS FIFO Queue + │ ▼ -Agent Runner ECS Service (separate ECS task/service) - │ polls queue, processes, puts result on Output Queue +ECSAgentRunner container (separate ECS service, auto-scales) + │ polls Input Queue, runs agent, puts result on Output Queue ▼ Output SQS FIFO Queue + │ ▼ -ECS REST Service Thread 2 → DynamoDB Response Store +ECSOutputConsumer (Thread 2) → DynamoDB Response Store +``` + +### Request Flow — REST Async + +Identical infrastructure to REST Sync. The difference is purely in `ECSQueueRequestHandler`: + +- `POST /api/v1/chat` returns **202 Accepted** with a `request_id` immediately after + enqueuing (Thread 1 does not wait on DynamoDB). +- `GET /api/v1/chat/{sessionId}?request_id=...` reads from DynamoDB Response Store + and returns the result when ready (or 404 while still processing). + +### Entrypoint Code + +**IO container — `app_rest_service.py`** (no agent definitions): + +```python +from agentkernel.deployment.aws.containerized import ECSIOHandler + +runner = ECSIOHandler.run + +if __name__ == "__main__": + runner() +``` + +**Agent Runner container — `app_agent_runner.py`**: + +```python +from agentkernel.deployment.aws import ECSAgentRunner +from agentkernel.openai import OpenAIModule + +OpenAIModule([...]) # agent definitions here only + +if __name__ == "__main__": + ECSAgentRunner.run() ``` -New AWS resources needed: +### Required AWS Resources + - `aws_sqs_queue` — Input Queue (FIFO) - `aws_sqs_queue` — Output Queue (FIFO) -- `aws_dynamodb_table` — Response Store (keyed by `session_id`, with TTL) -- IAM policies giving the REST Service ECS Task Role: - - `sqs:SendMessage` on Input Queue - - `sqs:ReceiveMessage / DeleteMessage / ChangeMessageVisibility` on Output Queue - - `dynamodb:PutItem / GetItem / Query / DeleteItem` on Response Store -- IAM policies giving the Agent Runner ECS Task Role: - - `sqs:ReceiveMessage / DeleteMessage / ChangeMessageVisibility` on Input Queue - - `sqs:SendMessage` on Output Queue -- Existing API Gateway + ALB routes are reused as-is; only the application code changes - (Thread 2 is added to the REST Service container). - -Environment variables to inject into the REST Service container: +- `aws_dynamodb_table` — Response Store (keyed by `request_id`, with TTL) +- IAM for IO container task role: `sqs:SendMessage` on Input Queue; `sqs:ReceiveMessage / DeleteMessage / ChangeMessageVisibility` on Output Queue; `dynamodb:PutItem / GetItem / Query / DeleteItem` on Response Store +- IAM for Agent Runner task role: `sqs:ReceiveMessage / DeleteMessage / ChangeMessageVisibility` on Input Queue; `sqs:SendMessage` on Output Queue + +All of these are provisioned automatically by the `yaalalabs/ak-containerized/aws` Terraform module when `enable_queue_mode = true`. + +### Required Environment Variables + +IO container: ``` -AK_EXECUTION__QUEUES__INPUT__URL = -AK_EXECUTION__QUEUES__OUTPUT__URL = +AK_EXECUTION__QUEUES__INPUT__URL = +AK_EXECUTION__QUEUES__OUTPUT__URL = AK_EXECUTION__RESPONSE_STORE__DYNAMODB__TABLE_NAME = ``` -Environment variables for the Agent Runner container: +Agent Runner container: ``` -AK_EXECUTION__QUEUES__INPUT__URL = -AK_EXECUTION__QUEUES__OUTPUT__URL = -AK_EXECUTION__QUEUES__INPUT__MAX_RECEIVE_COUNT = +AK_EXECUTION__QUEUES__INPUT__URL = +AK_EXECUTION__QUEUES__OUTPUT__URL = +AK_EXECUTION__QUEUES__INPUT__MAX_RECEIVE_COUNT = ``` -### REST Async — ECS Queue Mode - -Identical infrastructure to REST Sync. The difference is purely in the application: - -- `POST /api/v1/chat` returns **202 Accepted** with a `session_id` immediately after - enqueuing (Thread 1 does not wait). -- `GET /api/v1/chat/{sessionId}` reads from DynamoDB Response Store and returns the - result when ready (or 202 if still processing). - -No additional AWS resources are needed beyond the REST Sync setup. The API Gateway already -supports both `POST` and `GET` routes to the ALB. - ### Scaling the Agent Runner ECS Service Unlike Lambda (which auto-scales 1:1 with queue batches), ECS needs an explicit scaling policy. The recommended approach is **backlog-per-task target tracking**: -1. A Lambda function (or EventBridge scheduled rule) periodically reads - `ApproximateNumberOfMessages` from SQS and the current running task count. -2. It computes `BacklogPerTask = queueDepth / runningTasks` and publishes this as a - custom CloudWatch metric. -3. An ECS Target Tracking scaling policy scales the Agent Runner service to keep - `BacklogPerTask` at or below the configured target. +1. A Lambda function (EventBridge rule, 1-minute schedule) reads + `ApproximateNumberOfMessages` from the Input Queue and the current running task count. +2. It computes `BacklogPerTask = queueDepth / max(runningTasks, 1)` and publishes + this as a custom CloudWatch metric (`Custom/ECS/BacklogPerTask`). +3. An ECS Target Tracking policy scales the Agent Runner service to keep + `BacklogPerTask` at or below `backlog_target`. -This is covered in detail in `scalability.md` under the **SQS + ECS → Scaling** section. +The `scaling_config` block in the `yaalalabs/ak-containerized/aws` module provisions +this automatically. See the [containerized README](../ak-deployment/ak-aws/containerized/README.md#auto-scaling) for details. ### Key Differences vs Lambda | Aspect | Lambda | ECS | |--------|--------|-----| -| Input Queue trigger | Event Source Mapping (push) | Agent Runner polls the queue | -| Partial failure reporting | `batchItemFailures` return value | Failed messages not deleted (visibility timeout) | -| Scaling | Automatic, 1 Lambda per batch | Manual target tracking policy | -| Response Handler | Separate Lambda triggered by Output Queue ESM | Thread 2 inside the REST Service container | -| Session DB writes | Response Handler Lambda | REST Service Thread 2 | +| Input Queue trigger | Event Source Mapping (push) | `ECSAgentRunner` polls (`ECSSQSConsumer.run`) | +| Partial failure | `batchItemFailures` return value | Failed messages not deleted — visibility timeout retries | +| Scaling | Automatic, 1 Lambda per batch | `backlog-per-task` target tracking policy | +| Response Handler | Separate Lambda triggered by Output Queue ESM | `ECSOutputConsumer` (Thread 2 in IO container) | +| Crash recovery | Lambda restarts automatically | `ThreadRunner` calls `os._exit(1)` → ECS restarts the task | --- -## Summary of What's Already Built - -| Component | Lambda (done) | ECS (todo) | -|-----------|--------------|------------| -| Input/Output SQS Queues | ✅ `modules/queues/` | ❌ needs new TF resources | -| Agent Runner | ✅ `modules/agent-runner/` | ❌ separate ECS service | -| Request Handler / REST Service | ✅ `modules/request-handler/` | ❌ add queue env vars + thread 2 | -| Response Handler | ✅ `modules/response-handler/` | ❌ merged into REST Service Thread 2 | -| DynamoDB Response Store | ✅ provisioned inside serverless stack | ❌ needs new TF resource | -| WebSocket Mode | ✅ `modules/websocket-api-gateway/` + `modules/ws-connection-handler/` | ❌ not yet | +## Summary — Implementation Status + +| Component | Lambda | ECS | +|-----------|--------|-----| +| Input/Output SQS Queues | ✅ `modules/queues/` | ✅ `modules/queues/` (same TF module) | +| Agent Runner | ✅ `modules/agent-runner/` | ✅ `ECSAgentRunner` (`akagentrunner.py`) | +| IO Handler / REST Service | ✅ `modules/request-handler/` | ✅ `ECSIOHandler` (`ecs_io_handler.py`) | +| Output Queue Consumer | ✅ `modules/response-handler/` (separate Lambda) | ✅ `ECSOutputConsumer` (Thread 2 in IO container) | +| DynamoDB Response Store | ✅ serverless stack | ✅ containerized stack | +| Thread management | N/A | ✅ `ThreadRunner` (`core/thread_runner.py`) | +| WebSocket Mode | ✅ `modules/websocket-api-gateway/` + `modules/ws-connection-handler/` | ⚠️ `ECSOutputConsumer` supports WebSocket broadcast; API Gateway WebSocket wiring not yet in TF module | From b2c5c345b0abeb917503daedb3ea3d2921050e4f Mon Sep 17 00:00:00 2001 From: Lakindu Induwara Kumara Date: Sun, 28 Jun 2026 12:16:28 +0530 Subject: [PATCH 46/90] refactor sqs_consumer --- .../aws/containerized/core/sqs_consumer.py | 131 +++++++++--------- 1 file changed, 66 insertions(+), 65 deletions(-) diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py b/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py index 1023ada6e..2b4b1dcf9 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py @@ -1,7 +1,7 @@ import logging import time from abc import ABC, abstractmethod -from typing import Any, Dict +from typing import Any, Dict, List import boto3 @@ -26,7 +26,7 @@ class ECSSQSConsumer(ABC): next visibility-timeout cycle. """ - max_receive_count: int = 3 # overidden by classes that inherit this + max_receive_count: int = 3 # overridden by classes that inherit this _log = logging.getLogger("ak.ecs.sqsconsumer") @classmethod @@ -39,6 +39,25 @@ def _get_queue_url(cls) -> str: configure the queue externally as in Lambda. """ raise NotImplementedError + + @classmethod + def poll(cls, client) -> list: + """ + Receive one batch of SQS messages and return them. + + Override to customise MaxNumberOfMessages, WaitTimeSeconds, or + MessageAttributeNames. Overriding implementations must accept the + boto3 SQS client as the second argument and return a list of raw + boto3 receive_message records. + """ + resp = client.receive_message( + QueueUrl=cls._get_queue_url(), + MaxNumberOfMessages=10, + WaitTimeSeconds=20, + AttributeNames=["All"], + MessageAttributeNames=["All"], + ) + return resp.get("Messages", []) @classmethod @abstractmethod @@ -63,7 +82,48 @@ def on_permanent_failure(cls, record: Dict[str, Any]) -> None: :param record: Raw boto3 receive_message record. """ - pass + raise NotImplementedError + + @classmethod + def delete_message(cls, client, msg: dict) -> None: + client.delete_message( + QueueUrl=cls._get_queue_url(), + ReceiptHandle=msg["ReceiptHandle"], + ) + + @classmethod + def process_messages(cls, client, messages: List[Dict[str, Any]]) -> None: + """ + Dispatch each message in a batch: retry-count check → process_message + or on_permanent_failure → delete on success. + + On exception: logs and leaves the message in the queue so the + visibility timeout returns it for a retry — mirroring Lambda's + batchItemFailures behaviour. + """ + for msg in messages: + message_id = msg.get("MessageId", "") + receive_count = int(msg.get("Attributes", {}).get("ApproximateReceiveCount", "1")) + try: + if receive_count > cls.max_receive_count: + cls._log.warning( + f"Message {message_id} exceeded max_receive_count " + f"({receive_count} > {cls.max_receive_count})" + ) + cls.on_permanent_failure(msg) + cls.delete_message(client, msg) + continue + + cls.process_message(msg) + cls.delete_message(client, msg) + + except Exception: + cls._log.exception( + f"Failed to process message {message_id} — " + "leaving in queue for visibility-timeout retry" + ) + # Do NOT delete — visibility timeout returns it for retry, + # mirroring how Lambda batchItemFailures re-enqueues a record. @classmethod def run(cls) -> None: @@ -81,69 +141,10 @@ def run(cls) -> None: client = boto3.client("sqs") while True: try: - cls.poll(client) + messages = cls.poll(client) except Exception: cls._log.exception("Unexpected error in poll loop — retrying in 5 s") time.sleep(5) + continue - @classmethod - def poll(cls, client) -> None: - """ - Receive one batch of SQS messages and process each. - - Analogous to one Lambda invocation: handle() processes event.Records; - poll() processes one receive_message response. - - Override to customise MaxNumberOfMessages, WaitTimeSeconds, or - MessageAttributeNames. Overriding implementations must accept the - boto3 SQS client as the second argument. - """ - resp = client.receive_message( - QueueUrl=cls._get_queue_url(), - MaxNumberOfMessages=10, - WaitTimeSeconds=20, - AttributeNames=["All"], - MessageAttributeNames=["All"], - ) - for msg in resp.get("Messages", []): - cls._handle_message(client, msg) - - @classmethod - def _handle_message(cls, client, msg: dict) -> None: - """ - Retry-count check → dispatch to process_message or on_permanent_failure - → delete on success. - - On exception: logs and leaves the message in the queue so the - visibility timeout returns it for a retry — mirroring Lambda's - batchItemFailures behaviour. - """ - message_id = msg.get("MessageId", "") - receive_count = int(msg.get("Attributes", {}).get("ApproximateReceiveCount", "1")) - try: - if receive_count > cls.max_receive_count: - cls._log.warning( - f"Message {message_id} exceeded max_receive_count " - f"({receive_count} > {cls.max_receive_count})" - ) - cls.on_permanent_failure(msg) - cls.delete_message(client, msg) - return - - cls.process_message(msg) - cls.delete_message(client, msg) - - except Exception: - cls._log.exception( - f"Failed to process message {message_id} — " - "leaving in queue for visibility-timeout retry" - ) - # Do NOT delete — visibility timeout returns it for retry, - # mirroring how Lambda batchItemFailures re-enqueues a record. - - @classmethod - def delete_message(cls, client, msg: dict) -> None: - client.delete_message( - QueueUrl=cls._get_queue_url(), - ReceiptHandle=msg["ReceiptHandle"], - ) + cls.process_messages(client, messages) From 66ee9fabe9117070086df8e6cead438a89f86603 Mon Sep 17 00:00:00 2001 From: Lakindu Induwara Kumara Date: Sun, 28 Jun 2026 13:05:01 +0530 Subject: [PATCH 47/90] function naming update --- .../deployment/aws/containerized/core/sqs_consumer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py b/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py index 2b4b1dcf9..d0a806e3e 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py @@ -92,7 +92,7 @@ def delete_message(cls, client, msg: dict) -> None: ) @classmethod - def process_messages(cls, client, messages: List[Dict[str, Any]]) -> None: + def _process_batch(cls, client, messages: List[Dict[str, Any]]) -> None: """ Dispatch each message in a batch: retry-count check → process_message or on_permanent_failure → delete on success. @@ -147,4 +147,4 @@ def run(cls) -> None: time.sleep(5) continue - cls.process_messages(client, messages) + cls._process_batch(client, messages) From 5e4e09d0071e273dd2d4d76f6da8a51bb3333e5c Mon Sep 17 00:00:00 2001 From: Lakindu Induwara Kumara Date: Sun, 28 Jun 2026 13:45:02 +0530 Subject: [PATCH 48/90] update groups thread execution --- ak-py/src/agentkernel/core/config.py | 10 + .../aws/containerized/core/sqs_consumer.py | 129 ++++++++--- ak-py/tests/test_config.py | 11 + ak-py/tests/test_ecs_sqs_consumer_parallel.py | 200 ++++++++++++++++++ 4 files changed, 324 insertions(+), 26 deletions(-) create mode 100644 ak-py/tests/test_ecs_sqs_consumer_parallel.py diff --git a/ak-py/src/agentkernel/core/config.py b/ak-py/src/agentkernel/core/config.py index b9e597b42..64627a049 100644 --- a/ak-py/src/agentkernel/core/config.py +++ b/ak-py/src/agentkernel/core/config.py @@ -268,6 +268,16 @@ class _OutputQueueConfig(BaseModel): class _QueuesConfig(BaseModel): input: _InputQueueConfig = Field(default_factory=_InputQueueConfig, description="Input SQS queue configuration for async execution mode") output: _OutputQueueConfig = Field(default_factory=_OutputQueueConfig, description="Output SQS queue configuration for async execution mode") + parallel_workers: int = Field( + default=10, + description=( + "Max concurrent threads for processing MessageGroups within a batch. " + "Each distinct MessageGroupId gets its own thread; messages within a " + "group are still sequential. Tune based on LLM API rate limits and " + "downstream DB connection pool size. Override via env var " + "AK_EXECUTION__QUEUES__PARALLEL_WORKERS." + ), + ) class _LogLevelConfig(BaseModel): diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py b/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py index d0a806e3e..45b6d471c 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py @@ -1,6 +1,10 @@ +import asyncio +import inspect import logging import time from abc import ABC, abstractmethod +from collections import defaultdict +from concurrent.futures import ThreadPoolExecutor, as_completed from typing import Any, Dict, List import boto3 @@ -26,7 +30,8 @@ class ECSSQSConsumer(ABC): next visibility-timeout cycle. """ - max_receive_count: int = 3 # overridden by classes that inherit this + max_receive_count: int = 3 # overridden by classes that inherit this + _DEFAULT_PARALLEL_WORKERS: int = 10 _log = logging.getLogger("ak.ecs.sqsconsumer") @classmethod @@ -91,39 +96,111 @@ def delete_message(cls, client, msg: dict) -> None: ReceiptHandle=msg["ReceiptHandle"], ) + @classmethod + def _get_parallel_workers(cls) -> int: + try: + from .....core.config import AKConfig + return AKConfig.get().execution.queues.parallel_workers + except Exception: + return cls._DEFAULT_PARALLEL_WORKERS + + @staticmethod + def _get_message_group_key(msg: dict) -> str: + group_id = msg.get("Attributes", {}).get("MessageGroupId") + if group_id: + return group_id + return msg.get("MessageId", "") + + @classmethod + def _process_single(cls, client, msg: dict, group_id: str, loop=None) -> None: + message_id = msg.get("MessageId", "") + receive_count = int(msg.get("Attributes", {}).get("ApproximateReceiveCount", "1")) + try: + if receive_count > cls.max_receive_count: + cls._log.warning( + f"[group={group_id}] Message {message_id} exceeded " + f"max_receive_count ({receive_count} > {cls.max_receive_count})" + ) + cls.on_permanent_failure(msg) + cls.delete_message(client, msg) + return + + if loop is not None: + loop.run_until_complete(cls.process_message(msg)) + else: + cls.process_message(msg) + + cls.delete_message(client, msg) + + except Exception: + cls._log.exception( + f"[group={group_id}] Failed to process message {message_id} " + "— leaving in queue for visibility-timeout retry" + ) + # Do NOT delete — visibility timeout returns it for retry + + @classmethod + def _process_group(cls, client, messages: list, group_id: str) -> None: + # @classmethod wraps the underlying function, so inspect on cls.process_message + # returns False even for async classmethods. Unwrap __func__ first. + underlying_fn = getattr(cls.process_message, "__func__", cls.process_message) + is_async = inspect.iscoroutinefunction(underlying_fn) + + if is_async: + # Each group thread gets its own event loop — asyncio event loops are + # not thread-safe and must never be shared across threads. + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + try: + for msg in messages: + cls._process_single(client, msg, group_id, loop=loop) + finally: + loop.close() + else: + for msg in messages: + cls._process_single(client, msg, group_id) + @classmethod def _process_batch(cls, client, messages: List[Dict[str, Any]]) -> None: """ - Dispatch each message in a batch: retry-count check → process_message - or on_permanent_failure → delete on success. + Group messages by MessageGroupId and dispatch each group to its own + thread via ThreadPoolExecutor. Messages within a group execute + sequentially (preserving FIFO); messages across groups run concurrently. - On exception: logs and leaves the message in the queue so the - visibility timeout returns it for a retry — mirroring Lambda's - batchItemFailures behaviour. + On exception within a group: logs and leaves the message in the queue + so the visibility timeout returns it for retry. """ + if not messages: + return + + groups: dict = defaultdict(list) for msg in messages: - message_id = msg.get("MessageId", "") - receive_count = int(msg.get("Attributes", {}).get("ApproximateReceiveCount", "1")) - try: - if receive_count > cls.max_receive_count: - cls._log.warning( - f"Message {message_id} exceeded max_receive_count " - f"({receive_count} > {cls.max_receive_count})" - ) - cls.on_permanent_failure(msg) - cls.delete_message(client, msg) - continue + key = cls._get_message_group_key(msg) + groups[key].append(msg) - cls.process_message(msg) - cls.delete_message(client, msg) + max_workers = cls._get_parallel_workers() + cls._log.info( + f"{cls.__name__} dispatching {len(messages)} messages across " + f"{len(groups)} group(s) with max_workers={max_workers}" + ) - except Exception: - cls._log.exception( - f"Failed to process message {message_id} — " - "leaving in queue for visibility-timeout retry" - ) - # Do NOT delete — visibility timeout returns it for retry, - # mirroring how Lambda batchItemFailures re-enqueues a record. + with ThreadPoolExecutor( + max_workers=max_workers, thread_name_prefix="sqs-group" + ) as executor: + futures = { + executor.submit(cls._process_group, client, msgs, group_id): group_id + for group_id, msgs in groups.items() + } + for future in as_completed(futures): + group_id = futures[future] + exc = future.exception() + if exc is not None: + cls._log.exception( + f"[group={group_id}] Group processor raised unexpectedly", + exc_info=exc, + ) + else: + cls._log.debug(f"[group={group_id}] Completed successfully") @classmethod def run(cls) -> None: diff --git a/ak-py/tests/test_config.py b/ak-py/tests/test_config.py index ef9037d87..438bdaa8e 100644 --- a/ak-py/tests/test_config.py +++ b/ak-py/tests/test_config.py @@ -113,6 +113,17 @@ def test_session_cache_env(monkeypatch): assert cfg.session.cache.size == 500 +def test_parallel_workers_default(): + cfg = AKConfig() + assert cfg.execution.queues.parallel_workers == 10 + + +def test_parallel_workers_env_override(monkeypatch): + monkeypatch.setenv("AK_EXECUTION__QUEUES__PARALLEL_WORKERS", "20") + cfg = AKConfig() + assert cfg.execution.queues.parallel_workers == 20 + + def test_guardrail_pii_default(): cfg = AKConfig() assert cfg.guardrail.input.pii is True diff --git a/ak-py/tests/test_ecs_sqs_consumer_parallel.py b/ak-py/tests/test_ecs_sqs_consumer_parallel.py new file mode 100644 index 000000000..5c1f80da6 --- /dev/null +++ b/ak-py/tests/test_ecs_sqs_consumer_parallel.py @@ -0,0 +1,200 @@ +import asyncio +from threading import current_thread +from unittest.mock import MagicMock, call, patch + +import pytest + +from agentkernel.deployment.aws.containerized.core.sqs_consumer import ECSSQSConsumer + + +def _make_msg(message_id, group_id=None, receive_count=1): + attrs = {"ApproximateReceiveCount": str(receive_count)} + if group_id: + attrs["MessageGroupId"] = group_id + return { + "MessageId": message_id, + "ReceiptHandle": f"rh-{message_id}", + "Body": "{}", + "Attributes": attrs, + } + + +class _SyncConsumer(ECSSQSConsumer): + process_message = MagicMock() + on_permanent_failure = MagicMock() + + @classmethod + def _get_queue_url(cls): + return "https://sqs.test/sync-queue" + + +class _AsyncConsumer(ECSSQSConsumer): + on_permanent_failure = MagicMock() + + @classmethod + async def process_message(cls, record): + pass + + @classmethod + def _get_queue_url(cls): + return "https://sqs.test/async-queue" + + +@pytest.fixture(autouse=True) +def reset_mocks(): + _SyncConsumer.process_message.reset_mock() + _SyncConsumer.on_permanent_failure.reset_mock() + _AsyncConsumer.on_permanent_failure.reset_mock() + yield + + +class TestGetMessageGroupKey: + def test_returns_message_group_id_when_present(self): + msg = _make_msg("msg-1", group_id="group-A") + assert ECSSQSConsumer._get_message_group_key(msg) == "group-A" + + def test_falls_back_to_message_id_when_no_group(self): + msg = _make_msg("msg-2") + assert ECSSQSConsumer._get_message_group_key(msg) == "msg-2" + + def test_falls_back_to_unknown_when_no_ids(self): + assert ECSSQSConsumer._get_message_group_key({}) == "" + + +class TestGetParallelWorkers: + def test_returns_config_value_when_available(self): + mock_cfg = MagicMock() + mock_cfg.execution.queues.parallel_workers = 20 + with patch("agentkernel.core.config.AKConfig.get", return_value=mock_cfg) as mock_get: + result = ECSSQSConsumer._get_parallel_workers() + mock_get.assert_called_once() # verify AKConfig path was actually hit + assert result == 20 # 20 != default (10), so this can't pass trivially + + def test_returns_default_when_config_unavailable(self): + with patch( + "agentkernel.core.config.AKConfig.get", + side_effect=RuntimeError("config unavailable"), + ) as mock_get: + result = ECSSQSConsumer._get_parallel_workers() + mock_get.assert_called_once() # verify AKConfig.get was attempted before falling back + assert result == ECSSQSConsumer._DEFAULT_PARALLEL_WORKERS + + +class TestProcessBatch: + def test_empty_batch_returns_without_threads(self): + client = MagicMock() + _SyncConsumer._process_batch(client, []) + _SyncConsumer.process_message.assert_not_called() + + def test_three_messages_three_groups_all_processed(self): + client = MagicMock() + msgs = [ + _make_msg("m1", group_id="G1"), + _make_msg("m2", group_id="G2"), + _make_msg("m3", group_id="G3"), + ] + _SyncConsumer._process_batch(client, msgs) + assert _SyncConsumer.process_message.call_count == 3 + assert client.delete_message.call_count == 3 + + def test_two_messages_same_group_processed_in_order(self): + call_order = [] + + def record_call(msg): + call_order.append(msg["MessageId"]) + + _SyncConsumer.process_message.side_effect = record_call + + client = MagicMock() + msgs = [ + _make_msg("first", group_id="G1"), + _make_msg("second", group_id="G1"), + ] + _SyncConsumer._process_batch(client, msgs) + assert call_order == ["first", "second"] + assert client.delete_message.call_count == 2 + + def test_message_exceeds_max_receive_count(self): + client = MagicMock() + msg = _make_msg("m1", group_id="G1", receive_count=_SyncConsumer.max_receive_count + 1) + _SyncConsumer._process_batch(client, [msg]) + _SyncConsumer.on_permanent_failure.assert_called_once_with(msg) + _SyncConsumer.process_message.assert_not_called() + client.delete_message.assert_called_once() + + def test_process_message_raises_does_not_delete(self): + _SyncConsumer.process_message.side_effect = RuntimeError("boom") + client = MagicMock() + msg = _make_msg("m1", group_id="G1") + _SyncConsumer._process_batch(client, [msg]) + client.delete_message.assert_not_called() + _SyncConsumer.process_message.side_effect = None + + def test_failing_group_does_not_affect_other_groups(self): + call_order = [] + + def side_effect(msg): + if msg["MessageId"] == "bad": + raise RuntimeError("intentional failure") + call_order.append(msg["MessageId"]) + + _SyncConsumer.process_message.side_effect = side_effect + + client = MagicMock() + msgs = [ + _make_msg("bad", group_id="G1"), + _make_msg("good", group_id="G2"), + ] + _SyncConsumer._process_batch(client, msgs) + assert "good" in call_order + # bad message was not deleted + deleted_handles = [c.kwargs["ReceiptHandle"] for c in client.delete_message.call_args_list] + assert "rh-bad" not in deleted_handles + assert "rh-good" in deleted_handles + _SyncConsumer.process_message.side_effect = None + + +class TestECSOutputConsumerRegression: + """Verify ECSOutputConsumer's process_message uses the sync dispatch path.""" + + def test_process_message_is_sync_not_async(self): + import inspect + + from agentkernel.deployment.aws.containerized.akoutputconsumer import ECSOutputConsumer + + underlying = getattr( + ECSOutputConsumer.process_message, "__func__", ECSOutputConsumer.process_message + ) + assert not inspect.iscoroutinefunction(underlying), ( + "ECSOutputConsumer.process_message must be sync — " + "changing it to async would require a new event loop per group thread" + ) + + def test_sync_consumer_processes_and_deletes(self): + # Regression: the sync path (no loop) still calls process_message then delete_message + client = MagicMock() + msg = _make_msg("sync-reg", group_id="G1") + _SyncConsumer._process_batch(client, [msg]) + _SyncConsumer.process_message.assert_called_once_with(msg) + client.delete_message.assert_called_once() + + +class TestAsyncAutoDetection: + def test_async_process_message_uses_event_loop(self): + loops_created = [] + original_new_event_loop = asyncio.new_event_loop + + def track_loop(): + loop = original_new_event_loop() + loops_created.append(loop) + return loop + + client = MagicMock() + client.delete_message = MagicMock() + msg = _make_msg("async-msg", group_id="AG1") + + with patch("asyncio.new_event_loop", side_effect=track_loop): + _AsyncConsumer._process_batch(client, [msg]) + + assert len(loops_created) == 1 + client.delete_message.assert_called_once() From 4965e5ce51a2020b98e17b8874f234f76704eda3 Mon Sep 17 00:00:00 2001 From: Lakindu Induwara Kumara Date: Sun, 28 Jun 2026 14:27:00 +0530 Subject: [PATCH 49/90] updates, and refactors --- .../aws/containerized/akagentrunner.py | 2 +- .../aws/containerized/akoutputconsumer.py | 2 +- .../aws/containerized/core/sqs_consumer.py | 74 ++++++++++--------- 3 files changed, 41 insertions(+), 37 deletions(-) diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/akagentrunner.py b/ak-py/src/agentkernel/deployment/aws/containerized/akagentrunner.py index ac7b50ac0..0caf2d227 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/akagentrunner.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/akagentrunner.py @@ -32,7 +32,7 @@ class ECSAgentRunner(ECSSQSConsumer): max_receive_count = _config.execution.queues.input.max_receive_count @classmethod - def _get_queue_url(cls) -> str: + def get_queue_url(cls) -> str: return cls._config.execution.queues.input.url @classmethod diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/akoutputconsumer.py b/ak-py/src/agentkernel/deployment/aws/containerized/akoutputconsumer.py index 37c2f0457..804bcaef1 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/akoutputconsumer.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/akoutputconsumer.py @@ -29,7 +29,7 @@ class ECSOutputConsumer(ECSSQSConsumer): _websocket_handler = None @classmethod - def _get_queue_url(cls) -> str: + def get_queue_url(cls) -> str: return cls._config.execution.queues.output.url @classmethod diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py b/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py index 45b6d471c..9994974aa 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py @@ -33,10 +33,11 @@ class ECSSQSConsumer(ABC): max_receive_count: int = 3 # overridden by classes that inherit this _DEFAULT_PARALLEL_WORKERS: int = 10 _log = logging.getLogger("ak.ecs.sqsconsumer") + _client = None @classmethod @abstractmethod - def _get_queue_url(cls) -> str: + def get_queue_url(cls) -> str: """ Return the SQS queue URL to poll. @@ -44,19 +45,24 @@ def _get_queue_url(cls) -> str: configure the queue externally as in Lambda. """ raise NotImplementedError - + + @classmethod + def _get_client(cls): + if cls._client is None: + cls._client = boto3.client("sqs") + return cls._client + @classmethod - def poll(cls, client) -> list: + def poll(cls) -> list: """ Receive one batch of SQS messages and return them. Override to customise MaxNumberOfMessages, WaitTimeSeconds, or - MessageAttributeNames. Overriding implementations must accept the - boto3 SQS client as the second argument and return a list of raw - boto3 receive_message records. + MessageAttributeNames. Overriding implementations must return a list of + raw boto3 receive_message records. """ - resp = client.receive_message( - QueueUrl=cls._get_queue_url(), + resp = cls._get_client().receive_message( + QueueUrl=cls.get_queue_url(), MaxNumberOfMessages=10, WaitTimeSeconds=20, AttributeNames=["All"], @@ -88,14 +94,14 @@ def on_permanent_failure(cls, record: Dict[str, Any]) -> None: :param record: Raw boto3 receive_message record. """ raise NotImplementedError - + @classmethod - def delete_message(cls, client, msg: dict) -> None: - client.delete_message( - QueueUrl=cls._get_queue_url(), + def delete_message(cls, msg: dict) -> None: + cls._get_client().delete_message( + QueueUrl=cls.get_queue_url(), ReceiptHandle=msg["ReceiptHandle"], ) - + @classmethod def _get_parallel_workers(cls) -> int: try: @@ -105,14 +111,15 @@ def _get_parallel_workers(cls) -> int: return cls._DEFAULT_PARALLEL_WORKERS @staticmethod - def _get_message_group_key(msg: dict) -> str: + def _get_group_key(msg: dict) -> str: group_id = msg.get("Attributes", {}).get("MessageGroupId") if group_id: return group_id return msg.get("MessageId", "") @classmethod - def _process_single(cls, client, msg: dict, group_id: str, loop=None) -> None: + def _process_single(cls, msg: dict, event_loop=None) -> None: + group_id = cls._get_group_key(msg) message_id = msg.get("MessageId", "") receive_count = int(msg.get("Attributes", {}).get("ApproximateReceiveCount", "1")) try: @@ -122,15 +129,15 @@ def _process_single(cls, client, msg: dict, group_id: str, loop=None) -> None: f"max_receive_count ({receive_count} > {cls.max_receive_count})" ) cls.on_permanent_failure(msg) - cls.delete_message(client, msg) + cls.delete_message(msg) return - if loop is not None: - loop.run_until_complete(cls.process_message(msg)) + if event_loop is not None: + event_loop.run_until_complete(cls.process_message(msg)) else: cls.process_message(msg) - cls.delete_message(client, msg) + cls.delete_message(msg) except Exception: cls._log.exception( @@ -140,7 +147,7 @@ def _process_single(cls, client, msg: dict, group_id: str, loop=None) -> None: # Do NOT delete — visibility timeout returns it for retry @classmethod - def _process_group(cls, client, messages: list, group_id: str) -> None: + def _process_group(cls, messages: list) -> None: # @classmethod wraps the underlying function, so inspect on cls.process_message # returns False even for async classmethods. Unwrap __func__ first. underlying_fn = getattr(cls.process_message, "__func__", cls.process_message) @@ -149,19 +156,19 @@ def _process_group(cls, client, messages: list, group_id: str) -> None: if is_async: # Each group thread gets its own event loop — asyncio event loops are # not thread-safe and must never be shared across threads. - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) + new_event_loop = asyncio.new_event_loop() + asyncio.set_event_loop(new_event_loop) try: for msg in messages: - cls._process_single(client, msg, group_id, loop=loop) + cls._process_single(msg, event_loop=new_event_loop) finally: - loop.close() + new_event_loop.close() else: for msg in messages: - cls._process_single(client, msg, group_id) + cls._process_single(msg) @classmethod - def _process_batch(cls, client, messages: List[Dict[str, Any]]) -> None: + def _process_batch(cls, messages: List[Dict[str, Any]]) -> None: """ Group messages by MessageGroupId and dispatch each group to its own thread via ThreadPoolExecutor. Messages within a group execute @@ -175,7 +182,7 @@ def _process_batch(cls, client, messages: List[Dict[str, Any]]) -> None: groups: dict = defaultdict(list) for msg in messages: - key = cls._get_message_group_key(msg) + key = cls._get_group_key(msg) groups[key].append(msg) max_workers = cls._get_parallel_workers() @@ -184,11 +191,9 @@ def _process_batch(cls, client, messages: List[Dict[str, Any]]) -> None: f"{len(groups)} group(s) with max_workers={max_workers}" ) - with ThreadPoolExecutor( - max_workers=max_workers, thread_name_prefix="sqs-group" - ) as executor: + with ThreadPoolExecutor(max_workers=max_workers, thread_name_prefix="sqs-group") as executor: futures = { - executor.submit(cls._process_group, client, msgs, group_id): group_id + executor.submit(cls._process_group, msgs): group_id for group_id, msgs in groups.items() } for future in as_completed(futures): @@ -210,18 +215,17 @@ def run(cls) -> None: Analogous to the Lambda runtime invoking handle(event, context) per batch: the runtime drives Lambda; run() drives the ECS consumer. """ - queue_url = cls._get_queue_url() + queue_url = cls.get_queue_url() if not queue_url: raise ValueError(f"{cls.__name__}: queue URL is required") cls._log.info(f"{cls.__name__} starting — queue: {queue_url}") - client = boto3.client("sqs") while True: try: - messages = cls.poll(client) + messages = cls.poll() except Exception: cls._log.exception("Unexpected error in poll loop — retrying in 5 s") time.sleep(5) continue - cls._process_batch(client, messages) + cls._process_batch(messages) From 3fbe6cec040c64bdc81424f9ec8ca98a9c7f9d81 Mon Sep 17 00:00:00 2001 From: Lakindu Induwara Kumara Date: Sun, 28 Jun 2026 18:49:37 +0530 Subject: [PATCH 50/90] example import fixes --- .../openai-dynamodb-scalable/app_agent_runner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/aws-containerized/openai-dynamodb-scalable/app_agent_runner.py b/examples/aws-containerized/openai-dynamodb-scalable/app_agent_runner.py index 90c46effe..a455ae91c 100644 --- a/examples/aws-containerized/openai-dynamodb-scalable/app_agent_runner.py +++ b/examples/aws-containerized/openai-dynamodb-scalable/app_agent_runner.py @@ -1,4 +1,4 @@ -from agentkernel.deployment.aws.containerized import ECSAgentRunner +from agentkernel.aws import ECSAgentRunner from agentkernel.openai import OpenAIModule from agents import Agent From 8b4e1c79cb67d8eff6292d45f6a70ddef70efbb8 Mon Sep 17 00:00:00 2001 From: Lakindu Induwara Kumara Date: Sun, 28 Jun 2026 19:00:52 +0530 Subject: [PATCH 51/90] example import fix 2 --- .../openai-dynamodb-scalable/app_rest_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/aws-containerized/openai-dynamodb-scalable/app_rest_service.py b/examples/aws-containerized/openai-dynamodb-scalable/app_rest_service.py index 913de87b0..a970c3a18 100644 --- a/examples/aws-containerized/openai-dynamodb-scalable/app_rest_service.py +++ b/examples/aws-containerized/openai-dynamodb-scalable/app_rest_service.py @@ -1,4 +1,4 @@ -from agentkernel.deployment.aws.containerized import ECSIOHandler +from agentkernel.aws import ECSIOHandler runner = ECSIOHandler.run From 0bd220fe37f3dd8007f2222c8aca21ba59fe696f Mon Sep 17 00:00:00 2001 From: Lakindu Induwara Kumara Date: Mon, 29 Jun 2026 18:02:18 +0530 Subject: [PATCH 52/90] docuemntation and skills updates --- ak-deployment/ak-aws/containerized/README.md | 18 +++--- .../skills/ak-cloud-deploy/SKILL.md | 31 ++++----- docs/{ => docs/advanced}/queue-mode-guide.md | 20 +++--- docs/docs/deployment/aws-containerized.md | 63 ++++++++++--------- 4 files changed, 70 insertions(+), 62 deletions(-) rename docs/{ => docs/advanced}/queue-mode-guide.md (96%) diff --git a/ak-deployment/ak-aws/containerized/README.md b/ak-deployment/ak-aws/containerized/README.md index 9c5a76732..d3f8be971 100644 --- a/ak-deployment/ak-aws/containerized/README.md +++ b/ak-deployment/ak-aws/containerized/README.md @@ -97,20 +97,18 @@ The REST service handles HTTP requests while Agent Runner processes the actual a ```hcl module "containerized_agents" { source = "yaalalabs/ak-containerized/aws" - version = "0.5.1" + version = "0.6.0" product_alias = "my-agent" env_alias = "dev" module_name = "chatbot" region = "us-east-1" - # Docker image path - package_path = "./dist" - # REST Service configuration rest_service = { - cpu = 256 - memory = 512 + package_path = "./dist" + cpu = 256 + memory = 512 environment_variables = { OPENAI_API_KEY = var.api_key } @@ -126,17 +124,16 @@ module "containerized_agents" { ```hcl module "containerized_agents" { source = "yaalalabs/ak-containerized/aws" - version = "0.5.1" + version = "0.6.0" product_alias = "my-agent" env_alias = "prod" module_name = "assistant" region = "us-east-1" - package_path = "./dist-rest-service" - # REST Service (handles HTTP requests) rest_service = { + package_path = "./dist-rest-service" cpu = 512 memory = 1024 desired_count = 2 @@ -190,12 +187,13 @@ module "containerized_agents" { ```hcl rest_service = { + package_path = null # Path to build Docker image (required) cpu = 256 # Fargate CPU units (256, 512, 1024, etc.) memory = 512 # Fargate memory in MiB desired_count = 1 # Number of tasks container_port = 8000 # Container port health_check_endpoint = "/health" # Health check path - image_uri = null # Docker image (defaults to package_path) + image_uri = null # Pre-built image URI (alternative to package_path) command = null # Override Docker CMD environment_variables = {} # Service-specific env vars } diff --git a/ak-py/src/agentkernel/skills/ak-cloud-deploy/SKILL.md b/ak-py/src/agentkernel/skills/ak-cloud-deploy/SKILL.md index 700e57012..c70a525c1 100644 --- a/ak-py/src/agentkernel/skills/ak-cloud-deploy/SKILL.md +++ b/ak-py/src/agentkernel/skills/ak-cloud-deploy/SKILL.md @@ -579,18 +579,19 @@ module "containerized_agents" { product_alias = var.product_alias env_alias = var.env_alias module_name = var.module_name - package_path = "../dist" region = var.region product_display_name = "AK ECS Deployment" - ecs_container_port = 8000 - ecs_desired_count = 2 + rest_service = { + package_path = "../dist" + container_port = 8000 + desired_count = 2 + environment_variables = { + OPENAI_API_KEY = var.openai_api_key + } + } create_dynamodb_memory_table = true - - environment_variables = { - OPENAI_API_KEY = var.openai_api_key - } } ``` @@ -604,7 +605,7 @@ Use this for high-throughput or long-running agents. Two separate ECS services s **`app_rest_service.py`** (IO container entrypoint — NO agent definitions here): ```python -from agentkernel.deployment.aws.containerized import ECSIOHandler +from agentkernel.aws import ECSIOHandler runner = ECSIOHandler.run @@ -615,13 +616,15 @@ if __name__ == "__main__": **`app_agent_runner.py`** (Agent Runner container entrypoint): ```python -from agentkernel.deployment.aws import ECSAgentRunner +from agentkernel.aws import ECSAgentRunner from agentkernel.openai import OpenAIModule OpenAIModule([...]) # register agents here only +handler = ECSAgentRunner.run + if __name__ == "__main__": - ECSAgentRunner.run() + handler() ``` **`config.yaml`** (same file included in both images — queue URLs and table names are injected by Terraform): @@ -641,17 +644,15 @@ session: ```hcl module "containerized_agents" { source = "yaalalabs/ak-containerized/aws" - version = "0.5.1" + version = "0.6.0" product_alias = var.product_alias env_alias = var.env_alias module_name = var.module_name region = var.region - # IO container image - package_path = "../dist-rest-service" - rest_service = { + package_path = "../dist-rest-service" cpu = 512 memory = 1024 desired_count = 2 @@ -676,7 +677,7 @@ module "containerized_agents" { cpu = 1024 memory = 2048 desired_count = 1 - image_uri = module.agent_runner_image.docker_image_uri + package_path = "../dist-agent-runner" command = ["python", "app_agent_runner.py"] environment_variables = { OPENAI_API_KEY = var.openai_api_key diff --git a/docs/queue-mode-guide.md b/docs/docs/advanced/queue-mode-guide.md similarity index 96% rename from docs/queue-mode-guide.md rename to docs/docs/advanced/queue-mode-guide.md index e4b704eb7..ea5a38433 100644 --- a/docs/queue-mode-guide.md +++ b/docs/docs/advanced/queue-mode-guide.md @@ -1,4 +1,8 @@ -# Queue Mode — How It Works in Lambda and ECS +--- +sidebar_position: 10 +--- + +# Queue Mode Guide This document explains the queue mode architecture for both Lambda (serverless) and ECS (containerized) deployments. @@ -180,7 +184,7 @@ Identical infrastructure to REST Sync. The difference is purely in `ECSQueueRequ **IO container — `app_rest_service.py`** (no agent definitions): ```python -from agentkernel.deployment.aws.containerized import ECSIOHandler +from agentkernel.aws import ECSIOHandler runner = ECSIOHandler.run @@ -191,13 +195,15 @@ if __name__ == "__main__": **Agent Runner container — `app_agent_runner.py`**: ```python -from agentkernel.deployment.aws import ECSAgentRunner +from agentkernel.aws import ECSAgentRunner from agentkernel.openai import OpenAIModule OpenAIModule([...]) # agent definitions here only +handler = ECSAgentRunner.run + if __name__ == "__main__": - ECSAgentRunner.run() + handler() ``` ### Required AWS Resources @@ -223,8 +229,8 @@ AK_EXECUTION__RESPONSE_STORE__DYNAMODB__TABLE_NAME = Agent Runner container: ``` -AK_EXECUTION__QUEUES__INPUT__URL = -AK_EXECUTION__QUEUES__OUTPUT__URL = +AK_EXECUTION__QUEUES__INPUT__URL = +AK_EXECUTION__QUEUES__OUTPUT__URL = AK_EXECUTION__QUEUES__INPUT__MAX_RECEIVE_COUNT = ``` @@ -241,7 +247,7 @@ policy. The recommended approach is **backlog-per-task target tracking**: `BacklogPerTask` at or below `backlog_target`. The `scaling_config` block in the `yaalalabs/ak-containerized/aws` module provisions -this automatically. See the [containerized README](../ak-deployment/ak-aws/containerized/README.md#auto-scaling) for details. +this automatically. See the [AWS Containerized deployment docs](/docs/deployment/aws-containerized#auto-scaling-for-resilience) for details. ### Key Differences vs Lambda diff --git a/docs/docs/deployment/aws-containerized.md b/docs/docs/deployment/aws-containerized.md index 35cb677ff..55d5eb077 100644 --- a/docs/docs/deployment/aws-containerized.md +++ b/docs/docs/deployment/aws-containerized.md @@ -71,7 +71,7 @@ If either thread crashes, `ThreadRunner` calls `os._exit(1)` so ECS restarts the **Entrypoint — `app_rest_service.py`** (no agent definitions): ```python -from agentkernel.deployment.aws.containerized import ECSIOHandler +from agentkernel.aws import ECSIOHandler runner = ECSIOHandler.run @@ -86,13 +86,15 @@ Extends `ECSSQSConsumer`: polls the Input Queue in a blocking loop, executes the **Entrypoint — `app_agent_runner.py`**: ```python -from agentkernel.deployment.aws import ECSAgentRunner +from agentkernel.aws import ECSAgentRunner from agentkernel.openai import OpenAIModule OpenAIModule([...]) # register agents here only +handler = ECSAgentRunner.run + if __name__ == "__main__": - ECSAgentRunner.run() + handler() ``` ### Terraform @@ -104,27 +106,37 @@ enable_queue_mode = true queue_mode_type = "sync" # or "async" rest_service = { - command = ["python", "app_rest_service.py"] - # ... + package_path = "../dist-rest-service" + command = ["python", "app_rest_service.py"] + cpu = 256 + memory = 512 + desired_count = 1 } agent_runner = { - command = ["python", "app_agent_runner.py"] - image_uri = "..." # separate image with agent definitions - # ... + package_path = "../dist-agent-runner" + command = ["python", "app_agent_runner.py"] + cpu = 1024 + memory = 2048 + desired_count = 1 + environment_variables = { + OPENAI_API_KEY = var.openai_api_key + } } scaling_config = { - enabled = true - min_count = 1 - max_count = 10 - backlog_target = 5 + enabled = true + min_count = 1 + max_count = 10 + backlog_target = 5 + scale_in_cooldown = 180 + scale_out_cooldown = 60 } ``` For the full example see [examples/aws-containerized/openai-dynamodb-scalable](https://github.com/yaalalabs/agent-kernel/tree/develop/examples/aws-containerized/openai-dynamodb-scalable). -For queue mode internals see [queue-mode-guide.md](../../queue-mode-guide.md). +For queue mode internals see [Queue Mode Guide](/docs/advanced/queue-mode-guide). ## Advantages @@ -188,14 +200,18 @@ Application Load Balancer performs continuous health monitoring: 4. Failed tasks replaced automatically 5. Connection draining ensures graceful shutdown -### Auto-Scaling for Resilience (Available soon) +### Auto-Scaling for Resilience ECS Service auto-scaling maintains capacity during failures and load spikes. +In queue mode, the Agent Runner scales automatically based on queue depth using a custom CloudWatch metric (`Custom/ECS/BacklogPerTask`). A Lambda function runs every minute, computes `BacklogPerTask = QueueDepth / max(RunningTasks, 1)`, and a Target Tracking policy adjusts the task count to keep this metric at or below `backlog_target`. + +Enable this in the `scaling_config` block — see [Scalable Queue Mode](#scalable-queue-mode) for configuration details. + **Auto-scaling triggers:** +- Queue backlog per task (queue mode — recommended) - CPU utilization - Memory utilization -- Request count per target - Custom CloudWatch metrics **Benefits:** @@ -257,7 +273,7 @@ docker stop container-id - Load balanced across remaining tasks - Metrics show recovery -[Learn more about fault tolerance →](../core-concepts/fault-tolerance) +[Learn more about fault tolerance →](/docs/core-concepts/fault-tolerance) ## Session Storage @@ -306,22 +322,9 @@ export AK_SESSION__DYNAMODB__TTL=604800 # 7 days - Simplicity and low operational overhead preferred - Variable workload patterns - AWS-native infrastructure +- Moderate latency is acceptable (single-digit milliseconds) [See DynamoDB configuration details →](/docs/core-concepts/session#dynamodb-storage) -``` - -**Benefits:** -- Fully managed, serverless -- Auto-scaling -- No infrastructure to maintain -- Pay-per-use pricing -- No VPC complexity - -**Use when:** -- You want serverless infrastructure -- Moderate latency is acceptable (single-digit milliseconds) -- Simplified infrastructure management -- AWS-native integration preferred **Requirements:** - DynamoDB table with partition key `session_id` (String) and sort key `key` (String) From 8b2c84a432acb4fe530a8509c22b52cc8933fb1b Mon Sep 17 00:00:00 2001 From: Lakindu Induwara Kumara Date: Mon, 29 Jun 2026 19:29:01 +0530 Subject: [PATCH 53/90] version update --- .../openai-dynamodb-scalable/pyproject.toml | 2 +- .../openai-dynamodb-scalable/uv.lock | 22 ++++++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/examples/aws-containerized/openai-dynamodb-scalable/pyproject.toml b/examples/aws-containerized/openai-dynamodb-scalable/pyproject.toml index b198be29c..efa85221c 100644 --- a/examples/aws-containerized/openai-dynamodb-scalable/pyproject.toml +++ b/examples/aws-containerized/openai-dynamodb-scalable/pyproject.toml @@ -5,7 +5,7 @@ description = "Demo of OpenAI Agents SDK based agents running in Agent Kernel on readme = "README.md" requires-python = ">=3.12" dependencies = [ - "agentkernel[openai,api,aws]>=0.5.1" + "agentkernel[openai,api,aws]>=0.6.0" ] [dependency-groups] diff --git a/examples/aws-containerized/openai-dynamodb-scalable/uv.lock b/examples/aws-containerized/openai-dynamodb-scalable/uv.lock index 53b5e719a..7cb9f0229 100644 --- a/examples/aws-containerized/openai-dynamodb-scalable/uv.lock +++ b/examples/aws-containerized/openai-dynamodb-scalable/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 3 +revision = 2 requires-python = ">=3.12" resolution-markers = [ "python_full_version >= '3.15' and sys_platform == 'win32'", @@ -18,7 +18,7 @@ resolution-markers = [ [[package]] name = "agentkernel" -version = "0.5.1" +version = "0.6.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -27,9 +27,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e2/65/f97a7982036dc964850da2fc40a91f4e220cf3bc872588f4217091cb0b31/agentkernel-0.5.1.tar.gz", hash = "sha256:12608b13fd68402da84f36300bc3428bc5ee9423b195711d9cc60c051b76302c", size = 200882, upload-time = "2026-06-09T06:59:47.376Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c8/7c/18a5747d4b73820349455be59a705064fbc6a4e67098cf2ce8c90da24b03/agentkernel-0.6.0.tar.gz", hash = "sha256:6402fdfac32cd58d37b8e2b250c0c1adadbbcf1e8a7a2a1fef6c1cd06742d327", size = 201752, upload-time = "2026-06-24T05:04:51.214Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/aa/5b/ac6c2c3ce8262687d7682f5cebb525977aa2ddd7601692727ed3110e1aaa/agentkernel-0.5.1-py3-none-any.whl", hash = "sha256:2a6f9fd6aab2ef32155adcd290d4a114758b74847cbe094fa387813323df1c2a", size = 284510, upload-time = "2026-06-09T06:59:45.578Z" }, + { url = "https://files.pythonhosted.org/packages/0b/2d/ca32d0bf05dc5e215ee53cd4de96d3215a9377f33b4a54653445acf3f7f1/agentkernel-0.6.0-py3-none-any.whl", hash = "sha256:77271504f2141b4fa06b15bae4047623a2be7ea2c2df49c8b57dc2c7f5217c1e", size = 285399, upload-time = "2026-06-24T05:04:49.276Z" }, ] [package.optional-dependencies] @@ -287,7 +287,7 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "agentkernel", extras = ["openai", "api", "aws"], specifier = ">=0.5.1" }] +requires-dist = [{ name = "agentkernel", extras = ["openai", "api", "aws"], specifier = ">=0.6.0" }] [package.metadata.requires-dev] dev = [ @@ -1012,7 +1012,9 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c4/37/4549f149c9797c21b32c2683c33522af22522099de128b2406672526d005/greenlet-3.5.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:fa4f98af3a528f0c3fd592a26df7f376f93329c8f4d987f6bb979057af8bf5e2", size = 286220, upload-time = "2026-05-20T13:07:28.463Z" }, { url = "https://files.pythonhosted.org/packages/38/ff/a4f436709716965eaab9f36ea7b906c8a927fbe32fb1372a2071d964f6b1/greenlet-3.5.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ffea73584b216150eab159b6d12348fb253e68757974de1e2c40d8a318ac89ed", size = 601585, upload-time = "2026-05-20T14:00:06.141Z" }, { url = "https://files.pythonhosted.org/packages/65/ad/54bc3fcee3ad368a61b19b67d88117f7a8c29727bf71fffdeda81fbd946e/greenlet-3.5.1-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1072b4f9edcc1e192d9283a66a3e68d6b84c561de33a83d7858beb9ba1effe10", size = 614215, upload-time = "2026-05-20T14:05:42.675Z" }, + { url = "https://files.pythonhosted.org/packages/7c/6c/de5b1b388cd2d9fbdfeab324863daba37d54e6e233ddbefd70b385a8c591/greenlet-3.5.1-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:89101bfd5011e069be974903cb3a4e4523845e4ece2d62dcd8d358933c0ef249", size = 620094, upload-time = "2026-05-20T14:09:09.18Z" }, { url = "https://files.pythonhosted.org/packages/40/69/b91cda0647df839483201545913514c2827ebea5e5ccdf931842763bc127/greenlet-3.5.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:add5217d68b31130f0beca584d7fef4878327d2e31642b66618a14eef312b63b", size = 611358, upload-time = "2026-05-20T13:14:26.37Z" }, + { url = "https://files.pythonhosted.org/packages/4a/43/1204baffab8a6476464795a7ccf394a3248d4f22c9f87173a15b36b6d971/greenlet-3.5.1-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:e6cd99ea59dd5d89f0c956606571d79bfe6f68c9eb7f4a4083a41a7f1587edee", size = 422782, upload-time = "2026-05-20T14:01:39.597Z" }, { url = "https://files.pythonhosted.org/packages/59/90/3cf77e080350cd02fa307bb2abf05df48f4482c240275bbd2c203ba8bb1c/greenlet-3.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a5ea42a752d47a145eae922b605cd1634665ac3d5ec1e72402d5048e8d60d207", size = 1570475, upload-time = "2026-05-20T14:02:25.29Z" }, { url = "https://files.pythonhosted.org/packages/65/2c/18cece62045e74598c3c393f70dce4a63f56222015ba29a5d4eeb04f764c/greenlet-3.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c5551170cf4f5ff5623e9af81323751979fee2c731e2287b61f73cd27257b823", size = 1635625, upload-time = "2026-05-20T13:14:34.027Z" }, { url = "https://files.pythonhosted.org/packages/30/f5/310d104ddf41eb5a70f4c268d22508dfb0c3c8e86fec152be34d0d2ed819/greenlet-3.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:3c8bb982ad117d29478ef8f5533e97df21f1e2befd17a299257b0c96d1371c0b", size = 238791, upload-time = "2026-05-20T13:10:39.018Z" }, @@ -1020,7 +1022,9 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/27/69/7f7e5372d998b81001899b1c0823c957aa413ba0f2662e65821611cc31e4/greenlet-3.5.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:51518ff74664078fc51bffcc6fc529b0df5ae58da192691cee765d45ce944a2b", size = 285060, upload-time = "2026-05-20T13:08:51.899Z" }, { url = "https://files.pythonhosted.org/packages/b1/bf/387f9b6b865fd2ae0d0be09e0004827295a01b71be76ed350dd1e28a91a4/greenlet-3.5.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ffdb3c0bb002c99cd8f298957e046c3dbf6006b5b7cdf11a4e19194624a0a0a", size = 604370, upload-time = "2026-05-20T14:00:07.492Z" }, { url = "https://files.pythonhosted.org/packages/32/f5/169ce3d4e4c67291bd18f8cbe0299c9f3e45102c7f1fb3c14780c93e4532/greenlet-3.5.1-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7715a5a2c3378ba602c3a440558261e13a820bb53a82693aacd7b7f6d964e283", size = 616987, upload-time = "2026-05-20T14:05:44.237Z" }, + { url = "https://files.pythonhosted.org/packages/19/ba/c24110c55dffa55aa6e1d98b45310da33801aeba7686ff0190fe5d46fd32/greenlet-3.5.1-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d40a890035c0058cadbdc4af7569800fd28a0e527a0fdbb7b5f9418f176846ce", size = 622911, upload-time = "2026-05-20T14:09:10.598Z" }, { url = "https://files.pythonhosted.org/packages/ee/e5/7f2e41d5273be07e77560d61ea4e56485b4d6c316d2a84518c62d1364061/greenlet-3.5.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc71ff466927a201b08305acac451ebe1aedfcea002f62f1f2f2ac2ac1e6a135", size = 613911, upload-time = "2026-05-20T13:14:27.539Z" }, + { url = "https://files.pythonhosted.org/packages/ec/7b/d20db2e8a5ad6c038702f3179b136f93f0a3d1a21a0c0777f3e470cdf4b2/greenlet-3.5.1-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:67821bb03e4e98664490edb787ff6af501194c29bbee0f5c1dfdcf1dc3d9d436", size = 425228, upload-time = "2026-05-20T14:01:40.837Z" }, { url = "https://files.pythonhosted.org/packages/c5/a4/fbdc67579b73615a1f91615e814303cc71e06128f7baaba87be79b8fb90c/greenlet-3.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cd443683db272ebaaca03af98c0b063ab30db70ea8a31a1559f35e3f7b744ccd", size = 1570689, upload-time = "2026-05-20T14:02:27.225Z" }, { url = "https://files.pythonhosted.org/packages/e6/b4/77abbe35078be39718a46cd49caf16bceb35662f97a34101dca28aa98e47/greenlet-3.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:089fff7a6ce8d9316d1f65ebc00273a56be258c1725b32b94de90a3a979557e1", size = 1635602, upload-time = "2026-05-20T13:14:36.344Z" }, { url = "https://files.pythonhosted.org/packages/37/f7/129f27ca700845b8ee8ca88ce7f43435a1239c2eddb7677fc938822762cf/greenlet-3.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:110a1ca7b49b014b097f6078272c3f4ed31af45b254de5228b79adba879f6af9", size = 238683, upload-time = "2026-05-20T13:11:50.57Z" }, @@ -1028,7 +1032,9 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8a/cb/c62454606daf5640369c94d8a9dd540599b1bfc090e2d2180cb77f4038d2/greenlet-3.5.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d8ab31c9de8651a2facdd5c5bb0011f2380dd1a7af78ce2adf4b56095294fc07", size = 285579, upload-time = "2026-05-20T13:08:56.396Z" }, { url = "https://files.pythonhosted.org/packages/ec/71/c4270398c2eba968a6071af1dfbdcaeee6ec1c24bc8b435b8cc452700da6/greenlet-3.5.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e300185139abc337ade480c327183adf42a875ac7181bfe66d7d4efea31fbea", size = 651106, upload-time = "2026-05-20T14:00:09.448Z" }, { url = "https://files.pythonhosted.org/packages/1a/ab/71e34b78a44ec271fb5f550c17bc46d301ddc5953890d935f270b0dcdb5a/greenlet-3.5.1-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7ffdb990dcaa0234cf9845aead5df2e3c3a8b6507d409274dd87e0d5ab05ffc2", size = 663478, upload-time = "2026-05-20T14:05:45.88Z" }, + { url = "https://files.pythonhosted.org/packages/c6/2d/2d80842910da44f78c286532d084b8a5c3717c844ae80ceb3858738ae89a/greenlet-3.5.1-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c09df69dc1712d131332054a858a3e5cca400967fa3a672e2324fbb0971448c", size = 667767, upload-time = "2026-05-20T14:09:12.15Z" }, { url = "https://files.pythonhosted.org/packages/77/96/4efd6fa5c62c85426a0c19077a586258ebc3a2a146ff2493e4312a697a22/greenlet-3.5.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2f82b3597e9d83b63408affed0b48fd0f54935edac4302237b9a837be0dae33c", size = 660800, upload-time = "2026-05-20T13:14:29.129Z" }, + { url = "https://files.pythonhosted.org/packages/e9/d3/dad2eecedfbb1ed7050a20dcfae40c1442b74bc7423608be2c7e03ee7133/greenlet-3.5.1-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:a4764e0bfc6a4d114c865b32520805c16a990ef5f286a514413b05d5ecd6a23d", size = 470786, upload-time = "2026-05-20T14:01:42.064Z" }, { url = "https://files.pythonhosted.org/packages/7a/e0/6c71401a25cac7000261304e866a2f2cc04dc74810d40e2f118aa4799495/greenlet-3.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c0141e37414c10164e702b8fb1473304221ad98f71600850c6ef7ff4880feba0", size = 1617518, upload-time = "2026-05-20T14:02:28.662Z" }, { url = "https://files.pythonhosted.org/packages/41/26/c5c06643e8c0af9e7bf18e16cb51d0ab7625155f0392e1c9015d66d556cd/greenlet-3.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:50ae25a67bea74ea41fb14b960bc532df73eb713417b2d61892dced82fe8d3bc", size = 1681593, upload-time = "2026-05-20T13:14:39.417Z" }, { url = "https://files.pythonhosted.org/packages/8a/bd/e11a108317485075e68af9d23039619b86b28130c3b50d227d42edece64b/greenlet-3.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:8a17c42330e261299766b75ac1ea32caa437a9453c8f65d16a13140db378ecd3", size = 239800, upload-time = "2026-05-20T13:09:30.128Z" }, @@ -1036,14 +1042,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/90/12/41bf27fde4d3605d3773ae57751eda182b8be2f5398011c041173b1d9534/greenlet-3.5.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:ea8da1e900d758d078810d4255d8c6aa572181896a31ec79d779eb79c3adc9ad", size = 293637, upload-time = "2026-05-20T13:12:35.529Z" }, { url = "https://files.pythonhosted.org/packages/44/44/ba14b23e9757707050c2f397d305bbcae62e5d7cad122f8b6baec5ae4a1f/greenlet-3.5.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a19570c52a21420dcbc94e661994bc325c0b5b11304540fed514586da5dc8f2e", size = 650840, upload-time = "2026-05-20T14:00:11.079Z" }, { url = "https://files.pythonhosted.org/packages/a8/37/5ddc2b686a6844f91abecef43411842426da2e1573f60b49ecf2547f4ae1/greenlet-3.5.1-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3d955c89b75eeca4723d7cc14135f393cd47c32e2a6cb4a8e4c6e760a26b0986", size = 656416, upload-time = "2026-05-20T14:05:47.118Z" }, + { url = "https://files.pythonhosted.org/packages/8c/46/5987dcd1a2570ba84f3b187536b2ca3ae97613387e57f5cfa99df068fe5e/greenlet-3.5.1-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ea37d5a157eb9493820d3792ac4ece28619a394391d2b9f2f78057d396ff0f0f", size = 656607, upload-time = "2026-05-20T14:09:13.949Z" }, { url = "https://files.pythonhosted.org/packages/e1/f0/d17510297c35a2992712f0bf84de3779749999f7d3d63aa1f09db7c62dbe/greenlet-3.5.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2daaaebd1a5aa88c49045b6baf9310b3263796bd88db713edf37cf53e7bb4e", size = 654397, upload-time = "2026-05-20T13:14:30.696Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c1/6da0a9ddcc29d7e51ef14883fa3dc1e53b3f4ffba00582106c7bf55da1d8/greenlet-3.5.1-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:8d8a23250ea3ec7b36de8fa4b541e9e2db3ee82915cc060ab0631609ad8b28de", size = 488287, upload-time = "2026-05-20T14:01:43.143Z" }, { url = "https://files.pythonhosted.org/packages/37/eb/147387705bb89092645b012586e7273cb5ed3c90ef7eaf3a69173eaf0209/greenlet-3.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3bfbd69cc349e43bf3a8ae1c85548ff0718efc887615c2db16c3833d7b0b072d", size = 1614469, upload-time = "2026-05-20T14:02:30.192Z" }, { url = "https://files.pythonhosted.org/packages/a6/4e/37ee0da7732b7aa9896f17e15579a9df34b9fcb9dd494f0adfa749af6623/greenlet-3.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4378720dd888136c27215a0214d32a4d37c3852765d45bc37aad0623423cfd78", size = 1675115, upload-time = "2026-05-20T13:14:40.972Z" }, { url = "https://files.pythonhosted.org/packages/57/f3/97dfcf4a6eb5077f8a672234216fb5923eb89f2cab7081cb10b2cf75b605/greenlet-3.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:45718441607f9325d948db98cbc691276059316d0358c188c246da4e1d4d23d2", size = 245246, upload-time = "2026-05-20T13:12:22.646Z" }, { url = "https://files.pythonhosted.org/packages/5d/73/d7f72e34b582f694f4a9b248162db7b09cc458a259ba8f0c0bfa1a34ea7d/greenlet-3.5.1-cp315-cp315-macosx_11_0_universal2.whl", hash = "sha256:2baee5ca02031757ffe8cc3d69f0cc0aec7065ce362622da74f32d3bcab1c541", size = 285575, upload-time = "2026-05-20T13:12:07.043Z" }, { url = "https://files.pythonhosted.org/packages/df/59/fa9c6e87dc8ad27a95dabe2f29f372b733d05a8a67470f6c901ed9975655/greenlet-3.5.1-cp315-cp315-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b1ec3274918a81d3ea778b9e75b56b72b33f300edb6cf7f3a7fe1dae56683de", size = 656428, upload-time = "2026-05-20T14:00:12.556Z" }, { url = "https://files.pythonhosted.org/packages/f6/f9/e753408871eaa61dfe35e619cfc67512b036fde99893685d50eea9e07146/greenlet-3.5.1-cp315-cp315-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:111e2390ffffc47d5840b01711dd7fac07d4c09283d0283e7f3264b14e284c64", size = 667064, upload-time = "2026-05-20T14:05:48.662Z" }, + { url = "https://files.pythonhosted.org/packages/dc/74/807a047255bf1e09303627c46dc043dca596b6958a354d904f32ab382005/greenlet-3.5.1-cp315-cp315-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:10a9a1c0bfbc93d41156ffcb90c75fbc05544054faf15dcc1fdf9765f8b607f0", size = 672962, upload-time = "2026-05-20T14:09:15.532Z" }, { url = "https://files.pythonhosted.org/packages/96/27/5565b5b40389f1c7753003a07e21892fda8660926787036d5bc0308b8113/greenlet-3.5.1-cp315-cp315-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e630136e905fe5ff43e86945ae41220b6d1470956a39220e708110ac48d01ea5", size = 665697, upload-time = "2026-05-20T13:14:32.943Z" }, + { url = "https://files.pythonhosted.org/packages/76/32/19d4e13225193c29b13e308015223f7d75fd3d8623d49dd19040d2ce8ec1/greenlet-3.5.1-cp315-cp315-manylinux_2_39_riscv64.whl", hash = "sha256:ef08c1567c78074b22d1a200183d52d04a14df447bf70bcbb6a3507a48e776fc", size = 476047, upload-time = "2026-05-20T14:01:44.39Z" }, { url = "https://files.pythonhosted.org/packages/cf/82/e7de4178c0c2d1c9a5a3be3cc0b33e46a85b3ee4a77c071bf7ad8600e079/greenlet-3.5.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:975eac34b44a7077ca4d421348455b94f0f518246a7f14bc6d2fdcfe5b584368", size = 1621256, upload-time = "2026-05-20T14:02:31.91Z" }, { url = "https://files.pythonhosted.org/packages/00/10/f2dddcf7dacac17dfc68691809589adad06135eb28930429cf58a6467a2f/greenlet-3.5.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:9ab3c3a0b2ae6198e67c898dad5215a49f9ae0d0081b3c3ec59f333e39eeca26", size = 1685956, upload-time = "2026-05-20T13:14:42.55Z" }, { url = "https://files.pythonhosted.org/packages/22/17/4a232b32133230ada52f70e9d7f5b65b0caef8772f01849bd8d149e7e4ca/greenlet-3.5.1-cp315-cp315-win_amd64.whl", hash = "sha256:cbfc69be86e10dcfef5b1e6269d1d6926552aa89ee39e1de3353360c1b6989ab", size = 239802, upload-time = "2026-05-20T13:13:15.481Z" }, @@ -1051,7 +1061,9 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7a/57/816d9cff29119da3505b3d6a5e14a8af89006ac36f47f891ff293ee05af1/greenlet-3.5.1-cp315-cp315t-macosx_11_0_universal2.whl", hash = "sha256:a6fdf2433a5441ef9a95464f7c3e674775da1c8c1177fff311cee1acad4626ed", size = 293877, upload-time = "2026-05-20T13:10:19.078Z" }, { url = "https://files.pythonhosted.org/packages/23/a1/59b0a7c7d140ff1a75626680b9a9899b79a9176cab298b394968fb023295/greenlet-3.5.1-cp315-cp315t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7546556f0d649f99f6a361098a55f761181bb2ea12ff150bb16d26092ad88244", size = 655333, upload-time = "2026-05-20T14:00:14.758Z" }, { url = "https://files.pythonhosted.org/packages/72/1b/5efe127597625042218939d01855109f352779050768b670b52edcc16a6c/greenlet-3.5.1-cp315-cp315t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d5ee3ea898009fa898f85f9982255d35278c477bebe185beca249cab42d4526c", size = 659443, upload-time = "2026-05-20T14:05:50.159Z" }, + { url = "https://files.pythonhosted.org/packages/c9/9d/1dcdf7b95ab3cf8c7b6d7277c18a5e167312f2b362ddfcc5d5e6d8d84b43/greenlet-3.5.1-cp315-cp315t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a57b0d05a0448eed231d59c0ceb287dde984551e54cbc51ac2d4865712838e9c", size = 659998, upload-time = "2026-05-20T14:09:16.912Z" }, { url = "https://files.pythonhosted.org/packages/6c/6d/c404246ea4d22d097a7426d0efb5b781bd7eb67715f09e79001bd552ab18/greenlet-3.5.1-cp315-cp315t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5c81f74d204d3edd136ebfd50dce53acbb776995d721a0fe801626cfc93b8cd", size = 658356, upload-time = "2026-05-20T13:14:35.091Z" }, + { url = "https://files.pythonhosted.org/packages/05/7e/c4959664fc231d587d66d8e81f2095e98056ba1954beafdcbe635e251052/greenlet-3.5.1-cp315-cp315t-manylinux_2_39_riscv64.whl", hash = "sha256:b0703c2cef53e01baec47f7a3868009913ad71ec678bbecb42a6f40895e4ce62", size = 494470, upload-time = "2026-05-20T14:01:45.611Z" }, { url = "https://files.pythonhosted.org/packages/51/02/f8ee37fb6d2219329f350af241c27fcf12df57e723d11f6fc6d3bacdadaa/greenlet-3.5.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:2c18ef16bf6d4dd410e4dd52996888ea1497be26892fe5bbc73580aba4287b8e", size = 1619216, upload-time = "2026-05-20T14:02:33.403Z" }, { url = "https://files.pythonhosted.org/packages/93/c5/3dc9475ace2c7a3680da12372cddd7f1ac874eb410a1ac48d3e9dab83782/greenlet-3.5.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:17d86354f0ae6b61bf9be5148d0dd34e06c3cb7c602c671f79f29ac3b150e659", size = 1678427, upload-time = "2026-05-20T13:14:43.71Z" }, { url = "https://files.pythonhosted.org/packages/df/4e/750c15c317a41ffb36f0bf40b933e3d744a7dede61889f74443ea69690cf/greenlet-3.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:e7516cf6ae6b8a582c2770a0caed47b8a48373ed732c33d69a72913ae6ac923e", size = 245225, upload-time = "2026-05-20T13:13:59.366Z" }, From 3793a8eadd4be9d42102fb9b4121f77b1c6d7ce8 Mon Sep 17 00:00:00 2001 From: Lakindu Induwara Kumara Date: Mon, 29 Jun 2026 19:56:21 +0530 Subject: [PATCH 54/90] bugfix --- ak-deployment/ak-aws/containerized/state.tf | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/ak-deployment/ak-aws/containerized/state.tf b/ak-deployment/ak-aws/containerized/state.tf index a10fafb4d..7d95c4f07 100644 --- a/ak-deployment/ak-aws/containerized/state.tf +++ b/ak-deployment/ak-aws/containerized/state.tf @@ -49,9 +49,6 @@ locals { } } : {} gateway_endpoints_map = merge(local.default_gateway_map, local.mcp_gateway_map, local.user_gateway_map) - - # Resolved ECR image URI — uses external if provided, otherwise falls back to the built image - ecr_image_uri = var.ecr_image_uri != null ? var.ecr_image_uri : module.docker_image[0].docker_image_uri } module "vpc" { @@ -79,7 +76,7 @@ module "redis" { } module "docker_image" { - count = var.ecr_image_uri == null ? 1 : 0 + count = 1 source = "yaalalabs/ak-common/aws//modules/ecr" version = "0.6.0" env_alias = var.env_alias From 40fdeb7da2831ae6311546e10816661fb9eff3a3 Mon Sep 17 00:00:00 2001 From: Lakindu Induwara Kumara Date: Wed, 1 Jul 2026 11:30:34 +0530 Subject: [PATCH 55/90] threadrunner updates --- .../aws/containerized/core/__init__.py | 1 - .../aws/containerized/core/sqs_consumer.py | 31 ++++++------ .../aws/containerized/core/thread_runner.py | 47 ----------------- .../aws/containerized/ecs_io_handler.py | 19 +++++-- .../agentkernel/deployment/common/__init__.py | 1 + .../deployment/common/thread_runner.py | 50 +++++++++++++++++++ 6 files changed, 80 insertions(+), 69 deletions(-) delete mode 100644 ak-py/src/agentkernel/deployment/aws/containerized/core/thread_runner.py create mode 100644 ak-py/src/agentkernel/deployment/common/thread_runner.py diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/core/__init__.py b/ak-py/src/agentkernel/deployment/aws/containerized/core/__init__.py index 50bbb8e00..927b7714e 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/core/__init__.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/core/__init__.py @@ -1,2 +1 @@ from .sqs_consumer import ECSSQSConsumer -from .thread_runner import ThreadRunner diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py b/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py index 9994974aa..0e920914d 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py @@ -4,11 +4,12 @@ import time from abc import ABC, abstractmethod from collections import defaultdict -from concurrent.futures import ThreadPoolExecutor, as_completed from typing import Any, Dict, List import boto3 +from ....common import ThreadRunner + class ECSSQSConsumer(ABC): """ @@ -171,7 +172,7 @@ def _process_group(cls, messages: list) -> None: def _process_batch(cls, messages: List[Dict[str, Any]]) -> None: """ Group messages by MessageGroupId and dispatch each group to its own - thread via ThreadPoolExecutor. Messages within a group execute + thread via ThreadRunner.run. Messages within a group execute sequentially (preserving FIFO); messages across groups run concurrently. On exception within a group: logs and leaves the message in the queue @@ -191,21 +192,17 @@ def _process_batch(cls, messages: List[Dict[str, Any]]) -> None: f"{len(groups)} group(s) with max_workers={max_workers}" ) - with ThreadPoolExecutor(max_workers=max_workers, thread_name_prefix="sqs-group") as executor: - futures = { - executor.submit(cls._process_group, msgs): group_id - for group_id, msgs in groups.items() - } - for future in as_completed(futures): - group_id = futures[future] - exc = future.exception() - if exc is not None: - cls._log.exception( - f"[group={group_id}] Group processor raised unexpectedly", - exc_info=exc, - ) - else: - cls._log.debug(f"[group={group_id}] Completed successfully") + ThreadRunner.run( + [ + ThreadRunner.Task( + execution_function=cls._process_group, + item=msgs, + thread_name=f"sqs-group-{gid}", + ) + for gid, msgs in groups.items() + ], + max_workers=max_workers, + ) @classmethod def run(cls) -> None: diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/core/thread_runner.py b/ak-py/src/agentkernel/deployment/aws/containerized/core/thread_runner.py deleted file mode 100644 index 1eecfc8be..000000000 --- a/ak-py/src/agentkernel/deployment/aws/containerized/core/thread_runner.py +++ /dev/null @@ -1,47 +0,0 @@ -import logging -import os -from concurrent.futures import ThreadPoolExecutor, wait, FIRST_COMPLETED -from typing import Callable - -_log = logging.getLogger("ak.thread_runner") - - -class ThreadRunner: - """ - Runs multiple callables as concurrent threads. - - If a thread raises, logs the exception. When exit_on_failure=True (default), - also calls os._exit(1) so the container restarts cleanly via ECS. - If a thread returns normally (no exception), it is logged as an unexpected - exit but os._exit is NOT called regardless of exit_on_failure. - """ - - @staticmethod - def run( - *targets: Callable, - max_workers: int | None = None, - thread_names: list[str] | None = None, - exit_on_failure: bool = True, - ) -> None: - names = thread_names or [getattr(t, "__name__", f"thread-{i}") for i, t in enumerate(targets)] - - with ThreadPoolExecutor(max_workers=max_workers or len(targets)) as executor: - futures = {executor.submit(t): name for t, name in zip(targets, names)} - - done, _ = wait(futures.keys(), return_when=FIRST_COMPLETED) - - failed = False - for future in done: - name = futures[future] - exc = future.exception() - if exc is not None: - _log.exception(f"[{name}] crashed", exc_info=exc) - failed = True - else: - _log.error(f"[{name}] exited unexpectedly (no exception)") - - # os._exit must fire here, inside the `with` block, before - # executor.shutdown(wait=True) blocks indefinitely on the other - # thread (which is an infinite poll loop and never returns). - if failed and exit_on_failure: - os._exit(1) diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/ecs_io_handler.py b/ak-py/src/agentkernel/deployment/aws/containerized/ecs_io_handler.py index 820aa0049..9cc060ce5 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/ecs_io_handler.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/ecs_io_handler.py @@ -2,7 +2,7 @@ from ....api.http import RESTAPI from ....core.config import AKConfig -from .core import ThreadRunner +from ...common import ThreadRunner from .akoutputconsumer import ECSOutputConsumer @@ -35,7 +35,18 @@ def run(cls) -> None: cls._log.info(f"ECSIOHandler starting — mode={mode}") ThreadRunner.run( - lambda: RESTAPI.run(handlers=[ECSQueueRequestHandler()]), # lambda needed here to wrap the function so that it turns into a callable, because otherwise the rest api will be run here itself - ECSOutputConsumer.run, - thread_names=["rest-api", "output-queue-consumer"], + [ + ThreadRunner.Task( + execution_function=lambda: RESTAPI.run( + handlers=[ECSQueueRequestHandler()] + ), # lambda needed here to wrap the function so that it turns into a callable, because otherwise the rest api will be run here itself + thread_name="rest-api", + stop_all_on_failure=True, + ), + ThreadRunner.Task( + execution_function=lambda: ECSOutputConsumer.run(), + thread_name="output-queue-consumer", + stop_all_on_failure=True, + ), + ] ) diff --git a/ak-py/src/agentkernel/deployment/common/__init__.py b/ak-py/src/agentkernel/deployment/common/__init__.py index e69de29bb..9b0e13f8f 100644 --- a/ak-py/src/agentkernel/deployment/common/__init__.py +++ b/ak-py/src/agentkernel/deployment/common/__init__.py @@ -0,0 +1 @@ +from .thread_runner import ThreadRunner diff --git a/ak-py/src/agentkernel/deployment/common/thread_runner.py b/ak-py/src/agentkernel/deployment/common/thread_runner.py new file mode 100644 index 000000000..604ea0e4b --- /dev/null +++ b/ak-py/src/agentkernel/deployment/common/thread_runner.py @@ -0,0 +1,50 @@ +import logging +import os +from concurrent.futures import ThreadPoolExecutor, as_completed +from dataclasses import dataclass +from typing import Any, Callable + +_log = logging.getLogger("ak.thread_runner") + + +class ThreadRunner: + """ + Runs Task instances concurrently — each as task.execution_function(task.item) — and reacts to + each completion in turn. + """ + + @dataclass + class Task: + execution_function: Callable + thread_name: str + item: Any = None + stop_task_on_failure: bool = True + stop_all_on_failure: bool = False + + def __post_init__(self) -> None: + if self.stop_all_on_failure and not self.stop_task_on_failure: + raise ValueError("stop_all_on_failure=True requires stop_task_on_failure=True") + + def _submit(self, executor: ThreadPoolExecutor): + args = () if self.item is None else (self.item,) + return executor.submit(self.execution_function, *args) + + @staticmethod + def run(tasks: list[Task], max_workers: int | None = None) -> None: + if not tasks: + return + + with ThreadPoolExecutor(max_workers=max_workers or len(tasks)) as executor: + futures = {task._submit(executor): task for task in tasks} + for future in as_completed(futures): + task = futures[future] + exc = future.exception() + if exc is not None: + if task.stop_task_on_failure: + _log.exception(f"[{task.thread_name}] raised unexpectedly", exc_info=exc) + if task.stop_all_on_failure: + os._exit(1) # stops the entire container + else: + _log.debug(f"[{task.thread_name}] raised (stop_task_on_failure=False, ignoring)") + else: + _log.debug(f"[{task.thread_name}] completed") From c2477c318605e749c3d4a31fc9a6df8e6972912f Mon Sep 17 00:00:00 2001 From: Lakindu Induwara Kumara Date: Wed, 1 Jul 2026 11:34:10 +0530 Subject: [PATCH 56/90] updates to thread runner --- .../deployment/aws/containerized/core/sqs_consumer.py | 7 +++---- .../deployment/aws/containerized/ecs_io_handler.py | 6 +++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py b/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py index 0e920914d..4c84a1238 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py @@ -193,15 +193,14 @@ def _process_batch(cls, messages: List[Dict[str, Any]]) -> None: ) ThreadRunner.run( - [ + tasks = [ ThreadRunner.Task( execution_function=cls._process_group, item=msgs, thread_name=f"sqs-group-{gid}", - ) - for gid, msgs in groups.items() + ) for gid, msgs in groups.items() ], - max_workers=max_workers, + max_workers = max_workers, ) @classmethod diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/ecs_io_handler.py b/ak-py/src/agentkernel/deployment/aws/containerized/ecs_io_handler.py index 9cc060ce5..e129aef67 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/ecs_io_handler.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/ecs_io_handler.py @@ -35,18 +35,18 @@ def run(cls) -> None: cls._log.info(f"ECSIOHandler starting — mode={mode}") ThreadRunner.run( - [ + tasks=[ ThreadRunner.Task( execution_function=lambda: RESTAPI.run( handlers=[ECSQueueRequestHandler()] ), # lambda needed here to wrap the function so that it turns into a callable, because otherwise the rest api will be run here itself thread_name="rest-api", - stop_all_on_failure=True, ), ThreadRunner.Task( execution_function=lambda: ECSOutputConsumer.run(), thread_name="output-queue-consumer", stop_all_on_failure=True, ), - ] + ], + max_workers=2 ) From 8af89525e9bef6c3d25c72312b0db2582e44c283 Mon Sep 17 00:00:00 2001 From: Lakindu Induwara Kumara Date: Wed, 1 Jul 2026 12:36:51 +0530 Subject: [PATCH 57/90] logs update --- ak-py/src/agentkernel/deployment/common/thread_runner.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ak-py/src/agentkernel/deployment/common/thread_runner.py b/ak-py/src/agentkernel/deployment/common/thread_runner.py index 604ea0e4b..445618754 100644 --- a/ak-py/src/agentkernel/deployment/common/thread_runner.py +++ b/ak-py/src/agentkernel/deployment/common/thread_runner.py @@ -27,6 +27,7 @@ def __post_init__(self) -> None: def _submit(self, executor: ThreadPoolExecutor): args = () if self.item is None else (self.item,) + _log.info(f"[{self.thread_name}] submitting to executer") return executor.submit(self.execution_function, *args) @staticmethod From 54ce00b16114a491894d26b175e6c65a66c1e733 Mon Sep 17 00:00:00 2001 From: Lakindu Induwara Kumara Date: Wed, 1 Jul 2026 13:02:06 +0530 Subject: [PATCH 58/90] logs updates --- .../aws/containerized/core/sqs_consumer.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py b/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py index 4c84a1238..d8ea3e7c7 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py @@ -62,6 +62,7 @@ def poll(cls) -> list: MessageAttributeNames. Overriding implementations must return a list of raw boto3 receive_message records. """ + cls._log.debug("Polling queue for messages") resp = cls._get_client().receive_message( QueueUrl=cls.get_queue_url(), MaxNumberOfMessages=10, @@ -69,7 +70,9 @@ def poll(cls) -> list: AttributeNames=["All"], MessageAttributeNames=["All"], ) - return resp.get("Messages", []) + messages = resp.get("Messages", []) + cls._log.debug(f"Poll received {len(messages)} message(s)") + return messages @classmethod @abstractmethod @@ -123,6 +126,7 @@ def _process_single(cls, msg: dict, event_loop=None) -> None: group_id = cls._get_group_key(msg) message_id = msg.get("MessageId", "") receive_count = int(msg.get("Attributes", {}).get("ApproximateReceiveCount", "1")) + cls._log.debug( f"[group={group_id}] Processing message {message_id} (receive_count={receive_count})") try: if receive_count > cls.max_receive_count: cls._log.warning( @@ -139,12 +143,10 @@ def _process_single(cls, msg: dict, event_loop=None) -> None: cls.process_message(msg) cls.delete_message(msg) + cls._log.debug(f"[group={group_id}] Processed and deleted message {message_id}") except Exception: - cls._log.exception( - f"[group={group_id}] Failed to process message {message_id} " - "— leaving in queue for visibility-timeout retry" - ) + cls._log.exception(f"[group={group_id}] Failed to process message {message_id} — leaving in queue for visibility-timeout retry") # Do NOT delete — visibility timeout returns it for retry @classmethod @@ -153,6 +155,7 @@ def _process_group(cls, messages: list) -> None: # returns False even for async classmethods. Unwrap __func__ first. underlying_fn = getattr(cls.process_message, "__func__", cls.process_message) is_async = inspect.iscoroutinefunction(underlying_fn) + cls._log.debug(f"Processing group of {len(messages)} message(s) (is_async={is_async})") if is_async: # Each group thread gets its own event loop — asyncio event loops are @@ -168,6 +171,8 @@ def _process_group(cls, messages: list) -> None: for msg in messages: cls._process_single(msg) + cls._log.debug(f"Finished processing group of {len(messages)} message(s)") + @classmethod def _process_batch(cls, messages: List[Dict[str, Any]]) -> None: """ From 8b5c44c688867399bfdd8a0c0c922fa880b86220 Mon Sep 17 00:00:00 2001 From: Lakindu Induwara Kumara Date: Wed, 1 Jul 2026 14:06:49 +0530 Subject: [PATCH 59/90] some logs updates and normal updates --- .../deployment/aws/containerized/core/sqs_consumer.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py b/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py index d8ea3e7c7..fbebb1b69 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py @@ -62,7 +62,6 @@ def poll(cls) -> list: MessageAttributeNames. Overriding implementations must return a list of raw boto3 receive_message records. """ - cls._log.debug("Polling queue for messages") resp = cls._get_client().receive_message( QueueUrl=cls.get_queue_url(), MaxNumberOfMessages=10, @@ -71,7 +70,6 @@ def poll(cls) -> list: MessageAttributeNames=["All"], ) messages = resp.get("Messages", []) - cls._log.debug(f"Poll received {len(messages)} message(s)") return messages @classmethod @@ -228,5 +226,7 @@ def run(cls) -> None: cls._log.exception("Unexpected error in poll loop — retrying in 5 s") time.sleep(5) continue - - cls._process_batch(messages) + if messages: + cls._log.debug(f"Processing batch of {len(messages)} message(s)") + cls._process_batch(messages) + cls._log.debug("Batch processing complete") From bbcc821e5a995ff11d29c53dc57eba97d4bbec85 Mon Sep 17 00:00:00 2001 From: Lakindu Induwara Kumara Date: Wed, 1 Jul 2026 16:23:02 +0530 Subject: [PATCH 60/90] updated poll method params --- .../deployment/aws/containerized/core/sqs_consumer.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py b/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py index fbebb1b69..e8d319cc2 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py @@ -64,8 +64,6 @@ def poll(cls) -> list: """ resp = cls._get_client().receive_message( QueueUrl=cls.get_queue_url(), - MaxNumberOfMessages=10, - WaitTimeSeconds=20, AttributeNames=["All"], MessageAttributeNames=["All"], ) From fd8de3e43b81717b0747645450f6704e720a18fd Mon Sep 17 00:00:00 2001 From: Lakindu Induwara Kumara Date: Thu, 2 Jul 2026 11:48:25 +0530 Subject: [PATCH 61/90] updates to consumers --- ak-py/src/agentkernel/core/config.py | 9 +- .../aws/containerized/core/sqs_consumer.py | 133 ++++--------- ak-py/tests/test_config.py | 10 +- ak-py/tests/test_ecs_sqs_consumer_parallel.py | 179 ++++++------------ 4 files changed, 105 insertions(+), 226 deletions(-) diff --git a/ak-py/src/agentkernel/core/config.py b/ak-py/src/agentkernel/core/config.py index 64627a049..4c473cd6a 100644 --- a/ak-py/src/agentkernel/core/config.py +++ b/ak-py/src/agentkernel/core/config.py @@ -268,14 +268,11 @@ class _OutputQueueConfig(BaseModel): class _QueuesConfig(BaseModel): input: _InputQueueConfig = Field(default_factory=_InputQueueConfig, description="Input SQS queue configuration for async execution mode") output: _OutputQueueConfig = Field(default_factory=_OutputQueueConfig, description="Output SQS queue configuration for async execution mode") - parallel_workers: int = Field( + no_of_consumers: int = Field( default=10, description=( - "Max concurrent threads for processing MessageGroups within a batch. " - "Each distinct MessageGroupId gets its own thread; messages within a " - "group are still sequential. Tune based on LLM API rate limits and " - "downstream DB connection pool size. Override via env var " - "AK_EXECUTION__QUEUES__PARALLEL_WORKERS." + "Number of independent consumer threads that each poll the input queue " + "in a continuous loop. Override via env var AK_EXECUTION__QUEUES__NO_OF_CONSUMERS." ), ) diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py b/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py index e8d319cc2..69ab9276b 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py @@ -3,11 +3,10 @@ import logging import time from abc import ABC, abstractmethod -from collections import defaultdict -from typing import Any, Dict, List - +from typing import Any, Dict import boto3 +from .....core.config import AKConfig from ....common import ThreadRunner @@ -103,128 +102,80 @@ def delete_message(cls, msg: dict) -> None: ) @classmethod - def _get_parallel_workers(cls) -> int: + def _get_num_consumers(cls) -> int: try: - from .....core.config import AKConfig - return AKConfig.get().execution.queues.parallel_workers + return AKConfig.get().execution.queues.no_of_consumers except Exception: return cls._DEFAULT_PARALLEL_WORKERS - @staticmethod - def _get_group_key(msg: dict) -> str: - group_id = msg.get("Attributes", {}).get("MessageGroupId") - if group_id: - return group_id - return msg.get("MessageId", "") - @classmethod - def _process_single(cls, msg: dict, event_loop=None) -> None: - group_id = cls._get_group_key(msg) + def _process_single(cls, msg: dict) -> None: message_id = msg.get("MessageId", "") receive_count = int(msg.get("Attributes", {}).get("ApproximateReceiveCount", "1")) - cls._log.debug( f"[group={group_id}] Processing message {message_id} (receive_count={receive_count})") + cls._log.debug(f"Processing message {message_id} (receive_count={receive_count})") try: if receive_count > cls.max_receive_count: cls._log.warning( - f"[group={group_id}] Message {message_id} exceeded " - f"max_receive_count ({receive_count} > {cls.max_receive_count})" + f"Message {message_id} exceeded max_receive_count " + f"({receive_count} > {cls.max_receive_count})" ) cls.on_permanent_failure(msg) cls.delete_message(msg) return - if event_loop is not None: - event_loop.run_until_complete(cls.process_message(msg)) + underlying_fn = getattr(cls.process_message, "__func__", cls.process_message) + if inspect.iscoroutinefunction(underlying_fn): + asyncio.run(cls.process_message(msg)) else: cls.process_message(msg) cls.delete_message(msg) - cls._log.debug(f"[group={group_id}] Processed and deleted message {message_id}") + cls._log.debug(f"Processed and deleted message {message_id}") except Exception: - cls._log.exception(f"[group={group_id}] Failed to process message {message_id} — leaving in queue for visibility-timeout retry") + cls._log.exception( + f"Failed to process message {message_id} — leaving in queue for visibility-timeout retry" + ) # Do NOT delete — visibility timeout returns it for retry @classmethod - def _process_group(cls, messages: list) -> None: - # @classmethod wraps the underlying function, so inspect on cls.process_message - # returns False even for async classmethods. Unwrap __func__ first. - underlying_fn = getattr(cls.process_message, "__func__", cls.process_message) - is_async = inspect.iscoroutinefunction(underlying_fn) - cls._log.debug(f"Processing group of {len(messages)} message(s) (is_async={is_async})") - - if is_async: - # Each group thread gets its own event loop — asyncio event loops are - # not thread-safe and must never be shared across threads. - new_event_loop = asyncio.new_event_loop() - asyncio.set_event_loop(new_event_loop) + def _consumer_loop(cls) -> None: + while True: try: - for msg in messages: - cls._process_single(msg, event_loop=new_event_loop) - finally: - new_event_loop.close() - else: - for msg in messages: - cls._process_single(msg) - - cls._log.debug(f"Finished processing group of {len(messages)} message(s)") - - @classmethod - def _process_batch(cls, messages: List[Dict[str, Any]]) -> None: - """ - Group messages by MessageGroupId and dispatch each group to its own - thread via ThreadRunner.run. Messages within a group execute - sequentially (preserving FIFO); messages across groups run concurrently. - - On exception within a group: logs and leaves the message in the queue - so the visibility timeout returns it for retry. - """ - if not messages: - return - - groups: dict = defaultdict(list) - for msg in messages: - key = cls._get_group_key(msg) - groups[key].append(msg) - - max_workers = cls._get_parallel_workers() - cls._log.info( - f"{cls.__name__} dispatching {len(messages)} messages across " - f"{len(groups)} group(s) with max_workers={max_workers}" - ) + messages = cls.poll() + except Exception: + cls._log.exception("Unexpected error in poll loop — retrying in 5 s") + time.sleep(5) + continue - ThreadRunner.run( - tasks = [ - ThreadRunner.Task( - execution_function=cls._process_group, - item=msgs, - thread_name=f"sqs-group-{gid}", - ) for gid, msgs in groups.items() - ], - max_workers = max_workers, - ) + if messages: + cls._log.debug(f"Processing batch of {len(messages)} message(s)") + for msg in messages: + cls._process_single(msg) @classmethod def run(cls) -> None: """ Block forever, polling the queue. Call as the container entry-point. - Analogous to the Lambda runtime invoking handle(event, context) per - batch: the runtime drives Lambda; run() drives the ECS consumer. + Starts `no_of_consumers` long-lived threads, each independently + polling and processing messages in a loop. """ queue_url = cls.get_queue_url() if not queue_url: raise ValueError(f"{cls.__name__}: queue URL is required") - cls._log.info(f"{cls.__name__} starting — queue: {queue_url}") - while True: - try: - messages = cls.poll() - except Exception: - cls._log.exception("Unexpected error in poll loop — retrying in 5 s") - time.sleep(5) - continue - if messages: - cls._log.debug(f"Processing batch of {len(messages)} message(s)") - cls._process_batch(messages) - cls._log.debug("Batch processing complete") + num_consumers = cls._get_num_consumers() + cls._log.info(f"{cls.__name__} starting — queue: {queue_url}, consumers: {num_consumers}") + + ThreadRunner.run( + tasks=[ + ThreadRunner.Task( + execution_function=cls._consumer_loop, + thread_name=f"ar-sqs-consumer-{i}", + stop_all_on_failure=True, + ) + for i in range(num_consumers) + ], + max_workers=num_consumers, + ) diff --git a/ak-py/tests/test_config.py b/ak-py/tests/test_config.py index 438bdaa8e..58ee5a71e 100644 --- a/ak-py/tests/test_config.py +++ b/ak-py/tests/test_config.py @@ -113,15 +113,15 @@ def test_session_cache_env(monkeypatch): assert cfg.session.cache.size == 500 -def test_parallel_workers_default(): +def test_no_of_consumers_default(): cfg = AKConfig() - assert cfg.execution.queues.parallel_workers == 10 + assert cfg.execution.queues.no_of_consumers == 10 -def test_parallel_workers_env_override(monkeypatch): - monkeypatch.setenv("AK_EXECUTION__QUEUES__PARALLEL_WORKERS", "20") +def test_no_of_consumers_env_override(monkeypatch): + monkeypatch.setenv("AK_EXECUTION__QUEUES__NO_OF_CONSUMERS", "20") cfg = AKConfig() - assert cfg.execution.queues.parallel_workers == 20 + assert cfg.execution.queues.no_of_consumers == 20 def test_guardrail_pii_default(): diff --git a/ak-py/tests/test_ecs_sqs_consumer_parallel.py b/ak-py/tests/test_ecs_sqs_consumer_parallel.py index 5c1f80da6..1b93cb734 100644 --- a/ak-py/tests/test_ecs_sqs_consumer_parallel.py +++ b/ak-py/tests/test_ecs_sqs_consumer_parallel.py @@ -1,42 +1,39 @@ -import asyncio -from threading import current_thread -from unittest.mock import MagicMock, call, patch +from unittest.mock import MagicMock, patch import pytest from agentkernel.deployment.aws.containerized.core.sqs_consumer import ECSSQSConsumer -def _make_msg(message_id, group_id=None, receive_count=1): - attrs = {"ApproximateReceiveCount": str(receive_count)} - if group_id: - attrs["MessageGroupId"] = group_id +def _make_msg(message_id, receive_count=1): return { "MessageId": message_id, "ReceiptHandle": f"rh-{message_id}", "Body": "{}", - "Attributes": attrs, + "Attributes": {"ApproximateReceiveCount": str(receive_count)}, } class _SyncConsumer(ECSSQSConsumer): process_message = MagicMock() on_permanent_failure = MagicMock() + delete_message = MagicMock() @classmethod - def _get_queue_url(cls): + def get_queue_url(cls): return "https://sqs.test/sync-queue" class _AsyncConsumer(ECSSQSConsumer): on_permanent_failure = MagicMock() + delete_message = MagicMock() @classmethod async def process_message(cls, record): pass @classmethod - def _get_queue_url(cls): + def get_queue_url(cls): return "https://sqs.test/async-queue" @@ -44,114 +41,78 @@ def _get_queue_url(cls): def reset_mocks(): _SyncConsumer.process_message.reset_mock() _SyncConsumer.on_permanent_failure.reset_mock() + _SyncConsumer.delete_message.reset_mock() _AsyncConsumer.on_permanent_failure.reset_mock() + _AsyncConsumer.delete_message.reset_mock() yield -class TestGetMessageGroupKey: - def test_returns_message_group_id_when_present(self): - msg = _make_msg("msg-1", group_id="group-A") - assert ECSSQSConsumer._get_message_group_key(msg) == "group-A" - - def test_falls_back_to_message_id_when_no_group(self): - msg = _make_msg("msg-2") - assert ECSSQSConsumer._get_message_group_key(msg) == "msg-2" - - def test_falls_back_to_unknown_when_no_ids(self): - assert ECSSQSConsumer._get_message_group_key({}) == "" - - -class TestGetParallelWorkers: +class TestGetNumConsumers: def test_returns_config_value_when_available(self): mock_cfg = MagicMock() - mock_cfg.execution.queues.parallel_workers = 20 + mock_cfg.execution.queues.no_of_consumers = 20 with patch("agentkernel.core.config.AKConfig.get", return_value=mock_cfg) as mock_get: - result = ECSSQSConsumer._get_parallel_workers() - mock_get.assert_called_once() # verify AKConfig path was actually hit - assert result == 20 # 20 != default (10), so this can't pass trivially + result = ECSSQSConsumer._get_num_consumers() + mock_get.assert_called_once() + assert result == 20 def test_returns_default_when_config_unavailable(self): with patch( "agentkernel.core.config.AKConfig.get", side_effect=RuntimeError("config unavailable"), ) as mock_get: - result = ECSSQSConsumer._get_parallel_workers() - mock_get.assert_called_once() # verify AKConfig.get was attempted before falling back + result = ECSSQSConsumer._get_num_consumers() + mock_get.assert_called_once() assert result == ECSSQSConsumer._DEFAULT_PARALLEL_WORKERS -class TestProcessBatch: - def test_empty_batch_returns_without_threads(self): - client = MagicMock() - _SyncConsumer._process_batch(client, []) - _SyncConsumer.process_message.assert_not_called() - - def test_three_messages_three_groups_all_processed(self): - client = MagicMock() - msgs = [ - _make_msg("m1", group_id="G1"), - _make_msg("m2", group_id="G2"), - _make_msg("m3", group_id="G3"), - ] - _SyncConsumer._process_batch(client, msgs) - assert _SyncConsumer.process_message.call_count == 3 - assert client.delete_message.call_count == 3 - - def test_two_messages_same_group_processed_in_order(self): - call_order = [] - - def record_call(msg): - call_order.append(msg["MessageId"]) - - _SyncConsumer.process_message.side_effect = record_call - - client = MagicMock() - msgs = [ - _make_msg("first", group_id="G1"), - _make_msg("second", group_id="G1"), - ] - _SyncConsumer._process_batch(client, msgs) - assert call_order == ["first", "second"] - assert client.delete_message.call_count == 2 +class TestProcessSingle: + def test_processes_and_deletes_message(self): + msg = _make_msg("m1") + _SyncConsumer._process_single(msg) + _SyncConsumer.process_message.assert_called_once_with(msg) + _SyncConsumer.delete_message.assert_called_once_with(msg) def test_message_exceeds_max_receive_count(self): - client = MagicMock() - msg = _make_msg("m1", group_id="G1", receive_count=_SyncConsumer.max_receive_count + 1) - _SyncConsumer._process_batch(client, [msg]) + msg = _make_msg("m1", receive_count=_SyncConsumer.max_receive_count + 1) + _SyncConsumer._process_single(msg) _SyncConsumer.on_permanent_failure.assert_called_once_with(msg) _SyncConsumer.process_message.assert_not_called() - client.delete_message.assert_called_once() + _SyncConsumer.delete_message.assert_called_once_with(msg) def test_process_message_raises_does_not_delete(self): _SyncConsumer.process_message.side_effect = RuntimeError("boom") - client = MagicMock() - msg = _make_msg("m1", group_id="G1") - _SyncConsumer._process_batch(client, [msg]) - client.delete_message.assert_not_called() + msg = _make_msg("m1") + _SyncConsumer._process_single(msg) + _SyncConsumer.delete_message.assert_not_called() _SyncConsumer.process_message.side_effect = None - def test_failing_group_does_not_affect_other_groups(self): - call_order = [] - - def side_effect(msg): - if msg["MessageId"] == "bad": - raise RuntimeError("intentional failure") - call_order.append(msg["MessageId"]) - - _SyncConsumer.process_message.side_effect = side_effect - - client = MagicMock() - msgs = [ - _make_msg("bad", group_id="G1"), - _make_msg("good", group_id="G2"), - ] - _SyncConsumer._process_batch(client, msgs) - assert "good" in call_order - # bad message was not deleted - deleted_handles = [c.kwargs["ReceiptHandle"] for c in client.delete_message.call_args_list] - assert "rh-bad" not in deleted_handles - assert "rh-good" in deleted_handles - _SyncConsumer.process_message.side_effect = None + def test_async_process_message_is_run_and_deleted(self): + msg = _make_msg("async-msg") + _AsyncConsumer._process_single(msg) + _AsyncConsumer.delete_message.assert_called_once_with(msg) + + +class TestConsumerLoop: + def test_stops_after_poll_raises_once_processed_batch(self): + msg = _make_msg("m1") + poll_results = [[msg], RuntimeError("stop")] + + def fake_poll(): + result = poll_results.pop(0) + if isinstance(result, Exception): + raise result + return result + + with patch.object(_SyncConsumer, "poll", side_effect=fake_poll), patch( + "agentkernel.deployment.aws.containerized.core.sqs_consumer.time.sleep", + side_effect=RuntimeError("stop-loop"), + ): + with pytest.raises(RuntimeError, match="stop-loop"): + _SyncConsumer._consumer_loop() + + _SyncConsumer.process_message.assert_called_once_with(msg) + _SyncConsumer.delete_message.assert_called_once_with(msg) class TestECSOutputConsumerRegression: @@ -166,35 +127,5 @@ def test_process_message_is_sync_not_async(self): ECSOutputConsumer.process_message, "__func__", ECSOutputConsumer.process_message ) assert not inspect.iscoroutinefunction(underlying), ( - "ECSOutputConsumer.process_message must be sync — " - "changing it to async would require a new event loop per group thread" + "ECSOutputConsumer.process_message must be sync for this regression check" ) - - def test_sync_consumer_processes_and_deletes(self): - # Regression: the sync path (no loop) still calls process_message then delete_message - client = MagicMock() - msg = _make_msg("sync-reg", group_id="G1") - _SyncConsumer._process_batch(client, [msg]) - _SyncConsumer.process_message.assert_called_once_with(msg) - client.delete_message.assert_called_once() - - -class TestAsyncAutoDetection: - def test_async_process_message_uses_event_loop(self): - loops_created = [] - original_new_event_loop = asyncio.new_event_loop - - def track_loop(): - loop = original_new_event_loop() - loops_created.append(loop) - return loop - - client = MagicMock() - client.delete_message = MagicMock() - msg = _make_msg("async-msg", group_id="AG1") - - with patch("asyncio.new_event_loop", side_effect=track_loop): - _AsyncConsumer._process_batch(client, [msg]) - - assert len(loops_created) == 1 - client.delete_message.assert_called_once() From cde815b27c4959ec364b02c543752c1e64d23d89 Mon Sep 17 00:00:00 2001 From: Lakindu Induwara Kumara Date: Thu, 2 Jul 2026 12:57:33 +0530 Subject: [PATCH 62/90] depends on added to backlog mitrc aws cont agent-runner main.tf --- ak-deployment/ak-aws/containerized/modules/agent-runner/main.tf | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ak-deployment/ak-aws/containerized/modules/agent-runner/main.tf b/ak-deployment/ak-aws/containerized/modules/agent-runner/main.tf index 709c57f1e..7a3437164 100644 --- a/ak-deployment/ak-aws/containerized/modules/agent-runner/main.tf +++ b/ak-deployment/ak-aws/containerized/modules/agent-runner/main.tf @@ -391,6 +391,8 @@ resource "aws_lambda_function" "backlog_metric" { } tags = var.tags + + depends_on = [aws_cloudwatch_log_group.backlog_metric_lambda] } resource "aws_cloudwatch_log_group" "backlog_metric_lambda" { From 079453f6dd8bd425d2582493c683b020c5a60019 Mon Sep 17 00:00:00 2001 From: Lakindu Induwara Kumara Date: Thu, 2 Jul 2026 17:56:59 +0530 Subject: [PATCH 63/90] config vchanges --- ak-py/src/agentkernel/core/config.py | 28 +++++++++++++++--- .../aws/containerized/akagentrunner.py | 1 + .../aws/containerized/akoutputconsumer.py | 1 + .../aws/containerized/core/sqs_consumer.py | 29 ++++++++----------- ak-py/tests/test_config.py | 10 +++---- 5 files changed, 43 insertions(+), 26 deletions(-) diff --git a/ak-py/src/agentkernel/core/config.py b/ak-py/src/agentkernel/core/config.py index 4c473cd6a..ff73ae034 100644 --- a/ak-py/src/agentkernel/core/config.py +++ b/ak-py/src/agentkernel/core/config.py @@ -256,6 +256,15 @@ class _InputQueueConfig(BaseModel): max_receive_count: int = Field( default=3, description="Maximum number of times a message can be received from input queue before being treated as permanently failed" ) + no_of_consumers: int = Field( + default=10, + description=( + "Number of independent consumer threads that each poll the input queue " + "in a continuous loop. Only used by ECS containerized deployments — never set for " + "serverless (Lambda) mode, which has no consumer threads. " + "Override via env var AK_EXECUTION__QUEUES__INPUT__NO_OF_CONSUMERS." + ), + ) class _OutputQueueConfig(BaseModel): @@ -263,16 +272,27 @@ class _OutputQueueConfig(BaseModel): max_receive_count: int = Field( default=3, description="Maximum number of times a message can be received from output queue before being treated as permanently failed" ) + no_of_consumers: int = Field( + default=1, + description=( + "Number of independent consumer threads that each poll the output queue " + "in a continuous loop. Only used by ECS containerized deployments — never set for " + "serverless (Lambda) mode, which has no consumer threads. " + "Override via env var AK_EXECUTION__QUEUES__OUTPUT__NO_OF_CONSUMERS." + ), + ) class _QueuesConfig(BaseModel): input: _InputQueueConfig = Field(default_factory=_InputQueueConfig, description="Input SQS queue configuration for async execution mode") output: _OutputQueueConfig = Field(default_factory=_OutputQueueConfig, description="Output SQS queue configuration for async execution mode") - no_of_consumers: int = Field( - default=10, + batch_size: Optional[int] = Field( + default=None, description=( - "Number of independent consumer threads that each poll the input queue " - "in a continuous loop. Override via env var AK_EXECUTION__QUEUES__NO_OF_CONSUMERS." + "Max number of messages fetched per SQS receive call, common to input and output queues. " + "Only used by ECS containerized deployments — never set for serverless (Lambda) mode, which " + "controls batch size via the Event Source Mapping instead. " + "Controlled by the Terraform deployment via env var AK_EXECUTION__QUEUES__BATCH_SIZE — do not set in config.yaml." ), ) diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/akagentrunner.py b/ak-py/src/agentkernel/deployment/aws/containerized/akagentrunner.py index 0caf2d227..f1a138ec7 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/akagentrunner.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/akagentrunner.py @@ -30,6 +30,7 @@ class ECSAgentRunner(ECSSQSConsumer): _chat_service: ChatService | None = None _config = AKConfig.get() max_receive_count = _config.execution.queues.input.max_receive_count + num_consumers = _config.execution.queues.input.no_of_consumers @classmethod def get_queue_url(cls) -> str: diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/akoutputconsumer.py b/ak-py/src/agentkernel/deployment/aws/containerized/akoutputconsumer.py index 804bcaef1..ef50ebbd9 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/akoutputconsumer.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/akoutputconsumer.py @@ -24,6 +24,7 @@ class ECSOutputConsumer(ECSSQSConsumer): _log = logging.getLogger("ak.ecs.outputconsumer") _config = AKConfig.get() max_receive_count = _config.execution.queues.output.max_receive_count + num_consumers = _config.execution.queues.output.no_of_consumers _response_store = None _websocket_handler = None diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py b/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py index 69ab9276b..98e249bb4 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py @@ -20,9 +20,10 @@ class ECSSQSConsumer(ABC): Unlike Lambda (push-triggered), ECS actively polls SQS. The poll() template method represents one SQS receive cycle — analogous to one Lambda invocation - calling handle(). Override poll() to customise MaxNumberOfMessages, - WaitTimeSeconds, or MessageAttributeNames; override process_message and - on_permanent_failure for business logic. + calling handle(). Override poll() to customise WaitTimeSeconds or + MessageAttributeNames; override process_message and on_permanent_failure + for business logic. MaxNumberOfMessages comes from + execution.queues.batch_size (set via Terraform, never config.yaml). Contract for on_permanent_failure implementations: must be internally defensive (catch their own exceptions). If on_permanent_failure raises, the @@ -31,7 +32,7 @@ class ECSSQSConsumer(ABC): """ max_receive_count: int = 3 # overridden by classes that inherit this - _DEFAULT_PARALLEL_WORKERS: int = 10 + num_consumers: int = 10 # overridden by classes that inherit this _log = logging.getLogger("ak.ecs.sqsconsumer") _client = None @@ -57,12 +58,13 @@ def poll(cls) -> list: """ Receive one batch of SQS messages and return them. - Override to customise MaxNumberOfMessages, WaitTimeSeconds, or - MessageAttributeNames. Overriding implementations must return a list of - raw boto3 receive_message records. + Override to customise WaitTimeSeconds or MessageAttributeNames. + Overriding implementations must return a list of raw boto3 + receive_message records. """ resp = cls._get_client().receive_message( QueueUrl=cls.get_queue_url(), + MaxNumberOfMessages=AKConfig.get().execution.queues.batch_size or 10, AttributeNames=["All"], MessageAttributeNames=["All"], ) @@ -101,13 +103,6 @@ def delete_message(cls, msg: dict) -> None: ReceiptHandle=msg["ReceiptHandle"], ) - @classmethod - def _get_num_consumers(cls) -> int: - try: - return AKConfig.get().execution.queues.no_of_consumers - except Exception: - return cls._DEFAULT_PARALLEL_WORKERS - @classmethod def _process_single(cls, msg: dict) -> None: message_id = msg.get("MessageId", "") @@ -158,21 +153,21 @@ def run(cls) -> None: """ Block forever, polling the queue. Call as the container entry-point. - Starts `no_of_consumers` long-lived threads, each independently + Starts `num_consumers` long-lived threads, each independently polling and processing messages in a loop. """ queue_url = cls.get_queue_url() if not queue_url: raise ValueError(f"{cls.__name__}: queue URL is required") - num_consumers = cls._get_num_consumers() + num_consumers = cls.num_consumers cls._log.info(f"{cls.__name__} starting — queue: {queue_url}, consumers: {num_consumers}") ThreadRunner.run( tasks=[ ThreadRunner.Task( execution_function=cls._consumer_loop, - thread_name=f"ar-sqs-consumer-{i}", + thread_name=f"sqs-consumer-{i}", stop_all_on_failure=True, ) for i in range(num_consumers) diff --git a/ak-py/tests/test_config.py b/ak-py/tests/test_config.py index 58ee5a71e..d358c6b13 100644 --- a/ak-py/tests/test_config.py +++ b/ak-py/tests/test_config.py @@ -113,15 +113,15 @@ def test_session_cache_env(monkeypatch): assert cfg.session.cache.size == 500 -def test_no_of_consumers_default(): +def test_batch_size_default(): cfg = AKConfig() - assert cfg.execution.queues.no_of_consumers == 10 + assert cfg.execution.queues.batch_size is None -def test_no_of_consumers_env_override(monkeypatch): - monkeypatch.setenv("AK_EXECUTION__QUEUES__NO_OF_CONSUMERS", "20") +def test_batch_size_env_override(monkeypatch): + monkeypatch.setenv("AK_EXECUTION__QUEUES__BATCH_SIZE", "5") cfg = AKConfig() - assert cfg.execution.queues.no_of_consumers == 20 + assert cfg.execution.queues.batch_size == 5 def test_guardrail_pii_default(): From 31fcfaca45ff76be0147b63a7ad66900f33ac31f Mon Sep 17 00:00:00 2001 From: Lakindu Induwara Kumara Date: Thu, 2 Jul 2026 17:57:34 +0530 Subject: [PATCH 64/90] terraform updates for batch size --- ak-deployment/ak-aws/containerized/README.md | 3 +++ ak-deployment/ak-aws/containerized/modules/README.md | 2 ++ .../ak-aws/containerized/modules/agent-runner/main.tf | 1 + .../ak-aws/containerized/modules/agent-runner/variables.tf | 3 ++- .../ak-aws/containerized/modules/rest-service/main.tf | 1 + .../ak-aws/containerized/modules/rest-service/variables.tf | 7 +++++++ ak-deployment/ak-aws/containerized/rest_service.tf | 1 + ak-deployment/ak-aws/containerized/variables.tf | 7 +++++++ 8 files changed, 24 insertions(+), 1 deletion(-) diff --git a/ak-deployment/ak-aws/containerized/README.md b/ak-deployment/ak-aws/containerized/README.md index d3f8be971..ab130e235 100644 --- a/ak-deployment/ak-aws/containerized/README.md +++ b/ak-deployment/ak-aws/containerized/README.md @@ -230,6 +230,7 @@ queue_config = { sqs_managed_sse_enabled = true # Enable SSE for queues max_message_size = 262144 # 256 KB max message size receive_wait_time_seconds = 0 # Long polling wait time + batch_size = 10 # Max messages fetched per SQS receive call (1-10), ECS consumers only # Input queue (requests → agent runner) input_queue_visibility_timeout = 60 # Should be >= processing time @@ -260,6 +261,8 @@ scaling_config = { } ``` +`queue_config.batch_size` is injected into both the REST service and Agent Runner ECS tasks as `AK_EXECUTION__QUEUES__BATCH_SIZE`. It only applies to ECS containerized deployments (never serverless/Lambda, which controls batch size via the Event Source Mapping) and is intentionally controlled only via Terraform — it must never be set in `config.yaml`. + ## Deployment Modes ### Non-Queue Mode (Default) diff --git a/ak-deployment/ak-aws/containerized/modules/README.md b/ak-deployment/ak-aws/containerized/modules/README.md index e82b92638..f9e5551b1 100644 --- a/ak-deployment/ak-aws/containerized/modules/README.md +++ b/ak-deployment/ak-aws/containerized/modules/README.md @@ -139,6 +139,7 @@ rest_service = { - `AK_EXECUTION__QUEUES__INPUT__URL` (if queue mode enabled) - `AK_EXECUTION__QUEUES__OUTPUT__URL` (if queue mode enabled) - `AK_EXECUTION__RESPONSE_STORE__DYNAMODB__TABLE_NAME` (if queue mode enabled) +- `AK_EXECUTION__QUEUES__BATCH_SIZE` (if queue mode enabled, from root `queue_config.batch_size`) ### 3. `agent-runner/` @@ -194,6 +195,7 @@ scaling_config = { - `AK_EXECUTION__QUEUES__INPUT__URL` - `AK_EXECUTION__QUEUES__OUTPUT__URL` - `AK_EXECUTION__QUEUES__INPUT__MAX_RECEIVE_COUNT` +- `AK_EXECUTION__QUEUES__BATCH_SIZE` (from root `queue_config.batch_size`) - `AK_SESSION__REDIS__URL` (if Redis enabled) - `AK_SESSION__DYNAMODB__TABLE_NAME` (if DynamoDB memory table enabled) diff --git a/ak-deployment/ak-aws/containerized/modules/agent-runner/main.tf b/ak-deployment/ak-aws/containerized/modules/agent-runner/main.tf index 7a3437164..43224458c 100644 --- a/ak-deployment/ak-aws/containerized/modules/agent-runner/main.tf +++ b/ak-deployment/ak-aws/containerized/modules/agent-runner/main.tf @@ -7,6 +7,7 @@ locals { AK_EXECUTION__QUEUES__INPUT__URL = var.input_queue_url AK_EXECUTION__QUEUES__OUTPUT__URL = var.output_queue_url AK_EXECUTION__QUEUES__INPUT__MAX_RECEIVE_COUNT = tostring(max(1, var.queue_config.input_queue_max_receive_count - 1)) + AK_EXECUTION__QUEUES__BATCH_SIZE = tostring(var.queue_config.batch_size) }, var.redis_url != null ? { AK_SESSION__REDIS__URL = var.redis_url } : {}, var.dynamodb_memory_table_arn != null ? { diff --git a/ak-deployment/ak-aws/containerized/modules/agent-runner/variables.tf b/ak-deployment/ak-aws/containerized/modules/agent-runner/variables.tf index 4222e9f07..a4f215983 100644 --- a/ak-deployment/ak-aws/containerized/modules/agent-runner/variables.tf +++ b/ak-deployment/ak-aws/containerized/modules/agent-runner/variables.tf @@ -100,9 +100,10 @@ variable "agent_runner" { } variable "queue_config" { - description = "Queue configuration for input max_receive_count" + description = "Queue configuration for input max_receive_count and SQS batch size" type = object({ input_queue_max_receive_count = number + batch_size = number }) } diff --git a/ak-deployment/ak-aws/containerized/modules/rest-service/main.tf b/ak-deployment/ak-aws/containerized/modules/rest-service/main.tf index fbd517fdd..b6ef7bd66 100644 --- a/ak-deployment/ak-aws/containerized/modules/rest-service/main.tf +++ b/ak-deployment/ak-aws/containerized/modules/rest-service/main.tf @@ -12,6 +12,7 @@ locals { AK_EXECUTION__QUEUES__INPUT__URL = var.input_queue_url AK_EXECUTION__QUEUES__OUTPUT__URL = var.output_queue_url AK_EXECUTION__RESPONSE_STORE__DYNAMODB__TABLE_NAME = var.response_store_table_name + AK_EXECUTION__QUEUES__BATCH_SIZE = tostring(var.queue_config.batch_size) } : {} ) } diff --git a/ak-deployment/ak-aws/containerized/modules/rest-service/variables.tf b/ak-deployment/ak-aws/containerized/modules/rest-service/variables.tf index 697da5ad9..5d9c46bd3 100644 --- a/ak-deployment/ak-aws/containerized/modules/rest-service/variables.tf +++ b/ak-deployment/ak-aws/containerized/modules/rest-service/variables.tf @@ -115,6 +115,13 @@ variable "response_store_table_name" { default = null } +variable "queue_config" { + description = "Queue configuration for SQS batch size (for queue mode)" + type = object({ + batch_size = number + }) +} + variable "tags" { type = map(string) description = "Resource tags" diff --git a/ak-deployment/ak-aws/containerized/rest_service.tf b/ak-deployment/ak-aws/containerized/rest_service.tf index 297df07f3..f3ff05320 100644 --- a/ak-deployment/ak-aws/containerized/rest_service.tf +++ b/ak-deployment/ak-aws/containerized/rest_service.tf @@ -39,6 +39,7 @@ module "rest_service" { input_queue_url = var.enable_queue_mode ? module.queues[0].input_queue_url : null output_queue_url = var.enable_queue_mode ? module.queues[0].output_queue_url : null response_store_table_name = var.enable_queue_mode ? aws_dynamodb_table.response_store[0].name : null + queue_config = var.queue_config tags = var.tags } diff --git a/ak-deployment/ak-aws/containerized/variables.tf b/ak-deployment/ak-aws/containerized/variables.tf index fed0e5597..91248b79a 100644 --- a/ak-deployment/ak-aws/containerized/variables.tf +++ b/ak-deployment/ak-aws/containerized/variables.tf @@ -245,6 +245,7 @@ variable "queue_config" { sqs_managed_sse_enabled = optional(bool, true) max_message_size = optional(number, 262144) # 256 KB receive_wait_time_seconds = optional(number, 0) + batch_size = optional(number, 10) # Max messages fetched per SQS receive call (ECS consumers only, 1-10) # Input queue settings input_queue_visibility_timeout = optional(number, 60) @@ -266,6 +267,7 @@ variable "queue_config" { sqs_managed_sse_enabled = true max_message_size = 262144 receive_wait_time_seconds = 0 + batch_size = 10 input_queue_visibility_timeout = 60 input_queue_message_retention_seconds = 1800 input_queue_max_receive_count = 5 @@ -277,6 +279,11 @@ variable "queue_config" { output_queue_create_dlq = false output_queue_dlq_message_retention_seconds = 1800 } + + validation { + condition = var.queue_config.batch_size >= 1 && var.queue_config.batch_size <= 10 + error_message = "queue_config.batch_size must be between 1 and 10 (SQS ReceiveMessage limit)." + } } # --- Agent Runner Configuration Object --- From 2c9fd61411374f18b22340f57227ec2bb647fc80 Mon Sep 17 00:00:00 2001 From: Lakindu Induwara Kumara Date: Thu, 2 Jul 2026 19:40:49 +0530 Subject: [PATCH 65/90] async handling of rest api --- .../aws/containerized/ecs_queue_handler.py | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py b/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py index 2b0ed5b62..4e7c4265e 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py @@ -7,6 +7,7 @@ Used by ECSIOHandler (Thread 1 — REST API) when queue mode is enabled. """ +import asyncio import logging from typing import Any, Dict @@ -76,8 +77,9 @@ async def enqueue_and_wait(body: BaseRunRequest): f"[REQUEST START] session_id={body.session_id}, request_id={request_id}, agent={body.agent}, prompt={body.prompt[:50]}" ) - # Send to Input Queue - queue_result = SQSHandler.send_message_to_input_queue( + # Send to Input Queue (sync boto3 call — offload so it doesn't block the event loop) + queue_result = await asyncio.to_thread( + SQSHandler.send_message_to_input_queue, message_body=body.model_dump(), message_group_id=body.session_id, message_deduplication_id=request_id, @@ -91,7 +93,7 @@ async def enqueue_and_wait(body: BaseRunRequest): # Wait for response in DynamoDB self._log.info(f"[WAITING] Polling DynamoDB for request_id={request_id}") - response = self._wait_for_response(request_id=request_id, session_id=body.session_id) + response = await self._wait_for_response(request_id=request_id, session_id=body.session_id) if not response: raise HTTPException( @@ -140,7 +142,7 @@ async def poll_response(session_id: str, request_id: str = None): self._log.info(f"Polling for response: request_id={effective_request_id}, session_id={session_id}") - response = self._get_response_store().get_message(effective_request_id) + response = await asyncio.to_thread(self._get_response_store().get_message, effective_request_id) if not response: raise HTTPException( @@ -181,7 +183,7 @@ async def poll_response(session_id: str, request_id: str = None): return router - def _wait_for_response(self, request_id: str, session_id: str, max_retries: int = None, delay: int = None) -> Dict[str, Any]: + async def _wait_for_response(self, request_id: str, session_id: str, max_retries: int = None, delay: int = None) -> Dict[str, Any]: """ Wait for response to appear in DynamoDB Response Store. @@ -196,12 +198,13 @@ def _wait_for_response(self, request_id: str, session_id: str, max_retries: int if delay is None: delay = self._config.execution.response_store.delay - import time - self._log.info(f"[WAIT START] request_id={request_id}, max_retries={max_retries}, delay={delay}s") for attempt in range(max_retries): - response = self._get_response_store().get_message(request_id) + # Offload the sync boto3 DynamoDB call and use asyncio.sleep so this + # coroutine yields the event loop — otherwise a long-running wait for + # one request would starve every other concurrent request. + response = await asyncio.to_thread(self._get_response_store().get_message, request_id) if response: self._log.info(f"[WAIT SUCCESS] Found response on attempt {attempt + 1}/{max_retries} for request_id={request_id}") return response @@ -210,7 +213,7 @@ def _wait_for_response(self, request_id: str, session_id: str, max_retries: int self._log.debug( f"[WAIT RETRY] Response not ready, waiting {delay}s (attempt {attempt + 1}/{max_retries}) for request_id={request_id}" ) - time.sleep(delay) + await asyncio.sleep(delay) self._log.warning(f"[WAIT TIMEOUT] Response not found after {max_retries} attempts for request_id={request_id}") return None From 613d9dcabb84ae5218dd58353333992c76d94ff9 Mon Sep 17 00:00:00 2001 From: Lakindu Induwara Kumara Date: Thu, 2 Jul 2026 21:11:18 +0530 Subject: [PATCH 66/90] thread runner update to use threading instead of concurrent --- .../deployment/common/thread_runner.py | 60 ++++-- ak-py/tests/test_thread_runner.py | 179 ++++++++++++++++++ 2 files changed, 219 insertions(+), 20 deletions(-) create mode 100644 ak-py/tests/test_thread_runner.py diff --git a/ak-py/src/agentkernel/deployment/common/thread_runner.py b/ak-py/src/agentkernel/deployment/common/thread_runner.py index 445618754..2b3a91081 100644 --- a/ak-py/src/agentkernel/deployment/common/thread_runner.py +++ b/ak-py/src/agentkernel/deployment/common/thread_runner.py @@ -1,7 +1,8 @@ import logging -import os -from concurrent.futures import ThreadPoolExecutor, as_completed +import sys +import threading from dataclasses import dataclass +from queue import Queue from typing import Any, Callable _log = logging.getLogger("ak.thread_runner") @@ -11,6 +12,10 @@ class ThreadRunner: """ Runs Task instances concurrently — each as task.execution_function(task.item) — and reacts to each completion in turn. + + Each Task gets its own threading.Thread (instead of a ThreadPoolExecutor worker), so a + finished task's OS thread is torn down and reclaimed immediately rather than lingering + idle until every task in the batch — including any that never finish — has completed. """ @dataclass @@ -25,27 +30,42 @@ def __post_init__(self) -> None: if self.stop_all_on_failure and not self.stop_task_on_failure: raise ValueError("stop_all_on_failure=True requires stop_task_on_failure=True") - def _submit(self, executor: ThreadPoolExecutor): - args = () if self.item is None else (self.item,) - _log.info(f"[{self.thread_name}] submitting to executer") - return executor.submit(self.execution_function, *args) - @staticmethod def run(tasks: list[Task], max_workers: int | None = None) -> None: if not tasks: return - with ThreadPoolExecutor(max_workers=max_workers or len(tasks)) as executor: - futures = {task._submit(executor): task for task in tasks} - for future in as_completed(futures): - task = futures[future] - exc = future.exception() - if exc is not None: - if task.stop_task_on_failure: - _log.exception(f"[{task.thread_name}] raised unexpectedly", exc_info=exc) - if task.stop_all_on_failure: - os._exit(1) # stops the entire container - else: - _log.debug(f"[{task.thread_name}] raised (stop_task_on_failure=False, ignoring)") + semaphore = threading.Semaphore(max_workers or len(tasks)) + completions: Queue = Queue() # Thread-safe mailbox every worker thread reports its completion to. + + def _target(task: "ThreadRunner.Task") -> None: + args = () if task.item is None else (task.item,) + with semaphore: + try: + task.execution_function(*args) + except Exception as exc: + completions.put((task, exc)) + else: + completions.put((task, None)) + # the thread ends itself once execution_function returns (or raises), and Python/the OS reclaim it. + + # daemon=True: on stop_all_on_failure, sys.exit() below only unwinds the calling thread the interpreter still waits for every non-daemon thread before + # actually exiting, which would hang forever behind a never-ending task. Daemon threads are abandoned instead. + threads = [ + threading.Thread(target=_target, args=(task,), name=task.thread_name, daemon=True) + for task in tasks + ] + for thread in threads: + thread.start() + + for _ in tasks: + task, exc = completions.get() # Pulling from a shared queue exactly len(tasks) times yields tasks in true completion order (whichever finishes first is handled first) + if exc is not None: + if task.stop_task_on_failure: + _log.exception(f"[{task.thread_name}] raised unexpectedly", exc_info=exc) + if task.stop_all_on_failure: + sys.exit(1) else: - _log.debug(f"[{task.thread_name}] completed") + _log.debug(f"[{task.thread_name}] raised (stop_task_on_failure=False, ignoring)") + else: + _log.debug(f"[{task.thread_name}] completed") diff --git a/ak-py/tests/test_thread_runner.py b/ak-py/tests/test_thread_runner.py new file mode 100644 index 000000000..06e643ed5 --- /dev/null +++ b/ak-py/tests/test_thread_runner.py @@ -0,0 +1,179 @@ +import threading +import time + +import pytest + +from agentkernel.deployment.common.thread_runner import ThreadRunner + + +class TestTaskValidation: + def test_stop_all_on_failure_requires_stop_task_on_failure(self): + with pytest.raises(ValueError): + ThreadRunner.Task( + execution_function=lambda: None, + thread_name="t", + stop_task_on_failure=False, + stop_all_on_failure=True, + ) + + def test_default_flags(self): + task = ThreadRunner.Task(execution_function=lambda: None, thread_name="t") + assert task.stop_task_on_failure is True + assert task.stop_all_on_failure is False + + +class TestRun: + def test_empty_task_list_returns_immediately(self): + ThreadRunner.run([]) # should not raise or block + + def test_runs_all_tasks_to_completion(self): + results = [] + lock = threading.Lock() + + def make_fn(value): + def fn(): + with lock: + results.append(value) + return fn + + tasks = [ + ThreadRunner.Task(execution_function=make_fn(i), thread_name=f"t{i}") + for i in range(5) + ] + ThreadRunner.run(tasks) + assert sorted(results) == [0, 1, 2, 3, 4] + + def test_passes_item_to_execution_function(self): + seen = [] + task = ThreadRunner.Task( + execution_function=lambda item: seen.append(item), + thread_name="t", + item="hello", + ) + ThreadRunner.run([task]) + assert seen == ["hello"] + + def test_thread_name_is_set_on_os_thread(self): + captured = {} + + def fn(): + captured["name"] = threading.current_thread().name + + task = ThreadRunner.Task(execution_function=fn, thread_name="my-thread") + ThreadRunner.run([task]) + assert captured["name"] == "my-thread" + + def test_task_thread_is_daemon(self): + """Task threads must be daemon so a never-ending one can't block process/interpreter + shutdown when sys.exit(1) is raised elsewhere on stop_all_on_failure.""" + captured = {} + + def fn(): + captured["is_daemon"] = threading.current_thread().daemon + + task = ThreadRunner.Task(execution_function=fn, thread_name="t") + ThreadRunner.run([task]) + assert captured["is_daemon"] is True + + def test_finished_task_thread_does_not_linger_alive(self): + """A finished task's thread must not still be alive once run() has processed + its completion, even while another task is still running indefinitely.""" + finished_thread_name = {} + never_finishes = threading.Event() + + def fast_fn(): + finished_thread_name["name"] = threading.current_thread().name + + def slow_fn(): + never_finishes.wait(timeout=5) + + tasks = [ + ThreadRunner.Task(execution_function=fast_fn, thread_name="fast"), + ThreadRunner.Task(execution_function=slow_fn, thread_name="slow"), + ] + + result_holder = {} + + def run_in_background(): + ThreadRunner.run(tasks) + result_holder["done"] = True + + runner_thread = threading.Thread(target=run_in_background) + runner_thread.start() + + # Give the fast task time to complete and be reclaimed while the slow task + # is still running (run() itself is still blocked on the slow task). + deadline = time.time() + 5 + while "name" not in finished_thread_name and time.time() < deadline: + time.sleep(0.01) + + # Poll until the fast thread is no longer alive — it should be reclaimed + # promptly, independent of the still-running slow task. + fast_name = finished_thread_name["name"] + deadline = time.time() + 5 + fast_still_alive = True + while time.time() < deadline: + fast_still_alive = any( + t.name == fast_name and t.is_alive() for t in threading.enumerate() + ) + if not fast_still_alive: + break + time.sleep(0.01) + + assert not fast_still_alive, "fast task's thread lingered after completion" + assert "done" not in result_holder, "run() should still be blocked on the slow task" + + never_finishes.set() + runner_thread.join(timeout=5) + assert result_holder.get("done") is True + + def test_stop_all_on_failure_exits_immediately_without_waiting_on_never_ending_task( + self, monkeypatch + ): + exit_called = threading.Event() + exit_code = {} + + def fake_exit(code): + exit_code["code"] = code + exit_called.set() + + monkeypatch.setattr("agentkernel.deployment.common.thread_runner.sys.exit", fake_exit) + + never_finishes = threading.Event() + + def slow_fn(): + never_finishes.wait(timeout=10) + + def crashing_fn(): + time.sleep(0.05) + raise RuntimeError("boom") + + tasks = [ + ThreadRunner.Task(execution_function=slow_fn, thread_name="slow"), + ThreadRunner.Task( + execution_function=crashing_fn, + thread_name="crasher", + stop_task_on_failure=True, + stop_all_on_failure=True, + ), + ] + + runner_thread = threading.Thread(target=ThreadRunner.run, args=(tasks,)) + runner_thread.start() + + assert exit_called.wait(timeout=5), "sys.exit was not called promptly on crash" + assert exit_code["code"] == 1 + + never_finishes.set() + runner_thread.join(timeout=5) + + def test_stop_task_on_failure_false_ignores_exception(self): + def crashing_fn(): + raise RuntimeError("boom") + + task = ThreadRunner.Task( + execution_function=crashing_fn, + thread_name="t", + stop_task_on_failure=False, + ) + ThreadRunner.run([task]) # should not raise From 235abc4c60461f32bca3bdf40c9bc92e831b2f57 Mon Sep 17 00:00:00 2001 From: Lakindu Induwara Kumara Date: Thu, 2 Jul 2026 23:26:25 +0530 Subject: [PATCH 67/90] threading updates, and config value updates --- ak-py/src/agentkernel/core/config.py | 7 +++++-- .../deployment/aws/containerized/core/sqs_consumer.py | 2 +- .../src/agentkernel/deployment/common/thread_runner.py | 10 ++++++---- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/ak-py/src/agentkernel/core/config.py b/ak-py/src/agentkernel/core/config.py index ff73ae034..26b0b90ff 100644 --- a/ak-py/src/agentkernel/core/config.py +++ b/ak-py/src/agentkernel/core/config.py @@ -257,8 +257,9 @@ class _InputQueueConfig(BaseModel): default=3, description="Maximum number of times a message can be received from input queue before being treated as permanently failed" ) no_of_consumers: int = Field( - default=10, + default=5, description=( + "Only used in Containerized deployments " "Number of independent consumer threads that each poll the input queue " "in a continuous loop. Only used by ECS containerized deployments — never set for " "serverless (Lambda) mode, which has no consumer threads. " @@ -273,8 +274,9 @@ class _OutputQueueConfig(BaseModel): default=3, description="Maximum number of times a message can be received from output queue before being treated as permanently failed" ) no_of_consumers: int = Field( - default=1, + default=5, description=( + "Only used in Containerized deployments " "Number of independent consumer threads that each poll the output queue " "in a continuous loop. Only used by ECS containerized deployments — never set for " "serverless (Lambda) mode, which has no consumer threads. " @@ -289,6 +291,7 @@ class _QueuesConfig(BaseModel): batch_size: Optional[int] = Field( default=None, description=( + "NOT used in serverless deployments" "Max number of messages fetched per SQS receive call, common to input and output queues. " "Only used by ECS containerized deployments — never set for serverless (Lambda) mode, which " "controls batch size via the Event Source Mapping instead. " diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py b/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py index 98e249bb4..135f94544 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py @@ -64,7 +64,7 @@ def poll(cls) -> list: """ resp = cls._get_client().receive_message( QueueUrl=cls.get_queue_url(), - MaxNumberOfMessages=AKConfig.get().execution.queues.batch_size or 10, + MaxNumberOfMessages=AKConfig.get().execution.queues.batch_size, AttributeNames=["All"], MessageAttributeNames=["All"], ) diff --git a/ak-py/src/agentkernel/deployment/common/thread_runner.py b/ak-py/src/agentkernel/deployment/common/thread_runner.py index 2b3a91081..ea491a2e9 100644 --- a/ak-py/src/agentkernel/deployment/common/thread_runner.py +++ b/ak-py/src/agentkernel/deployment/common/thread_runner.py @@ -1,5 +1,5 @@ import logging -import sys +import os import threading from dataclasses import dataclass from queue import Queue @@ -49,8 +49,8 @@ def _target(task: "ThreadRunner.Task") -> None: completions.put((task, None)) # the thread ends itself once execution_function returns (or raises), and Python/the OS reclaim it. - # daemon=True: on stop_all_on_failure, sys.exit() below only unwinds the calling thread the interpreter still waits for every non-daemon thread before - # actually exiting, which would hang forever behind a never-ending task. Daemon threads are abandoned instead. + # daemon=True: without this, the interpreter would wait for every non-daemon thread before + # exiting, which would hang forever behind a never-ending task. Daemon threads are abandoned instead. threads = [ threading.Thread(target=_target, args=(task,), name=task.thread_name, daemon=True) for task in tasks @@ -64,7 +64,9 @@ def _target(task: "ThreadRunner.Task") -> None: if task.stop_task_on_failure: _log.exception(f"[{task.thread_name}] raised unexpectedly", exc_info=exc) if task.stop_all_on_failure: - sys.exit(1) + _log.debug(f"[{task.thread_name}] stopping all processes") + logging.shutdown() + os._exit(1) else: _log.debug(f"[{task.thread_name}] raised (stop_task_on_failure=False, ignoring)") else: From 98fe0392e8b5f125494c4926c74b466a28d48559 Mon Sep 17 00:00:00 2001 From: Lakindu Induwara Kumara Date: Fri, 3 Jul 2026 01:01:02 +0530 Subject: [PATCH 68/90] docs and skill updates --- ak-py/README.md | 44 ++++++++++++++----- .../skills/ak-cloud-deploy/SKILL.md | 11 +++-- docs/docs/advanced/queue-mode-guide.md | 29 +++++++++--- docs/docs/core-concepts/configuration.md | 16 ++++++- docs/docs/deployment/aws-containerized.md | 6 +-- docs/docs/deployment/aws-serverless.md | 12 ++++- 6 files changed, 91 insertions(+), 27 deletions(-) diff --git a/ak-py/README.md b/ak-py/README.md index d58888bd5..2dfee3b13 100644 --- a/ak-py/README.md +++ b/ak-py/README.md @@ -3,19 +3,16 @@ [![PyPI version](https://badge.fury.io/py/agentkernel.svg)](https://badge.fury.io/py/agentkernel) [![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/) -Agent Kernel is a lightweight **multi-cloud AI agent runtime** and adapter layer for building and running AI agents across multiple frameworks and cloud providers. Deploy the same agent code to **AWS, Azure, or GCP** without modification. Migrate your existing agents to Agent Kernel and instantly utilize pre-built execution and testing capabilities. - -**Supported Cloud Platforms:** AWS, Azure, GCP +Agent Kernel is a lightweight **AI agent runtime** and adapter layer for building and running AI agents across multiple frameworks. Migrate your existing agents to Agent Kernel and instantly utilize pre-built execution and testing capabilities. Deploy the same agent code without modification — see the "Multi-Cloud Deployment" section below for supported platforms. ## Features - **Unified API**: Common abstractions (Agent, Runner, Session, Module, Runtime) across frameworks - **Multi-Framework Support**: OpenAI Agents SDK, CrewAI, LangGraph, Google ADK, and Smolagents -- **Multi-Cloud Deployment**: Deploy to AWS (Lambda, ECS/Fargate), Azure (Functions, Container Apps), or GCP (Cloud Run serverless/containerized) with the same code -- **Session Management**: Built-in session abstraction with multi-cloud storage (Redis, DynamoDB, Cosmos DB, Firestore) +- **Session Management**: Built-in session abstraction with pluggable storage backends - **Knowledge Bases**: Unified `KnowledgeBase` interface with ChromaDB, Neo4j, and Starburst/Trino backends via `KnowledgeBuilder` -- **Flexible Deployment**: Interactive CLI, REST API, serverless (AWS Lambda, Azure Functions, GCP Cloud Run), containerized (AWS ECS, Azure Container Apps, GCP Cloud Run) -- **Pluggable Architecture**: Easy to extend with custom framework adapters and cloud providers +- **Flexible Deployment**: Interactive CLI, REST API, serverless, or containerized deployment — see the "Multi-Cloud Deployment" section below +- **Pluggable Architecture**: Easy to extend with custom framework adapters - **MCP Server**: Built-in Model Context Protocol server for exposing agents as MCP tools and exposing any custom tool - **A2A Server**: Built-in Agent-to-Agent communication server for exposing agents with a simple configuration change - **REST API**: Built-in REST API server for agent interaction @@ -168,7 +165,9 @@ Then interact with your agents: ## Multi-Cloud Deployment -Deploy your agents to AWS or Azure using the built-in cloud deployment handlers. +**Supported Cloud Platforms:** AWS, Azure, GCP + +Deploy your agents to AWS, Azure, or GCP using the built-in cloud deployment handlers. ### AWS Lambda Deployment @@ -365,7 +364,7 @@ Configure queue-backed and serverless execution behavior. - **Field**: `execution.mode` - **Options**: `rest_sync`, `rest_async`, `stream`, `async` - **Default**: `None` - - **Description**: Selects the Lambda execution mode + - **Description**: Selects the execution mode used for queue-backed and serverless request handling - **Environment Variable**: `AK_EXECUTION__MODE` - **Queues** @@ -392,6 +391,24 @@ Configure queue-backed and serverless execution behavior. - **Default**: `3` - **Environment Variable**: `AK_EXECUTION__QUEUES__OUTPUT__MAX_RECEIVE_COUNT` + - **Input Queue Consumer Count** + - **Field**: `execution.queues.input.no_of_consumers` + - **Default**: `5` + - **Description**: Number of independent consumer threads that each poll the input queue in a continuous loop. Only used by containerized deployments — never set for serverless deployments, which have no consumer threads. + - **Environment Variable**: `AK_EXECUTION__QUEUES__INPUT__NO_OF_CONSUMERS` + + - **Output Queue Consumer Count** + - **Field**: `execution.queues.output.no_of_consumers` + - **Default**: `5` + - **Description**: Number of independent consumer threads that each poll the output queue in a continuous loop. Only used by containerized deployments — never set for serverless deployments, which have no consumer threads. + - **Environment Variable**: `AK_EXECUTION__QUEUES__OUTPUT__NO_OF_CONSUMERS` + + - **Queue Batch Size** + - **Field**: `execution.queues.batch_size` + - **Default**: `None` + - **Description**: Max number of messages fetched per receive call, shared by the input and output queues. Only used by containerized deployments — never set for serverless deployments, which control batch size differently. Controlled by the deployment tooling via env var `AK_EXECUTION__QUEUES__BATCH_SIZE` — do not set in `config.yaml`. + - **Environment Variable**: `AK_EXECUTION__QUEUES__BATCH_SIZE` + - **Response Store** - **Field**: `execution.response_store` - **Description**: Response persistence settings used by the serverless response handler @@ -421,7 +438,7 @@ Configure queue-backed and serverless execution behavior. - **Environment Variables**: `AK_EXECUTION__RESPONSE_STORE__DYNAMODB__TABLE_NAME`, `AK_EXECUTION__RESPONSE_STORE__DYNAMODB__TTL` - **Description**: DynamoDB-backed response storage with table name and TTL -Use either Redis or DynamoDB for the response store backend. The runtime accepts `BaseRunRequest` payloads directly, normalizes them internally when queueing is required, and uses `request_id` plus optional `user_id` as SQS message attributes. +Use either Redis or DynamoDB for the response store backend. The runtime accepts `BaseRunRequest` payloads directly, normalizes them internally when queueing is required, and uses `request_id` plus optional `user_id` as queue message attributes. #### API Configuration @@ -924,11 +941,14 @@ execution: mode: rest_sync queues: input: - url: https://sqs..amazonaws.com// + url: https://queue.example.com// max_receive_count: 3 + no_of_consumers: 5 # Containerized deployments only, ignored by serverless deployments output: - url: https://sqs..amazonaws.com// + url: https://queue.example.com// max_receive_count: 3 + no_of_consumers: 5 # Containerized deployments only, ignored by serverless deployments + # batch_size is set by the deployment tooling — set via AK_EXECUTION__QUEUES__BATCH_SIZE, never here response_store: type: redis retry_count: 5 diff --git a/ak-py/src/agentkernel/skills/ak-cloud-deploy/SKILL.md b/ak-py/src/agentkernel/skills/ak-cloud-deploy/SKILL.md index c70a525c1..2481a3201 100644 --- a/ak-py/src/agentkernel/skills/ak-cloud-deploy/SKILL.md +++ b/ak-py/src/agentkernel/skills/ak-cloud-deploy/SKILL.md @@ -599,8 +599,8 @@ module "containerized_agents" { Use this for high-throughput or long-running agents. Two separate ECS services share SQS queues: -- **IO container** (`ECSIOHandler`) — Thread 1: FastAPI REST API; Thread 2: output queue consumer → DynamoDB / WebSocket. -- **Agent Runner container** (`ECSAgentRunner`) — polls the input queue, executes the agent, puts the result on the output queue. +- **IO container** (`ECSIOHandler`) — Thread 1: FastAPI REST API; Thread 2: output queue consumer → DynamoDB / WebSocket. Thread 2 is actually `queues.output.no_of_consumers` (default 5) parallel polling threads. +- **Agent Runner container** (`ECSAgentRunner`) — runs `queues.input.no_of_consumers` (default 5) parallel threads, each polling the input queue, executing the agent, and putting the result on the output queue. **`app_rest_service.py`** (IO container entrypoint — NO agent definitions here): @@ -627,10 +627,15 @@ if __name__ == "__main__": handler() ``` -**`config.yaml`** (same file included in both images — queue URLs and table names are injected by Terraform): +**`config.yaml`** (same file included in both images — queue URLs and table names are injected by Terraform; `batch_size` is Terraform-only, never set here): ```yaml execution: + queues: + input: + no_of_consumers: 5 # parallel input-poll threads in the Agent Runner container (default 5) + output: + no_of_consumers: 5 # parallel output-poll threads in the IO container (default 5) response_store: type: dynamodb retry_count: 30 diff --git a/docs/docs/advanced/queue-mode-guide.md b/docs/docs/advanced/queue-mode-guide.md index ea5a38433..6e5553a0e 100644 --- a/docs/docs/advanced/queue-mode-guide.md +++ b/docs/docs/advanced/queue-mode-guide.md @@ -95,6 +95,10 @@ Same as REST Sync except: 1. The `POST` returns immediately (202) with the session/job ID. 2. The client polls `GET /api/v1/chat/{sessionId}` to retrieve the result. 3. The Request Handler uses **separate routes** for the POST and GET. +4. The poll request must include the same `session_id` the original request was + submitted with. The Request Handler validates the stored response's `session_id` + against it and returns `NOT_FOUND` on a mismatch, so a response can't be read back + under the wrong session. ### WebSocket (Async) Mode @@ -127,16 +131,27 @@ The ECS deployment uses the same pipeline as Lambda, **except Lambda functions a replaced by long-running ECS services**. The IO container runs two threads via `ThreadRunner`; the Agent Runner is a separate ECS service that extends `ECSSQSConsumer`. +Both `ECSSQSConsumer` subclasses (`ECSAgentRunner` and `ECSOutputConsumer`) are +themselves internally multi-threaded: `ECSSQSConsumer.run()` starts `num_consumers` +independent long-lived threads (also via `ThreadRunner`), each running its own +blocking long-poll loop against the same queue. So "Thread 2" of the IO container +is really `no_of_consumers` output-queue-polling threads, and the Agent Runner +container runs `no_of_consumers` input-queue-polling threads — not a single loop. +`num_consumers` defaults to 5 and is configured per-queue via +`execution.queues.input.no_of_consumers` / `execution.queues.output.no_of_consumers` +(ECS only — ignored by Lambda). If any consumer thread crashes, `ThreadRunner` calls +`os._exit(1)` so ECS restarts the whole task. + ### Python Class Hierarchy | Class | Container | Role | |-------|-----------|------| | `ECSIOHandler` | IO container | Entrypoint: starts Thread 1 + Thread 2 via `ThreadRunner` | | `ECSQueueRequestHandler` | IO container / Thread 1 | FastAPI: `POST /api/v1/chat` enqueues; `GET /api/v1/chat/{id}` polls | -| `ECSOutputConsumer` | IO container / Thread 2 | Extends `ECSSQSConsumer` — polls Output Queue → DynamoDB / WebSocket | -| `ECSAgentRunner` | Agent Runner container | Extends `ECSSQSConsumer` — polls Input Queue, runs agent, sends to Output Queue | -| `ECSSQSConsumer` | both | Abstract base: SQS long-poll loop, retry/permanent-failure logic | -| `ThreadRunner` | IO container | Runs N callables as peer threads; calls `os._exit(1)` if any thread crashes | +| `ECSOutputConsumer` | IO container / Thread 2 | Extends `ECSSQSConsumer` — runs `no_of_consumers` threads polling Output Queue → DynamoDB / WebSocket | +| `ECSAgentRunner` | Agent Runner container | Extends `ECSSQSConsumer` — runs `no_of_consumers` threads polling Input Queue, running the agent, sending to Output Queue | +| `ECSSQSConsumer` | both | Abstract base: spins up `num_consumers` poll-loop threads via `ThreadRunner`; each thread does its own long-poll/retry/permanent-failure handling | +| `ThreadRunner` | both | Runs N callables as peer threads; calls `os._exit(1)` if any thread crashes | ### Request Flow — REST Sync @@ -223,6 +238,8 @@ IO container: ``` AK_EXECUTION__QUEUES__INPUT__URL = AK_EXECUTION__QUEUES__OUTPUT__URL = +AK_EXECUTION__QUEUES__OUTPUT__NO_OF_CONSUMERS = # output-queue consumer threads, default 5 +AK_EXECUTION__QUEUES__BATCH_SIZE = # Terraform-set only, never in config.yaml AK_EXECUTION__RESPONSE_STORE__DYNAMODB__TABLE_NAME = ``` @@ -232,6 +249,8 @@ Agent Runner container: AK_EXECUTION__QUEUES__INPUT__URL = AK_EXECUTION__QUEUES__OUTPUT__URL = AK_EXECUTION__QUEUES__INPUT__MAX_RECEIVE_COUNT = +AK_EXECUTION__QUEUES__INPUT__NO_OF_CONSUMERS = # input-queue consumer threads, default 5 +AK_EXECUTION__QUEUES__BATCH_SIZE = # Terraform-set only, never in config.yaml ``` ### Scaling the Agent Runner ECS Service @@ -270,5 +289,5 @@ this automatically. See the [AWS Containerized deployment docs](/docs/deployment | IO Handler / REST Service | ✅ `modules/request-handler/` | ✅ `ECSIOHandler` (`ecs_io_handler.py`) | | Output Queue Consumer | ✅ `modules/response-handler/` (separate Lambda) | ✅ `ECSOutputConsumer` (Thread 2 in IO container) | | DynamoDB Response Store | ✅ serverless stack | ✅ containerized stack | -| Thread management | N/A | ✅ `ThreadRunner` (`core/thread_runner.py`) | +| Thread management | N/A | ✅ `ThreadRunner` (`deployment/common/thread_runner.py`) | | WebSocket Mode | ✅ `modules/websocket-api-gateway/` + `modules/ws-connection-handler/` | ⚠️ `ECSOutputConsumer` supports WebSocket broadcast; API Gateway WebSocket wiring not yet in TF module | diff --git a/docs/docs/core-concepts/configuration.md b/docs/docs/core-concepts/configuration.md index 201196edc..4db3d2b43 100644 --- a/docs/docs/core-concepts/configuration.md +++ b/docs/docs/core-concepts/configuration.md @@ -76,9 +76,11 @@ execution: input: url: "https://sqs.us-east-1.amazonaws.com/123456789012/agent-input" # Input SQS queue URL max_receive_count: 3 # Maximum number of times a message can be received from input queue before being treated as permanently failed + no_of_consumers: 5 # Containerized deployments only — consumer threads polling the input queue (ignored by serverless) output: url: "https://sqs.us-east-1.amazonaws.com/123456789012/agent-output" # Output SQS queue URL max_receive_count: 3 # Maximum number of times a message can be received from output queue before being treated as permanently failed + no_of_consumers: 5 # Containerized deployments only — consumer threads polling the output queue (ignored by serverless) response_store: type: redis # Response store type: redis or dynamodb (required for rest_sync and rest_async modes) retry_count: 5 # Number of retry attempts for response store reads @@ -227,11 +229,13 @@ Alternatively, use `config.json`: "queues": { "input": { "url": "https://sqs.us-east-1.amazonaws.com/123456789012/agent-input", - "max_receive_count": 3 + "max_receive_count": 3, + "no_of_consumers": 5 }, "output": { "url": "https://sqs.us-east-1.amazonaws.com/123456789012/agent-output", - "max_receive_count": 3 + "max_receive_count": 3, + "no_of_consumers": 5 } }, "response_store": { @@ -514,6 +518,11 @@ export AK_EXECUTION__QUEUES__INPUT__MAX_RECEIVE_COUNT=3 export AK_EXECUTION__QUEUES__OUTPUT__URL=https://sqs.us-east-1.amazonaws.com/123456789012/agent-output export AK_EXECUTION__QUEUES__OUTPUT__MAX_RECEIVE_COUNT=3 +# Containerized deployments only — ignored by serverless +export AK_EXECUTION__QUEUES__INPUT__NO_OF_CONSUMERS=5 # Consumer threads polling the input queue (default: 5) +export AK_EXECUTION__QUEUES__OUTPUT__NO_OF_CONSUMERS=5 # Consumer threads polling the output queue (default: 5) +export AK_EXECUTION__QUEUES__BATCH_SIZE=10 # Max messages per SQS receive call; set by Terraform, never in config.yaml + # Response store configuration export AK_EXECUTION__RESPONSE_STORE__TYPE=redis # Options: 'redis', 'dynamodb' export AK_EXECUTION__RESPONSE_STORE__RETRY_COUNT=5 @@ -670,9 +679,12 @@ execution: input: url: "https://sqs.us-east-1.amazonaws.com/123456789012/agent-input" # Input SQS queue URL max_receive_count: 3 # Max receive count before message is treated as failed + no_of_consumers: 5 # Containerized only — consumer threads polling the input queue (ignored by serverless) output: url: "https://sqs.us-east-1.amazonaws.com/123456789012/agent-output" # Output SQS queue URL max_receive_count: 3 # Max receive count before message is treated as failed + no_of_consumers: 5 # Containerized only — consumer threads polling the output queue (ignored by serverless) + # batch_size is ECS-only and Terraform-controlled via AK_EXECUTION__QUEUES__BATCH_SIZE — do not set here response_store: # Response storage configuration (required for rest_sync and rest_async modes) type: "redis" # Response store type: 'redis' or 'dynamodb' retry_count: 5 # Number of retry attempts for response store reads diff --git a/docs/docs/deployment/aws-containerized.md b/docs/docs/deployment/aws-containerized.md index 55d5eb077..0bf842ee1 100644 --- a/docs/docs/deployment/aws-containerized.md +++ b/docs/docs/deployment/aws-containerized.md @@ -64,9 +64,9 @@ graph TB `ECSIOHandler.run()` starts two threads via `ThreadRunner`: - **Thread 1** — `RESTAPI.run(handlers=[ECSQueueRequestHandler()])`: FastAPI/uvicorn. Handles `POST /api/v1/chat` (enqueues to Input Queue, then either waits on DynamoDB or returns a `request_id`) and `GET /api/v1/chat/{session_id}` (polls DynamoDB for the result). -- **Thread 2** — `ECSOutputConsumer.run()`: polls the Output Queue and writes results to DynamoDB (REST modes) or broadcasts via WebSocket. +- **Thread 2** — `ECSOutputConsumer.run()`: polls the Output Queue and writes results to DynamoDB (REST modes) or broadcasts via WebSocket. Internally this "thread" is actually `execution.queues.output.no_of_consumers` (default 5) independent long-poll threads, so multiple output messages are processed concurrently. -If either thread crashes, `ThreadRunner` calls `os._exit(1)` so ECS restarts the task cleanly. +If any thread crashes, `ThreadRunner` calls `os._exit(1)` so ECS restarts the task cleanly. **Entrypoint — `app_rest_service.py`** (no agent definitions): @@ -81,7 +81,7 @@ if __name__ == "__main__": ### Container 2 — Agent Runner (`ECSAgentRunner`) -Extends `ECSSQSConsumer`: polls the Input Queue in a blocking loop, executes the agent, and puts the result on the Output Queue. +Extends `ECSSQSConsumer`: runs `execution.queues.input.no_of_consumers` (default 5) independent threads, each polling the Input Queue in a blocking loop, executing the agent, and putting the result on the Output Queue. **Entrypoint — `app_agent_runner.py`**: diff --git a/docs/docs/deployment/aws-serverless.md b/docs/docs/deployment/aws-serverless.md index 997ea159e..7ffe1512b 100644 --- a/docs/docs/deployment/aws-serverless.md +++ b/docs/docs/deployment/aws-serverless.md @@ -708,11 +708,17 @@ curl -X POST https://{api-id}.execute-api.us-east-1.amazonaws.com/agents/api/v1/ **2. Poll for the response** +`session_id` is required in the poll body and must match the `session_id` the +response was stored under — the poll endpoint validates this and returns +`NOT_FOUND` on a mismatch, so clients must send back the same `session_id` +used in the original submit request. + ```bash curl -X GET https://{api-id}.execute-api.us-east-1.amazonaws.com/agents/api/v1/chat \ -H "Content-Type: application/json" \ -d '{ - "request_id": "req-123" + "request_id": "req-123", + "session_id": "user-123" }' ``` @@ -725,7 +731,9 @@ curl -X GET https://{api-id}.execute-api.us-east-1.amazonaws.com/agents/api/v1/c } ``` -If the response is not available yet, the poll endpoint returns a `NOT_FOUND` body with the same `request_id` so clients can retry. +If the response is not available yet, or the `session_id` does not match the one the +response was stored under, the poll endpoint returns a `NOT_FOUND` body with the +same `request_id` so clients can retry. #### `async` (WebSocket) From f94dedaa33808f103a701363da6084bef5b97851 Mon Sep 17 00:00:00 2001 From: Lakindu Induwara Kumara Date: Fri, 3 Jul 2026 01:14:45 +0530 Subject: [PATCH 69/90] lint fixes --- .../agentkernel/deployment/aws/__init__.py | 2 +- .../aws/containerized/akoutputconsumer.py | 26 ++++--------------- .../aws/containerized/core/sqs_consumer.py | 10 +++---- .../aws/containerized/ecs_io_handler.py | 2 +- .../deployment/common/thread_runner.py | 11 ++++---- ak-py/tests/test_ecs_sqs_consumer_parallel.py | 17 ++++++------ ak-py/tests/test_thread_runner.py | 14 +++------- 7 files changed, 27 insertions(+), 55 deletions(-) diff --git a/ak-py/src/agentkernel/deployment/aws/__init__.py b/ak-py/src/agentkernel/deployment/aws/__init__.py index 4b64961e9..5a92d735f 100644 --- a/ak-py/src/agentkernel/deployment/aws/__init__.py +++ b/ak-py/src/agentkernel/deployment/aws/__init__.py @@ -12,7 +12,7 @@ __version__ = "0.1.0" from .containerized import ECSAgentRunner, ECSIOHandler, ECSOutputConsumer +from .containerized.core import ECSSQSConsumer from .core.sqs_handler import SQSHandler from .serverless import APIGatewayAuthorizer, Lambda, ResponseHandler, ServerlessAgentRunner, WebsocketConnectionHandler -from .containerized.core import ECSSQSConsumer from .serverless.core import LambdaSQSConsumer diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/akoutputconsumer.py b/ak-py/src/agentkernel/deployment/aws/containerized/akoutputconsumer.py index ef50ebbd9..1c9ac3708 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/akoutputconsumer.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/akoutputconsumer.py @@ -44,10 +44,7 @@ def _get_websocket_handler(cls) -> WebSocketHandler: if cls._websocket_handler is None: ws_config = cls._config.websocket_api if not ws_config.connection_table or not ws_config.connection_table.table_name: - raise ValueError( - "websocket_api.connection_table.table_name is required " - "for ECSOutputConsumer in WebSocket mode" - ) + raise ValueError("websocket_api.connection_table.table_name is required " "for ECSOutputConsumer in WebSocket mode") cls._websocket_handler = WebSocketHandler( conn_table_name=ws_config.connection_table.table_name, ttl=ws_config.connection_table.ttl, @@ -78,10 +75,7 @@ def process_message(cls, record: Dict[str, Any]) -> None: f"session_id={message['session_id']}, body_keys={list(message.get('body', {}).keys()) if isinstance(message.get('body'), dict) else 'N/A'}" ) cls._get_response_store().add_message(message) - cls._log.info( - f"[OUTPUT DONE] Stored response — session_id={message['session_id']} " - f"request_id={message['request_id']}" - ) + cls._log.info(f"[OUTPUT DONE] Stored response — session_id={message['session_id']} " f"request_id={message['request_id']}") @classmethod def on_permanent_failure(cls, record: Dict[str, Any]) -> None: @@ -96,10 +90,7 @@ def on_permanent_failure(cls, record: Dict[str, Any]) -> None: :param record: boto3 SQS ``receive_message`` record """ max_retries = cls._config.execution.queues.output.max_receive_count - cls._log.error( - f"Permanent failure for output message {record.get('MessageId')} " - f"after {max_retries} retries" - ) + cls._log.error(f"Permanent failure for output message {record.get('MessageId')} " f"after {max_retries} retries") try: message_attributes = SQSHandler.get_message_custom_attributes(record) @@ -119,19 +110,12 @@ def on_permanent_failure(cls, record: Dict[str, Any]) -> None: user_id=user_id, ) else: - cls._log.warning( - "Cannot broadcast permanent-failure error: " - "endpoint_url or user_id missing" - ) + cls._log.warning("Cannot broadcast permanent-failure error: " "endpoint_url or user_id missing") else: error_body = json.dumps(error_payload) message = cls._construct_message_for_store(record, body=error_body) cls._get_response_store().add_message(message) - cls._log.info( - f"Stored permanent-failure error — " - f"session_id={message['session_id']} " - f"request_id={message['request_id']}" - ) + cls._log.info(f"Stored permanent-failure error — " f"session_id={message['session_id']} " f"request_id={message['request_id']}") except Exception: cls._log.exception("Failed to handle permanent-failure output message") diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py b/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py index 135f94544..ace958d62 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py @@ -4,6 +4,7 @@ import time from abc import ABC, abstractmethod from typing import Any, Dict + import boto3 from .....core.config import AKConfig @@ -110,10 +111,7 @@ def _process_single(cls, msg: dict) -> None: cls._log.debug(f"Processing message {message_id} (receive_count={receive_count})") try: if receive_count > cls.max_receive_count: - cls._log.warning( - f"Message {message_id} exceeded max_receive_count " - f"({receive_count} > {cls.max_receive_count})" - ) + cls._log.warning(f"Message {message_id} exceeded max_receive_count " f"({receive_count} > {cls.max_receive_count})") cls.on_permanent_failure(msg) cls.delete_message(msg) return @@ -128,9 +126,7 @@ def _process_single(cls, msg: dict) -> None: cls._log.debug(f"Processed and deleted message {message_id}") except Exception: - cls._log.exception( - f"Failed to process message {message_id} — leaving in queue for visibility-timeout retry" - ) + cls._log.exception(f"Failed to process message {message_id} — leaving in queue for visibility-timeout retry") # Do NOT delete — visibility timeout returns it for retry @classmethod diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/ecs_io_handler.py b/ak-py/src/agentkernel/deployment/aws/containerized/ecs_io_handler.py index e129aef67..8bae99c0d 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/ecs_io_handler.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/ecs_io_handler.py @@ -48,5 +48,5 @@ def run(cls) -> None: stop_all_on_failure=True, ), ], - max_workers=2 + max_workers=2, ) diff --git a/ak-py/src/agentkernel/deployment/common/thread_runner.py b/ak-py/src/agentkernel/deployment/common/thread_runner.py index ea491a2e9..18ba4f83b 100644 --- a/ak-py/src/agentkernel/deployment/common/thread_runner.py +++ b/ak-py/src/agentkernel/deployment/common/thread_runner.py @@ -36,7 +36,7 @@ def run(tasks: list[Task], max_workers: int | None = None) -> None: return semaphore = threading.Semaphore(max_workers or len(tasks)) - completions: Queue = Queue() # Thread-safe mailbox every worker thread reports its completion to. + completions: Queue = Queue() # Thread-safe mailbox every worker thread reports its completion to. def _target(task: "ThreadRunner.Task") -> None: args = () if task.item is None else (task.item,) @@ -51,15 +51,14 @@ def _target(task: "ThreadRunner.Task") -> None: # daemon=True: without this, the interpreter would wait for every non-daemon thread before # exiting, which would hang forever behind a never-ending task. Daemon threads are abandoned instead. - threads = [ - threading.Thread(target=_target, args=(task,), name=task.thread_name, daemon=True) - for task in tasks - ] + threads = [threading.Thread(target=_target, args=(task,), name=task.thread_name, daemon=True) for task in tasks] for thread in threads: thread.start() for _ in tasks: - task, exc = completions.get() # Pulling from a shared queue exactly len(tasks) times yields tasks in true completion order (whichever finishes first is handled first) + task, exc = ( + completions.get() + ) # Pulling from a shared queue exactly len(tasks) times yields tasks in true completion order (whichever finishes first is handled first) if exc is not None: if task.stop_task_on_failure: _log.exception(f"[{task.thread_name}] raised unexpectedly", exc_info=exc) diff --git a/ak-py/tests/test_ecs_sqs_consumer_parallel.py b/ak-py/tests/test_ecs_sqs_consumer_parallel.py index 1b93cb734..5e657d615 100644 --- a/ak-py/tests/test_ecs_sqs_consumer_parallel.py +++ b/ak-py/tests/test_ecs_sqs_consumer_parallel.py @@ -104,9 +104,12 @@ def fake_poll(): raise result return result - with patch.object(_SyncConsumer, "poll", side_effect=fake_poll), patch( - "agentkernel.deployment.aws.containerized.core.sqs_consumer.time.sleep", - side_effect=RuntimeError("stop-loop"), + with ( + patch.object(_SyncConsumer, "poll", side_effect=fake_poll), + patch( + "agentkernel.deployment.aws.containerized.core.sqs_consumer.time.sleep", + side_effect=RuntimeError("stop-loop"), + ), ): with pytest.raises(RuntimeError, match="stop-loop"): _SyncConsumer._consumer_loop() @@ -123,9 +126,5 @@ def test_process_message_is_sync_not_async(self): from agentkernel.deployment.aws.containerized.akoutputconsumer import ECSOutputConsumer - underlying = getattr( - ECSOutputConsumer.process_message, "__func__", ECSOutputConsumer.process_message - ) - assert not inspect.iscoroutinefunction(underlying), ( - "ECSOutputConsumer.process_message must be sync for this regression check" - ) + underlying = getattr(ECSOutputConsumer.process_message, "__func__", ECSOutputConsumer.process_message) + assert not inspect.iscoroutinefunction(underlying), "ECSOutputConsumer.process_message must be sync for this regression check" diff --git a/ak-py/tests/test_thread_runner.py b/ak-py/tests/test_thread_runner.py index 06e643ed5..97b9e69e5 100644 --- a/ak-py/tests/test_thread_runner.py +++ b/ak-py/tests/test_thread_runner.py @@ -34,12 +34,10 @@ def make_fn(value): def fn(): with lock: results.append(value) + return fn - tasks = [ - ThreadRunner.Task(execution_function=make_fn(i), thread_name=f"t{i}") - for i in range(5) - ] + tasks = [ThreadRunner.Task(execution_function=make_fn(i), thread_name=f"t{i}") for i in range(5)] ThreadRunner.run(tasks) assert sorted(results) == [0, 1, 2, 3, 4] @@ -113,9 +111,7 @@ def run_in_background(): deadline = time.time() + 5 fast_still_alive = True while time.time() < deadline: - fast_still_alive = any( - t.name == fast_name and t.is_alive() for t in threading.enumerate() - ) + fast_still_alive = any(t.name == fast_name and t.is_alive() for t in threading.enumerate()) if not fast_still_alive: break time.sleep(0.01) @@ -127,9 +123,7 @@ def run_in_background(): runner_thread.join(timeout=5) assert result_holder.get("done") is True - def test_stop_all_on_failure_exits_immediately_without_waiting_on_never_ending_task( - self, monkeypatch - ): + def test_stop_all_on_failure_exits_immediately_without_waiting_on_never_ending_task(self, monkeypatch): exit_called = threading.Event() exit_code = {} From e5579a2a1b10e149cf309196fdb0f2e5aaebf43c Mon Sep 17 00:00:00 2001 From: Lakindu Induwara Kumara Date: Fri, 3 Jul 2026 13:35:27 +0530 Subject: [PATCH 70/90] bugfix imports --- ak-py/src/agentkernel/deployment/aws/containerized/__init__.py | 1 - .../agentkernel/deployment/aws/containerized/ecs_io_handler.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/__init__.py b/ak-py/src/agentkernel/deployment/aws/containerized/__init__.py index d745291c0..c64636f69 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/__init__.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/__init__.py @@ -1,4 +1,3 @@ from .akagentrunner import ECSAgentRunner from .akoutputconsumer import ECSOutputConsumer from .ecs_io_handler import ECSIOHandler -from .ecs_queue_handler import ECSQueueRequestHandler diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/ecs_io_handler.py b/ak-py/src/agentkernel/deployment/aws/containerized/ecs_io_handler.py index 8bae99c0d..7771ac98f 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/ecs_io_handler.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/ecs_io_handler.py @@ -1,6 +1,5 @@ import logging -from ....api.http import RESTAPI from ....core.config import AKConfig from ...common import ThreadRunner from .akoutputconsumer import ECSOutputConsumer @@ -29,6 +28,7 @@ class ECSIOHandler: @classmethod def run(cls) -> None: + from ....api.http import RESTAPI from .ecs_queue_handler import ECSQueueRequestHandler mode = cls._config.execution.mode From a8899b2c4f396e70ac5d1e14d1b66c3218967c47 Mon Sep 17 00:00:00 2001 From: Lakindu Induwara Kumara Date: Sun, 5 Jul 2026 19:08:53 +0530 Subject: [PATCH 71/90] threadrunner test updates --- ak-py/tests/test_ecs_sqs_consumer_parallel.py | 20 +++---------------- ak-py/tests/test_thread_runner.py | 2 +- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/ak-py/tests/test_ecs_sqs_consumer_parallel.py b/ak-py/tests/test_ecs_sqs_consumer_parallel.py index 5e657d615..acb626594 100644 --- a/ak-py/tests/test_ecs_sqs_consumer_parallel.py +++ b/ak-py/tests/test_ecs_sqs_consumer_parallel.py @@ -47,23 +47,9 @@ def reset_mocks(): yield -class TestGetNumConsumers: - def test_returns_config_value_when_available(self): - mock_cfg = MagicMock() - mock_cfg.execution.queues.no_of_consumers = 20 - with patch("agentkernel.core.config.AKConfig.get", return_value=mock_cfg) as mock_get: - result = ECSSQSConsumer._get_num_consumers() - mock_get.assert_called_once() - assert result == 20 - - def test_returns_default_when_config_unavailable(self): - with patch( - "agentkernel.core.config.AKConfig.get", - side_effect=RuntimeError("config unavailable"), - ) as mock_get: - result = ECSSQSConsumer._get_num_consumers() - mock_get.assert_called_once() - assert result == ECSSQSConsumer._DEFAULT_PARALLEL_WORKERS +class TestNumConsumers: + def test_base_class_default(self): + assert ECSSQSConsumer.num_consumers == 10 class TestProcessSingle: diff --git a/ak-py/tests/test_thread_runner.py b/ak-py/tests/test_thread_runner.py index 97b9e69e5..679ad8fd1 100644 --- a/ak-py/tests/test_thread_runner.py +++ b/ak-py/tests/test_thread_runner.py @@ -131,7 +131,7 @@ def fake_exit(code): exit_code["code"] = code exit_called.set() - monkeypatch.setattr("agentkernel.deployment.common.thread_runner.sys.exit", fake_exit) + monkeypatch.setattr("agentkernel.deployment.common.thread_runner.os._exit", fake_exit) never_finishes = threading.Event() From ecdba523c01efeb12e9cfa535b60afe4d6f7d636 Mon Sep 17 00:00:00 2001 From: Lakindu Induwara Kumara Date: Mon, 6 Jul 2026 15:22:17 +0530 Subject: [PATCH 72/90] updated examples to match new terraform containerized format --- examples/aws-containerized/adk/deploy/main.tf | 12 ++++++----- .../crewai-auth/deploy/main.tf | 21 +++++++++++-------- .../aws-containerized/crewai/deploy/main.tf | 19 ++++++++++------- .../aws-containerized/mcp/multi/README.md | 16 +++++++------- .../mcp/multi/deploy/main.tf | 14 +++++++------ .../openai-dynamodb/README.md | 2 +- .../openai-dynamodb/deploy/main.tf | 15 +++++++------ 7 files changed, 56 insertions(+), 43 deletions(-) diff --git a/examples/aws-containerized/adk/deploy/main.tf b/examples/aws-containerized/adk/deploy/main.tf index 3b252c642..db1cbc3df 100644 --- a/examples/aws-containerized/adk/deploy/main.tf +++ b/examples/aws-containerized/adk/deploy/main.tf @@ -7,16 +7,18 @@ module "containered_agents" { product_alias = var.product_alias env_alias = var.env_alias module_name = var.module_name - package_path = "../dist" container_type = "ecs" region = var.region vpc_id = var.vpc_id private_subnet_ids = var.private_subnet_ids product_display_name = "AK Google ADK Containered Example" - ecs_container_port = 8000 - # Environment variables passed to container - environment_variables = { - OPENAI_API_KEY = var.openai_api_key + rest_service = { + package_path = "../dist" + container_port = 8000 + # Environment variables passed to container + environment_variables = { + OPENAI_API_KEY = var.openai_api_key + } } } diff --git a/examples/aws-containerized/crewai-auth/deploy/main.tf b/examples/aws-containerized/crewai-auth/deploy/main.tf index 85a9b61de..0e8e4e3b5 100644 --- a/examples/aws-containerized/crewai-auth/deploy/main.tf +++ b/examples/aws-containerized/crewai-auth/deploy/main.tf @@ -11,13 +11,11 @@ module "containered_agents" { product_alias = var.product_alias env_alias = var.env_alias module_name = var.module_name - package_path = "../dist" container_type = "ecs" region = var.region vpc_id = "vpc-09033229d67314c1c" private_subnet_ids = ["subnet-00e888e445f16d1b1", "subnet-0ab5240262cd77119"] product_display_name = "AK CrewAI Auth Containerized Example" - ecs_container_port = 8000 gateway_endpoints = [ { path = "app", @@ -30,12 +28,17 @@ module "containered_agents" { overwrite_path = "/whoami" } ] - # Environment variables passed to container - environment_variables = { - OPENAI_API_KEY = var.openai_api_key, - CREWAI_STORAGE_DIR = "/tmp/crewai", - EMBEDCHAIN_DB_PATH = "/tmp/crewai/embedchain.db", - HOME = "/tmp", - SOME_OTHER_KEY = "Some Other Value" + + rest_service = { + package_path = "../dist" + container_port = 8000 + # Environment variables passed to container + environment_variables = { + OPENAI_API_KEY = var.openai_api_key, + CREWAI_STORAGE_DIR = "/tmp/crewai", + EMBEDCHAIN_DB_PATH = "/tmp/crewai/embedchain.db", + HOME = "/tmp", + SOME_OTHER_KEY = "Some Other Value" + } } } diff --git a/examples/aws-containerized/crewai/deploy/main.tf b/examples/aws-containerized/crewai/deploy/main.tf index 1f8a05465..02326b8f4 100644 --- a/examples/aws-containerized/crewai/deploy/main.tf +++ b/examples/aws-containerized/crewai/deploy/main.tf @@ -11,13 +11,11 @@ module "containered_agents" { product_alias = var.product_alias env_alias = var.env_alias module_name = var.module_name - package_path = "../dist" container_type = "ecs" region = var.region vpc_id = var.vpc_id private_subnet_ids = var.private_subnet_ids product_display_name = "AK CrewAI Containerized Example" - ecs_container_port = 8000 gateway_endpoints = [ { path = "app", @@ -30,11 +28,16 @@ module "containered_agents" { overwrite_path = "/whoami" } ] - # Environment variables passed to container - environment_variables = { - OPENAI_API_KEY = var.openai_api_key, - CREWAI_STORAGE_DIR = "/tmp/crewai", - EMBEDCHAIN_DB_PATH = "/tmp/crewai/embedchain.db", - HOME = "/tmp" + + rest_service = { + package_path = "../dist" + container_port = 8000 + # Environment variables passed to container + environment_variables = { + OPENAI_API_KEY = var.openai_api_key, + CREWAI_STORAGE_DIR = "/tmp/crewai", + EMBEDCHAIN_DB_PATH = "/tmp/crewai/embedchain.db", + HOME = "/tmp" + } } } diff --git a/examples/aws-containerized/mcp/multi/README.md b/examples/aws-containerized/mcp/multi/README.md index fca5d5ee0..1c6240d72 100644 --- a/examples/aws-containerized/mcp/multi/README.md +++ b/examples/aws-containerized/mcp/multi/README.md @@ -35,13 +35,12 @@ This automatically creates an MCP endpoint at: # Containerized module configuration for deploying MCP in ECS module "containered_agents" { source = "yaalalabs/ak-containerized/aws" - version = "0.2.9" + version = "0.6.0" # Basic ECS configuration product_alias = var.product_alias env_alias = var.env_alias module_name = var.module_name - package_path = "../dist" container_type = "ecs" region = var.region @@ -50,18 +49,19 @@ module "containered_agents" { product_display_name = "MCP Containerized Example" - # Container & networking - ecs_container_port = 8000 - # Optional dependencies create_redis_cluster = true # Enable MCP server enable_mcp_server = true # MCP endpoint => ///mcp - # Environment variables passed to the container - environment_variables = { - OPENAI_API_KEY = var.openai_api_key + # REST service (container build, networking, and runtime env vars) + rest_service = { + package_path = "../dist" + container_port = 8000 + environment_variables = { + OPENAI_API_KEY = var.openai_api_key + } } } ``` diff --git a/examples/aws-containerized/mcp/multi/deploy/main.tf b/examples/aws-containerized/mcp/multi/deploy/main.tf index 552c1587f..b4b7900ce 100644 --- a/examples/aws-containerized/mcp/multi/deploy/main.tf +++ b/examples/aws-containerized/mcp/multi/deploy/main.tf @@ -7,18 +7,20 @@ module "containered_agents" { product_alias = var.product_alias env_alias = var.env_alias module_name = var.module_name - package_path = "../dist" container_type = "ecs" region = var.region vpc_id = var.vpc_id private_subnet_ids = var.private_subnet_ids product_display_name = "MCP Containered Example" - ecs_container_port = 8000 create_redis_cluster = true - enable_mcp_server = true # endpoint = ///mcp + enable_mcp_server = true # endpoint = ///mcp - # Environment variables passed to container - environment_variables = { - OPENAI_API_KEY = var.openai_api_key + rest_service = { + package_path = "../dist" + container_port = 8000 + # Environment variables passed to container + environment_variables = { + OPENAI_API_KEY = var.openai_api_key + } } } diff --git a/examples/aws-containerized/openai-dynamodb/README.md b/examples/aws-containerized/openai-dynamodb/README.md index 27918ee45..c9ddc8fae 100644 --- a/examples/aws-containerized/openai-dynamodb/README.md +++ b/examples/aws-containerized/openai-dynamodb/README.md @@ -58,7 +58,7 @@ The `deploy/deploy.sh` script builds the Docker image, pushes it to ECR, and the If you prefer to have Terraform build the image locally instead of using a pre-built ECR image, you can: 1. Set `ecr_image_uri = null` in `terraform.tfvars` -2. Set `package_path = "../dist"` in `deploy/main.tf` +2. Remove the `image_uri` line from the `rest_service` block in `deploy/main.tf` (keep `package_path = "../dist"`) 3. The module will build and push the image from the local source during `terraform apply` However, the default approach (using an external ECR image) is recommended for production deployments as it provides better control over the build and versioning process. diff --git a/examples/aws-containerized/openai-dynamodb/deploy/main.tf b/examples/aws-containerized/openai-dynamodb/deploy/main.tf index 7ac5607da..2118acfb5 100644 --- a/examples/aws-containerized/openai-dynamodb/deploy/main.tf +++ b/examples/aws-containerized/openai-dynamodb/deploy/main.tf @@ -13,11 +13,14 @@ module "containered_agents" { create_dynamodb_memory_table = true private_subnet_ids = var.private_subnet_ids product_display_name = "OpenAI Agents" - ecs_container_port = 8000 - ecr_image_uri = var.ecr_image_uri - # Environment variables passed to container - environment_variables = { - OPENAI_API_KEY = var.openai_api_key + rest_service = { + package_path = "../dist" + container_port = 8000 + image_uri = var.ecr_image_uri + # Environment variables passed to container + environment_variables = { + OPENAI_API_KEY = var.openai_api_key + } } -} +} \ No newline at end of file From 6a78b6e6b2376967efade7958ff4cf3c01eebd25 Mon Sep 17 00:00:00 2001 From: Lakindu Induwara Kumara Date: Mon, 6 Jul 2026 18:01:38 +0530 Subject: [PATCH 73/90] review update 1 --- ak-py/src/agentkernel/core/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ak-py/src/agentkernel/core/config.py b/ak-py/src/agentkernel/core/config.py index ac74b46f6..379c8095a 100644 --- a/ak-py/src/agentkernel/core/config.py +++ b/ak-py/src/agentkernel/core/config.py @@ -261,7 +261,7 @@ class _InputQueueConfig(BaseModel): description=( "Only used in Containerized deployments " "Number of independent consumer threads that each poll the input queue " - "in a continuous loop. Only used by ECS containerized deployments — never set for " + "in a continuous loop. Only used by ECS containerized deployments, it is not used for " "serverless (Lambda) mode, which has no consumer threads. " "Override via env var AK_EXECUTION__QUEUES__INPUT__NO_OF_CONSUMERS." ), @@ -278,7 +278,7 @@ class _OutputQueueConfig(BaseModel): description=( "Only used in Containerized deployments " "Number of independent consumer threads that each poll the output queue " - "in a continuous loop. Only used by ECS containerized deployments — never set for " + "in a continuous loop. Only used by ECS containerized deployments, it is not used for " "serverless (Lambda) mode, which has no consumer threads. " "Override via env var AK_EXECUTION__QUEUES__OUTPUT__NO_OF_CONSUMERS." ), From c7d7661856999eed74cc57fb12f1f1ed7e7a5122 Mon Sep 17 00:00:00 2001 From: Lakindu Induwara Kumara Date: Mon, 6 Jul 2026 18:39:14 +0530 Subject: [PATCH 74/90] remove unwanted files --- .../debug_response_store.py | 125 ------------------ 1 file changed, 125 deletions(-) delete mode 100644 examples/aws-containerized/openai-dynamodb-scalable/debug_response_store.py diff --git a/examples/aws-containerized/openai-dynamodb-scalable/debug_response_store.py b/examples/aws-containerized/openai-dynamodb-scalable/debug_response_store.py deleted file mode 100644 index 0b30cb7cb..000000000 --- a/examples/aws-containerized/openai-dynamodb-scalable/debug_response_store.py +++ /dev/null @@ -1,125 +0,0 @@ -#!/usr/bin/env python3 -""" -Debug script to inspect DynamoDB Response Store. - -This helps diagnose issues with response caching and UUID generation. - -Usage: - python debug_response_store.py [--table-name ] [--list] [--clear] -""" - -import argparse -import sys -from decimal import Decimal - -import boto3 -from boto3.dynamodb.conditions import Attr - - -def decimal_default(obj): - """JSON serializer for Decimal objects.""" - if isinstance(obj, Decimal): - return float(obj) - raise TypeError - - -def list_responses(table_name: str): - """List all responses in the DynamoDB table.""" - dynamodb = boto3.resource("dynamodb") - table = dynamodb.Table(table_name) - - print(f"\n=== Scanning DynamoDB Table: {table_name} ===\n") - - try: - response = table.scan() - items = response.get("Items", []) - - if not items: - print("No items found in the table.") - return - - print(f"Found {len(items)} item(s):\n") - - for idx, item in enumerate(items, 1): - print(f"--- Item {idx} ---") - print(f"request_id: {item.get('request_id', 'N/A')}") - print(f"session_id: {item.get('session_id', 'N/A')}") - - body = item.get("body", {}) - if isinstance(body, dict): - print(f"body.session_id: {body.get('session_id', 'N/A')}") - print(f"body.message: {body.get('message', 'N/A')[:100] if body.get('message') else 'N/A'}...") - else: - print(f"body: {str(body)[:100]}...") - - if "expiry_time" in item: - print(f"expiry_time: {item['expiry_time']}") - - print() - - except Exception as e: - print(f"Error scanning table: {e}") - sys.exit(1) - - -def clear_responses(table_name: str): - """Clear all responses from the DynamoDB table.""" - dynamodb = boto3.resource("dynamodb") - table = dynamodb.Table(table_name) - - print(f"\n=== Clearing DynamoDB Table: {table_name} ===\n") - - try: - response = table.scan() - items = response.get("Items", []) - - if not items: - print("No items to delete.") - return - - print(f"Deleting {len(items)} item(s)...") - - with table.batch_writer() as batch: - for item in items: - request_id = item.get("request_id") - if request_id: - batch.delete_item(Key={"request_id": request_id}) - print(f" Deleted: request_id={request_id}, session_id={item.get('session_id', 'N/A')}") - - print(f"\nSuccessfully deleted {len(items)} item(s).") - - except Exception as e: - print(f"Error clearing table: {e}") - sys.exit(1) - - -def main(): - parser = argparse.ArgumentParser(description="Debug DynamoDB Response Store") - parser.add_argument( - "--table-name", - default="ak-oai-scl-ecs-dev-response-store", - help="DynamoDB table name (default: ak-oai-scl-ecs-dev-response-store)", - ) - parser.add_argument("--list", action="store_true", help="List all responses in the table") - parser.add_argument("--clear", action="store_true", help="Clear all responses from the table") - - args = parser.parse_args() - - if not args.list and not args.clear: - parser.print_help() - print("\nError: Please specify --list or --clear") - sys.exit(1) - - if args.list: - list_responses(args.table_name) - - if args.clear: - confirm = input(f"\nAre you sure you want to clear all items from {args.table_name}? (yes/no): ") - if confirm.lower() == "yes": - clear_responses(args.table_name) - else: - print("Clear operation cancelled.") - - -if __name__ == "__main__": - main() From 08891629cb35169044fb81d92c2b411766153fe4 Mon Sep 17 00:00:00 2001 From: Lakindu Induwara Kumara Date: Tue, 7 Jul 2026 07:44:30 +0530 Subject: [PATCH 75/90] QueueConsumer abstract skeleton class addition --- .../aws/containerized/core/sqs_consumer.py | 6 +- .../aws/serverless/core/sqs_consumer.py | 10 +++- .../agentkernel/deployment/common/__init__.py | 1 + .../deployment/common/queue_consumer.py | 55 +++++++++++++++++++ docs/docs/advanced/queue-mode-guide.md | 3 +- 5 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 ak-py/src/agentkernel/deployment/common/queue_consumer.py diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py b/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py index ace958d62..a8eb51534 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py @@ -2,16 +2,16 @@ import inspect import logging import time -from abc import ABC, abstractmethod +from abc import abstractmethod from typing import Any, Dict import boto3 from .....core.config import AKConfig -from ....common import ThreadRunner +from ....common import QueueConsumer, ThreadRunner -class ECSSQSConsumer(ABC): +class ECSSQSConsumer(QueueConsumer): """ Base class for ECS services that consume SQS queues via manual long-polling. diff --git a/ak-py/src/agentkernel/deployment/aws/serverless/core/sqs_consumer.py b/ak-py/src/agentkernel/deployment/aws/serverless/core/sqs_consumer.py index 63b34327b..bc5c61aee 100644 --- a/ak-py/src/agentkernel/deployment/aws/serverless/core/sqs_consumer.py +++ b/ak-py/src/agentkernel/deployment/aws/serverless/core/sqs_consumer.py @@ -1,14 +1,20 @@ import logging import traceback -from abc import ABC, abstractmethod +from abc import abstractmethod from typing import Any, Dict, List +from ....common import QueueConsumer -class LambdaSQSConsumer(ABC): + +class LambdaSQSConsumer(QueueConsumer): """ Base class for AWS Lambda consumers triggered by an SQS Event Source Mapping. Subclasses should override `process_message` to implement business logic. + + Lambda is push-triggered: the Event Source Mapping polls SQS and deletes + successfully-processed messages on our behalf, so `poll` and `delete_message` + have no role to play here and are intentionally left unimplemented. """ max_receive_count: int = 3 # Fallback value, actual configurable values are defined in the subclasses diff --git a/ak-py/src/agentkernel/deployment/common/__init__.py b/ak-py/src/agentkernel/deployment/common/__init__.py index 9b0e13f8f..382d65d2a 100644 --- a/ak-py/src/agentkernel/deployment/common/__init__.py +++ b/ak-py/src/agentkernel/deployment/common/__init__.py @@ -1 +1,2 @@ +from .queue_consumer import QueueConsumer from .thread_runner import ThreadRunner diff --git a/ak-py/src/agentkernel/deployment/common/queue_consumer.py b/ak-py/src/agentkernel/deployment/common/queue_consumer.py new file mode 100644 index 000000000..7886d2382 --- /dev/null +++ b/ak-py/src/agentkernel/deployment/common/queue_consumer.py @@ -0,0 +1,55 @@ +from abc import ABC, abstractmethod +from typing import Any, Dict, List + + +class QueueConsumer(ABC): + """ + Shared abstract interface for queue-backed consumers, regardless of whether the + underlying platform pushes messages to the consumer (e.g. Lambda + SQS Event Source + Mapping) or requires the consumer to actively pull them (e.g. ECS long-polling SQS). + + Subclasses implement the four primitives below in whatever way fits their delivery + model; shared retry/failure semantics (max_receive_count) live on this base class. + """ + + max_receive_count: int = 3 # Fallback value, actual configurable values are defined in the subclasses + + @classmethod + @abstractmethod + def poll(cls) -> List[Dict[str, Any]]: + """ + Retrieve the next batch of messages to process. + + :return: A list of raw queue records to be passed to process_message. + """ + raise NotImplementedError + + @classmethod + @abstractmethod + def process_message(cls, record: Dict[str, Any]) -> None: + """ + Process a single queue message. + :param record: Single message record, as returned by poll(). + :return: None + """ + raise NotImplementedError + + @classmethod + @abstractmethod + def on_permanent_failure(cls, record: Dict[str, Any]) -> None: + """ + Called when a message is treated as permanently failed (retry limit reached). + :param record: The message record that exceeded the retry threshold. + :return: None + """ + raise NotImplementedError + + @classmethod + @abstractmethod + def delete_message(cls, record: Dict[str, Any]) -> None: + """ + Acknowledge/remove a message from the queue after it has been handled. + :param record: The message record to acknowledge. + :return: None + """ + raise NotImplementedError diff --git a/docs/docs/advanced/queue-mode-guide.md b/docs/docs/advanced/queue-mode-guide.md index 6e5553a0e..efb0f893a 100644 --- a/docs/docs/advanced/queue-mode-guide.md +++ b/docs/docs/advanced/queue-mode-guide.md @@ -150,7 +150,8 @@ container runs `no_of_consumers` input-queue-polling threads — not a single lo | `ECSQueueRequestHandler` | IO container / Thread 1 | FastAPI: `POST /api/v1/chat` enqueues; `GET /api/v1/chat/{id}` polls | | `ECSOutputConsumer` | IO container / Thread 2 | Extends `ECSSQSConsumer` — runs `no_of_consumers` threads polling Output Queue → DynamoDB / WebSocket | | `ECSAgentRunner` | Agent Runner container | Extends `ECSSQSConsumer` — runs `no_of_consumers` threads polling Input Queue, running the agent, sending to Output Queue | -| `ECSSQSConsumer` | both | Abstract base: spins up `num_consumers` poll-loop threads via `ThreadRunner`; each thread does its own long-poll/retry/permanent-failure handling | +| `ECSSQSConsumer` | both | Extends `QueueConsumer`; spins up `num_consumers` poll-loop threads via `ThreadRunner`; each thread does its own long-poll/retry/permanent-failure handling | +| `QueueConsumer` | shared (Lambda + ECS) | Abstract base declaring `poll`, `process_message`, `on_permanent_failure`, `delete_message` — also the base of `LambdaSQSConsumer` (the Lambda-side equivalent, which leaves `poll`/`delete_message` unimplemented since the SQS Event Source Mapping handles those for Lambda) | | `ThreadRunner` | both | Runs N callables as peer threads; calls `os._exit(1)` if any thread crashes | ### Request Flow — REST Sync From 8fabe82bc9b41f2376e5737606055098202a7086 Mon Sep 17 00:00:00 2001 From: Lakindu Induwara Kumara Date: Tue, 7 Jul 2026 17:44:51 +0530 Subject: [PATCH 76/90] thread runner and docuentation updates --- .agents/skills/ak-dev-architecture/SKILL.md | 102 +++++++++++++++--- .../aws/containerized/core/sqs_consumer.py | 3 +- .../aws/containerized/ecs_io_handler.py | 1 + .../deployment/common/thread_runner.py | 37 +++++-- .../skills/ak-cloud-deploy/SKILL.md | 4 +- ak-py/tests/test_thread_runner.py | 10 ++ docs/docs/advanced/queue-mode-guide.md | 12 ++- docs/docs/core-concepts/configuration.md | 4 +- docs/docs/deployment/aws-containerized.md | 7 +- 9 files changed, 144 insertions(+), 36 deletions(-) diff --git a/.agents/skills/ak-dev-architecture/SKILL.md b/.agents/skills/ak-dev-architecture/SKILL.md index bf1503075..dfadf952f 100644 --- a/.agents/skills/ak-dev-architecture/SKILL.md +++ b/.agents/skills/ak-dev-architecture/SKILL.md @@ -6,7 +6,7 @@ description: > interact, or before making changes to core functionality. Covers Session, Agent, Runner, Module, Runtime, AgentService, AKConfig, tools, hooks, multimodal, the adapter pattern, and the AWS ECS containerized deployment classes (ECSIOHandler, ECSOutputConsumer, - ECSAgentRunner, ECSSQSConsumer, ThreadRunner). + ECSAgentRunner, ECSSQSConsumer, QueueConsumer, ThreadRunner). license: Apache-2.0 metadata: author: yaalalabs @@ -218,12 +218,16 @@ ak-py/src/agentkernel/ │ ├── a2a/ # Agent-to-Agent server │ └── mcp/ # MCP server ├── deployment/ # Cloud deployment adapters +│ ├── common/ # Shared across Lambda + ECS +│ │ ├── thread_runner.py # ThreadRunner — run N callables as peer threads +│ │ ├── queue_consumer.py # QueueConsumer — ABC shared by ECSSQSConsumer + LambdaSQSConsumer +│ │ ├── response_store.py # ResponseStore +│ │ └── websocket_connection_store.py │ ├── aws/ │ │ ├── serverless/ # Lambda handlers: Lambda, ResponseHandler, ServerlessAgentRunner, etc. │ │ ├── containerized/ # ECS Fargate handlers │ │ │ ├── core/ -│ │ │ │ ├── sqs_consumer.py # ECSSQSConsumer — ABC: SQS poll loop -│ │ │ │ └── thread_runner.py # ThreadRunner — run N callables as peer threads +│ │ │ │ └── sqs_consumer.py # ECSSQSConsumer — extends QueueConsumer: SQS poll loop │ │ │ ├── akagentrunner.py # ECSAgentRunner — polls Input Queue, runs agent │ │ │ ├── akoutputconsumer.py # ECSOutputConsumer — polls Output Queue, writes to DB/WS │ │ │ ├── ecs_io_handler.py # ECSIOHandler — entrypoint: wires both threads @@ -272,8 +276,9 @@ The containerized deployment runs on ECS Fargate and uses a two-container archit | Class | File | Role | |---|---|---| -| `ECSSQSConsumer` | `containerized/core/sqs_consumer.py` | Abstract base: SQS long-poll loop, retry/DLQ logic | -| `ThreadRunner` | `containerized/core/thread_runner.py` | Runs N callables as peer threads via `ThreadPoolExecutor` | +| `QueueConsumer` | `deployment/common/queue_consumer.py` | Abstract base shared by `ECSSQSConsumer` and `LambdaSQSConsumer`: declares `poll`, `process_message`, `on_permanent_failure`, `delete_message` | +| `ECSSQSConsumer` | `containerized/core/sqs_consumer.py` | Extends `QueueConsumer`: SQS long-poll loop, retry/DLQ logic | +| `ThreadRunner` | `deployment/common/thread_runner.py` | Runs N callables as peer threads (one `threading.Thread` per `Task`, gated by a `Semaphore`) | | `ECSOutputConsumer` | `containerized/akoutputconsumer.py` | Extends `ECSSQSConsumer` — polls Output Queue, writes to DynamoDB or broadcasts via WebSocket | | `ECSAgentRunner` | `containerized/akagentrunner.py` | Extends `ECSSQSConsumer` — polls Input Queue, runs the agent, sends to Output Queue | | `ECSIOHandler` | `containerized/ecs_io_handler.py` | Entrypoint for the IO container: wires REST API + output consumer as peer threads | @@ -289,24 +294,90 @@ Container 1 — ECSIOHandler — polls Output Queue, writes to DynamoDB / broadcasts via WebSocket Container 2 — ECSAgentRunner - Main thread: ECSSQSConsumer.run() - — polls Input Queue, runs agent, sends result to Output Queue + N threads (ThreadRunner): ECSSQSConsumer._consumer_loop, one per + execution.queues.input.no_of_consumers (default 5) + — each polls Input Queue, runs agent, sends result to Output Queue ``` ### ECSSQSConsumer Contract -- **`_get_queue_url(cls) → str`** *(abstract)*: return the SQS queue URL to poll. -- **`process_message(cls, record)`** *(abstract)*: handle one message; called on every successful receive. -- **`on_permanent_failure(cls, record)`** *(abstract)*: called when `ApproximateReceiveCount > max_receive_count`; **must catch its own exceptions** — if it raises, the message is not deleted and loops back. -- **`delete_message(cls, client, msg)`** *(public)*: subclasses may call this directly when manual deletion is needed. +- **`get_queue_url(cls) → str`** *(abstract)*: return the SQS queue URL to poll. +- **`process_message(cls, record)`** *(abstract, from `QueueConsumer`)*: handle one message; called on every successful receive. +- **`on_permanent_failure(cls, record)`** *(abstract, from `QueueConsumer`)*: called when `ApproximateReceiveCount > max_receive_count`; **must catch its own exceptions** — if it raises, the message is not deleted and loops back. +- **`delete_message(cls, msg: dict)`** *(public)*: subclasses may call this directly when manual deletion is needed. - **`run(cls)`**: blocking poll loop — the container entry-point. ### ThreadRunner Contract -`ThreadRunner.run(*targets, thread_names=..., exit_on_failure=True)` submits all callables to a `ThreadPoolExecutor` and waits for `FIRST_COMPLETED`: +`ThreadRunner.run(tasks: list[ThreadRunner.Task], max_workers=None) -> dict[Task, Any]` starts one +`threading.Thread` per `Task` (daemon, so a never-ending task can't block interpreter shutdown), +gated by a `Semaphore(max_workers or len(tasks))`, and drains completions off a shared queue until +every task in that call has reported in. It returns a dict keyed by the exact `Task` instance, +populated only for tasks that completed without raising. -- Thread **raises** → logs exception; if `exit_on_failure=True`, calls `os._exit(1)` inside the `with` block so the container restarts cleanly via ECS (the `_exit` is placed before `executor.shutdown(wait=True)` to avoid blocking on the other infinite-loop thread). -- Thread **returns normally** (no exception) → logs unexpected exit; `os._exit` is **not** called. +Each `ThreadRunner.Task` has: +- `stop_task_on_failure` (default `True`) — log and ignore vs. log only, on that task's own exception. +- `stop_all_on_failure` (default `False`) — also bring down the whole `run()` call on that task's failure. Requires `stop_task_on_failure=True`. +- `graceful` (default `False`) — only meaningful with `stop_all_on_failure=True`. Requires it, or raises `ValueError`. + +On a task raising with `stop_all_on_failure=True`: +- `graceful=False` → logs the exception, `logging.shutdown()` + `os._exit(1)` **immediately**, without waiting on other tasks. +- `graceful=True` → logs the exception, sets a **class-level singleton** `ThreadRunner.shutdown_event` (a `threading.Event`), and keeps draining the *other tasks started by this same `run()` call*. Cooperating tasks (e.g. `ECSSQSConsumer._consumer_loop`) check `ThreadRunner.shutdown_event.is_set()` in their loop condition and return once set. Only after every task from this call has reported completion does it check `shutdown_event` and call `os._exit(1)` — so it never waits on tasks it didn't itself start (e.g. the IO container's `rest-api` thread, which doesn't check the event at all and is simply killed when `os._exit(1)` fires). + +#### How to Use ThreadRunner + +`ThreadRunner` is internal-only — not part of the public API, never imported by user application +code. Reach for it when adding a new internal component that needs several peer threads with +uniform failure handling (as opposed to a raw `threading.Thread`, which gives you none of the +crash/result/shutdown plumbing below for free). + +```python +from agentkernel.deployment.common import ThreadRunner + +def poll_forever(): + while not ThreadRunner.shutdown_event.is_set(): + ... # do one unit of work per iteration, so the shutdown check is actually reached + +def compute(item): + return item * 2 # a task's return value shows up in the results dict, keyed by its Task + +results = ThreadRunner.run( + tasks=[ + ThreadRunner.Task( + execution_function=poll_forever, + thread_name="poller", + stop_all_on_failure=True, + graceful=True, # opt in only if this task's execution_function actually checks + # shutdown_event in its loop — otherwise "graceful" drain never ends + ), + ThreadRunner.Task(execution_function=compute, thread_name="compute-1", item=5), + ], +) +``` + +Guidance: +- A task with a `while` loop that should participate in a graceful shutdown **must** check + `ThreadRunner.shutdown_event.is_set()` (or `.wait(timeout)` for a sleep/backoff) once per + iteration before setting `graceful=True` on it — `graceful=True` on a task that never checks the + event just makes that `run()` call hang forever waiting for it to return (see "No hard timeout + on graceful drain" caveat below). +- `stop_all_on_failure=True` always requires `stop_task_on_failure=True` (the default), and + `graceful=True` always requires `stop_all_on_failure=True` — both raise `ValueError` in + `Task.__post_init__` if violated. +- Only set `graceful=True` on tasks in a `run()` call where every *other* task in that same call is + either similarly cooperative or expected to be killed abruptly anyway — e.g. `ECSIOHandler` + deliberately leaves its `rest-api` task non-cooperative (it never checks `shutdown_event`), + accepting that it's simply cut off whenever `os._exit(1)` eventually fires. +- `ThreadRunner.shutdown_event` is a **process-wide singleton**, not scoped to one `run()` call — + once any call sets it, every other `run()` call in the process sees it set too, and will + `os._exit(1)` once its own tasks finish draining. This is deliberate (a fatal failure anywhere + should bring the whole process down), but it also means **tests must reset it between cases** + (`ThreadRunner.shutdown_event.clear()`), or a graceful-path test will leave every later test in + the same run silently triggering a real `os._exit(1)`. See the `autouse` fixture in + `ak-py/tests/test_thread_runner.py`. +- No hard timeout on graceful drain: if a task in the *same batch* as the failure never checks + `shutdown_event` and never naturally returns, that `run()` call's drain loop — and therefore its + `os._exit(1)` — never fires. ### Entry Point Pattern @@ -338,7 +409,8 @@ from agentkernel.deployment.aws import ( ECSIOHandler, # Container 1 entry-point ECSOutputConsumer, # Subclass ECSSQSConsumer for custom output processing ) -from agentkernel.deployment.aws.containerized.core import ECSSQSConsumer, ThreadRunner +from agentkernel.deployment.aws.containerized.core import ECSSQSConsumer +from agentkernel.deployment.common import ThreadRunner ``` ## Execution Flow diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py b/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py index a8eb51534..06dcdd29e 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py @@ -131,7 +131,7 @@ def _process_single(cls, msg: dict) -> None: @classmethod def _consumer_loop(cls) -> None: - while True: + while not ThreadRunner.shutdown_event.is_set(): try: messages = cls.poll() except Exception: @@ -165,6 +165,7 @@ def run(cls) -> None: execution_function=cls._consumer_loop, thread_name=f"sqs-consumer-{i}", stop_all_on_failure=True, + graceful=True, ) for i in range(num_consumers) ], diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/ecs_io_handler.py b/ak-py/src/agentkernel/deployment/aws/containerized/ecs_io_handler.py index 7771ac98f..2df9c60a9 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/ecs_io_handler.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/ecs_io_handler.py @@ -46,6 +46,7 @@ def run(cls) -> None: execution_function=lambda: ECSOutputConsumer.run(), thread_name="output-queue-consumer", stop_all_on_failure=True, + graceful=True, ), ], max_workers=2, diff --git a/ak-py/src/agentkernel/deployment/common/thread_runner.py b/ak-py/src/agentkernel/deployment/common/thread_runner.py index 18ba4f83b..b278d7761 100644 --- a/ak-py/src/agentkernel/deployment/common/thread_runner.py +++ b/ak-py/src/agentkernel/deployment/common/thread_runner.py @@ -18,22 +18,27 @@ class ThreadRunner: idle until every task in the batch — including any that never finish — has completed. """ - @dataclass + shutdown_event: threading.Event = threading.Event() + + @dataclass(eq=False) class Task: execution_function: Callable thread_name: str item: Any = None stop_task_on_failure: bool = True stop_all_on_failure: bool = False + graceful: bool = False def __post_init__(self) -> None: if self.stop_all_on_failure and not self.stop_task_on_failure: raise ValueError("stop_all_on_failure=True requires stop_task_on_failure=True") + if self.graceful and not self.stop_all_on_failure: + raise ValueError("graceful=True requires stop_all_on_failure=True") @staticmethod - def run(tasks: list[Task], max_workers: int | None = None) -> None: + def run(tasks: list[Task], max_workers: int | None = None) -> dict[Task, Any]: if not tasks: - return + return {} semaphore = threading.Semaphore(max_workers or len(tasks)) completions: Queue = Queue() # Thread-safe mailbox every worker thread reports its completion to. @@ -42,11 +47,11 @@ def _target(task: "ThreadRunner.Task") -> None: args = () if task.item is None else (task.item,) with semaphore: try: - task.execution_function(*args) + result = task.execution_function(*args) except Exception as exc: - completions.put((task, exc)) + completions.put((task, None, exc)) else: - completions.put((task, None)) + completions.put((task, result, None)) # the thread ends itself once execution_function returns (or raises), and Python/the OS reclaim it. # daemon=True: without this, the interpreter would wait for every non-daemon thread before @@ -55,18 +60,30 @@ def _target(task: "ThreadRunner.Task") -> None: for thread in threads: thread.start() + results: dict[ThreadRunner.Task, Any] = {} for _ in tasks: - task, exc = ( + task, result, exc = ( completions.get() ) # Pulling from a shared queue exactly len(tasks) times yields tasks in true completion order (whichever finishes first is handled first) if exc is not None: if task.stop_task_on_failure: _log.exception(f"[{task.thread_name}] raised unexpectedly", exc_info=exc) if task.stop_all_on_failure: - _log.debug(f"[{task.thread_name}] stopping all processes") - logging.shutdown() - os._exit(1) + if task.graceful: + _log.debug(f"[{task.thread_name}] gracefully stopping all") + ThreadRunner.shutdown_event.set() + else: + _log.debug(f"[{task.thread_name}] stopping all processes") + logging.shutdown() + os._exit(1) else: _log.debug(f"[{task.thread_name}] raised (stop_task_on_failure=False, ignoring)") else: + results[task] = result _log.debug(f"[{task.thread_name}] completed") + + if ThreadRunner.shutdown_event.is_set(): + logging.shutdown() + os._exit(1) + + return results diff --git a/ak-py/src/agentkernel/skills/ak-cloud-deploy/SKILL.md b/ak-py/src/agentkernel/skills/ak-cloud-deploy/SKILL.md index 163930fce..fc2111221 100644 --- a/ak-py/src/agentkernel/skills/ak-cloud-deploy/SKILL.md +++ b/ak-py/src/agentkernel/skills/ak-cloud-deploy/SKILL.md @@ -772,8 +772,8 @@ dependencies = [ **Key rules:** - Agent definitions (`OpenAIModule([...])`) go in `app_agent_runner.py` only — never in `app_rest_service.py`. -- `ECSIOHandler` starts two threads via `ThreadRunner`; if either thread crashes, the container exits (`os._exit(1)`) so ECS can restart it. -- `ECSAgentRunner` and `ECSOutputConsumer` both extend `ECSSQSConsumer` — extend either class to customise message processing. +- `ECSIOHandler` starts two threads via `ThreadRunner`; if the output-consumer pool crashes, sibling consumer threads finish their in-flight message first (graceful drain via a shared `shutdown_event`), then the container exits (`os._exit(1)`) so ECS can restart it. The REST API thread doesn't participate in the drain — it's just terminated at that point. +- `ECSAgentRunner` and `ECSOutputConsumer` both extend `ECSSQSConsumer` (itself a `QueueConsumer` — the same base `LambdaSQSConsumer` extends) — extend either class to customise message processing. ## Azure Serverless (Functions + APIM) diff --git a/ak-py/tests/test_thread_runner.py b/ak-py/tests/test_thread_runner.py index 679ad8fd1..c86321a53 100644 --- a/ak-py/tests/test_thread_runner.py +++ b/ak-py/tests/test_thread_runner.py @@ -6,6 +6,16 @@ from agentkernel.deployment.common.thread_runner import ThreadRunner +@pytest.fixture(autouse=True) +def _reset_shutdown_event(): + """shutdown_event is a class-level singleton — any test that sets it (e.g. a + graceful=True case) would otherwise leave every later test in the session + seeing it set, silently triggering their own (real, unmocked) os._exit(1).""" + ThreadRunner.shutdown_event.clear() + yield + ThreadRunner.shutdown_event.clear() + + class TestTaskValidation: def test_stop_all_on_failure_requires_stop_task_on_failure(self): with pytest.raises(ValueError): diff --git a/docs/docs/advanced/queue-mode-guide.md b/docs/docs/advanced/queue-mode-guide.md index efb0f893a..ad3e34129 100644 --- a/docs/docs/advanced/queue-mode-guide.md +++ b/docs/docs/advanced/queue-mode-guide.md @@ -139,8 +139,12 @@ is really `no_of_consumers` output-queue-polling threads, and the Agent Runner container runs `no_of_consumers` input-queue-polling threads — not a single loop. `num_consumers` defaults to 5 and is configured per-queue via `execution.queues.input.no_of_consumers` / `execution.queues.output.no_of_consumers` -(ECS only — ignored by Lambda). If any consumer thread crashes, `ThreadRunner` calls -`os._exit(1)` so ECS restarts the whole task. +(ECS only — ignored by Lambda). If any consumer thread crashes, `ThreadRunner` triggers a +graceful shutdown: it sets a shared `shutdown_event`, waits for the sibling consumer +threads in that same pool to finish their current poll/message and return, then calls +`os._exit(1)` so ECS restarts the whole task. The REST API thread does not check +`shutdown_event` — it is simply terminated along with everything else the moment +`os._exit(1)` fires. ### Python Class Hierarchy @@ -152,7 +156,7 @@ container runs `no_of_consumers` input-queue-polling threads — not a single lo | `ECSAgentRunner` | Agent Runner container | Extends `ECSSQSConsumer` — runs `no_of_consumers` threads polling Input Queue, running the agent, sending to Output Queue | | `ECSSQSConsumer` | both | Extends `QueueConsumer`; spins up `num_consumers` poll-loop threads via `ThreadRunner`; each thread does its own long-poll/retry/permanent-failure handling | | `QueueConsumer` | shared (Lambda + ECS) | Abstract base declaring `poll`, `process_message`, `on_permanent_failure`, `delete_message` — also the base of `LambdaSQSConsumer` (the Lambda-side equivalent, which leaves `poll`/`delete_message` unimplemented since the SQS Event Source Mapping handles those for Lambda) | -| `ThreadRunner` | both | Runs N callables as peer threads; calls `os._exit(1)` if any thread crashes | +| `ThreadRunner` | both | Runs N callables as peer threads; on a crash it either exits immediately or, if the failing task opts into `graceful=True` (the SQS consumer pools do), sets a shared `shutdown_event` and waits for sibling tasks in that same `run()` call to finish before calling `os._exit(1)` | ### Request Flow — REST Sync @@ -277,7 +281,7 @@ this automatically. See the [AWS Containerized deployment docs](/docs/deployment | Partial failure | `batchItemFailures` return value | Failed messages not deleted — visibility timeout retries | | Scaling | Automatic, 1 Lambda per batch | `backlog-per-task` target tracking policy | | Response Handler | Separate Lambda triggered by Output Queue ESM | `ECSOutputConsumer` (Thread 2 in IO container) | -| Crash recovery | Lambda restarts automatically | `ThreadRunner` calls `os._exit(1)` → ECS restarts the task | +| Crash recovery | Lambda restarts automatically | `ThreadRunner` drains sibling consumer threads gracefully, then calls `os._exit(1)` → ECS restarts the task | --- diff --git a/docs/docs/core-concepts/configuration.md b/docs/docs/core-concepts/configuration.md index 4db3d2b43..45c065680 100644 --- a/docs/docs/core-concepts/configuration.md +++ b/docs/docs/core-concepts/configuration.md @@ -69,7 +69,7 @@ websocket_api: ttl: 3600 # Connection TTL in seconds for automatic cleanup chat_route: "chat" # Default route for chat messages -# Execution configuration (for AWS serverless deployments) +# Execution configuration (for AWS serverless and containerized deployments) execution: mode: rest_sync # Execution mode: rest_sync, rest_async, stream, or async (WebSocket) queues: @@ -672,7 +672,7 @@ guardrail: model: "gpt-4o-mini" # LLM model for guardrail validation config_path: "" # Path to guardrail configuration JSON file -# Execution configuration (for AWS serverless deployments) +# Execution configuration (for AWS serverless and containerized deployments) execution: mode: "rest_sync" # Execution mode: 'rest_sync', 'rest_async', 'stream', or 'async' queues: # Queue URLs for queue-based execution diff --git a/docs/docs/deployment/aws-containerized.md b/docs/docs/deployment/aws-containerized.md index 0bf842ee1..c62934209 100644 --- a/docs/docs/deployment/aws-containerized.md +++ b/docs/docs/deployment/aws-containerized.md @@ -66,7 +66,10 @@ graph TB - **Thread 1** — `RESTAPI.run(handlers=[ECSQueueRequestHandler()])`: FastAPI/uvicorn. Handles `POST /api/v1/chat` (enqueues to Input Queue, then either waits on DynamoDB or returns a `request_id`) and `GET /api/v1/chat/{session_id}` (polls DynamoDB for the result). - **Thread 2** — `ECSOutputConsumer.run()`: polls the Output Queue and writes results to DynamoDB (REST modes) or broadcasts via WebSocket. Internally this "thread" is actually `execution.queues.output.no_of_consumers` (default 5) independent long-poll threads, so multiple output messages are processed concurrently. -If any thread crashes, `ThreadRunner` calls `os._exit(1)` so ECS restarts the task cleanly. +If any thread crashes, `ThreadRunner` triggers a graceful shutdown: the output-consumer pool +finishes in-flight messages first (it cooperates via a shared `shutdown_event`), then +`os._exit(1)` is called so ECS restarts the task cleanly. The REST API thread doesn't check +`shutdown_event` and is simply terminated at that point along with everything else. **Entrypoint — `app_rest_service.py`** (no agent definitions): @@ -81,7 +84,7 @@ if __name__ == "__main__": ### Container 2 — Agent Runner (`ECSAgentRunner`) -Extends `ECSSQSConsumer`: runs `execution.queues.input.no_of_consumers` (default 5) independent threads, each polling the Input Queue in a blocking loop, executing the agent, and putting the result on the Output Queue. +Extends `ECSSQSConsumer` (which in turn extends the shared `QueueConsumer` base also used by Lambda's `LambdaSQSConsumer`): runs `execution.queues.input.no_of_consumers` (default 5) independent threads, each polling the Input Queue in a blocking loop, executing the agent, and putting the result on the Output Queue. **Entrypoint — `app_agent_runner.py`**: From 1fc3f192bb7688d0a44196c1f5d8bcf6c751fc83 Mon Sep 17 00:00:00 2001 From: Lakindu Induwara Kumara Date: Tue, 7 Jul 2026 17:59:39 +0530 Subject: [PATCH 77/90] graceful fix update --- .../agentkernel/deployment/aws/containerized/ecs_io_handler.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/ecs_io_handler.py b/ak-py/src/agentkernel/deployment/aws/containerized/ecs_io_handler.py index 2df9c60a9..f4f6fd626 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/ecs_io_handler.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/ecs_io_handler.py @@ -41,12 +41,13 @@ def run(cls) -> None: handlers=[ECSQueueRequestHandler()] ), # lambda needed here to wrap the function so that it turns into a callable, because otherwise the rest api will be run here itself thread_name="rest-api", + stop_all_on_failure=True, + graceful=True ), ThreadRunner.Task( execution_function=lambda: ECSOutputConsumer.run(), thread_name="output-queue-consumer", stop_all_on_failure=True, - graceful=True, ), ], max_workers=2, From d8c032c0f324094b55ee6387315770bc0d82e2fe Mon Sep 17 00:00:00 2001 From: Lakindu Induwara Kumara Date: Tue, 7 Jul 2026 21:35:33 +0530 Subject: [PATCH 78/90] thread runner imporvements and fixes --- .../aws/containerized/core/sqs_consumer.py | 2 + .../aws/containerized/ecs_io_handler.py | 8 +++- .../deployment/common/thread_runner.py | 9 ++-- ak-py/tests/test_thread_runner.py | 46 +++++++++++++++++++ 4 files changed, 59 insertions(+), 6 deletions(-) diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py b/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py index 06dcdd29e..a332be138 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/core/sqs_consumer.py @@ -157,6 +157,8 @@ def run(cls) -> None: raise ValueError(f"{cls.__name__}: queue URL is required") num_consumers = cls.num_consumers + if num_consumers < 1: + raise ValueError(f"{cls.__name__}: num_consumers must be >= 1, got {num_consumers}") cls._log.info(f"{cls.__name__} starting — queue: {queue_url}, consumers: {num_consumers}") ThreadRunner.run( diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/ecs_io_handler.py b/ak-py/src/agentkernel/deployment/aws/containerized/ecs_io_handler.py index f4f6fd626..20cb750b2 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/ecs_io_handler.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/ecs_io_handler.py @@ -42,12 +42,16 @@ def run(cls) -> None: ), # lambda needed here to wrap the function so that it turns into a callable, because otherwise the rest api will be run here itself thread_name="rest-api", stop_all_on_failure=True, - graceful=True + graceful=True, + # uvicorn.run() has no wiring to ThreadRunner.shutdown_event — it can only be + # stopped by an OS signal, so it can never report a completion in response to + # a graceful shutdown triggered by the other task. Don't require one. + awaited_on_shutdown=False, ), ThreadRunner.Task( execution_function=lambda: ECSOutputConsumer.run(), thread_name="output-queue-consumer", - stop_all_on_failure=True, + stop_all_on_failure=True ), ], max_workers=2, diff --git a/ak-py/src/agentkernel/deployment/common/thread_runner.py b/ak-py/src/agentkernel/deployment/common/thread_runner.py index b278d7761..df4a7456d 100644 --- a/ak-py/src/agentkernel/deployment/common/thread_runner.py +++ b/ak-py/src/agentkernel/deployment/common/thread_runner.py @@ -28,6 +28,7 @@ class Task: stop_task_on_failure: bool = True stop_all_on_failure: bool = False graceful: bool = False + awaited_on_shutdown: bool = True def __post_init__(self) -> None: if self.stop_all_on_failure and not self.stop_task_on_failure: @@ -61,10 +62,9 @@ def _target(task: "ThreadRunner.Task") -> None: thread.start() results: dict[ThreadRunner.Task, Any] = {} - for _ in tasks: - task, result, exc = ( - completions.get() - ) # Pulling from a shared queue exactly len(tasks) times yields tasks in true completion order (whichever finishes first is handled first) + pending = {task for task in tasks if task.awaited_on_shutdown} + while pending: + task, result, exc = completions.get() # blocks until the next task (in true completion order) reports in if exc is not None: if task.stop_task_on_failure: _log.exception(f"[{task.thread_name}] raised unexpectedly", exc_info=exc) @@ -81,6 +81,7 @@ def _target(task: "ThreadRunner.Task") -> None: else: results[task] = result _log.debug(f"[{task.thread_name}] completed") + pending.discard(task) if ThreadRunner.shutdown_event.is_set(): logging.shutdown() diff --git a/ak-py/tests/test_thread_runner.py b/ak-py/tests/test_thread_runner.py index c86321a53..297bf0eff 100644 --- a/ak-py/tests/test_thread_runner.py +++ b/ak-py/tests/test_thread_runner.py @@ -171,6 +171,52 @@ def crashing_fn(): never_finishes.set() runner_thread.join(timeout=5) + def test_graceful_failure_does_not_hang_on_unstoppable_sibling(self, monkeypatch): + """A graceful=True failure must still reach the final os._exit(1), even when the + sibling task can never report a completion (e.g. a blocking call like uvicorn.run() + with no wiring to shutdown_event) — that sibling must be awaited_on_shutdown=False.""" + exit_called = threading.Event() + exit_code = {} + + def fake_exit(code): + exit_code["code"] = code + exit_called.set() + + monkeypatch.setattr("agentkernel.deployment.common.thread_runner.os._exit", fake_exit) + + never_finishes = threading.Event() + + def unstoppable_fn(): + never_finishes.wait(timeout=10) # never checks shutdown_event — can't be told to stop + + def crashing_fn(): + time.sleep(0.05) + raise RuntimeError("boom") + + tasks = [ + ThreadRunner.Task( + execution_function=unstoppable_fn, + thread_name="unstoppable", + awaited_on_shutdown=False, + ), + ThreadRunner.Task( + execution_function=crashing_fn, + thread_name="crasher", + stop_task_on_failure=True, + stop_all_on_failure=True, + graceful=True, + ), + ] + + runner_thread = threading.Thread(target=ThreadRunner.run, args=(tasks,)) + runner_thread.start() + + assert exit_called.wait(timeout=5), "graceful shutdown never reached os._exit(1) — hung on the unstoppable sibling" + assert exit_code["code"] == 1 + + never_finishes.set() + runner_thread.join(timeout=5) + def test_stop_task_on_failure_false_ignores_exception(self): def crashing_fn(): raise RuntimeError("boom") From f594f7997b903eed3c1a434a8275e6a685ae4fb6 Mon Sep 17 00:00:00 2001 From: Lakindu Induwara Kumara Date: Tue, 7 Jul 2026 21:58:17 +0530 Subject: [PATCH 79/90] doc updates --- .agents/skills/ak-dev-architecture/SKILL.md | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/.agents/skills/ak-dev-architecture/SKILL.md b/.agents/skills/ak-dev-architecture/SKILL.md index dfadf952f..bbfb8b278 100644 --- a/.agents/skills/ak-dev-architecture/SKILL.md +++ b/.agents/skills/ak-dev-architecture/SKILL.md @@ -319,6 +319,7 @@ Each `ThreadRunner.Task` has: - `stop_task_on_failure` (default `True`) — log and ignore vs. log only, on that task's own exception. - `stop_all_on_failure` (default `False`) — also bring down the whole `run()` call on that task's failure. Requires `stop_task_on_failure=True`. - `graceful` (default `False`) — only meaningful with `stop_all_on_failure=True`. Requires it, or raises `ValueError`. +- `awaited_on_shutdown` (default `True`) — whether `run()`'s drain loop waits for this task to report a completion before proceeding. Set `False` for a task that can never observe `shutdown_event` and has no other way to be told to stop (e.g. a blocking call like `uvicorn.run()`) — otherwise a `graceful=True` failure elsewhere in the same `run()` call hangs forever waiting for it. Every thread's completion still lands on the shared queue regardless of this flag; `run()` just doesn't count that task toward "everyone's reported in." On a task raising with `stop_all_on_failure=True`: - `graceful=False` → logs the exception, `logging.shutdown()` + `os._exit(1)` **immediately**, without waiting on other tasks. @@ -359,15 +360,16 @@ Guidance: - A task with a `while` loop that should participate in a graceful shutdown **must** check `ThreadRunner.shutdown_event.is_set()` (or `.wait(timeout)` for a sleep/backoff) once per iteration before setting `graceful=True` on it — `graceful=True` on a task that never checks the - event just makes that `run()` call hang forever waiting for it to return (see "No hard timeout - on graceful drain" caveat below). + event just makes that `run()` call hang forever waiting for it to return, *unless* that task is + also marked `awaited_on_shutdown=False` (see below). - `stop_all_on_failure=True` always requires `stop_task_on_failure=True` (the default), and `graceful=True` always requires `stop_all_on_failure=True` — both raise `ValueError` in `Task.__post_init__` if violated. - Only set `graceful=True` on tasks in a `run()` call where every *other* task in that same call is - either similarly cooperative or expected to be killed abruptly anyway — e.g. `ECSIOHandler` - deliberately leaves its `rest-api` task non-cooperative (it never checks `shutdown_event`), - accepting that it's simply cut off whenever `os._exit(1)` eventually fires. + either similarly cooperative, or marked `awaited_on_shutdown=False` — e.g. `ECSIOHandler` + deliberately marks its `rest-api` task (`uvicorn.run()`, which never checks `shutdown_event` and + can only be stopped by an OS signal) as `awaited_on_shutdown=False`, so the drain loop doesn't + wait on it and it's simply cut off whenever `os._exit(1)` eventually fires. - `ThreadRunner.shutdown_event` is a **process-wide singleton**, not scoped to one `run()` call — once any call sets it, every other `run()` call in the process sees it set too, and will `os._exit(1)` once its own tasks finish draining. This is deliberate (a fatal failure anywhere @@ -375,9 +377,10 @@ Guidance: (`ThreadRunner.shutdown_event.clear()`), or a graceful-path test will leave every later test in the same run silently triggering a real `os._exit(1)`. See the `autouse` fixture in `ak-py/tests/test_thread_runner.py`. -- No hard timeout on graceful drain: if a task in the *same batch* as the failure never checks - `shutdown_event` and never naturally returns, that `run()` call's drain loop — and therefore its - `os._exit(1)` — never fires. +- No hard timeout on graceful drain: if a task in the *same batch* as the failure is + `awaited_on_shutdown=True` (the default) but never checks `shutdown_event` and never naturally + returns, that `run()` call's drain loop — and therefore its `os._exit(1)` — never fires. Mark such + a task `awaited_on_shutdown=False` to opt it out of the drain instead. ### Entry Point Pattern From d1f6cfad8b502c7d27a3cb422484fe31cdf0bdf4 Mon Sep 17 00:00:00 2001 From: Lakindu Induwara Kumara Date: Wed, 8 Jul 2026 10:05:22 +0530 Subject: [PATCH 80/90] output consumer no of workers change --- ak-py/src/agentkernel/core/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ak-py/src/agentkernel/core/config.py b/ak-py/src/agentkernel/core/config.py index 379c8095a..e01acbd94 100644 --- a/ak-py/src/agentkernel/core/config.py +++ b/ak-py/src/agentkernel/core/config.py @@ -274,7 +274,7 @@ class _OutputQueueConfig(BaseModel): default=3, description="Maximum number of times a message can be received from output queue before being treated as permanently failed" ) no_of_consumers: int = Field( - default=5, + default=2, description=( "Only used in Containerized deployments " "Number of independent consumer threads that each poll the output queue " From 08964e8b8fe60c7c47be044019fbdc69c41efd9c Mon Sep 17 00:00:00 2001 From: Lakindu Induwara Kumara Date: Wed, 8 Jul 2026 13:51:33 +0530 Subject: [PATCH 81/90] updates to queue handler abc calss --- .../aws/containerized/ecs_queue_handler.py | 16 +++-- .../deployment/aws/core/sqs_handler.py | 3 +- .../deployment/common/queue_handler.py | 60 +++++++++++++++++++ 3 files changed, 74 insertions(+), 5 deletions(-) create mode 100644 ak-py/src/agentkernel/deployment/common/queue_handler.py diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py b/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py index 4e7c4265e..6690d4dd1 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py @@ -16,6 +16,7 @@ from ....api.handler import RESTRequestHandler from ....core.config import AKConfig from ....core.model import BaseRunRequest, ExecutionMode +from ...common.queue_handler import QueueHandler from ..core.response_store import ResponseDBHandler from ..core.sqs_handler import SQSHandler @@ -37,13 +38,20 @@ def __init__(self): self._log = logging.getLogger("ak.ecs.queue_handler") self._config = AKConfig.get() self._response_store = None + self._queue_handler = None - def _get_response_store(self): + def get_response_store(self): """Lazy initialization of response store.""" if self._response_store is None: self._response_store = ResponseDBHandler().get_store() return self._response_store + def get_queue_handler(self) -> QueueHandler: + """Lazy initialization of queue handler.""" + if self._queue_handler is None: + self._queue_handler = SQSHandler + return self._queue_handler + def get_router(self) -> APIRouter: """ Returns the APIRouter for queue-based endpoints. @@ -79,7 +87,7 @@ async def enqueue_and_wait(body: BaseRunRequest): # Send to Input Queue (sync boto3 call — offload so it doesn't block the event loop) queue_result = await asyncio.to_thread( - SQSHandler.send_message_to_input_queue, + self.get_queue_handler().send_message_to_input_queue, message_body=body.model_dump(), message_group_id=body.session_id, message_deduplication_id=request_id, @@ -142,7 +150,7 @@ async def poll_response(session_id: str, request_id: str = None): self._log.info(f"Polling for response: request_id={effective_request_id}, session_id={session_id}") - response = await asyncio.to_thread(self._get_response_store().get_message, effective_request_id) + response = await asyncio.to_thread(self.get_response_store().get_message, effective_request_id) if not response: raise HTTPException( @@ -204,7 +212,7 @@ async def _wait_for_response(self, request_id: str, session_id: str, max_retries # Offload the sync boto3 DynamoDB call and use asyncio.sleep so this # coroutine yields the event loop — otherwise a long-running wait for # one request would starve every other concurrent request. - response = await asyncio.to_thread(self._get_response_store().get_message, request_id) + response = await asyncio.to_thread(self.get_response_store().get_message, request_id) if response: self._log.info(f"[WAIT SUCCESS] Found response on attempt {attempt + 1}/{max_retries} for request_id={request_id}") return response diff --git a/ak-py/src/agentkernel/deployment/aws/core/sqs_handler.py b/ak-py/src/agentkernel/deployment/aws/core/sqs_handler.py index 4300a5e78..6874dde0d 100644 --- a/ak-py/src/agentkernel/deployment/aws/core/sqs_handler.py +++ b/ak-py/src/agentkernel/deployment/aws/core/sqs_handler.py @@ -8,9 +8,10 @@ from pydantic import BaseModel, ConfigDict from ....core.config import AKConfig +from ...common.queue_handler import QueueHandler -class SQSHandler: +class SQSHandler(QueueHandler): """Shared helper for building and sending SQS messages. When used in a Non-Agent Kernel lambda/environment, the following environment variables diff --git a/ak-py/src/agentkernel/deployment/common/queue_handler.py b/ak-py/src/agentkernel/deployment/common/queue_handler.py new file mode 100644 index 000000000..ba1f619cc --- /dev/null +++ b/ak-py/src/agentkernel/deployment/common/queue_handler.py @@ -0,0 +1,60 @@ +from abc import ABC, abstractmethod +from typing import Any, List, Optional + + +class QueueHandler(ABC): + """ + Abstract base class for input/output queue messaging handlers. + """ + + @classmethod + @abstractmethod + def send_message_to_input_queue( + cls, + message_group_id: Optional[str] = None, + message_deduplication_id: Optional[str] = None, + message_body: Optional[Any] = None, + request_id: Optional[str] = None, + user_id: Optional[str] = None, + custom_message_attributes: Optional[List[Any]] = None, + **extra_kwargs: Any, + ) -> Any: + """ + Send a message to the input queue. + + :param message_group_id: The FIFO message group id, if required + :param message_deduplication_id: The FIFO deduplication id, if required + :param message_body: The payload to send + :param request_id: Optional request ID custom attribute + :param user_id: Optional user ID custom attribute + :param custom_message_attributes: Additional custom message attributes + :param extra_kwargs: Additional implementation-specific keyword arguments + :return: The underlying queue provider's send response + """ + pass + + @classmethod + @abstractmethod + def send_message_to_output_queue( + cls, + message_group_id: Optional[str] = None, + message_deduplication_id: Optional[str] = None, + message_body: Optional[Any] = None, + request_id: Optional[str] = None, + user_id: Optional[str] = None, + custom_message_attributes: Optional[List[Any]] = None, + **extra_kwargs: Any, + ) -> Any: + """ + Send a message to the output queue. + + :param message_group_id: The FIFO message group id, if required + :param message_deduplication_id: The FIFO deduplication id, if required + :param message_body: The payload to send + :param request_id: Optional request ID custom attribute + :param user_id: Optional user ID custom attribute + :param custom_message_attributes: Additional custom message attributes + :param extra_kwargs: Additional implementation-specific keyword arguments + :return: The underlying queue provider's send response + """ + pass From 5bdb2e4882b5b527b500b0130a477b5243ef57c2 Mon Sep 17 00:00:00 2001 From: Lakindu Induwara Kumara Date: Wed, 8 Jul 2026 14:22:46 +0530 Subject: [PATCH 82/90] added QueueRequesthandler --- .../aws/containerized/ecs_queue_handler.py | 196 +--------------- .../common/queue_request_handler.py | 222 ++++++++++++++++++ 2 files changed, 226 insertions(+), 192 deletions(-) create mode 100644 ak-py/src/agentkernel/deployment/common/queue_request_handler.py diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py b/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py index 6690d4dd1..dd558282f 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/ecs_queue_handler.py @@ -7,36 +7,22 @@ Used by ECSIOHandler (Thread 1 — REST API) when queue mode is enabled. """ -import asyncio -import logging -from typing import Any, Dict - -from fastapi import APIRouter, HTTPException - -from ....api.handler import RESTRequestHandler -from ....core.config import AKConfig -from ....core.model import BaseRunRequest, ExecutionMode from ...common.queue_handler import QueueHandler +from ...common.queue_request_handler import QueueRequestHandler from ..core.response_store import ResponseDBHandler from ..core.sqs_handler import SQSHandler -class ECSQueueRequestHandler(RESTRequestHandler): +class ECSQueueRequestHandler(QueueRequestHandler): """ - Queue-aware REST request handler for ECS deployments. - - Handles POST /api/v1/chat by: - 1. Enqueuing the request to Input SQS Queue - 2. Waiting for response in DynamoDB Response Store (sync mode) - 3. Returning the response to the client + ECS + SQS + DynamoDB/Redis implementation of QueueRequestHandler. This bypasses ChatService entirely - NO agent validation happens here. Agent validation and execution occurs in the Agent Runner service. """ def __init__(self): - self._log = logging.getLogger("ak.ecs.queue_handler") - self._config = AKConfig.get() + super().__init__(logger_name="ak.ecs.queue_handler") self._response_store = None self._queue_handler = None @@ -51,177 +37,3 @@ def get_queue_handler(self) -> QueueHandler: if self._queue_handler is None: self._queue_handler = SQSHandler return self._queue_handler - - def get_router(self) -> APIRouter: - """ - Returns the APIRouter for queue-based endpoints. - - - POST /api/v1/chat: Enqueue request and wait for response (sync mode) - - GET /api/v1/chat/{session_id}: Poll for response (async mode) - """ - router = APIRouter() - - @router.post("/api/v1/chat") - async def enqueue_and_wait(body: BaseRunRequest): - """ - Enqueue request to Input Queue. - - In REST_SYNC mode: Wait for response in DynamoDB Response Store. - In REST_ASYNC mode: Return request_id immediately. - """ - try: - # Validate required fields - if not body.session_id: - raise HTTPException(status_code=400, detail="session_id is required") - if not body.prompt: - raise HTTPException(status_code=400, detail="prompt is required") - - # Generate unique request_id (different from session_id) - import uuid - - request_id = str(uuid.uuid4()) - - self._log.info( - f"[REQUEST START] session_id={body.session_id}, request_id={request_id}, agent={body.agent}, prompt={body.prompt[:50]}" - ) - - # Send to Input Queue (sync boto3 call — offload so it doesn't block the event loop) - queue_result = await asyncio.to_thread( - self.get_queue_handler().send_message_to_input_queue, - message_body=body.model_dump(), - message_group_id=body.session_id, - message_deduplication_id=request_id, - request_id=request_id, # This becomes a custom message attribute - ) - - self._log.info(f"[ENQUEUED] MessageId={queue_result.get('MessageId')}, request_id={request_id}") - - # Handle based on execution mode - if self._config.execution.mode == ExecutionMode.REST_SYNC: - # Wait for response in DynamoDB - self._log.info(f"[WAITING] Polling DynamoDB for request_id={request_id}") - - response = await self._wait_for_response(request_id=request_id, session_id=body.session_id) - - if not response: - raise HTTPException( - status_code=504, - detail={ - "error": f"No response received for request_id: {request_id}", - "session_id": body.session_id, - "request_id": request_id, - }, - ) - - self._log.info(f"[RESPONSE FOUND] request_id={request_id}, response_keys={list(response.keys())}") - - # Return the response body - return response.get("body", response) - - elif self._config.execution.mode == ExecutionMode.REST_ASYNC: - # Return request_id for polling - return {"status": "ACCEPTED", "request_id": request_id, "session_id": body.session_id} - - else: - raise HTTPException(status_code=500, detail=f"Unsupported execution mode: {self._config.execution.mode}") - - except HTTPException: - raise - except Exception as e: - self._log.error(f"Error processing request: {e}", exc_info=True) - raise HTTPException(status_code=500, detail={"error": str(e), "session_id": body.session_id if body else None}) - - @router.get("/api/v1/chat/{session_id}") - async def poll_response(session_id: str, request_id: str = None): - """ - Poll for response (REST_ASYNC mode only). - - :param session_id: Session identifier (must match the session in DynamoDB) - :param request_id: Optional specific request to poll for - """ - try: - if self._config.execution.mode != ExecutionMode.REST_ASYNC: - raise HTTPException(status_code=404, detail="GET endpoint only available in REST_ASYNC mode") - - if not request_id: - raise HTTPException(status_code=400, detail={"error": "request_id is required", "session_id": session_id}) - - effective_request_id = request_id - - self._log.info(f"Polling for response: request_id={effective_request_id}, session_id={session_id}") - - response = await asyncio.to_thread(self.get_response_store().get_message, effective_request_id) - - if not response: - raise HTTPException( - status_code=404, - detail={ - "error": "NOT_FOUND", - "message": f"No response message found for request_id '{effective_request_id}'. The message may be unavailable. Please try again.", - "request_id": effective_request_id, - "session_id": session_id, - }, - ) - - # SECURITY: Validate that the session_id in the response matches the URL path - response_session_id = response.get("session_id") - if response_session_id != session_id: - self._log.warning( - f"Session ID mismatch: URL session_id={session_id}, " - f"response session_id={response_session_id}, request_id={effective_request_id}" - ) - raise HTTPException( - status_code=404, - detail={ - "error": "NOT_FOUND", - "message": f"No response message found for request_id '{effective_request_id}'. The message may be unavailable. Please try again.", - "request_id": effective_request_id, - "session_id": session_id, - }, - ) - - # Return the response body - return response.get("body", response) - - except HTTPException: - raise - except Exception as e: - self._log.error(f"Error polling response: {e}", exc_info=True) - raise HTTPException(status_code=500, detail={"error": str(e), "session_id": session_id}) - - return router - - async def _wait_for_response(self, request_id: str, session_id: str, max_retries: int = None, delay: int = None) -> Dict[str, Any]: - """ - Wait for response to appear in DynamoDB Response Store. - - :param request_id: Request identifier to wait for - :param session_id: Session identifier - :param max_retries: Maximum number of retry attempts (from config if None) - :param delay: Delay between retries in seconds (from config if None) - :return: Response dict from DynamoDB, or None if not found - """ - if max_retries is None: - max_retries = self._config.execution.response_store.retry_count - if delay is None: - delay = self._config.execution.response_store.delay - - self._log.info(f"[WAIT START] request_id={request_id}, max_retries={max_retries}, delay={delay}s") - - for attempt in range(max_retries): - # Offload the sync boto3 DynamoDB call and use asyncio.sleep so this - # coroutine yields the event loop — otherwise a long-running wait for - # one request would starve every other concurrent request. - response = await asyncio.to_thread(self.get_response_store().get_message, request_id) - if response: - self._log.info(f"[WAIT SUCCESS] Found response on attempt {attempt + 1}/{max_retries} for request_id={request_id}") - return response - - if attempt < max_retries - 1: - self._log.debug( - f"[WAIT RETRY] Response not ready, waiting {delay}s (attempt {attempt + 1}/{max_retries}) for request_id={request_id}" - ) - await asyncio.sleep(delay) - - self._log.warning(f"[WAIT TIMEOUT] Response not found after {max_retries} attempts for request_id={request_id}") - return None diff --git a/ak-py/src/agentkernel/deployment/common/queue_request_handler.py b/ak-py/src/agentkernel/deployment/common/queue_request_handler.py new file mode 100644 index 000000000..4e656f239 --- /dev/null +++ b/ak-py/src/agentkernel/deployment/common/queue_request_handler.py @@ -0,0 +1,222 @@ +""" +Abstract Queue Request Handler + +Provides the common REST request/response flow for queue-based deployments: +enqueue the request to an input queue, then either wait for a synchronous +response in a response store or return immediately for async polling. + +Concrete deployments (ECS+SQS, GCP+Pub/Sub, Docker+Kafka, etc.) only need to +supply their queue handler and response store implementations by overriding +get_queue_handler() and get_response_store() — this class bypasses +ChatService entirely, so NO agent validation happens here. Agent validation +and execution occurs in the Agent Runner service. +""" + +import asyncio +import logging +import uuid +from abc import abstractmethod +from typing import Any, Dict + +from fastapi import APIRouter, HTTPException + +from ...api.handler import RESTRequestHandler +from ...core.config import AKConfig +from ...core.model import BaseRunRequest, ExecutionMode +from .queue_handler import QueueHandler +from .response_store import ResponseStore + + +class QueueRequestHandler(RESTRequestHandler): + """ + Queue-aware REST request handler shared by all queue-based deployments. + + - POST /api/v1/chat: Enqueue request and wait for response (sync mode) + - GET /api/v1/chat/{session_id}: Poll for response (async mode) + """ + + def __init__(self, logger_name: str = "ak.deployment.queue_handler"): + self._log = logging.getLogger(logger_name) + self._config = AKConfig.get() + + @abstractmethod + def get_response_store(self) -> ResponseStore: + """Return the ResponseStore implementation used to poll for responses.""" + pass + + @abstractmethod + def get_queue_handler(self) -> QueueHandler: + """Return the QueueHandler implementation used to enqueue requests.""" + pass + + def get_router(self) -> APIRouter: + """ + Returns the APIRouter for queue-based endpoints. + + - POST /api/v1/chat: Enqueue request and wait for response (sync mode) + - GET /api/v1/chat/{session_id}: Poll for response (async mode) + """ + router = APIRouter() + + @router.post("/api/v1/chat") + async def enqueue_and_wait(body: BaseRunRequest): + """ + Enqueue request to Input Queue. + + In REST_SYNC mode: Wait for response in the Response Store. + In REST_ASYNC mode: Return request_id immediately. + """ + try: + # Validate required fields + if not body.session_id: + raise HTTPException(status_code=400, detail="session_id is required") + if not body.prompt: + raise HTTPException(status_code=400, detail="prompt is required") + + # Generate unique request_id (different from session_id) + request_id = str(uuid.uuid4()) + + self._log.info( + f"[REQUEST START] session_id={body.session_id}, request_id={request_id}, agent={body.agent}, prompt={body.prompt[:50]}" + ) + + # Send to Input Queue (sync call — offload so it doesn't block the event loop) + queue_result = await asyncio.to_thread( + self.get_queue_handler().send_message_to_input_queue, + message_body=body.model_dump(), + message_group_id=body.session_id, + message_deduplication_id=request_id, + request_id=request_id, # This becomes a custom message attribute + ) + + self._log.info(f"[ENQUEUED] MessageId={queue_result.get('MessageId')}, request_id={request_id}") + + # Handle based on execution mode + if self._config.execution.mode == ExecutionMode.REST_SYNC: + # Wait for response in the Response Store + self._log.info(f"[WAITING] Polling response store for request_id={request_id}") + + response = await self._wait_for_response(request_id=request_id, session_id=body.session_id) + + if not response: + raise HTTPException( + status_code=504, + detail={ + "error": f"No response received for request_id: {request_id}", + "session_id": body.session_id, + "request_id": request_id, + }, + ) + + self._log.info(f"[RESPONSE FOUND] request_id={request_id}, response_keys={list(response.keys())}") + + # Return the response body + return response.get("body", response) + + elif self._config.execution.mode == ExecutionMode.REST_ASYNC: + # Return request_id for polling + return {"status": "ACCEPTED", "request_id": request_id, "session_id": body.session_id} + + else: + raise HTTPException(status_code=500, detail=f"Unsupported execution mode: {self._config.execution.mode}") + + except HTTPException: + raise + except Exception as e: + self._log.error(f"Error processing request: {e}", exc_info=True) + raise HTTPException(status_code=500, detail={"error": str(e), "session_id": body.session_id if body else None}) + + @router.get("/api/v1/chat/{session_id}") + async def poll_response(session_id: str, request_id: str = None): + """ + Poll for response (REST_ASYNC mode only). + + :param session_id: Session identifier (must match the session in the response store) + :param request_id: Optional specific request to poll for + """ + try: + if self._config.execution.mode != ExecutionMode.REST_ASYNC: + raise HTTPException(status_code=404, detail="GET endpoint only available in REST_ASYNC mode") + + if not request_id: + raise HTTPException(status_code=400, detail={"error": "request_id is required", "session_id": session_id}) + + effective_request_id = request_id + + self._log.info(f"Polling for response: request_id={effective_request_id}, session_id={session_id}") + + response = await asyncio.to_thread(self.get_response_store().get_message, effective_request_id) + + if not response: + raise HTTPException( + status_code=404, + detail={ + "error": "NOT_FOUND", + "message": f"No response message found for request_id '{effective_request_id}'. The message may be unavailable. Please try again.", + "request_id": effective_request_id, + "session_id": session_id, + }, + ) + + # SECURITY: Validate that the session_id in the response matches the URL path + response_session_id = response.get("session_id") + if response_session_id != session_id: + self._log.warning( + f"Session ID mismatch: URL session_id={session_id}, " + f"response session_id={response_session_id}, request_id={effective_request_id}" + ) + raise HTTPException( + status_code=404, + detail={ + "error": "NOT_FOUND", + "message": f"No response message found for request_id '{effective_request_id}'. The message may be unavailable. Please try again.", + "request_id": effective_request_id, + "session_id": session_id, + }, + ) + + # Return the response body + return response.get("body", response) + + except HTTPException: + raise + except Exception as e: + self._log.error(f"Error polling response: {e}", exc_info=True) + raise HTTPException(status_code=500, detail={"error": str(e), "session_id": session_id}) + + return router + + async def _wait_for_response(self, request_id: str, session_id: str, max_retries: int = None, delay: int = None) -> Dict[str, Any]: + """ + Wait for response to appear in the Response Store. + + :param request_id: Request identifier to wait for + :param session_id: Session identifier + :param max_retries: Maximum number of retry attempts (from config if None) + :param delay: Delay between retries in seconds (from config if None) + :return: Response dict from the response store, or None if not found + """ + if max_retries is None: + max_retries = self._config.execution.response_store.retry_count + if delay is None: + delay = self._config.execution.response_store.delay + + self._log.info(f"[WAIT START] request_id={request_id}, max_retries={max_retries}, delay={delay}s") + + for attempt in range(max_retries): + # Offload the sync response-store call and use asyncio.sleep so this + # coroutine yields the event loop — otherwise a long-running wait for + # one request would starve every other concurrent request. + response = await asyncio.to_thread(self.get_response_store().get_message, request_id) + if response: + self._log.info(f"[WAIT SUCCESS] Found response on attempt {attempt + 1}/{max_retries} for request_id={request_id}") + return response + + if attempt < max_retries - 1: + self._log.debug( + f"[WAIT RETRY] Response not ready, waiting {delay}s (attempt {attempt + 1}/{max_retries}) for request_id={request_id}" + ) + await asyncio.sleep(delay) + + self._log.warning(f"[WAIT TIMEOUT] Response not found after {max_retries} attempts for request_id={request_id}") + return None From 8516f6cdd3a7a5c857ecd1c8dadacab4d2b2b864 Mon Sep 17 00:00:00 2001 From: Lakindu Induwara Kumara Date: Wed, 8 Jul 2026 14:34:53 +0530 Subject: [PATCH 83/90] comment update --- .../deployment/aws/containerized/ecs_io_handler.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/ecs_io_handler.py b/ak-py/src/agentkernel/deployment/aws/containerized/ecs_io_handler.py index 20cb750b2..40a2e6145 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/ecs_io_handler.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/ecs_io_handler.py @@ -43,10 +43,7 @@ def run(cls) -> None: thread_name="rest-api", stop_all_on_failure=True, graceful=True, - # uvicorn.run() has no wiring to ThreadRunner.shutdown_event — it can only be - # stopped by an OS signal, so it can never report a completion in response to - # a graceful shutdown triggered by the other task. Don't require one. - awaited_on_shutdown=False, + awaited_on_shutdown=False, # uvicorn.run() isn't wired to shutdown_event and only stops via OS signal, so it can't report completion. ), ThreadRunner.Task( execution_function=lambda: ECSOutputConsumer.run(), From a053515a87b3752b61e8948411a42789170b0e50 Mon Sep 17 00:00:00 2001 From: Lakindu Induwara Kumara Date: Wed, 8 Jul 2026 16:29:43 +0530 Subject: [PATCH 84/90] review changes --- .../aws/serverless/core/router/rest_lambda.py | 4 -- .../common/queue_request_handler.py | 38 +------------------ .../deployment/common/response_store.py | 37 +++++++++++++++--- 3 files changed, 33 insertions(+), 46 deletions(-) diff --git a/ak-py/src/agentkernel/deployment/aws/serverless/core/router/rest_lambda.py b/ak-py/src/agentkernel/deployment/aws/serverless/core/router/rest_lambda.py index a2bb55288..56ebc1ae0 100644 --- a/ak-py/src/agentkernel/deployment/aws/serverless/core/router/rest_lambda.py +++ b/ak-py/src/agentkernel/deployment/aws/serverless/core/router/rest_lambda.py @@ -226,12 +226,8 @@ def poll_operation(payload: BaseRequest) -> Dict[str, Any]: self._log.info(f"Performing REST_ASYNC poll operation for payload: '{payload}'") request_id = payload.request_id - if not request_id: - raise ValueError("request_id is required for polling") session_id_from_body = payload.body.session_id if payload.body else None - if not session_id_from_body: - raise ValueError("session_id is required in request body for polling") message = self._get_message(payload) self._log.info(f"Fetched message from database: {message}") diff --git a/ak-py/src/agentkernel/deployment/common/queue_request_handler.py b/ak-py/src/agentkernel/deployment/common/queue_request_handler.py index 4e656f239..e5e304d26 100644 --- a/ak-py/src/agentkernel/deployment/common/queue_request_handler.py +++ b/ak-py/src/agentkernel/deployment/common/queue_request_handler.py @@ -16,7 +16,6 @@ import logging import uuid from abc import abstractmethod -from typing import Any, Dict from fastapi import APIRouter, HTTPException @@ -96,7 +95,7 @@ async def enqueue_and_wait(body: BaseRunRequest): # Wait for response in the Response Store self._log.info(f"[WAITING] Polling response store for request_id={request_id}") - response = await self._wait_for_response(request_id=request_id, session_id=body.session_id) + response = await self.get_response_store().get_message_with_retry(request_id, True, async_mode=True) if not response: raise HTTPException( @@ -185,38 +184,3 @@ async def poll_response(session_id: str, request_id: str = None): raise HTTPException(status_code=500, detail={"error": str(e), "session_id": session_id}) return router - - async def _wait_for_response(self, request_id: str, session_id: str, max_retries: int = None, delay: int = None) -> Dict[str, Any]: - """ - Wait for response to appear in the Response Store. - - :param request_id: Request identifier to wait for - :param session_id: Session identifier - :param max_retries: Maximum number of retry attempts (from config if None) - :param delay: Delay between retries in seconds (from config if None) - :return: Response dict from the response store, or None if not found - """ - if max_retries is None: - max_retries = self._config.execution.response_store.retry_count - if delay is None: - delay = self._config.execution.response_store.delay - - self._log.info(f"[WAIT START] request_id={request_id}, max_retries={max_retries}, delay={delay}s") - - for attempt in range(max_retries): - # Offload the sync response-store call and use asyncio.sleep so this - # coroutine yields the event loop — otherwise a long-running wait for - # one request would starve every other concurrent request. - response = await asyncio.to_thread(self.get_response_store().get_message, request_id) - if response: - self._log.info(f"[WAIT SUCCESS] Found response on attempt {attempt + 1}/{max_retries} for request_id={request_id}") - return response - - if attempt < max_retries - 1: - self._log.debug( - f"[WAIT RETRY] Response not ready, waiting {delay}s (attempt {attempt + 1}/{max_retries}) for request_id={request_id}" - ) - await asyncio.sleep(delay) - - self._log.warning(f"[WAIT TIMEOUT] Response not found after {max_retries} attempts for request_id={request_id}") - return None diff --git a/ak-py/src/agentkernel/deployment/common/response_store.py b/ak-py/src/agentkernel/deployment/common/response_store.py index 34dc4b60b..01dc2db2a 100644 --- a/ak-py/src/agentkernel/deployment/common/response_store.py +++ b/ak-py/src/agentkernel/deployment/common/response_store.py @@ -1,3 +1,4 @@ +import asyncio import time from abc import ABC, abstractmethod from typing import Dict @@ -33,16 +34,21 @@ def get_message(self, request_id: str, get_and_delete: bool = False) -> Dict | N """ pass - def get_message_with_retry(self, request_id: str, get_and_delete: bool = False) -> Dict | None: + def get_message_with_retry(self, request_id: str, get_and_delete: bool = False, async_mode: bool = False): """ Wait until a message exists for a request ID and retrieve it. + :param request_id: Request ID :param get_and_delete: Delete the message after retrieval when True - :return: message record as dict or None if not found + :param async_mode: If True, returns an awaitable that polls using + asyncio.sleep/asyncio.to_thread instead of blocking, so an async caller + can await it without holding a worker thread for the whole wait. + :return: message record as dict or None if not found (or an awaitable of the same, when async_mode=True) """ - response_store_config = AKConfig.get().execution.response_store - retry_count = response_store_config.retry_count - delay = response_store_config.delay + if async_mode: + return self._get_message_with_retry_async(request_id, get_and_delete) + + retry_count, delay = self._get_retry_config() for attempt in range(retry_count): self._log.debug("Attempt %d/%d for request_id=%s", attempt + 1, retry_count, request_id) message = self.get_message(request_id, get_and_delete=get_and_delete) @@ -52,6 +58,27 @@ def get_message_with_retry(self, request_id: str, get_and_delete: bool = False) time.sleep(delay) return None + async def _get_message_with_retry_async(self, request_id: str, get_and_delete: bool = False) -> Dict | None: + """ + Async counterpart of get_message_with_retry — yields the event loop via + asyncio.sleep between attempts instead of blocking a thread for the full wait. + """ + retry_count, delay = self._get_retry_config() + for attempt in range(retry_count): + self._log.debug("Attempt %d/%d for request_id=%s", attempt + 1, retry_count, request_id) + message = await asyncio.to_thread(self.get_message, request_id, get_and_delete) + if message is not None: + return message + if attempt < retry_count - 1: + await asyncio.sleep(delay) + return None + + @staticmethod + def _get_retry_config() -> tuple[int, int]: + """Read (retry_count, delay) for get_message_with_retry from config.""" + response_store_config = AKConfig.get().execution.response_store + return response_store_config.retry_count, response_store_config.delay + @abstractmethod def delete_message(self, request_id: str) -> None: """ From 2ccb5aead8d1af917e97387864f62e72165c7cad Mon Sep 17 00:00:00 2001 From: Lakindu Induwara Kumara Date: Thu, 9 Jul 2026 07:26:23 +0530 Subject: [PATCH 85/90] review updates, and reverted lambda code to the way before --- .../aws/containerized/ecs_io_handler.py | 6 +---- .../aws/serverless/core/router/rest_lambda.py | 26 ++++--------------- 2 files changed, 6 insertions(+), 26 deletions(-) diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/ecs_io_handler.py b/ak-py/src/agentkernel/deployment/aws/containerized/ecs_io_handler.py index 40a2e6145..b62aef6c9 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/ecs_io_handler.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/ecs_io_handler.py @@ -45,11 +45,7 @@ def run(cls) -> None: graceful=True, awaited_on_shutdown=False, # uvicorn.run() isn't wired to shutdown_event and only stops via OS signal, so it can't report completion. ), - ThreadRunner.Task( - execution_function=lambda: ECSOutputConsumer.run(), - thread_name="output-queue-consumer", - stop_all_on_failure=True - ), + ThreadRunner.Task(execution_function=lambda: ECSOutputConsumer.run(), thread_name="output-queue-consumer", stop_all_on_failure=True), ], max_workers=2, ) diff --git a/ak-py/src/agentkernel/deployment/aws/serverless/core/router/rest_lambda.py b/ak-py/src/agentkernel/deployment/aws/serverless/core/router/rest_lambda.py index 56ebc1ae0..287ca7216 100644 --- a/ak-py/src/agentkernel/deployment/aws/serverless/core/router/rest_lambda.py +++ b/ak-py/src/agentkernel/deployment/aws/serverless/core/router/rest_lambda.py @@ -226,33 +226,17 @@ def poll_operation(payload: BaseRequest) -> Dict[str, Any]: self._log.info(f"Performing REST_ASYNC poll operation for payload: '{payload}'") request_id = payload.request_id - - session_id_from_body = payload.body.session_id if payload.body else None - message = self._get_message(payload) self._log.info(f"Fetched message from database: {message}") - - if not message: - response_body = self._build_failure_body( + response_body = ( + message + if message + else self._build_failure_body( request_id=request_id, status="NOT_FOUND", message=f"No response message found for request_id '{request_id}'. The message may be unavailable. Please try again.", ) - else: - # SECURITY: Validate that the session_id in the response matches the request - response_session_id = message.get("session_id") - if response_session_id != session_id_from_body: - self._log.warning( - f"Session ID mismatch: request session_id={session_id_from_body}, " - f"response session_id={response_session_id}, request_id={request_id}" - ) - response_body = self._build_failure_body( - request_id=request_id, - status="NOT_FOUND", - message=f"No response message found for request_id '{request_id}'. The message may be unavailable. Please try again.", - ) - else: - response_body = message + ) self._log.info(f"Returning response for REST_ASYNC poll operation: '{response_body}'") return response_body From 1c8ef84e27a58c0c8fb9fb3400e28dc77f80ffa4 Mon Sep 17 00:00:00 2001 From: yl-nuwan Date: Thu, 9 Jul 2026 09:27:54 +0530 Subject: [PATCH 86/90] Refactor queue mode configuration and update module versions - Changed variable names from `enable_queue_mode` to `queue_mode` and `queue_mode_type` to `execution_mode` for clarity and consistency across modules. - Updated the version of the SQS module from 0.5.1 to 0.6.0 in the queues module. - Adjusted the rest service module to reflect the new variable names and ensure proper integration with queue mode. - Modified outputs to utilize the new `queue_mode` variable for conditional outputs related to SQS queues and DynamoDB response store. - Updated documentation and examples to align with the new variable names and configurations. - Ensured all relevant modules and examples are consistent with the new queue mode implementation. --- ak-deployment/ak-aws/containerized/README.md | 58 +++++++++------- .../ak-aws/containerized/api_gateway_async.tf | 4 +- .../ak-aws/containerized/dynamodb.tf | 2 +- ak-deployment/ak-aws/containerized/iam.tf | 8 +-- .../ak-aws/containerized/modules/README.md | 65 +++++++++++------- .../containerized/modules/queues/main.tf | 4 +- .../modules/rest-service/main.tf | 20 +++--- .../modules/rest-service/variables.tf | 2 +- ak-deployment/ak-aws/containerized/outputs.tf | 8 +-- .../ak-aws/containerized/queue_mode.tf | 4 +- .../ak-aws/containerized/rest_service.tf | 16 ++--- ak-deployment/ak-aws/containerized/state.tf | 4 +- .../ak-aws/containerized/variables.tf | 66 +++++++++---------- .../skills/ak-cloud-deploy/SKILL.md | 4 +- docs/docs/advanced/queue-mode-guide.md | 2 +- docs/docs/deployment/aws-containerized.md | 4 +- .../openai-dynamodb-scalable/README.md | 8 +-- .../openai-dynamodb-scalable/deploy/main.tf | 35 +--------- .../streaming-openai/deploy/main.tf | 34 +++++----- 19 files changed, 174 insertions(+), 174 deletions(-) diff --git a/ak-deployment/ak-aws/containerized/README.md b/ak-deployment/ak-aws/containerized/README.md index ab130e235..5131a8b57 100644 --- a/ak-deployment/ak-aws/containerized/README.md +++ b/ak-deployment/ak-aws/containerized/README.md @@ -103,7 +103,7 @@ module "containerized_agents" { env_alias = "dev" module_name = "chatbot" region = "us-east-1" - + # REST Service configuration rest_service = { package_path = "./dist" @@ -113,7 +113,7 @@ module "containerized_agents" { OPENAI_API_KEY = var.api_key } } - + # Session storage create_redis_cluster = true } @@ -130,7 +130,7 @@ module "containerized_agents" { env_alias = "prod" module_name = "assistant" region = "us-east-1" - + # REST Service (handles HTTP requests) rest_service = { package_path = "./dist-rest-service" @@ -142,11 +142,11 @@ module "containerized_agents" { OPENAI_API_KEY = var.api_key } } - + # Enable queue mode - enable_queue_mode = true - queue_mode_type = "async" # or "sync" - + queue_mode = true + execution_mode = "async" # or "sync" + # Queue configuration queue_config = { input_queue_visibility_timeout = 120 @@ -154,7 +154,7 @@ module "containerized_agents" { input_queue_create_dlq = true output_queue_create_dlq = true } - + # Agent Runner (processes from queue) agent_runner = { cpu = 1024 @@ -166,7 +166,7 @@ module "containerized_agents" { OPENAI_API_KEY = var.api_key } } - + # Auto scaling scaling_config = { enabled = true @@ -176,7 +176,7 @@ module "containerized_agents" { scale_in_cooldown = 180 scale_out_cooldown = 60 } - + create_dynamodb_memory_table = true } ``` @@ -214,6 +214,7 @@ agent_runner = { ``` **Image Resolution Priority:** + 1. If `package_path` is provided → Build Docker image from path 2. Else if `image_uri` is provided → Use specified image 3. Else → Use REST service image (from `package_path`) @@ -231,14 +232,14 @@ queue_config = { max_message_size = 262144 # 256 KB max message size receive_wait_time_seconds = 0 # Long polling wait time batch_size = 10 # Max messages fetched per SQS receive call (1-10), ECS consumers only - + # Input queue (requests → agent runner) input_queue_visibility_timeout = 60 # Should be >= processing time input_queue_message_retention_seconds = 1800 # 30 minutes input_queue_max_receive_count = 5 # Before DLQ input_queue_create_dlq = false # Create dead letter queue input_queue_dlq_message_retention_seconds = 1800 - + # Output queue (agent runner → REST service) output_queue_visibility_timeout = 60 output_queue_message_retention_seconds = 1800 @@ -270,13 +271,15 @@ scaling_config = { Direct synchronous execution. The REST service contains both request handling and agent logic. **Use when:** + - Simple, low-volume workloads - Quick response times required - No need for background processing **Configuration:** + ```hcl -enable_queue_mode = false # This is the default +queue_mode = false # This is the default ``` ### Queue Mode - Sync @@ -284,14 +287,16 @@ enable_queue_mode = false # This is the default Requests use queues but client connection stays open until response is ready. **Use when:** + - Need queue benefits (reliability, retry) - Can tolerate slightly longer response times - Want to keep simple client code **Configuration:** + ```hcl -enable_queue_mode = true -queue_mode_type = "sync" +queue_mode = true +execution_mode = "sync" ``` ### Queue Mode - Async @@ -299,17 +304,20 @@ queue_mode_type = "sync" Requests return immediately with a request ID. Client polls for results. **Use when:** + - Long-running agent tasks - High-volume concurrent requests - Need maximum scalability **Configuration:** + ```hcl -enable_queue_mode = true -queue_mode_type = "async" +queue_mode = true +execution_mode = "async" ``` **Client flow:** + ```bash # 1. Submit request curl -X POST .../chat -d '{"prompt":"..."}' @@ -327,6 +335,7 @@ curl -X GET .../chat/{session_id}?request_id=... The agent runner automatically scales based on queue backlog: 1. **Lambda function** runs every minute calculating: + ``` BacklogPerTask = QueueDepth / max(RunningTasks, 1) ``` @@ -357,13 +366,14 @@ scaling_config = { The `backlog_target` determines how aggressively to scale: -| Target | Behavior | Use Case | -|--------|----------|----------| -| 5-10 | Aggressive scaling | Cost-sensitive, can tolerate queue buildup | -| 2-5 | Balanced | General purpose | -| 1 | Very aggressive | Low-latency, cost is less important | +| Target | Behavior | Use Case | +| ------ | ------------------ | ------------------------------------------ | +| 5-10 | Aggressive scaling | Cost-sensitive, can tolerate queue buildup | +| 2-5 | Balanced | General purpose | +| 1 | Very aggressive | Low-latency, cost is less important | **Example:** + - Queue has 100 messages - Target is 10 messages per task - System scales to 10 tasks @@ -373,6 +383,7 @@ The `backlog_target` determines how aggressively to scale: ### Cost Optimization **Scale to zero:** + ```hcl scaling_config = { enabled = true @@ -381,6 +392,7 @@ scaling_config = { ``` **Gradual scale-in:** + ```hcl scaling_config = { scale_in_cooldown = 300 # Wait 5min before scaling in @@ -390,12 +402,14 @@ scaling_config = { ### Monitoring **CloudWatch Metrics:** + - `Custom/ECS/BacklogPerTask` - Custom metric - `AWS/SQS/ApproximateNumberOfMessagesVisible` - Queue depth - `AWS/ECS/CPUUtilization` - Task CPU usage - `AWS/ECS/MemoryUtilization` - Task memory usage **CloudWatch Logs:** + - `/aws/lambda/{prefix}-backlog-metric` - Scaling Lambda logs - `/ecs/{prefix}-agent-runner` - Agent runner logs diff --git a/ak-deployment/ak-aws/containerized/api_gateway_async.tf b/ak-deployment/ak-aws/containerized/api_gateway_async.tf index 6768e2cab..8122958ab 100644 --- a/ak-deployment/ak-aws/containerized/api_gateway_async.tf +++ b/ak-deployment/ak-aws/containerized/api_gateway_async.tf @@ -2,7 +2,7 @@ # Only needed for rest_async mode — adds GET /api/{version}/{endpoint}/{sessionId} resource "aws_apigatewayv2_integration" "async_get" { - count = var.enable_queue_mode && var.queue_mode_type == "async" ? 1 : 0 + count = var.queue_mode && var.execution_mode == "async" ? 1 : 0 api_id = aws_apigatewayv2_api.http_api.id integration_type = "HTTP_PROXY" integration_method = "ANY" @@ -17,7 +17,7 @@ resource "aws_apigatewayv2_integration" "async_get" { } resource "aws_apigatewayv2_route" "async_get" { - count = var.enable_queue_mode && var.queue_mode_type == "async" ? 1 : 0 + count = var.queue_mode && var.execution_mode == "async" ? 1 : 0 api_id = aws_apigatewayv2_api.http_api.id route_key = "GET ${local.api_base_segment_with_version}/${var.agent_endpoint}/{sessionId}" target = "integrations/${aws_apigatewayv2_integration.async_get[0].id}" diff --git a/ak-deployment/ak-aws/containerized/dynamodb.tf b/ak-deployment/ak-aws/containerized/dynamodb.tf index 71ab9229f..e5b35758c 100644 --- a/ak-deployment/ak-aws/containerized/dynamodb.tf +++ b/ak-deployment/ak-aws/containerized/dynamodb.tf @@ -1,7 +1,7 @@ # ---------- DynamoDB Response Store ---------- resource "aws_dynamodb_table" "response_store" { - count = var.enable_queue_mode ? 1 : 0 + count = var.queue_mode ? 1 : 0 name = "${local.prefix}-response-store" billing_mode = "PAY_PER_REQUEST" diff --git a/ak-deployment/ak-aws/containerized/iam.tf b/ak-deployment/ak-aws/containerized/iam.tf index 55e4b05d2..365759611 100644 --- a/ak-deployment/ak-aws/containerized/iam.tf +++ b/ak-deployment/ak-aws/containerized/iam.tf @@ -1,7 +1,7 @@ # ---------- REST Service IAM Policies ---------- resource "aws_iam_policy" "rest_service_sqs_policy" { - count = var.enable_queue_mode ? 1 : 0 + count = var.queue_mode ? 1 : 0 name = "${local.prefix}-rest-svc-sqs" description = "Allow REST Service ECS task to send to Input Queue and consume from Output Queue" @@ -38,7 +38,7 @@ resource "aws_iam_policy" "rest_service_sqs_policy" { } resource "aws_iam_policy" "rest_service_response_store_policy" { - count = var.enable_queue_mode ? 1 : 0 + count = var.queue_mode ? 1 : 0 name = "${local.prefix}-rest-svc-response-store" description = "Allow REST Service ECS task to read/write the DynamoDB response store" @@ -69,13 +69,13 @@ resource "aws_iam_policy" "rest_service_response_store_policy" { } resource "aws_iam_role_policy_attachment" "rest_service_sqs_attachment" { - count = var.enable_queue_mode ? 1 : 0 + count = var.queue_mode ? 1 : 0 role = module.rest_service.task_role_name policy_arn = aws_iam_policy.rest_service_sqs_policy[0].arn } resource "aws_iam_role_policy_attachment" "rest_service_response_store_attachment" { - count = var.enable_queue_mode ? 1 : 0 + count = var.queue_mode ? 1 : 0 role = module.rest_service.task_role_name policy_arn = aws_iam_policy.rest_service_response_store_policy[0].arn } diff --git a/ak-deployment/ak-aws/containerized/modules/README.md b/ak-deployment/ak-aws/containerized/modules/README.md index f9e5551b1..c09878f09 100644 --- a/ak-deployment/ak-aws/containerized/modules/README.md +++ b/ak-deployment/ak-aws/containerized/modules/README.md @@ -5,6 +5,7 @@ This directory contains reusable Terraform modules for the containerized Agent K ## Architecture Overview ### Non-Queue Mode (Direct Execution) + ``` ┌─────────────┐ ┌─────┐ ┌──────────────┐ │ API Gateway │─────▶│ ALB │─────▶│ REST Service │ @@ -17,6 +18,7 @@ This directory contains reusable Terraform modules for the containerized Agent K ``` ### Queue Mode (Async Execution) + ``` ┌─────────────┐ ┌─────┐ ┌──────────────┐ │ API Gateway │─────▶│ ALB │─────▶│ REST Service │ @@ -61,25 +63,27 @@ This directory contains reusable Terraform modules for the containerized Agent K **Purpose**: Manages SQS queues for queue-based execution mode **Resources Created**: + - Input Queue (FIFO SQS) - Output Queue (FIFO SQS) - Dead Letter Queues (optional) **Input Variables**: + ```hcl queue_config = { # Shared settings sqs_managed_sse_enabled = true max_message_size = 262144 receive_wait_time_seconds = 0 - + # Input queue input_queue_visibility_timeout = 60 input_queue_message_retention_seconds = 1800 input_queue_max_receive_count = 5 input_queue_create_dlq = false input_queue_dlq_message_retention_seconds = 1800 - + # Output queue output_queue_visibility_timeout = 60 output_queue_message_retention_seconds = 1800 @@ -90,6 +94,7 @@ queue_config = { ``` **Outputs**: + - `input_queue_arn` - `input_queue_url` - `output_queue_arn` @@ -100,6 +105,7 @@ queue_config = { **Purpose**: Manages the main ECS service that handles HTTP requests **Resources Created**: + - ECS Task Definition and Service - Application Load Balancer (ALB) - Target Group and Listener @@ -108,6 +114,7 @@ queue_config = { - IAM Policy for DynamoDB memory access **Input Variables**: + ```hcl rest_service = { cpu = 256 @@ -122,6 +129,7 @@ rest_service = { ``` **Outputs**: + - `service_arn` - `service_name` - `task_role_name` @@ -134,6 +142,7 @@ rest_service = { - `alb_security_group_id` **Environment Variables Injected**: + - `AK_SESSION__REDIS__URL` (if Redis enabled) - `AK_SESSION__DYNAMODB__TABLE_NAME` (if DynamoDB memory table enabled) - `AK_EXECUTION__QUEUES__INPUT__URL` (if queue mode enabled) @@ -146,6 +155,7 @@ rest_service = { **Purpose**: Manages the Agent Runner ECS service that processes messages from the input queue **Resources Created**: + - ECS Task Definition and Service - IAM Execution Role - IAM Task Role @@ -154,6 +164,7 @@ rest_service = { - CloudWatch Log Group **Optional Auto Scaling Resources** (when `scaling_config.enabled = true`): + - Lambda Function (BacklogPerTask metric calculator) - IAM Role and Policy for Lambda - CloudWatch Log Group for Lambda @@ -164,6 +175,7 @@ rest_service = { - Application Auto Scaling Policy (Target Tracking) **Input Variables**: + ```hcl agent_runner = { cpu = 512 @@ -185,6 +197,7 @@ scaling_config = { ``` **Outputs**: + - `service_name` - `service_arn` - `task_role_arn` @@ -192,6 +205,7 @@ scaling_config = { - `security_group_id` **Environment Variables Injected**: + - `AK_EXECUTION__QUEUES__INPUT__URL` - `AK_EXECUTION__QUEUES__OUTPUT__URL` - `AK_EXECUTION__QUEUES__INPUT__MAX_RECEIVE_COUNT` @@ -203,6 +217,7 @@ scaling_config = { When `scaling_config.enabled = true`: 1. **Lambda Function** runs every minute to calculate: + ``` BacklogPerTask = ApproximateNumberOfMessages / max(runningCount, 1) ``` @@ -229,9 +244,9 @@ module "containerized_agents" { env_alias = "dev" module_name = "chatbot" region = "us-east-1" - + package_path = "./dist" - + rest_service = { cpu = 256 memory = 512 @@ -239,7 +254,7 @@ module "containerized_agents" { OPENAI_API_KEY = var.api_key } } - + create_redis_cluster = true } ``` @@ -254,9 +269,9 @@ module "containerized_agents" { env_alias = "prod" module_name = "assistant" region = "us-east-1" - + package_path = "./dist-rest-service" - + # REST Service handles HTTP requests rest_service = { cpu = 512 @@ -267,18 +282,18 @@ module "containerized_agents" { OPENAI_API_KEY = var.api_key } } - + # Queue mode for async processing - enable_queue_mode = true - queue_mode_type = "async" - + queue_mode = true + execution_mode = "async" + queue_config = { input_queue_visibility_timeout = 120 output_queue_visibility_timeout = 60 input_queue_create_dlq = true output_queue_create_dlq = true } - + # Agent Runner processes queue messages agent_runner = { cpu = 1024 @@ -290,7 +305,7 @@ module "containerized_agents" { OPENAI_API_KEY = var.api_key } } - + # Auto scaling based on queue depth scaling_config = { enabled = true @@ -300,7 +315,7 @@ module "containerized_agents" { scale_in_cooldown = 180 scale_out_cooldown = 60 } - + create_dynamodb_memory_table = true } ``` @@ -321,20 +336,20 @@ module "containerized_agents" { ✅ **Maintainability**: Modules can be updated independently ✅ **Flexibility**: Easy to enable/disable features (scaling, DLQ, etc.) ✅ **Reusability**: Modules can be used in other projects -✅ **Type Safety**: Terraform validates object structure +✅ **Type Safety**: Terraform validates object structure ## Comparison with Serverless -| Feature | Serverless | Containerized | -|---------|-----------|---------------| -| **Request Handler** | Lambda Function | ECS Task (REST Service) | -| **Agent Runner** | Lambda Function | ECS Task | -| **Compute** | AWS Lambda | AWS Fargate | -| **Scaling** | Lambda auto-scales | Custom ECS auto-scaling | -| **Config Pattern** | `request_handler` object | `rest_service` object | -| **Queue Module** | ✅ | ✅ | -| **Scaling Config** | Built-in | `scaling_config` object | -| **Load Balancer** | API Gateway direct | ALB + API Gateway | +| Feature | Serverless | Containerized | +| ------------------- | ------------------------ | ----------------------- | +| **Request Handler** | Lambda Function | ECS Task (REST Service) | +| **Agent Runner** | Lambda Function | ECS Task | +| **Compute** | AWS Lambda | AWS Fargate | +| **Scaling** | Lambda auto-scales | Custom ECS auto-scaling | +| **Config Pattern** | `request_handler` object | `rest_service` object | +| **Queue Module** | ✅ | ✅ | +| **Scaling Config** | Built-in | `scaling_config` object | +| **Load Balancer** | API Gateway direct | ALB + API Gateway | ## Migration from Old Structure diff --git a/ak-deployment/ak-aws/containerized/modules/queues/main.tf b/ak-deployment/ak-aws/containerized/modules/queues/main.tf index 25479e51b..ecf45480c 100644 --- a/ak-deployment/ak-aws/containerized/modules/queues/main.tf +++ b/ak-deployment/ak-aws/containerized/modules/queues/main.tf @@ -4,7 +4,7 @@ data "aws_region" "current" {} module "input_queue" { source = "yaalalabs/ak-common/aws//modules/sqs" - version = "0.5.1" + version = "0.6.0" product_alias = var.product_alias env_alias = var.env_alias @@ -40,7 +40,7 @@ module "input_queue" { module "output_queue" { source = "yaalalabs/ak-common/aws//modules/sqs" - version = "0.5.1" + version = "0.6.0" product_alias = var.product_alias env_alias = var.env_alias diff --git a/ak-deployment/ak-aws/containerized/modules/rest-service/main.tf b/ak-deployment/ak-aws/containerized/modules/rest-service/main.tf index b6ef7bd66..e7600f908 100644 --- a/ak-deployment/ak-aws/containerized/modules/rest-service/main.tf +++ b/ak-deployment/ak-aws/containerized/modules/rest-service/main.tf @@ -8,7 +8,7 @@ locals { AK_SESSION__DYNAMODB__TABLE_NAME = var.dynamodb_memory_table_name } : {}, # Queue mode — inject queue URLs and response store table name - var.enable_queue_mode ? { + var.queue_mode ? { AK_EXECUTION__QUEUES__INPUT__URL = var.input_queue_url AK_EXECUTION__QUEUES__OUTPUT__URL = var.output_queue_url AK_EXECUTION__RESPONSE_STORE__DYNAMODB__TABLE_NAME = var.response_store_table_name @@ -147,14 +147,14 @@ module "ecs_service" { source = "terraform-aws-modules/ecs/aws//modules/service" version = "6.10.0" - name = var.service_name - cluster_arn = var.ecs_cluster_arn - cpu = var.rest_service.cpu - memory = var.rest_service.memory - desired_count = var.rest_service.desired_count - launch_type = "FARGATE" - platform_version = "LATEST" - subnet_ids = var.subnet_ids + name = var.service_name + cluster_arn = var.ecs_cluster_arn + cpu = var.rest_service.cpu + memory = var.rest_service.memory + desired_count = var.rest_service.desired_count + launch_type = "FARGATE" + platform_version = "LATEST" + subnet_ids = var.subnet_ids security_group_ids = [aws_security_group.ecs_service.id] load_balancer = { @@ -180,7 +180,7 @@ module "ecs_service" { } # Attach DynamoDB access to the task role if a memory table exists - create_tasks_iam_role = true + create_tasks_iam_role = true tasks_iam_role_policies = var.create_dynamodb_memory_table ? { DynamoDB = aws_iam_policy.dynamodb_policy[0].arn } : {} diff --git a/ak-deployment/ak-aws/containerized/modules/rest-service/variables.tf b/ak-deployment/ak-aws/containerized/modules/rest-service/variables.tf index 5d9c46bd3..1e92edc67 100644 --- a/ak-deployment/ak-aws/containerized/modules/rest-service/variables.tf +++ b/ak-deployment/ak-aws/containerized/modules/rest-service/variables.tf @@ -91,7 +91,7 @@ variable "rest_service" { }) } -variable "enable_queue_mode" { +variable "queue_mode" { type = bool description = "Whether queue mode is enabled" default = false diff --git a/ak-deployment/ak-aws/containerized/outputs.tf b/ak-deployment/ak-aws/containerized/outputs.tf index 1d6d4547a..8bf508e8c 100644 --- a/ak-deployment/ak-aws/containerized/outputs.tf +++ b/ak-deployment/ak-aws/containerized/outputs.tf @@ -30,22 +30,22 @@ output "private_subnet_ids" { output "input_queue_url" { description = "URL of the SQS Input Queue (queue mode only)" - value = var.enable_queue_mode ? module.queues[0].input_queue_url : null + value = var.queue_mode ? module.queues[0].input_queue_url : null } output "output_queue_url" { description = "URL of the SQS Output Queue (queue mode only)" - value = var.enable_queue_mode ? module.queues[0].output_queue_url : null + value = var.queue_mode ? module.queues[0].output_queue_url : null } output "response_store_table_name" { description = "DynamoDB Response Store table name (queue mode only)" - value = var.enable_queue_mode ? aws_dynamodb_table.response_store[0].name : null + value = var.queue_mode ? aws_dynamodb_table.response_store[0].name : null } output "agent_runner_service_name" { description = "ECS Agent Runner service name (queue mode only)" - value = var.enable_queue_mode ? module.agent_runner[0].service_name : null + value = var.queue_mode ? module.agent_runner[0].service_name : null } output "rest_service_image_uri" { diff --git a/ak-deployment/ak-aws/containerized/queue_mode.tf b/ak-deployment/ak-aws/containerized/queue_mode.tf index 43d97af89..eeaae1bb7 100644 --- a/ak-deployment/ak-aws/containerized/queue_mode.tf +++ b/ak-deployment/ak-aws/containerized/queue_mode.tf @@ -4,7 +4,7 @@ # ---------- SQS Queues Module ---------- module "queues" { - count = var.enable_queue_mode ? 1 : 0 + count = var.queue_mode ? 1 : 0 source = "./modules/queues" product_alias = var.product_alias @@ -19,7 +19,7 @@ module "queues" { # ---------- Agent Runner Module ---------- module "agent_runner" { - count = var.enable_queue_mode ? 1 : 0 + count = var.queue_mode ? 1 : 0 source = "./modules/agent-runner" product_alias = var.product_alias diff --git a/ak-deployment/ak-aws/containerized/rest_service.tf b/ak-deployment/ak-aws/containerized/rest_service.tf index f3ff05320..b03731b66 100644 --- a/ak-deployment/ak-aws/containerized/rest_service.tf +++ b/ak-deployment/ak-aws/containerized/rest_service.tf @@ -19,10 +19,10 @@ module "rest_service" { service_name = local.service_name container_name = local.container_name - redis_url = local.redis_url - create_dynamodb_memory_table = var.create_dynamodb_memory_table - dynamodb_memory_table_arn = local.dynamodb_memory_table_arn - dynamodb_memory_table_name = local.dynamodb_memory_table_name + redis_url = local.redis_url + create_dynamodb_memory_table = var.create_dynamodb_memory_table + dynamodb_memory_table_arn = local.dynamodb_memory_table_arn + dynamodb_memory_table_name = local.dynamodb_memory_table_name rest_service = { cpu = var.rest_service.cpu @@ -35,10 +35,10 @@ module "rest_service" { environment_variables = merge(var.environment_variables, var.rest_service.environment_variables) } - enable_queue_mode = var.enable_queue_mode - input_queue_url = var.enable_queue_mode ? module.queues[0].input_queue_url : null - output_queue_url = var.enable_queue_mode ? module.queues[0].output_queue_url : null - response_store_table_name = var.enable_queue_mode ? aws_dynamodb_table.response_store[0].name : null + queue_mode = var.queue_mode + input_queue_url = var.queue_mode ? module.queues[0].input_queue_url : null + output_queue_url = var.queue_mode ? module.queues[0].output_queue_url : null + response_store_table_name = var.queue_mode ? aws_dynamodb_table.response_store[0].name : null queue_config = var.queue_config tags = var.tags diff --git a/ak-deployment/ak-aws/containerized/state.tf b/ak-deployment/ak-aws/containerized/state.tf index 7d95c4f07..95d98f660 100644 --- a/ak-deployment/ak-aws/containerized/state.tf +++ b/ak-deployment/ak-aws/containerized/state.tf @@ -87,9 +87,9 @@ module "docker_image" { # Agent Runner Docker Image (optional - only if package_path is provided) module "agent_runner_docker_image" { - count = var.enable_queue_mode && var.agent_runner.package_path != null ? 1 : 0 + count = var.queue_mode && var.agent_runner.package_path != null ? 1 : 0 source = "yaalalabs/ak-common/aws//modules/ecr" - version = "0.5.1" + version = "0.6.0" env_alias = var.env_alias module_name = "${var.module_name}-runner" product_alias = var.product_alias diff --git a/ak-deployment/ak-aws/containerized/variables.tf b/ak-deployment/ak-aws/containerized/variables.tf index 91248b79a..5c3b7225c 100644 --- a/ak-deployment/ak-aws/containerized/variables.tf +++ b/ak-deployment/ak-aws/containerized/variables.tf @@ -27,7 +27,7 @@ variable "module_name" { variable "environment_variables" { description = "Environment variables" type = any - default = {} + default = {} } variable "api_version" { @@ -51,8 +51,8 @@ variable "api_base_path" { variable "gateway_endpoints" { description = "List of HTTP API endpoints to expose. If empty, a default POST /api/{api_version}/{agent_endpoint} endpoint is created." type = list(object({ - path = string # The URL path segment that clients will access (e.g., "chat", "users", "webhook"). This becomes part of the full URL: https://your-domain.com/{api_base_path}/{api_version}/{path} - method = string # HTTP method for this endpoint (e.g., "GET", "POST", "PUT", "DELETE", "ANY"). "ANY" accepts all HTTP methods. "$default" is a special catch-all route. + path = string # The URL path segment that clients will access (e.g., "chat", "users", "webhook"). This becomes part of the full URL: https://your-domain.com/{api_base_path}/{api_version}/{path} + method = string # HTTP method for this endpoint (e.g., "GET", "POST", "PUT", "DELETE", "ANY"). "ANY" accepts all HTTP methods. "$default" is a special catch-all route. overwrite_path = string # The backend path that the ALB forwards requests to (e.g., "/api/v1/chat", "/internal/webhook"). This allows mapping external paths to different internal service endpoints. })) default = [] @@ -70,9 +70,9 @@ variable "gateway_endpoints" { } variable "tags" { - type = map(string) + type = map(string) description = "Resource tags" - default = {} + default = {} } variable "vpc_cidr" { @@ -82,15 +82,15 @@ variable "vpc_cidr" { } variable "public_subnet_cidrs" { - type = list(string) + type = list(string) description = "CIDR blocks for the public subnets" - default = ["10.0.1.0/24", "10.0.2.0/24"] + default = ["10.0.1.0/24", "10.0.2.0/24"] } variable "private_subnet_cidrs" { - type = list(string) + type = list(string) description = "CIDR blocks for the private subnets" - default = ["10.0.3.0/24", "10.0.4.0/24"] + default = ["10.0.3.0/24", "10.0.4.0/24"] } variable "vpc_id" { @@ -100,7 +100,7 @@ variable "vpc_id" { } variable "private_subnet_ids" { - type = list(string) + type = list(string) description = "When using an existing VPC to deploy, private subnet IDs need to be provided" default = null } @@ -129,8 +129,8 @@ variable "rest_service" { desired_count = optional(number, 1) container_port = optional(number, 8000) health_check_endpoint = optional(string, "/health") - package_path = string # Docker image source path (required) - image_uri = optional(string, null) # Or provide pre-built image URI + package_path = string # Docker image source path (required) + image_uri = optional(string, null) # Or provide pre-built image URI command = optional(list(string), null) environment_variables = optional(map(string), {}) }) @@ -216,19 +216,19 @@ data "aws_caller_identity" "current" {} # Queue Mode Configuration # --------------------------------------------------------------------------- -variable "enable_queue_mode" { +variable "queue_mode" { type = bool description = "Enable SQS queue mode. Creates Input/Output queues, DynamoDB response store, and Agent Runner ECS service." default = false } -variable "queue_mode_type" { +variable "execution_mode" { type = string description = "Queue mode type: 'sync' (client waits on same connection) or 'async' (client polls a separate GET endpoint)." default = "sync" validation { - condition = contains(["sync", "async"], var.queue_mode_type) - error_message = "queue_mode_type must be either 'sync' or 'async'." + condition = contains(["sync", "async"], var.execution_mode) + error_message = "execution_mode must be either 'sync' or 'async'." } } @@ -243,13 +243,13 @@ variable "queue_config" { # Shared settings sqs_managed_sse_enabled = optional(bool, true) - max_message_size = optional(number, 262144) # 256 KB + max_message_size = optional(number, 262144) # 256 KB receive_wait_time_seconds = optional(number, 0) batch_size = optional(number, 10) # Max messages fetched per SQS receive call (ECS consumers only, 1-10) # Input queue settings input_queue_visibility_timeout = optional(number, 60) - input_queue_message_retention_seconds = optional(number, 1800) # 30 minutes + input_queue_message_retention_seconds = optional(number, 1800) # 30 minutes input_queue_max_receive_count = optional(number, 5) input_queue_create_dlq = optional(bool, false) input_queue_dlq_message_retention_seconds = optional(number, 1800) @@ -262,17 +262,17 @@ variable "queue_config" { output_queue_dlq_message_retention_seconds = optional(number, 1800) }) default = { - input_queue_name = "input-queue" - output_queue_name = "output-queue" - sqs_managed_sse_enabled = true - max_message_size = 262144 - receive_wait_time_seconds = 0 - batch_size = 10 - input_queue_visibility_timeout = 60 - input_queue_message_retention_seconds = 1800 - input_queue_max_receive_count = 5 - input_queue_create_dlq = false - input_queue_dlq_message_retention_seconds = 1800 + input_queue_name = "input-queue" + output_queue_name = "output-queue" + sqs_managed_sse_enabled = true + max_message_size = 262144 + receive_wait_time_seconds = 0 + batch_size = 10 + input_queue_visibility_timeout = 60 + input_queue_message_retention_seconds = 1800 + input_queue_max_receive_count = 5 + input_queue_create_dlq = false + input_queue_dlq_message_retention_seconds = 1800 output_queue_visibility_timeout = 60 output_queue_message_retention_seconds = 1800 output_queue_max_receive_count = 5 @@ -294,8 +294,8 @@ variable "agent_runner" { cpu = optional(number, 512) memory = optional(number, 1024) desired_count = optional(number, 1) - package_path = optional(string, null) # Path to agent runner Docker source (builds separate image) - image_uri = optional(string, null) # Or provide pre-built image URI + package_path = optional(string, null) # Path to agent runner Docker source (builds separate image) + image_uri = optional(string, null) # Or provide pre-built image URI command = optional(list(string), null) environment_variables = optional(map(string), {}) }) @@ -334,8 +334,8 @@ variable "scaling_config" { validation { condition = ( !var.scaling_config.enabled || - var.enable_queue_mode + var.queue_mode ) - error_message = "scaling_config.enabled requires enable_queue_mode = true." + error_message = "scaling_config.enabled requires queue_mode = true." } } diff --git a/ak-py/src/agentkernel/skills/ak-cloud-deploy/SKILL.md b/ak-py/src/agentkernel/skills/ak-cloud-deploy/SKILL.md index fc2111221..bd6c52b28 100644 --- a/ak-py/src/agentkernel/skills/ak-cloud-deploy/SKILL.md +++ b/ak-py/src/agentkernel/skills/ak-cloud-deploy/SKILL.md @@ -726,8 +726,8 @@ module "containerized_agents" { } } - enable_queue_mode = true - queue_mode_type = "sync" # or "async" + queue_mode = true + execution_mode = "sync" # or "async" queue_config = { input_queue_visibility_timeout = 120 diff --git a/docs/docs/advanced/queue-mode-guide.md b/docs/docs/advanced/queue-mode-guide.md index ad3e34129..b85e05e4d 100644 --- a/docs/docs/advanced/queue-mode-guide.md +++ b/docs/docs/advanced/queue-mode-guide.md @@ -234,7 +234,7 @@ if __name__ == "__main__": - IAM for IO container task role: `sqs:SendMessage` on Input Queue; `sqs:ReceiveMessage / DeleteMessage / ChangeMessageVisibility` on Output Queue; `dynamodb:PutItem / GetItem / Query / DeleteItem` on Response Store - IAM for Agent Runner task role: `sqs:ReceiveMessage / DeleteMessage / ChangeMessageVisibility` on Input Queue; `sqs:SendMessage` on Output Queue -All of these are provisioned automatically by the `yaalalabs/ak-containerized/aws` Terraform module when `enable_queue_mode = true`. +All of these are provisioned automatically by the `yaalalabs/ak-containerized/aws` Terraform module when `queue_mode = true`. ### Required Environment Variables diff --git a/docs/docs/deployment/aws-containerized.md b/docs/docs/deployment/aws-containerized.md index c62934209..4c9aed6c2 100644 --- a/docs/docs/deployment/aws-containerized.md +++ b/docs/docs/deployment/aws-containerized.md @@ -105,8 +105,8 @@ if __name__ == "__main__": Enable queue mode in the `yaalalabs/ak-containerized/aws` module: ```hcl -enable_queue_mode = true -queue_mode_type = "sync" # or "async" +queue_mode = true +execution_mode = "sync" # or "async" rest_service = { package_path = "../dist-rest-service" diff --git a/examples/aws-containerized/openai-dynamodb-scalable/README.md b/examples/aws-containerized/openai-dynamodb-scalable/README.md index 5f6d6bac2..820a5a82b 100644 --- a/examples/aws-containerized/openai-dynamodb-scalable/README.md +++ b/examples/aws-containerized/openai-dynamodb-scalable/README.md @@ -27,18 +27,18 @@ This demo deploys the following AWS resources: ## Execution Mode -This example supports two queue modes configured via `queue_mode_type` in `deploy/main.tf`: +This example supports two queue modes configured via `execution_mode` in `deploy/main.tf`: ### REST_SYNC Mode (default) ```hcl -queue_mode_type = "sync" +execution_mode = "sync" ``` Client request blocks until agent completes. Response returned on same HTTP connection. ### REST_ASYNC Mode (optional) ```hcl -queue_mode_type = "async" +execution_mode = "async" ``` Client request returns immediately with `request_id`. Client polls separate GET endpoint for result. @@ -222,7 +222,7 @@ The Agent Runner automatically scales based on queue backlog per task. **Enable autoscaling in `deploy/main.tf`:** ```hcl -enable_queue_mode = true +queue_mode = true scaling_config = { enabled = true diff --git a/examples/aws-containerized/openai-dynamodb-scalable/deploy/main.tf b/examples/aws-containerized/openai-dynamodb-scalable/deploy/main.tf index 2a027073a..298165440 100644 --- a/examples/aws-containerized/openai-dynamodb-scalable/deploy/main.tf +++ b/examples/aws-containerized/openai-dynamodb-scalable/deploy/main.tf @@ -1,39 +1,10 @@ -provider "aws" { - region = var.region -} - -terraform { - required_providers { - aws = { - source = "hashicorp/aws" - version = ">= 6.11.0" - } - docker = { - source = "kreuzwerker/docker" - version = "3.6.2" - } - } - required_version = ">= 1.9.5" -} - -data "aws_ecr_authorization_token" "token" {} -data "aws_caller_identity" "current" {} - -provider "docker" { - registry_auth { - address = format("%v.dkr.ecr.%v.amazonaws.com", data.aws_caller_identity.current.account_id, var.region) - username = data.aws_ecr_authorization_token.token.user_name - password = data.aws_ecr_authorization_token.token.password - } -} - # --------------------------------------------------------------------------- # Containerized Agents Deployment # --------------------------------------------------------------------------- module "containerized_agents" { # When using from registry: source = "yaalalabs/ak-containerized/aws" - version = "0.5.1" + version = "0.6.0" product_alias = var.product_alias env_alias = var.env_alias @@ -67,8 +38,8 @@ module "containerized_agents" { # ---- Queue Mode ---- # Enable queue-based execution for scalable, async processing - enable_queue_mode = true - queue_mode_type = "sync" # "async" | "sync" + queue_mode = true + execution_mode = "sync" # "async" | "sync" # ---- Queue Configuration ---- # SQS queues for request/response handling diff --git a/examples/aws-serverless/streaming-openai/deploy/main.tf b/examples/aws-serverless/streaming-openai/deploy/main.tf index a9e2d7bd8..1eebe1d06 100644 --- a/examples/aws-serverless/streaming-openai/deploy/main.tf +++ b/examples/aws-serverless/streaming-openai/deploy/main.tf @@ -1,6 +1,6 @@ module "serverless_agents" { source = "yaalalabs/ak-serverless/aws" - version = "0.5.1" + version = "0.6.0" # Basic configuration product_alias = var.product_alias @@ -25,14 +25,14 @@ module "serverless_agents" { # Request handler configuration request_handler = { - module_name = "rqst-hdlr" - function_name = "rqh-func" - function_description = "Agent Kernel OpenAI WebSocket Streaming Sample Lambda" - handler_path = "lambda_request_handler.handler" - package_type = "LocalZip" - package_path = "../dist_request_handler.zip" - memory_size = 256 - timeout = 45 + module_name = "rqst-hdlr" + function_name = "rqh-func" + function_description = "Agent Kernel OpenAI WebSocket Streaming Sample Lambda" + handler_path = "lambda_request_handler.handler" + package_type = "LocalZip" + package_path = "../dist_request_handler.zip" + memory_size = 256 + timeout = 45 environment_variables = { "OPENAI_API_KEY" = var.openai_api_key } @@ -40,14 +40,14 @@ module "serverless_agents" { # Agent runner configuration agent_runner = { - module_name = "agent-runner" - function_name = "ar-func" - function_description = "Agent runner for processing OpenAI streaming requests" - timeout = 45 - memory_size = 512 - handler_path = "lambda_agent_runner.handler" - package_path = "../dist_agent_runner" - package_type = "Image" + module_name = "agent-runner" + function_name = "ar-func" + function_description = "Agent runner for processing OpenAI streaming requests" + timeout = 45 + memory_size = 512 + handler_path = "lambda_agent_runner.handler" + package_path = "../dist_agent_runner" + package_type = "Image" environment_variables = { "OPENAI_API_KEY" = var.openai_api_key } From 798ca06c71543e0e5ee0122b44d43034564845ae Mon Sep 17 00:00:00 2001 From: Lakindu Induwara Kumara Date: Thu, 9 Jul 2026 11:28:16 +0530 Subject: [PATCH 87/90] updated agent kernel version --- ak-py/pyproject.toml | 2 +- ak-py/uv.lock | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/ak-py/pyproject.toml b/ak-py/pyproject.toml index ced56fd2e..2d69afa81 100644 --- a/ak-py/pyproject.toml +++ b/ak-py/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "uv_build" [project] name = "agentkernel" -version = "0.6.0" +version = "0.6.1" description = "Agent Kernel - Unified AI Agents Runtime" readme = "README.md" requires-python = ">=3.12,<3.14" diff --git a/ak-py/uv.lock b/ak-py/uv.lock index 946a5b87c..20e21234c 100644 --- a/ak-py/uv.lock +++ b/ak-py/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 3 +revision = 2 requires-python = ">=3.12, <3.14" resolution-markers = [ "python_full_version >= '3.13' and sys_platform == 'win32'", @@ -47,7 +47,7 @@ wheels = [ [[package]] name = "agentkernel" -version = "0.6.0" +version = "0.6.1" source = { editable = "." } dependencies = [ { name = "deprecated" }, @@ -2085,7 +2085,9 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3f/7a/6bc2a7835731387ed303b9390ce68a116ab053df05450a59181239200454/greenlet-3.5.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:76dae33e97b52743a19210931ee3e78a88fe1438bc2fc4ee5e7512d289bfad4f", size = 288351, upload-time = "2026-06-17T17:36:17.019Z" }, { url = "https://files.pythonhosted.org/packages/57/1b/bd98062fcef6d0e9d0873ab6f2d029772e6ea342972ae43275bd6177900f/greenlet-3.5.2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:30252d191d6959df1d040b559a38fc017139606c5ecc2ad00416557c0355d742", size = 604273, upload-time = "2026-06-17T18:07:20.296Z" }, { url = "https://files.pythonhosted.org/packages/25/e6/fe392c522bf45d976abe7db2793f6ef4e87b053ebb869deeaae46aeb54da/greenlet-3.5.2-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1adc23c50f22b0f5979521909a8360ab4a3d3bef8b641ce633a04cf1b1c967ea", size = 616536, upload-time = "2026-06-17T18:29:43.205Z" }, + { url = "https://files.pythonhosted.org/packages/42/df/cdb1f75f07214f13110e7e3879531f11c26083bd480a56a9474c430ec44c/greenlet-3.5.2-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:87359c23eb4e8f1b16da68faad29bf5aeb80e3628d7d8e4aa2e41c36879ddedd", size = 621843, upload-time = "2026-06-17T18:39:27.507Z" }, { url = "https://files.pythonhosted.org/packages/68/4a/399ff81fa93a19d6a9df394cef0355f082dbc19ad41aba9593cd0ad444e2/greenlet-3.5.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f052fff492c52fdfa99bd3b3c1389a53de37dae76a0562741417f0d018f02b3", size = 613749, upload-time = "2026-06-17T17:39:28.148Z" }, + { url = "https://files.pythonhosted.org/packages/2e/25/36a3628a7edcfeefddd3101dc88039c79721c5f8d688db7ebed1cbaaa789/greenlet-3.5.2-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:f4d67c1684db3f9782c37ee4bade3f86f5a23a8fcf3f8359224106018ca40728", size = 424889, upload-time = "2026-06-17T18:41:19.469Z" }, { url = "https://files.pythonhosted.org/packages/a5/75/f519593f12ad43d08e28c03a95cfe2eeae011707dbc9dab0c4a263ce90f9/greenlet-3.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:120b77c2a18ebf629c3a7886f68c6d01e065654844ad468f15bb93ace66f2094", size = 1573725, upload-time = "2026-06-17T18:22:12.023Z" }, { url = "https://files.pythonhosted.org/packages/f1/bc/bc1ea4b0754c6c51bbf9d94677b0b1f7fbda8cbb404e44a896854fc0a940/greenlet-3.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a850f6224088ef7dcc70f1a545cb6b3d119c35d6dca63b925b9f35da0635cdad", size = 1638132, upload-time = "2026-06-17T17:40:06.971Z" }, { url = "https://files.pythonhosted.org/packages/36/c0/f0f5a34247df60de285f75f22e57f14027f4b3c43820981854b5b643ca6d/greenlet-3.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:89da99ee8345b458ea2f16831dad31c88ddcdec454b48704d569a0b8fb28f146", size = 239393, upload-time = "2026-06-17T17:33:47.09Z" }, @@ -2093,7 +2095,9 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d0/3c/bb37b9d40d65b0741a8b040ca5c307034d0a9822994dff5f825c88dd7a6b/greenlet-3.5.2-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:0629377725977252159de1ebd3c6e49c170a63856e585446797bb3d66d4d9c34", size = 287178, upload-time = "2026-06-17T17:35:25.132Z" }, { url = "https://files.pythonhosted.org/packages/f0/a6/0c5902393f492f8ceb19d0b5cf139284e3a11b333a049739643b1036b6f8/greenlet-3.5.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2ddf9eddc617681108dd071b3feabf3f4a4cd64846254aec4d4ceda098b639a", size = 606900, upload-time = "2026-06-17T18:07:21.692Z" }, { url = "https://files.pythonhosted.org/packages/d8/7c/42899c31d4b87148ae4e3f87f63e13398824be6241f4dde42ded95768a34/greenlet-3.5.2-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f41feb9f2b59e2e61ac9bea4e344ddd9396bf3cacb2583f73a3595ed7df6f8e7", size = 619265, upload-time = "2026-06-17T18:29:44.837Z" }, + { url = "https://files.pythonhosted.org/packages/6a/7e/28f991affb413b232b1e7d768db24c37b3f4d5daecc3f19b455d40bd2dea/greenlet-3.5.2-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9dc23f0e5ad76415457212a4b947d22ebe4dc80baf02adf7dd5647a90f38bb4e", size = 625044, upload-time = "2026-06-17T18:39:29.046Z" }, { url = "https://files.pythonhosted.org/packages/d3/52/4ff8c98d3cfe62b4515f8584ae14510a58f35c549cc5292b78d9b7a40b70/greenlet-3.5.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09201fa698768db245920b00fdc86ee3e73540f01ca6db162be9632642e1a473", size = 616187, upload-time = "2026-06-17T17:39:29.473Z" }, + { url = "https://files.pythonhosted.org/packages/29/05/0cc9ec660e7acff85f93b0a048b6654371c822c884add44c02a465cf70e0/greenlet-3.5.2-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:423167363c510a75b649f5cd58d873c29498ea03598b9e4b1c3b73e0f899f3d5", size = 427322, upload-time = "2026-06-17T18:41:20.892Z" }, { url = "https://files.pythonhosted.org/packages/c9/a6/269c8bf9aefc13361ce1088f0e392b154cb21005de7862e42b5d782b81fd/greenlet-3.5.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a1759fa4f14c398508cf20dc8037de55cc23ae8bd14c185c2718257837195ca5", size = 1573778, upload-time = "2026-06-17T18:22:13.497Z" }, { url = "https://files.pythonhosted.org/packages/1f/9b/391d015cbc6323e81b14c02cf825fdca7e0049c9bb489bf4ac72883118ba/greenlet-3.5.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b9318cdeb9abdbfdd8bc8464ee4a06dffde2c7846e1def138365a6240ab2c9a5", size = 1638092, upload-time = "2026-06-17T17:40:08.163Z" }, { url = "https://files.pythonhosted.org/packages/49/53/5b4df711f4356c62e85d9f819d87966d526d1cfb32bae49a8f7d6fc36ea4/greenlet-3.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:2c3b3311af72b3d3b03cc0f1ffd11f072e834be5d0444105cf715fc44434e39c", size = 239352, upload-time = "2026-06-17T17:38:51.593Z" }, From 69c6a276dfb31333adafdf1f260c633c4f643a3a Mon Sep 17 00:00:00 2001 From: yl-nuwan Date: Thu, 9 Jul 2026 11:49:30 +0530 Subject: [PATCH 88/90] Update Terraform provider versions and improve configuration structure --- agent/deploy/.terraform.lock.hcl | 82 ++++++++++++++++++++------------ agent/deploy/backend.tf | 10 ++-- agent/deploy/main.tf | 23 +++++---- 3 files changed, 70 insertions(+), 45 deletions(-) diff --git a/agent/deploy/.terraform.lock.hcl b/agent/deploy/.terraform.lock.hcl index b88c3ca42..4b9f3e94c 100644 --- a/agent/deploy/.terraform.lock.hcl +++ b/agent/deploy/.terraform.lock.hcl @@ -1,46 +1,68 @@ # This file is maintained automatically by "terraform init". # Manual edits may be lost in future updates. +provider "registry.terraform.io/hashicorp/archive" { + version = "2.8.0" + hashes = [ + "h1:jdmKm+xl6ZcQrijxapnZ94RVuz/G4vk7hsIa1N0VT5Q=", + "zh:0d14713fdc259fb377d0b899ad3c650a34194bd52194c863303ef22a65a580e2", + "zh:369b56040c7a8085d04e7e8ffac1e2b321a3170e502f788819bc34b868ec016f", + "zh:4d1a3b983ed6af5a52bfe12794674ae55cbadfa6021b37106ade68b433ad216a", + "zh:5c547549e26e083573c78a966ca68ce6d7df6bb8f3948f66a575f07da46b74ea", + "zh:6de093e62a975eb19a5e3017ce38e6e3cb639c17b79648d2000e0a8348f0e997", + "zh:7267936c2cdbc448efeb594d73e6b56a53d6a7ae14fe88cdd2a4133adc3302f0", + "zh:7482f023050ed426b4b45116e1761643bc33b1fd4ce4a6fab207ae2571f35940", + "zh:76bbd93b234e5a2927d98b511d86565700f549b570871a194c35f944b96cefb7", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:c6afc4bc1f002bac9c173007dd4da05fde788cd14c2916089f958c33fedb0dfa", + "zh:d3ba40bd806a3a08e9237dece679193c99afb2085de6b45d7f5d1f673cfcd368", + "zh:e1ad7ded53ecd6f0e5b473a3b44eae2b2e885653a56050ab583d387332be02e4", + "zh:e93e78575ce82be6084cc153c24ba8f385dc8d6880888ee66e918460c870953d", + ] +} + provider "registry.terraform.io/hashicorp/aws" { - version = "6.25.0" - constraints = ">= 4.22.0, >= 6.11.0, >= 6.21.0, >= 6.22.0" + version = "6.54.0" + constraints = ">= 4.22.0, >= 6.11.0, >= 6.21.0, >= 6.22.0, >= 6.28.0" hashes = [ - "h1:0XEc9eHELD/BtPNybqkzzaS3bYp2HSv9LwAfaGyCpOU=", - "zh:0f9621f719ec2051eabb94ca59aa4f13574487fbc1517b183293431c9d388e38", - "zh:2ffbedb2e3afcd82da8bfc540bd74e9611527bdafd00d6d1885f62e7d13bac74", - "zh:30fb4ab8b4af19da7b9ce95cb41fa9399f81383e1adc91801b770e7eeab651c3", - "zh:377cbaffe3ec8aa5bb594071df0e91f17ac9292a325ed73cebd69fe78c51f7ec", - "zh:3b65f5c98e03f1bfc5b71fa69521e785552ff9656860b25e211287910874037d", - "zh:4478fab7b111c40a9a2a9db6ec5331618cc8e5a8b591f651095c77b87e9f22b1", - "zh:4fdaa559c57aed5d24fa3d5cb59fed59e1e689c21d038fd336a3ba93b258803f", - "zh:7a751ecd0f2654746dd4041d0f6d894c3a1876a152ba4bb7805ec2c715259065", - "zh:866725b83f8d5587dab0559ac208ee6c181746871faa99ce551b535e19c7bb6a", + "h1:pdl5u/cxTU5ctqlHFr/Z4IPPVIDX9Gt+wBQbzZjLWlM=", + "zh:0557777baa82faa7907adce61a2a3f9b2a487070bb03f29df20a7502edfe966f", + "zh:0767256c36b9c4d4b66d4af882de480c6535ff9eac26410fca253e87b89cb478", + "zh:0be7595ef70273af5eb72d850d45eb264d39796d0578c7f3eda9988d7f0781e5", + "zh:0fd81edad00c8da35b37aa310c7466f7a8d61056b95b37b349c510d21a8a52f6", + "zh:11a976648c864d126a70eb2d40182a17ecfeb3c6c3bf790c4f7eafa5e4c204ba", + "zh:14065875294ea597303ae8e462189041067de3b1f5d97c9f2b04b5d14aeb4f7c", + "zh:55a76258b109f8394cad20c9e07f376fe3051920931bf43d1b62f4d7242f20c8", + "zh:69b04cc363dda4df3d703c3954ae87ef36b0fea8fe45a236db89f8647a879274", + "zh:79e3425219f59e285f128e88a7c294a6d33c7bfa73c57a42e31ac0d8daa2f20a", + "zh:8d6c46c8a215f73c3c1109695a9bfd8894852d0b1c592516f09efb8a84e08b0c", "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", - "zh:b16e3e2a8ccba4ceeeee961c708ef572c4a65e0001eaf09d08fa14cef01ab179", - "zh:dc897b2037bbb7f8d6456a4aa1ed82cbd4daddb173a184efdfe8c03a57557771", - "zh:de2344f23c980093a46dda3185f9052cda950d1b8ca9cf3c6e16b8c45fa23779", - "zh:ef538ec8a917715a1804c6735d44b756c32972d4fab71e15df87a59eb75dd57c", - "zh:f25cdfdac6798e7de4a1d3dd577a97c1ca200a12317a1fd5a4b9ea54cb05e868", + "zh:9db8f8fac77d6e999a2886df52bebb106b93ba8dfc4fea1e04decc8fda8070cd", + "zh:b4fc365f093b232531905547a50d4f62dbd9d5a843707867520b8b04e8b36e4f", + "zh:c9135045fbc561d30ec72f0b9335fcfe7590eed7e6b4eb8988455e6c8b4d348a", + "zh:db5f51f9f7037428cb7e8f8d43a63e0d3de498c9730fe98a3c539a75cc49208e", + "zh:f807b2a66174b20749c11d58cf7b2ffa8e89b4ec60dd70382959074f8490c387", ] } provider "registry.terraform.io/hashicorp/null" { - version = "3.2.4" + version = "3.3.0" constraints = ">= 2.0.0" hashes = [ - "h1:hkf5w5B6q8e2A42ND2CjAvgvSN3puAosDmOJb3zCVQM=", - "zh:59f6b52ab4ff35739647f9509ee6d93d7c032985d9f8c6237d1f8a59471bbbe2", + "h1:l+dm3lhmu4ys7GbvIldfn544olSPH0DOiYruuFSfQkY=", + "zh:021748b5ea3b5f6956f2e75c42c5cdc113b391fb98ac71364a4965d23b37000f", + "zh:3b27956f8541d46704fda234e0d535c2ae2a4b33411848b1ee262a1ec03568b0", + "zh:3de4ed47d6d0f4d8edba4a5092c7c9799950eda63989d8d0d2586e6afcb0aa20", + "zh:57ed8935c7d56dbc91cf2673534582cacfaab7a2f105f51d9f797e99df0c0c47", + "zh:58e176ba1d142827089e30e0711e007309a9f2726e8881986da5026e9778fdf4", + "zh:5949c4a3d4a93f841f155cdb7e991c087e637145c1630572e21948224f8f4923", + "zh:76d60f366b743003c1b085afa769b45b2198ee919927e45807d7d44fb42c067d", "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", - "zh:795c897119ff082133150121d39ff26cb5f89a730a2c8c26f3a9c1abf81a9c43", - "zh:7b9c7b16f118fbc2b05a983817b8ce2f86df125857966ad356353baf4bff5c0a", - "zh:85e33ab43e0e1726e5f97a874b8e24820b6565ff8076523cc2922ba671492991", - "zh:9d32ac3619cfc93eb3c4f423492a8e0f79db05fec58e449dee9b2d5873d5f69f", - "zh:9e15c3c9dd8e0d1e3731841d44c34571b6c97f5b95e8296a45318b94e5287a6e", - "zh:b4c2ab35d1b7696c30b64bf2c0f3a62329107bd1a9121ce70683dec58af19615", - "zh:c43723e8cc65bcdf5e0c92581dcbbdcbdcf18b8d2037406a5f2033b1e22de442", - "zh:ceb5495d9c31bfb299d246ab333f08c7fb0d67a4f82681fbf47f2a21c3e11ab5", - "zh:e171026b3659305c558d9804062762d168f50ba02b88b231d20ec99578a6233f", - "zh:ed0fe2acdb61330b01841fa790be00ec6beaac91d41f311fb8254f74eb6a711f", + "zh:79cd1bab1261a07f84e917191d7ddc4340ac5f5524283767256f7ffd7f87caf0", + "zh:8ec9083038cf710b30e319eaa467c9df7fa52bbd9969b61053a35bc2cdd2e0a6", + "zh:a6e502cb579685ab7aeb886c2bb11ddd9cfed74b41008592d57cbc3351a9218b", + "zh:acb74d6b4f66ff6acfcda315df802a7432170ef3955c9b432cb4580767004006", + "zh:f0ce55d8d9ffdb33dab612b1246f9bab060a9d54fc32ce2b4a038646155660af", ] } diff --git a/agent/deploy/backend.tf b/agent/deploy/backend.tf index 8b08ee05b..cb84b5648 100644 --- a/agent/deploy/backend.tf +++ b/agent/deploy/backend.tf @@ -4,10 +4,10 @@ terraform { backend "s3" { - bucket = "agent-kernel-terraform-state-bucket-dev" - key = "ak-agent/public/terraform.tfstate" - region = "ap-southeast-2" - use_lockfile = true - encrypt = true + bucket = "agent-kernel-terraform-state-bucket-dev" + key = "ak-agent/public/terraform.tfstate" + region = "ap-southeast-2" + use_lockfile = true + encrypt = true } } diff --git a/agent/deploy/main.tf b/agent/deploy/main.tf index 09df7272e..70d9df070 100644 --- a/agent/deploy/main.tf +++ b/agent/deploy/main.tf @@ -11,23 +11,26 @@ module "containered_agents" { product_alias = var.product_alias env_alias = var.env_alias module_name = var.module_name - package_path = "../dist" container_type = "ecs" region = var.region product_display_name = "AK Assistant" - ecs_container_port = 8000 enable_cors = true cors_allow_origins = ["http://localhost:3000", "https://kernel.yaala.ai"] cors_allow_methods = ["POST", "OPTIONS"] cors_allow_headers = ["content-type"] - throttling_rate_limit = "50" - throttling_burst_limit = "50" - # Environment variables passed to container - environment_variables = { - OPENAI_API_KEY = var.openai_api_key, - LANGFUSE_SECRET_KEY = var.langfuse_secret_key, - LANGFUSE_PUBLIC_KEY = var.langfuse_public_key, - LANGFUSE_BASE_URL = var.langfuse_base_url + throttling_rate_limit = 50 + throttling_burst_limit = 50 + + rest_service = { + package_path = "../dist" + container_port = 8000 + # Environment variables passed to container + environment_variables = { + OPENAI_API_KEY = var.openai_api_key, + LANGFUSE_SECRET_KEY = var.langfuse_secret_key, + LANGFUSE_PUBLIC_KEY = var.langfuse_public_key, + LANGFUSE_BASE_URL = var.langfuse_base_url + } } } From 76a5d357fa21a31678d75cfa32bbf00a0c202ec8 Mon Sep 17 00:00:00 2001 From: Lakindu Induwara Kumara Date: Thu, 9 Jul 2026 15:55:21 +0530 Subject: [PATCH 89/90] removed unwanted websocket mode parts --- .../aws/containerized/akoutputconsumer.py | 71 +++++-------------- .../aws/containerized/ecs_io_handler.py | 2 +- 2 files changed, 18 insertions(+), 55 deletions(-) diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/akoutputconsumer.py b/ak-py/src/agentkernel/deployment/aws/containerized/akoutputconsumer.py index 1c9ac3708..3d0e9ee02 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/akoutputconsumer.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/akoutputconsumer.py @@ -5,17 +5,15 @@ from typing import Any, Dict from ....core.config import AKConfig -from ....core.model import ExecutionMode from ..core.response_store import ResponseDBHandler from ..core.sqs_handler import SQSHandler -from ..core.websocket_service import WebSocketHandler from .core import ECSSQSConsumer class ECSOutputConsumer(ECSSQSConsumer): """ ECS Output Consumer — polls the Output Queue and writes results to the - DynamoDB Response Store or broadcasts via WebSocket. + DynamoDB Response Store. Extends ECSSQSConsumer so it inherits the blocking SQS poll loop via run(). Started as Thread 2 by ECSIOHandler. @@ -27,7 +25,6 @@ class ECSOutputConsumer(ECSSQSConsumer): num_consumers = _config.execution.queues.output.no_of_consumers _response_store = None - _websocket_handler = None @classmethod def get_queue_url(cls) -> str: @@ -39,53 +36,31 @@ def _get_response_store(cls): cls._response_store = ResponseDBHandler().get_store() return cls._response_store - @classmethod - def _get_websocket_handler(cls) -> WebSocketHandler: - if cls._websocket_handler is None: - ws_config = cls._config.websocket_api - if not ws_config.connection_table or not ws_config.connection_table.table_name: - raise ValueError("websocket_api.connection_table.table_name is required " "for ECSOutputConsumer in WebSocket mode") - cls._websocket_handler = WebSocketHandler( - conn_table_name=ws_config.connection_table.table_name, - ttl=ws_config.connection_table.ttl, - ) - return cls._websocket_handler - @classmethod def process_message(cls, record: Dict[str, Any]) -> None: """ - Process one message from the Output Queue. - - Dispatches based on ``execution.mode``: - - - ``async`` → push via WebSocket (PostToConnection) - - ``rest_sync`` / ``rest_async`` → write to DynamoDB Response Store + Process one message from the Output Queue by writing it to the + DynamoDB Response Store. :param record: boto3 SQS ``receive_message`` record """ message_id = record.get("MessageId") cls._log.info(f"[OUTPUT START] Processing output message {message_id}") - if cls._config.execution.mode == ExecutionMode.ASYNC: - cls._broadcast_via_websocket(record) - else: - message = cls._construct_message_for_store(record) - cls._log.info( - f"[OUTPUT STORE] Writing to DynamoDB — request_id={message['request_id']}, " - f"session_id={message['session_id']}, body_keys={list(message.get('body', {}).keys()) if isinstance(message.get('body'), dict) else 'N/A'}" - ) - cls._get_response_store().add_message(message) - cls._log.info(f"[OUTPUT DONE] Stored response — session_id={message['session_id']} " f"request_id={message['request_id']}") + message = cls._construct_message_for_store(record) + cls._log.info( + f"[OUTPUT STORE] Writing to DynamoDB — request_id={message['request_id']}, " + f"session_id={message['session_id']}, body_keys={list(message.get('body', {}).keys()) if isinstance(message.get('body'), dict) else 'N/A'}" + ) + cls._get_response_store().add_message(message) + cls._log.info(f"[OUTPUT DONE] Stored response — session_id={message['session_id']} " f"request_id={message['request_id']}") @classmethod def on_permanent_failure(cls, record: Dict[str, Any]) -> None: """ - Handle an output message that exceeded ``max_receive_count``. - - - ``async`` mode → broadcast error via WebSocket - - Other modes → write error entry to Response Store so the - waiting HTTP caller gets a response instead of - hanging indefinitely + Handle an output message that exceeded ``max_receive_count`` by + writing an error entry to the Response Store so the waiting HTTP + caller gets a response instead of hanging indefinitely. :param record: boto3 SQS ``receive_message`` record """ @@ -100,22 +75,10 @@ def on_permanent_failure(cls, record: Dict[str, Any]) -> None: "request_id": request_id, } - if cls._config.execution.mode == ExecutionMode.ASYNC: - endpoint_url = message_attributes.get("endpoint_url") - user_id = message_attributes.get("user_id") - if endpoint_url and user_id: - cls._get_websocket_handler().broadcast( - endpoint_url=endpoint_url, - message=error_payload, - user_id=user_id, - ) - else: - cls._log.warning("Cannot broadcast permanent-failure error: " "endpoint_url or user_id missing") - else: - error_body = json.dumps(error_payload) - message = cls._construct_message_for_store(record, body=error_body) - cls._get_response_store().add_message(message) - cls._log.info(f"Stored permanent-failure error — " f"session_id={message['session_id']} " f"request_id={message['request_id']}") + error_body = json.dumps(error_payload) + message = cls._construct_message_for_store(record, body=error_body) + cls._get_response_store().add_message(message) + cls._log.info(f"Stored permanent-failure error — " f"session_id={message['session_id']} " f"request_id={message['request_id']}") except Exception: cls._log.exception("Failed to handle permanent-failure output message") diff --git a/ak-py/src/agentkernel/deployment/aws/containerized/ecs_io_handler.py b/ak-py/src/agentkernel/deployment/aws/containerized/ecs_io_handler.py index b62aef6c9..61fd8ad2d 100644 --- a/ak-py/src/agentkernel/deployment/aws/containerized/ecs_io_handler.py +++ b/ak-py/src/agentkernel/deployment/aws/containerized/ecs_io_handler.py @@ -13,7 +13,7 @@ class ECSIOHandler: Thread 1 (rest-api): RESTAPI.run — FastAPI/uvicorn, ECSQueueRequestHandler registered. Thread 2 (output-queue-consumer): ECSOutputConsumer.run — polls the - Output Queue, writes to DB / WebSocket. + Output Queue, writes to DB. Usage:: From 06d656da8b29b765d2391a20e737da38bcdbd860 Mon Sep 17 00:00:00 2001 From: Lakindu Induwara Kumara Date: Thu, 9 Jul 2026 18:35:27 +0530 Subject: [PATCH 90/90] bumped version of terraform --- ak-deployment/ak-aws/containerized/modules/queues/main.tf | 4 ++-- ak-deployment/ak-aws/containerized/state.tf | 2 +- .../aws-containerized/openai-dynamodb-scalable/deploy/main.tf | 2 +- use-cases/waste-sorting-assistant/deploy/main.tf | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ak-deployment/ak-aws/containerized/modules/queues/main.tf b/ak-deployment/ak-aws/containerized/modules/queues/main.tf index ecf45480c..7deab0274 100644 --- a/ak-deployment/ak-aws/containerized/modules/queues/main.tf +++ b/ak-deployment/ak-aws/containerized/modules/queues/main.tf @@ -4,7 +4,7 @@ data "aws_region" "current" {} module "input_queue" { source = "yaalalabs/ak-common/aws//modules/sqs" - version = "0.6.0" + version = "0.6.1" product_alias = var.product_alias env_alias = var.env_alias @@ -40,7 +40,7 @@ module "input_queue" { module "output_queue" { source = "yaalalabs/ak-common/aws//modules/sqs" - version = "0.6.0" + version = "0.6.1" product_alias = var.product_alias env_alias = var.env_alias diff --git a/ak-deployment/ak-aws/containerized/state.tf b/ak-deployment/ak-aws/containerized/state.tf index b503657ce..d31b2acb1 100644 --- a/ak-deployment/ak-aws/containerized/state.tf +++ b/ak-deployment/ak-aws/containerized/state.tf @@ -89,7 +89,7 @@ module "docker_image" { module "agent_runner_docker_image" { count = var.queue_mode && var.agent_runner.package_path != null ? 1 : 0 source = "yaalalabs/ak-common/aws//modules/ecr" - version = "0.6.0" + version = "0.6.1" env_alias = var.env_alias module_name = "${var.module_name}-runner" product_alias = var.product_alias diff --git a/examples/aws-containerized/openai-dynamodb-scalable/deploy/main.tf b/examples/aws-containerized/openai-dynamodb-scalable/deploy/main.tf index 298165440..26bcd4212 100644 --- a/examples/aws-containerized/openai-dynamodb-scalable/deploy/main.tf +++ b/examples/aws-containerized/openai-dynamodb-scalable/deploy/main.tf @@ -4,7 +4,7 @@ module "containerized_agents" { # When using from registry: source = "yaalalabs/ak-containerized/aws" - version = "0.6.0" + version = "0.6.1" product_alias = var.product_alias env_alias = var.env_alias diff --git a/use-cases/waste-sorting-assistant/deploy/main.tf b/use-cases/waste-sorting-assistant/deploy/main.tf index cbb700a99..2ddc5ac42 100644 --- a/use-cases/waste-sorting-assistant/deploy/main.tf +++ b/use-cases/waste-sorting-assistant/deploy/main.tf @@ -4,7 +4,7 @@ locals { module "serverless_agents" { source = "yaalalabs/ak-serverless/aws" - version = "0.6.0" + version = "0.6.1" product_alias = var.product_alias env_alias = var.env_alias