diff --git a/.devcontainer/03-azure-01-04-ai-08-predictive-maintenance/Dockerfile b/.devcontainer/03-azure-01-04-ai-08-predictive-maintenance/Dockerfile new file mode 100644 index 000000000..d80709b7c --- /dev/null +++ b/.devcontainer/03-azure-01-04-ai-08-predictive-maintenance/Dockerfile @@ -0,0 +1,6 @@ +FROM mcr.microsoft.com/devcontainers/python:1-3.11-bookworm + +# Remove Yarn apt repo entirely to avoid expired GPG key errors during feature installs +USER root +RUN rm -f /etc/apt/sources.list.d/yarn.list \ + && rm -f /usr/share/keyrings/yarn.gpg diff --git a/.devcontainer/03-azure-01-04-ai-08-predictive-maintenance/devcontainer.json b/.devcontainer/03-azure-01-04-ai-08-predictive-maintenance/devcontainer.json new file mode 100644 index 000000000..be6fad617 --- /dev/null +++ b/.devcontainer/03-azure-01-04-ai-08-predictive-maintenance/devcontainer.json @@ -0,0 +1,65 @@ +{ + "name": "Azure / AI / Predictive Maintenance", + "build": { + "dockerfile": "Dockerfile" + }, + "forwardPorts": [ + 5231 + ], + "portsAttributes": { + "5231": { + "label": "workflow-api-http", + "visibility": "public", + "protocol": "http" + } + }, + "features": { + "ghcr.io/devcontainers/features/azure-cli:1": { + "installBicep": true + }, + "ghcr.io/devcontainer-community/devcontainer-features/astral.sh-uv:1": {}, + "ghcr.io/devcontainers/features/docker-in-docker:2": { + "version": "latest", + "enableNonRootDocker": "true" + }, + "ghcr.io/devcontainers/features/node:1": { + "version": "lts" + }, + "ghcr.io/devcontainers/features/dotnet:2": { + "version": "10.0", + "installUsingApt": true + } + }, + "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}/03-Azure/01-04-AI/08_Predictive_Maintenance", + "postAttachCommand": "bash /workspaces/${localWorkspaceFolderBasename}/.devcontainer/welcome.sh", + "remoteUser": "vscode", + "postCreateCommand": "uv sync --frozen && bash /workspaces/${localWorkspaceFolderBasename}/.devcontainer/03-azure-01-04-ai-08-predictive-maintenance/setup-dotnet.sh", + "customizations": { + "vscode": { + "extensions": [ + "github.copilot-chat", + "github.github-vscode-theme", + "kejun.markdown-alert", + "ms-python.debugpy", + "ms-python.python", + "ms-python.vscode-pylance", + "ms-dotnettools.csdevkit", + "ms-dotnettools.csharp", + "ms-dotnettools.vscode-dotnet-runtime", + "ms-toolsai.jupyter", + "ms-toolsai.jupyter-renderers", + "ms-azuretools.vscode-bicep", + "ms-windows-ai-studio.windows-ai-studio", + "humao.rest-client" + + ], + "settings": { + "workbench.colorTheme": "GitHub Dark Default", + "git.openRepositoryInParentFolders": "always", + "workbench.editorAssociations": { + "*.md": "vscode.markdown.preview.editor" + } + } + } + } +} diff --git a/.devcontainer/03-azure-01-04-ai-08-predictive-maintenance/setup-dotnet.sh b/.devcontainer/03-azure-01-04-ai-08-predictive-maintenance/setup-dotnet.sh new file mode 100644 index 000000000..9aab2b3e4 --- /dev/null +++ b/.devcontainer/03-azure-01-04-ai-08-predictive-maintenance/setup-dotnet.sh @@ -0,0 +1,12 @@ +#!/bin/bash +set -e + +echo "πŸ”§ Setting up .NET environment for Challenge 3 and 5..." + +# Install Aspire +echo "πŸ“¦ Installing .NET Aspire..." +curl -fsSL https://aspire.dev/install.sh | bash -s + +echo "βœ… .NET environment setup complete!" +echo "πŸ“Œ .NET SDK $(dotnet --version) is ready" +echo "πŸ“¦ All required NuGet packages are cached and ready to use" diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/.github/agents/agentplanning.agent.md b/03-Azure/01-04-AI/08_Predictive_Maintenance/.github/agents/agentplanning.agent.md new file mode 100644 index 000000000..edd214f53 --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/.github/agents/agentplanning.agent.md @@ -0,0 +1,305 @@ +--- +description: 'Expert agent for planning and developing intelligent maintenance agents using .NET, Microsoft Foundry Agents SDK, and multi-agent patterns.' +tools: ['runCommands', 'runTasks', 'edit', 'search', 'new', 'runSubagent', 'usages', 'problems'] +--- + +You are an expert AI agent specializing in building intelligent maintenance and repair planning systems using .NET, Microsoft Foundry Agents SDK, and multi-agent architectures. You help developers create production-ready agents for industrial IoT and predictive maintenance scenarios. + +## Your Expertise + +### Core Competencies +- **Multi-Agent Systems**: Design and implementation of coordinated agent workflows (Anomaly Detection β†’ Fault Diagnosis β†’ Repair Planning β†’ Scheduling) +- **.NET Development**: Modern C# patterns including primary constructors, async/await, dependency injection +- **Microsoft Foundry Agents SDK**: Creating and invoking Prompt Agents via `Azure.AI.Projects` and `Microsoft.Agents.AI` +- **Azure Cosmos DB**: NoSQL design patterns, efficient queries, partitioning strategies +- **Industrial IoT**: Predictive maintenance, threshold-based alerting, telemetry processing + +## Small-Model Robustness (CRITICAL) + +**IMPORTANT**: This agent may be used with smaller models (e.g., GPT-5-mini). Follow these constraints: + +1. **Produce complete files** - Generate 1-3 complete files per response, not partial snippets +2. **Keep it simple** - No extra abstractions, no extra models beyond what's specified +3. **Compile-first** - Always ensure code compiles before adding features +4. **Use pinned versions** - Always use the exact package versions listed below +5. **Use hardcoded mappings** - Implement faultβ†’skills/parts as in-memory dictionaries (see mappings section) + +## Required NuGet Package Versions + +Always use these exact versions: + +```xml + + + + + + + + + + + +``` + +**Target Framework**: `net10.0` + +Add this to suppress preview API warnings: +```xml +$(NoWarn);CA2252 +``` + +## Environment Variables + +Use these exact names: +- `AZURE_AI_PROJECT_ENDPOINT` - Azure AI Foundry project endpoint +- `MODEL_DEPLOYMENT_NAME` - Model deployment name (e.g., "gpt-4o") +- `COSMOS_ENDPOINT` - Cosmos DB endpoint +- `COSMOS_KEY` - Cosmos DB key +- `COSMOS_DATABASE_NAME` - Cosmos DB database name + +## Foundry Agents SDK Pattern (REQUIRED) + +**CRITICAL**: Use the Foundry Agents SDK pattern, NOT direct ChatCompletions. This is the correct pattern: + +```csharp +using Azure.AI.Projects; +using Azure.AI.Projects.OpenAI; +using Microsoft.Agents.AI; + +// 1. Create AIProjectClient (uses DefaultAzureCredential) +var projectClient = new AIProjectClient(new Uri(endpoint), new DefaultAzureCredential()); + +// 2. Define and register the agent +var definition = new PromptAgentDefinition(model: modelDeploymentName) +{ + Instructions = "Your system prompt here..." +}; +await projectClient.Agents.CreateAgentVersionAsync("AgentName", new AgentVersionCreationOptions(definition)); + +// 3. Get and invoke the agent +var agent = projectClient.GetAIAgent(name: "AgentName"); +var response = await agent.RunAsync(userPrompt, thread: null, options: null); +string result = response.Text ?? ""; +``` + +## Project Structure + +Generate this exact structure: + +``` +RepairPlannerAgent/ +β”œβ”€β”€ RepairPlannerAgent.csproj +β”œβ”€β”€ Program.cs +β”œβ”€β”€ RepairPlannerAgent.cs +β”œβ”€β”€ Models/ +β”‚ β”œβ”€β”€ DiagnosedFault.cs +β”‚ β”œβ”€β”€ Technician.cs +β”‚ β”œβ”€β”€ Part.cs +β”‚ β”œβ”€β”€ WorkOrder.cs +β”‚ β”œβ”€β”€ RepairTask.cs +β”‚ └── WorkOrderPartUsage.cs +└── Services/ + β”œβ”€β”€ CosmosDbService.cs + β”œβ”€β”€ CosmosDbOptions.cs + └── FaultMappingService.cs +``` + +## Key Components + +### 1. RepairPlannerAgent.cs (Main Agent Class) + +Use primary constructor pattern: + +```csharp +public sealed class RepairPlannerAgent( + AIProjectClient projectClient, + CosmosDbService cosmosDb, + IFaultMappingService faultMapping, + string modelDeploymentName, + ILogger logger) +{ + private const string AgentName = "RepairPlannerAgent"; + private const string AgentInstructions = """ + You are a Repair Planner Agent for tire manufacturing equipment. + Generate a repair plan with tasks, timeline, and resource allocation. + Return the response as valid JSON matching the WorkOrder schema. + + Output JSON with these fields: + - workOrderNumber, machineId, title, description + - type: "corrective" | "preventive" | "emergency" + - priority: "critical" | "high" | "medium" | "low" + - status, assignedTo (technician id or null), notes + - estimatedDuration: integer (minutes, e.g. 60 not "60 minutes") + - partsUsed: [{ partId, partNumber, quantity }] + - tasks: [{ sequence, title, description, estimatedDurationMinutes (integer), requiredSkills, safetyNotes }] + + IMPORTANT: All duration fields must be integers representing minutes (e.g. 90), not strings. + + Rules: + - Assign the most qualified available technician + - Include only relevant parts; empty array if none needed + - Tasks must be ordered and actionable + """; + + public async Task EnsureAgentVersionAsync(CancellationToken ct = default) + { + var definition = new PromptAgentDefinition(model: modelDeploymentName) { Instructions = AgentInstructions }; + await projectClient.Agents.CreateAgentVersionAsync(AgentName, new AgentVersionCreationOptions(definition), ct); + } + + public async Task PlanAndCreateWorkOrderAsync(DiagnosedFault fault, CancellationToken ct = default) + { + // 1. Get required skills and parts from mapping + // 2. Query technicians and parts from Cosmos DB + // 3. Build prompt and invoke agent + // 4. Parse response and apply defaults + // 5. Save to Cosmos DB + } +} +``` + +### 2. FaultMappingService.cs (Hardcoded Mappings) + +Implement as in-memory dictionaries: + +```csharp +public interface IFaultMappingService +{ + IReadOnlyList GetRequiredSkills(string faultType); + IReadOnlyList GetRequiredParts(string faultType); +} + +public sealed class FaultMappingService : IFaultMappingService +{ + private static readonly IReadOnlyDictionary> FaultToSkills = + new Dictionary>(StringComparer.OrdinalIgnoreCase) + { + // Use mappings from section below + }; + + private static readonly IReadOnlyDictionary> FaultToParts = + new Dictionary>(StringComparer.OrdinalIgnoreCase) + { + // Use mappings from section below + }; +} +``` + +### 3. Models with Dual JSON Attributes + +Use both `System.Text.Json` and `Newtonsoft.Json` attributes for Cosmos DB compatibility: + +```csharp +using System.Text.Json.Serialization; +using Newtonsoft.Json; + +public sealed class WorkOrder +{ + [JsonPropertyName("id")] + [JsonProperty("id")] + public string Id { get; set; } = string.Empty; + + // ... other properties with both attributes +} +``` + +### 4. JSON Parsing with Number Handling + +LLMs sometimes return numbers as strings. Handle this: + +```csharp +private static readonly JsonSerializerOptions JsonOptions = new() +{ + PropertyNameCaseInsensitive = true, + NumberHandling = JsonNumberHandling.AllowReadingFromString, +}; +``` + +## Fault β†’ Skills/Parts Mappings (CANONICAL SOURCE) + +Use these exact mappings in `FaultMappingService`: + +**Fault β†’ Required Skills:** +- `curing_temperature_excessive` β†’ `tire_curing_press`, `temperature_control`, `instrumentation`, `electrical_systems`, `plc_troubleshooting`, `mold_maintenance` +- `curing_cycle_time_deviation` β†’ `tire_curing_press`, `plc_troubleshooting`, `mold_maintenance`, `bladder_replacement`, `hydraulic_systems`, `instrumentation` +- `building_drum_vibration` β†’ `tire_building_machine`, `vibration_analysis`, `bearing_replacement`, `alignment`, `precision_alignment`, `drum_balancing`, `mechanical_systems` +- `ply_tension_excessive` β†’ `tire_building_machine`, `tension_control`, `servo_systems`, `precision_alignment`, `sensor_alignment`, `plc_programming` +- `extruder_barrel_overheating` β†’ `tire_extruder`, `temperature_control`, `rubber_processing`, `screw_maintenance`, `instrumentation`, `electrical_systems`, `motor_drives` +- `low_material_throughput` β†’ `tire_extruder`, `rubber_processing`, `screw_maintenance`, `motor_drives`, `temperature_control` +- `high_radial_force_variation` β†’ `tire_uniformity_machine`, `data_analysis`, `measurement_systems`, `tire_building_machine`, `tire_curing_press` +- `load_cell_drift` β†’ `tire_uniformity_machine`, `load_cell_calibration`, `measurement_systems`, `sensor_alignment`, `instrumentation` +- `mixing_temperature_excessive` β†’ `banbury_mixer`, `temperature_control`, `rubber_processing`, `instrumentation`, `electrical_systems`, `mechanical_systems` +- `excessive_mixer_vibration` β†’ `banbury_mixer`, `vibration_analysis`, `bearing_replacement`, `alignment`, `mechanical_systems`, `preventive_maintenance` + +**Fault β†’ Required Parts:** +- `curing_temperature_excessive` β†’ `TCP-HTR-4KW`, `GEN-TS-K400` +- `curing_cycle_time_deviation` β†’ `TCP-BLD-800`, `TCP-SEAL-200` +- `building_drum_vibration` β†’ `TBM-BRG-6220` +- `ply_tension_excessive` β†’ `TBM-LS-500N`, `TBM-SRV-5KW` +- `extruder_barrel_overheating` β†’ `EXT-HTR-BAND`, `GEN-TS-K400` +- `low_material_throughput` β†’ `EXT-SCR-250`, `EXT-DIE-TR` +- `high_radial_force_variation` β†’ (empty array) +- `load_cell_drift` β†’ `TUM-LC-2KN`, `TUM-ENC-5000` +- `mixing_temperature_excessive` β†’ `BMX-TIP-500`, `GEN-TS-K400` +- `excessive_mixer_vibration` β†’ `BMX-BRG-22320`, `BMX-SEAL-DP` + +**Default for unknown faults:** Return `["general_maintenance"]` for skills, empty array for parts. + +## Cosmos DB Structure + +Containers (partition keys): +- `Technicians` (partition key: `department`) +- `PartsInventory` (partition key: `category`) +- `WorkOrders` (partition key: `status`) + +## Code Style for Python Developers + +Add brief comments explaining C#-specific idioms: + +```csharp +// ??= means "assign if null" (like Python's: x = x or default_value) +wo.Priority ??= "medium"; + +// ?? means "if null, use this instead" (like Python's "or") +var name = technician.Name ?? "Unknown"; + +// Primary constructor - parameters become fields (like Python's __init__) +public sealed class MyClass(string name, ILogger logger) { } + +// await using - like Python's "async with" +await using var provider = services.BuildServiceProvider(); +``` + +## Response Pattern + +When generating code, always: + +1. **State what you'll create** - List the files +2. **Generate complete files** - No partial snippets +3. **Include all imports** - Don't assume implicit usings cover everything +4. **Show how to run** - Include the dotnet commands + +Example response structure: +``` +I'll create the following files: +1. Models/WorkOrder.cs - Work order data model +2. Services/FaultMappingService.cs - Skill/parts mappings + +[Complete file contents...] + +To build and run: +```bash +dotnet build +dotnet run +``` +``` + +## What NOT to Do + +- ❌ Don't use `Azure.AI.Inference` / `ChatCompletionsClient` - use Foundry Agents SDK +- ❌ Don't generate partial code snippets - generate complete files +- ❌ Don't add extra abstractions not in the spec +- ❌ Don't use different package versions than specified +- ❌ Don't skip error handling for JSON parsing +- ❌ Don't forget `NumberHandling.AllowReadingFromString` for LLM responses diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/.github/copilot-instructions.md b/03-Azure/01-04-AI/08_Predictive_Maintenance/.github/copilot-instructions.md new file mode 100644 index 000000000..9dfcbf129 --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/.github/copilot-instructions.md @@ -0,0 +1,72 @@ +# Copilot custom instructions (workshop mode) + +This repo is used for a workshop/exercise. + +## Key goal +Help the developer build the solution from context and instructions, **not** by copying an existing completed implementation. + +## Source of truth +- For Challenge 3 (Repair Planner), follow the agent guidance in `.github/agents/agentplanning.agent.md`. +- Use the **Foundry Agents SDK** pattern (`Azure.AI.Projects` + `Microsoft.Agents.AI`), not direct ChatCompletions. + +## Do not use solution code as a template +Unless the user explicitly asks to reveal or compare with a solution, **do not open, reference, or reuse code** from any folder intended to contain completed answers, for example: +- `walkthrough/**/RepairPlanner/**` +- `walkthrough/**` +- `**/*solution*/**` +- `**/*Solution*/**` +- `**/reference/**` + +If such folders exist in the workspace, treat them as off-limits. + +## How to proceed instead +- Ask clarifying questions only when required. +- Implement incrementally in the exercise project. +- Prefer small, verifiable changes; keep logging helpful. + +## Smaller-model friendly (CRITICAL) + +This workshop must work reliably with smaller models (e.g., GPT-5-mini). Follow these constraints: + +### Output constraints +- Prefer producing a small number of **complete files** per request (1-3) over partial snippets +- Keep responses structured: **What you will change**, **Files**, **How to run/verify** +- Avoid optional extras unless explicitly requested (no extra abstractions, no extra models) +- When uncertain, state assumptions explicitly and proceed with a safe default + +### Pinned versions (Challenge 3) +Always use these exact NuGet package versions: +- `Azure.AI.Projects` β†’ `2.1.0-beta.4` +- `Azure.Identity` β†’ `1.21.0` +- `Microsoft.Agents.AI` β†’ `1.20.0` +- `Microsoft.Agents.AI.Foundry` β†’ `1.20.0-preview.260831.1` +- `Microsoft.Extensions.AI` β†’ `10.9.0` +- `Microsoft.Extensions.AI.Abstractions` β†’ `10.9.0` +- `Microsoft.Azure.Cosmos` β†’ `3.63.0` +- `Microsoft.Extensions.DependencyInjection` β†’ `10.0.12` +- `Microsoft.Extensions.Logging` β†’ `10.0.12` +- `Microsoft.Extensions.Logging.Console` β†’ `10.0.12` +- `Newtonsoft.Json` β†’ `13.0.4` + +### Environment variables (Challenge 3) +- `AZURE_AI_PROJECT_ENDPOINT` +- `MODEL_DEPLOYMENT_NAME` +- `COSMOS_ENDPOINT` +- `COSMOS_KEY` +- `COSMOS_DATABASE_NAME` + +### Faultβ†’skills/parts mappings +Use the hardcoded mappings from `.github/agents/agentplanning.agent.md` - implement as in-memory dictionaries in `FaultMappingService`. + +## Code style for Python developers + +Add brief comments explaining C#-specific idioms: +- `??` and `??=` operators (null coalescing) +- Primary constructors +- `await using` (like Python's `async with`) + +## What NOT to do +- ❌ Don't use `Azure.AI.Inference` / `ChatCompletionsClient` - use Foundry Agents SDK +- ❌ Don't generate partial code snippets - generate complete files +- ❌ Don't add extra abstractions not in the spec +- ❌ Don't use different package versions than specified diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/README.md b/03-Azure/01-04-AI/08_Predictive_Maintenance/README.md new file mode 100644 index 000000000..a85211870 --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/README.md @@ -0,0 +1,307 @@ +# Agentic AI Hacks | Predictive Maintenance + +## Introduction + +Manufacturing teams need to turn raw machine telemetry into fast, consistent +maintenance decisions. Manual triage is slow, root-cause analysis depends on +scattered historical knowledge, and coordinating parts, technicians, and +schedules across systems is hard to keep consistent. + +This microhack builds an **agentic predictive maintenance workflow** for a +fictitious tire manufacturing company. You will implement five focused agents +that mirror a real maintenance process β€” from detecting an anomaly to +diagnosing its cause, planning the repair, scheduling a technician, and +ordering parts β€” using **Microsoft Agent Framework**, **Microsoft Foundry**, +and **Azure**. + +During this microhack, you will build individual agent applications (mostly in +Python, with one in .NET), connect them to shared factory data, and finally +orchestrate them into a single observable workflow hosted in **.NET Aspire**. + +--- + +## Scenario + +This microhack uses a fictitious tire manufacturing company as an example, but +the scenario is applicable to most manufacturing environments where machines +are involved in production. + +The image below illustrates the conceptual scenario. + +![Manufacturing scenario](./challenges/images/maintenance-scenario.png) + +❢ The factory operates a production line made up of multiple specialized +machines, each responsible for a distinct step in tire manufacturing (mixing, +extrusion, curing, inspection). + +❷ A key business metric is **Overall Equipment Effectiveness (OEE)** β€” how +effectively equipment is utilized during planned production time. Unplanned +downtime and quality losses directly reduce throughput and profitability. + +❸ Maintenance technicians perform both *planned* (time/usage-based) +maintenance and *condition-based* maintenance triggered by sensor readings and +detected anomalies. Because machines are complex, tasks require specific +skills, certifications, and safety procedures. + +❹ Spare parts are held in a local warehouse with limited stock. When +inventory falls below thresholds or parts are needed for a repair, +replenishment is coordinated with external suppliers, often with lead times +and ordering constraints. + +❺ The production environment integrates with multiple core systems (WMS, ERP, +CMMS, MES, QMS, knowledge bases). These systems coordinate production, +maintenance execution, inventory, quality, and scheduling β€” making end-to-end +orchestration essential. + +The maintenance process is complex and requires coordination across people, +parts, and systems. Common challenges include: + +* Detecting when a machine needs attention, who should be notified, and how + to prioritize the response. +* Diagnosing the root cause of irregular machine behavior using telemetry + plus shared knowledge and historical patterns. +* Determining which parts are required, whether they are in local stock, and + how to handle supplier lead times. +* Matching work to technicians with the right skills, certifications, and + safety requirements. +* Scheduling technicians based on availability, shifts, and constraints + (access windows, downtime limits). +* Finding the optimal maintenance window that minimizes production + disruption while keeping risk acceptable. + +Agents can help by coordinating data, decisions, and handoffs across these +steps. The intent is not to replace technicians or planners, but to reduce +time-to-triage and make the workflow more consistent and observable. + +The image below shows the agent roles used in this microhack and how they +interact with telemetry and existing factory systems. + +![Agentic factory orchestration](./challenges/images/agentic-maintenance-orchestration.png) + +❢ Telemetry is emitted from machine sensors and evaluated for deviations +(simple thresholds or an anomaly detection model). + +❷ The **Anomaly Classification Agent** classifies the situation +(normal/warning/critical), enriches it with machine context, and raises an +alert with an appropriate priority. + +❸ The **Fault Diagnosis Agent** proposes likely root causes using telemetry +plus documented historical knowledge (runbooks, prior work orders, technician +notes). + +❹ The **Repair Planner Agent** translates a diagnosis into an actionable +repair plan β€” required parts, estimated effort, and required +skills/certifications β€” and drafts a work order for review. + +❺ The **Maintenance Scheduler Agent** proposes a maintenance window based on +priority/risk, technician availability, and production constraints. + +❻ The **Parts Ordering Agent** checks inventory and suggests replenishment +orders based on supplier lead times and constraints. + +The factory process spans many systems, teams, and areas of responsibility. +This microhack embraces that reality: agents stay independent, but are +designed to collaborate through well-defined inputs/outputs and shared +observability. + +--- + +## Architecture + +You will build multiple agent applications (Python and .NET) and connect them +using a sequential workflow in the **Microsoft Agent Framework**. The workflow +itself is described in the scenario section above; the image below shows the +supporting platform components. + +![Factory Hackathon Architecture](./challenges/images/hackathon-architecture.png) + +❢ The documentation, starter code, and sample assets are available in this +repository, opened directly from your assigned lab account. + +❷ **GitHub Codespaces** provides a containerized development environment with +the required tools and extensions pre-installed, so everyone starts from a +known baseline. + +❸ You will build individual agent applications (mostly in Python, with one in +.NET) and eventually run the end-to-end workflow locally using **Aspire**, +which also gives you a single place to start the system and inspect service +logs. + +❹ **GitHub Copilot** is used to co-develop parts of the solution β€” one +challenge uses it to build an agent from scratch β€” and you can use it +throughout the microhack for implementation, refactoring, and troubleshooting. + +❺ Agents are registered in **Microsoft Foundry** and configured with the +selected model deployments plus the tools and data connections they need. + +❻ **API Management** acts as an AI gateway for selected endpoints. When +needed, agents access those APIs via **MCP**, which provides a consistent +interface for tool calling. + +❼ **Foundry IQ / knowledge sources** provide content for retrieval-augmented +generation (RAG), such as machine documentation, troubleshooting guides, and +historical notes. + +❽ Factory data (machines, thresholds, inventory, work orders) is stored in +**Cosmos DB** and **Blob Storage** and used by agents during analysis and +planning. + +❾ **Application Insights** (and related logs/traces) provides observability +across the workflow so you can troubleshoot agent behavior, tool calls, and +end-to-end requests. + +> [!NOTE] +> Manufacturing environments are complex: legacy systems, safety +> requirements, strict uptime targets, and processes that exist for good +> reasons β€” often split across distributed teams and both IT and OT domains. +> +> This microhack uses a deliberately simplified lab setup so you can focus on +> the learning objectives and the core patterns of agentic development +> (workflow design, tool integration, and observability), rather than the +> full complexity of factory networking, governance, and infrastructure. + +### The five agents + +| Agent | Role | Data source or control | Introduced in | +|---------------------------------|-----------------------------------------------------------------------|------------------------------------------------|---------------| +| Anomaly Classification Agent | Classifies telemetry deviations and raises prioritized alerts | Machine thresholds and telemetry | Challenge 2 | +| Fault Diagnosis Agent | Proposes likely root causes | Knowledge base and maintenance history | Challenge 2 | +| Repair Planner Agent | Drafts a repair plan and work order | Parts inventory and technician skills | Challenge 3 | +| Maintenance Scheduler Agent | Proposes a maintenance window | Technician availability and production windows | Challenge 4 | +| Parts Ordering Agent | Checks inventory and suggests replenishment | Parts inventory and suppliers | Challenge 4 | + +--- + +## Learning objectives + +By participating in this microhack, you will learn how to: + +* Implement five focused agents and connect them to data and tools +* Use GitHub Copilot, including a custom `agentplanning` coding agent, to + accelerate agent development while keeping changes reviewable +* Add persistent agent memory (threads) for maintenance scenarios where + history matters +* Instrument agents so you can inspect outputs, tool calls, logs, and traces +* Build an end-to-end agent workflow using the Microsoft Agent Framework and + sequential orchestration hosted in **Aspire**, with agent-to-agent (A2A) + communication + +--- + +## Requirements + +To complete the microhack, you will need: + +* A [GitHub account](https://github.com/signup) and access to the repository +* Familiarity with Python or .NET, including handling JSON data and making API calls +* Familiarity with generative AI solutions and Azure services +* Python 3.11+ and [uv](https://docs.astral.sh/uv/), plus .NET 10 (provided in + the devcontainer) +* Azure resources and environment values supplied by the MicroHack platform or + your event coach + +### Participant setup + +Complete all environment preparation in +[Challenge 1: Environment Setup and Data Foundation](./challenges/challenge-01.md). +It covers the Codespaces setup, dependency installation, Azure authentication, +and validation required before beginning the build challenges. + +--- + +## Repository structure + +```text +08_Predictive_Maintenance/ +|-- challenges/ # Challenge instructions (challenge-01 through challenge-05) +|-- src/ # Starter and required agent code, and the end-to-end workflow app +|-- labautomation/ # MicroHack provisioning and Azure deployment template +| `-- data/ # Sample factory data and knowledge base articles +|-- walkthrough/ # Reference solutions and optional supplementary material per challenge +|-- .github/agents/ # Custom GitHub Copilot agent used in Challenge 3 +|-- pyproject.toml # Pinned participant Python environment +`-- README.md +``` + +| Path | Who uses it | Purpose | +|-----------------------------------------|-----------------------------|----------------------------------------------------------------| +| [`challenges/`](./challenges/) | Attendees | Step-by-step instructions for the five challenges | +| [`labautomation/data/`](./labautomation/data/) | Attendees and scripts | Sample factory data used by the agents | +| [`src/`](./src/) | Attendees | Required starter agents and the end-to-end workflow application | +| [`labautomation/`](./labautomation/) | Platform and coaches | Per-lab provisioning and Azure Bicep deployment template | +| [`walkthrough/`](./walkthrough/) | Facilitators and attendees | Reference solutions and optional/supplementary material per challenge | +| [`.github/agents/`](./.github/agents/) | Attendees | Custom Copilot agent (`agentplanning`) used in Challenge 3 | + +--- + +## Challenges + +This microhack is divided into five challenges, each building on the previous +one. You'll start by setting up your environment, then progressively build +individual agents, and finally orchestrate them into a complete workflow. + +### Challenge structure + +Each challenge follows a consistent structure: + +| Section | Description | +|---------|-------------| +| 🎯 **Objective** | Learning goals for the challenge | +| 🧭 **Context and Background** | Scenario explanation, architecture diagrams, and technical concepts | +| βœ… **Tasks** | Step-by-step instructions to complete the challenge | +| πŸš€ **Go Further** | Optional stretch goals if you finish early | +| πŸ› οΈ **Troubleshooting** | Common problems and solutions | +| 🧠 **Conclusion and Reflection** | Recap, key takeaways, and further reading | + +Each challenge also includes a **"Stuck? See the walkthrough"** callout that +links to the matching `walkthrough/challenge-XX/solution-XX.md`, which adds +prerequisites, exact commands, expected results, troubleshooting, and a +validation checklist without repeating the challenge narrative. + +### Challenge list + +| # | Challenge | Description | Duration | +|---|-----------|-------------|----------| +| 1 | [Environment Setup and Data Foundation](./challenges/challenge-01.md) | Deploy Azure resources, configure environment variables, and seed sample factory data | 30 min | +| 2 | [Anomaly Detection and Fault Diagnosis Agents](./challenges/challenge-02.md) | Build an Anomaly Classification Agent and a Fault Diagnosis Agent for tire manufacturing telemetry | 60 min | +| 3 | [Repair Planner Agent and AI-Driven Development](./challenges/challenge-03.md) | Use the custom `agentplanning` Copilot agent to build a Repair Planner Agent in .NET | 60 min | +| 4 | [Maintenance Scheduler and Parts Ordering Agents](./challenges/challenge-04.md) | Build Scheduler and Parts Ordering agents with persistent memory and observability | 60 min | +| 5 | [Multi-Agent Orchestration](./challenges/challenge-05.md) | Compose the five agents into a workflow with Microsoft Agent Framework, hosted in Aspire | 60 min | + +> [!TIP] +> While it's possible to rush through the challenges, we encourage you to pause +> and reflect along the way. Consider how each pattern relates to your own +> context: what business processes in your environment could benefit from +> coordinated AI agents? + +### Solution walkthroughs + +* [Challenge 1 walkthrough](./walkthrough/challenge-01/solution-01.md) +* [Challenge 2 walkthrough](./walkthrough/challenge-02/solution-02.md) +* [Challenge 3 walkthrough](./walkthrough/challenge-03/solution-03.md) +* [Challenge 4 walkthrough](./walkthrough/challenge-04/solution-04.md) +* [Challenge 5 walkthrough](./walkthrough/challenge-05/solution-05.md) + +--- + +## Contributing + +This project welcomes contributions and suggestions. Most contributions require you to +agree to a Contributor License Agreement (CLA) declaring that you have the right to, +and do, grant us the rights to use your contribution. For details, visit the +[Microsoft CLA website](https://cla.opensource.microsoft.com/). + +This project has adopted the +[Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). +For more information, see the +[Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact +[opencode@microsoft.com](mailto:opencode@microsoft.com). + +## Trademarks + +This project may contain trademarks or logos for projects, products, or services. +Authorized use of Microsoft trademarks or logos is subject to the +[Microsoft Trademark and Brand Guidelines](https://www.microsoft.com/legal/intellectualproperty/trademarks/usage/general). +Use of Microsoft trademarks or logos in modified versions of this project must not +cause confusion or imply Microsoft sponsorship. Any use of third-party trademarks or +logos is subject to those third parties' policies. diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/challenge-01.md b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/challenge-01.md new file mode 100644 index 000000000..b775de077 --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/challenge-01.md @@ -0,0 +1,165 @@ +# Challenge 1 - Environment Setup and Data Foundation + +**[Home](../README.md)** | [Next challenge](challenge-02.md) + +> [!TIP] +> Stuck on a step? See the [Challenge 1 walkthrough](../walkthrough/challenge-01/solution-01.md) for exact commands, expected output, and a validation checklist. + +## 🎯 Objective + +Sign in with your assigned lab credentials, verify the pre-provisioned Azure resources and seeded factory data, and start the GitHub Codespaces development environment used throughout the rest of this microhack. + +## 🧭 Context and Background + +Before you can build any agents, the tire factory's data foundation and Azure resources need to be in place. The MicroHack platform provisions and seeds these ahead of time so you can focus on building agents rather than infrastructure. All resources for your lab live in a single Azure resource group, as illustrated below. + +Azure resources used in this microhack + +* A **Foundry Account** and **Foundry Project** with model deployments for `gpt-5.4`, `gpt-5.4-mini`, and `text-embedding-3-large` +* **API Management** with API proxies that read data from Cosmos DB +* **Cosmos DB** with containers for machines, thresholds, telemetry, the knowledge base, parts inventory, technicians, and work orders +* A **Storage account** with knowledge base wiki articles in Markdown +* **Azure AI Search** to query the wiki knowledge base +* **Application Insights** and **Log Analytics** for observability +* **Container Registry** and a **Container Apps Environment** for the agents you deploy in later challenges + +
+Cosmos DB data model (7 containers) + +| Container | Partition key | Purpose | Sample count | +|-----------|--------------|---------|--------------| +| `Machines` | `/type` | Equipment definitions | 5 machines | +| `Thresholds` | `/machineType` | Operating limits | 13 thresholds | +| `Telemetry` | `/machineId` | Sensor readings | 10 samples | +| `KnowledgeBase` | `/machineType` | Troubleshooting articles | 10 articles | +| `PartsInventory` | `/category` | Spare parts | 16 parts | +| `Technicians` | `/department` | Maintenance staff | 6 technicians | +| `WorkOrders` | `/status` | Maintenance history | 5 work orders | + +
+ +
+Seeded telemetry anomalies you will investigate in later challenges + +* πŸ”΄ **Machine 001** (Tire Curing Press A1): Temperature 179.2Β°C β€” exceeds the 178Β°C warning threshold +* πŸ”΄ **Machine 002** (Tire Building Machine B1): Drum vibration 3.2 mm/s β€” exceeds 3.0 mm/s +* πŸ”΄ **Machine 003** (Tire Extruder C1): Throughput 640 kg/h β€” below the 650 kg/h minimum +* πŸ”΄ **Machine 004** (Tire Uniformity Machine D1): Radial force variation 105 N β€” exceeds 100 N +* πŸ”΄ **Machine 005** (Banbury Mixer E1): Multiple warnings across temperature, power, and vibration + +
+ +The remaining challenges use this pre-provisioned environment and a repository hosted on GitHub. You will work from a GitHub Codespace configured with the Python, .NET, and Azure tooling required to build the predictive maintenance agents. + +Complete this setup with the credentials provided in your Hackbox. Use the assigned lab account rather than a personal or work account so that you have access to the correct Azure subscription, resource group, GitHub organization, and repository. + +## βœ… Tasks + +### 1. Sign in to Azure + +Open the [Azure portal](https://portal.azure.com) and sign in with the credentials provided in your Hackbox, choosing **Use another account** when prompted. + +In the Azure portal, select **Resource groups**, open the resource group assigned to you, and confirm that its resources have been deployed successfully. Verify that you can see the Foundry project, the Cosmos DB account, the Storage account, and the API Management instance described in [Context and Background](#-context-and-background). + +### 2. Sign in to GitHub + +Open [GitHub](https://github.com) and sign in with the credentials provided in your Hackbox, choosing **Use another account** and then **Sign in with your identity provider**. + +Open the organization associated with your lab account and select the `MicroHack` repository. Confirm that you can see the repository files and folders, including `03-Azure/01-04-AI/08_Predictive_Maintenance`. + +### 3. Create the development environment + +From the repository page, select **Code**, open the **Codespaces** tab, then use the `...` menu to choose **New with options**. For **Dev container configuration**, select **Azure / AI / Predictive Maintenance**, then select **Create codespace**. + +GitHub opens the Codespace in a new browser tab. Wait for the container setup to finish, then confirm that the repository files are visible in the Explorer and that the integrated terminal opens without errors. + +Open a terminal in the Codespace and verify that the required tools are installed: + +```bash +az --version +dotnet --version +``` + +Install the pinned Python dependencies and activate the environment: + +```bash +uv sync --frozen +source .venv/bin/activate +``` + +Sign in to Azure from the Codespace terminal with the assigned lab account: + +```bash +az login --use-device-code +``` + +### 4. Retrieve environment configuration + +From the repository root, run the setup script with the resource group name provided in your Hackbox: + +```bash +bash labautomation/get-keys.sh --resource-group "" +``` + +The script locates your deployed lab resources and creates a local `.env` file. It retrieves the Foundry project endpoint, Cosmos DB connection details, Storage account connection string, and Azure AI Search key and endpoint required by the remaining challenges. The file is excluded from Git and its secret values are not displayed. + +Make the environment variables available in your shell: + +```bash +export $(grep -v '^#' .env | xargs) +``` + +> [!TIP] +> Re-export the environment variables each time you open a new terminal or resume a stopped Codespace. + +### 5. Verify the seeded factory data + +The lab deployment seeds the Cosmos DB containers and uploads the knowledge base wiki articles automatically β€” you do not need to run a seeding step yourself. Confirm the data is present: + +```bash +az cosmosdb sql container list \ + --account-name "$COSMOS_ACCOUNT_NAME" \ + --resource-group "$RESOURCE_GROUP" \ + --database-name "FactoryOpsDB" \ + --query "[].id" --output tsv +``` + +You should see all seven containers listed in [Context and Background](#-context-and-background). Then confirm the knowledge base wiki articles were uploaded to Blob Storage: + +```bash +az storage blob list \ + --connection-string "$AZURE_STORAGE_CONNECTION_STRING" \ + --container-name machine-wiki \ + --query "length(@)" --output tsv +``` + +The expected result is `5` wiki articles, one per machine type. + +## πŸš€ Go Further + +Review the [solution architecture](../README.md#architecture) and identify which Azure resource each of the five agents will use in the upcoming challenges. + +## πŸ› οΈ Troubleshooting + +* **The Azure resource group is missing:** Confirm that you signed in with the assigned lab account and selected the assigned subscription in the Azure portal. +* **`get-keys.sh` reports an authorization error:** Confirm that the Codespace is signed in with the assigned lab account, and that it can read deployment outputs and list keys for the Foundry, Cosmos DB, Storage, and Azure AI Search resources. +* **A Cosmos DB container is missing or empty, or the `machine-wiki` blob container has fewer than 5 blobs:** See the [Challenge 1 walkthrough](../walkthrough/challenge-01/solution-01.md) for the fallback re-seed command. +* **The GitHub organization or repository is missing:** Sign out of GitHub, then sign in again through **Sign in with your identity provider** using the Hackbox credentials. +* **The Codespace does not finish starting:** Review the creation log for the failing step, then rebuild the container or recreate the Codespace. + +## 🧠 Conclusion and Reflection + +You have verified the Azure resources, confirmed the seeded factory data, and opened the configured development environment. This forms the foundation for the multi-agent predictive maintenance system you will build over the next four challenges. + +> [!IMPORTANT] +> This microhack uses simplified, key-based authentication and public network access for learning purposes. Production systems should implement managed identities, private endpoints, Azure Key Vault for secrets, and RBAC for fine-grained access control. + +If you want to learn more about what you covered in this challenge, check out the links below: + +* [Get started with Azure CLI](https://learn.microsoft.com/cli/azure/get-started-with-azure-cli) +* [Azure Cosmos DB documentation](https://learn.microsoft.com/azure/cosmos-db/) +* [Microsoft Foundry documentation](https://learn.microsoft.com/azure/ai-foundry/) +* [Azure AI Search documentation](https://learn.microsoft.com/azure/search/) +* [Predictive maintenance patterns](https://learn.microsoft.com/azure/architecture/data-guide/scenarios/predictive-maintenance) + +**Next:** [Challenge 2 - Anomaly Detection and Fault Diagnosis Agents](challenge-02.md) diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/challenge-02.md b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/challenge-02.md new file mode 100644 index 000000000..f2c226a87 --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/challenge-02.md @@ -0,0 +1,384 @@ +# Challenge 2 - Anomaly Detection and Fault Diagnosis Agents + +**[Home](../README.md)** | [Previous challenge](challenge-01.md) | [Next challenge](challenge-03.md) + +> [!TIP] +> Stuck on a step? See the [Challenge 2 walkthrough](../walkthrough/challenge-02/solution-02.md) for exact commands, expected output, and a validation checklist. + +## 🎯 Objective + +The goals for this challenge are: + +* Create two Foundry Agents in Python: an **Anomaly Classification Agent** and a **Fault Diagnosis Agent** +* Use MCP servers exposed through API Management for remote tool invocation +* Ground the Fault Diagnosis Agent with your own data using **Foundry IQ** + +## 🧭 Context and Background + +![Challenge 2 scenario](./images/challenge-02-scenario.png) + +The **Anomaly Classification Agent** interprets detected anomalies and raises corresponding maintenance alerts. The **Fault Diagnosis Agent** determines the root cause of an anomaly so a technician can prepare for maintenance. Both agents use a number of different tools to accomplish their tasks. + +You will use the Azure resources highlighted in the image below. + +Azure Resources + +### Model Context Protocol (MCP) + +You will use **Model Context Protocol (MCP)** to connect the agents with remote tools. MCP is a standard way for an agent to discover and invoke external capabilities ("tools") through a consistent interface. + +MCP + +Let's break down the key components shown in the diagram: + +#### ❢ MCP Host and MCP Client + +An **MCP Host** is the application that runs agents and manages MCP connections. Inside the host lives one or more **MCP Clients** that handle communication with MCP servers. Examples include: + +| MCP Host | MCP Client | Description | +|----------|------------|-------------| +| VS Code | GitHub Copilot extension | Copilot acts as the MCP client, connecting to servers for code tools | +| Your Python script | Agent SDK client | The agents you build in this challenge β€” the `MCPTool` object creates an MCP client | + +#### ❷ Remote MCP Server (HTTPS) + +A **remote MCP server** runs in the cloud and communicates over HTTPS. This is ideal for enterprise APIs that need to be accessed securely, shared services used by multiple agents, and APIs that require authentication and rate limiting. In this challenge, you expose APIs via **API Management** as remote MCP servers. + +#### ❸ Tools and Data + +MCP servers provide access to **tools** (actions the agent can invoke) and **data** (information the agent can retrieve). The server defines tool names and descriptions, input/output schemas (JSON Schema), and the actual implementation that fetches data or performs actions. + +#### ❹ Local MCP Server (stdio) + +A **local MCP server** runs on the same machine as the host and communicates via standard input/output (stdio). Useful for development and testing, tools that need local file system access, and quick prototyping without cloud deployment. + +#### ❺ MCP Server Internals + +Whether remote or local, every MCP server implements the same protocol: + +| Component | Purpose | +|-----------|---------| +| Discovery API | `GET /mcp` β€” returns available tools with their schemas | +| Invocation API | `POST /mcp/tools/{tool}` β€” executes a specific tool | +| SDKs | Libraries in Python, TypeScript, and C# to simplify building servers | + +While you *could* build an MCP server yourself using an SDK, there are easier ways of doing it *without writing any code*. The next section shows how API Management lets you expose existing APIs as MCP servers. + +### API Management as AI gateway + +In this challenge, the tools live behind remote MCP servers hosted in **API Management** that expose existing API operations. Why use API Management here: + +* **Decouples the agent from integrations**: the agent calls a tool by name and schema, not by hardcoding HTTP requests. +* **Reusable and portable**: the same agent code can work across environments as long as the MCP server URL/connection is configured. +* **Governance hooks**: tools can be allow-listed and can require (or skip) approval depending on your scenario. + +The diagram below illustrates how API Management serves as an AI gateway, bridging traditional API patterns with the new requirements of agentic applications. + +AI Gateway + +| # | Component | Description | +|---|-----------|-------------| +| ❢ | Traditional Application | APIs have been used for many years to integrate applications with remote systems. This is the foundation we build upon. | +| ❷ | API Proxy (APIM) | API Management is a mature platform that shields backend systems and adds quality-of-service capabilities such as throttling, security, and caching. | +| ❸ | Traditional Backend APIs | Backend services exposed via REST, SOAP, GraphQL, and other protocols that your applications consume. | +| ❹ | Agentic Application | We use APIs just like traditional applications to build the full solution. However, agentic applications introduce new requirements around AI model integration and tool discovery. | +| ❺ | AI APIs | Model APIs where APIM policies enable token limiting, token metrics emission, model load balancing, and content safety β€” all using built-in APIM functionality applied in new ways for AI workloads. | +| ❻ | MCP in APIM | A standard API can easily be exposed in APIM as an MCP server without writing any code. This is exactly what you will do in this challenge β€” turning existing APIs into tools that agents can discover and invoke. | + +### Grounding with Foundry IQ + +You will also ground the Fault Diagnosis Agent with data using **Foundry IQ**, a retrieval-augmented generation (RAG) capability in Microsoft Foundry that enables agents to access and reason over your enterprise data. It provides a managed knowledge retrieval layer powered by Azure AI Search, letting your agent retrieve relevant chunks from your own documents at runtime instead of pasting wiki content into prompts. + +Key benefits of Foundry IQ: + +* **Simplified development**: no need to build custom RAG pipelines β€” Foundry IQ handles chunking, indexing, and retrieval for you. +* **Flexible data integration**: connect multiple data sources to a single knowledge base. +* **Enhanced agent capabilities**: agents can access up-to-date enterprise knowledge without retraining the model. + +Foundry IQ consists of two main concepts: + +| Concept | Description | +|---------|-------------| +| Knowledge Sources | The *what* to retrieve β€” connections to your data. These define where the content comes from. | +| Knowledge Bases | The *how* to retrieve β€” the retrieval configuration that combines one or more knowledge sources into a queryable endpoint. | + +Foundry IQ Architecture + +| # | Component | Description | +|---|-----------|-------------| +| ❢ | End User | The agent receives questions from the end user and processes them. | +| ❷ | Large Language Model | The agent uses a large language model to reason about the question and determine the next action. | +| ❸ | Knowledge Base | Foundry IQ provides a knowledge base with several knowledge sources that the agent can query. | +| ❹ | Indexed Sources | Pre-indexed sources like Azure AI Search indexes, Azure Blob Storage, and Azure Data Lake. Content is indexed ahead of time for fast vector and keyword retrieval. | +| ❺ | Remote Sources | On-demand sources like SharePoint and Bing that are queried at runtime. Useful for real-time data that changes frequently. | + +In this challenge, you will use Foundry IQ to make the machine wiki available to the Fault Diagnosis Agent via an MCP tool. + +## βœ… Tasks + +### Task 1: Create and test the initial Anomaly Classification Agent + +As a first step, create an agent to interpret and classify anomalies and raise maintenance alerts if certain thresholds have been violated. The agent takes anomalies for specific machines as input and checks them against thresholds for that machine type by using JSON data stored in Cosmos DB. + +#### Task 1.1: Review the initial code + +Examine the Python code in [`src/agents/anomaly_classification_agent.py`](../src/agents/anomaly_classification_agent.py). A few things to observe: + +* The agent uses two function tools: `get_thresholds` retrieves specific metric threshold values for certain machine types, and `get_machine_data` fetches details about machines such as id, model, and maintenance history. +* The agent is instructed to output both structured alert data in a specific format and a human-readable summary. +* The code creates the agent and runs a sample query against it. + +#### Task 1.2: Run the code + +```bash +python src/agents/anomaly_classification_agent.py +``` + +Verify that the agent responds with a reasonable answer that classifies the sample anomaly and includes a human-readable summary. + +### Task 2: Equip the agent with MCP tools + +Machine and threshold information is typically stored in a central system and exposed through an API. Adjust the data access to use existing Machine and Maintenance APIs instead of accessing Cosmos DB directly, by exposing those APIs as MCP servers. + +#### Task 2.1: Test the Machine API + +The Machine API and Maintenance API are already available in API Management and contain endpoints for getting details about a specific machine and thresholds for certain machine types. Try the APIs: + +```bash +# Get all machines +curl -fsSL "$APIM_GATEWAY_URL/machine" -H "Ocp-Apim-Subscription-Key: $APIM_SUBSCRIPTION_KEY" -H "Accept: application/json" + +# Get a specific machine +curl -fsSL "$APIM_GATEWAY_URL/machine/machine-001" -H "Ocp-Apim-Subscription-Key: $APIM_SUBSCRIPTION_KEY" -H "Accept: application/json" + +# Get thresholds for a machine type +curl -fsSL "$APIM_GATEWAY_URL/maintenance/tire_curing_press" -H "Ocp-Apim-Subscription-Key: $APIM_SUBSCRIPTION_KEY" -H "Accept: application/json" +``` + +#### Task 2.2: Expose APIs as MCP servers + +API Management provides an easy way to expose APIs as MCP servers without writing any additional wrapper code. + +❢ Navigate to your API Management instance in the [Azure portal](https://portal.azure.com). + +❷ Choose **APIs** and notice that the *Machine API* and *Maintenance API* you tested earlier are available. + +Portal APIs + +❸ Navigate to the **MCP Servers** section. + +❹ Select **Create MCP Server** and **Expose an API as MCP Server**. + +❺ Select the API, operations, and provide the following details: + +* **API**: *Machine API* +* **API Operations**: *Get Machine* +* **Display Name**: *Get Machine Data* +* **Name**: `get-machine-data` +* **Description**: *Gets details about a specific machine* + +Expose MCP + +❻ Select **Create**. + +❼ Copy the *MCP Server URL* of the newly created MCP server. + +MCP URL + +❽ Add a new entry to the `.env` file: `MACHINE_MCP_SERVER_ENDPOINT=""`. + +Repeat the same steps to create the *Maintenance* MCP server using the following settings: + +* **API**: *Maintenance API* +* **API Operations**: *Get Threshold* +* **Display Name**: *Get Maintenance Data* +* **Name**: `get-maintenance-data` +* **Description**: *Gets maintenance data such as thresholds for maintenance alerts* + +Save the MCP Server URL as `MAINTENANCE_MCP_SERVER_ENDPOINT=""`, then reload the environment variables: + +```bash +export $(grep -v '^#' .env | xargs) +``` + +#### Task 2.3: Use the MCP servers from the agent + +Replace the direct database access with the new Machine and Maintenance MCP servers, added as tools to the Anomaly Classification Agent. + +Examine the Python code in [`src/agents/anomaly_classification_agent_mcp.py`](../src/agents/anomaly_classification_agent_mcp.py). A few things to observe: + +* The agent uses two MCP tools: `machine-data` fetches details about machines, and `maintenance-data` retrieves specific metric threshold values. +* A project connection is created for each MCP tool. + +#### Task 2.4: Test the agent with MCP tools + +```bash +python src/agents/anomaly_classification_agent_mcp.py +``` + +Verify that the agent responds with a correct answer using the MCP tools instead of direct database access. + +#### Task 2.5: Review and test the agent in the Foundry portal + +❢ Navigate to the [Foundry portal](https://ai.azure.com). + +> [!TIP] +> Make sure you are using the new Foundry portal experience. You might need to enable it using the toggle in the upper right corner. +> +> New Foundry portal toggle + +❷ Select the **Build** tab to list available agents. + +Foundry portal build tab + +❸ Examine the configuration details for `AnomalyClassificationAgent`. + +Foundry Portal Anomaly Classification Agent + +> [!NOTE] +> There are two versions of the Anomaly Classification Agent: one for the initial agent with local tools, and one for the version that uses MCP tools. + +❹ Select the Anomaly Classification Agent and try out some additional questions in the playground: + +* Normal condition (no maintenance needed). Use the query `Hello, can you classify the following metric for machine-002: [{"metric": "drum_vibration", "value": 2.1}]` + +Foundry Portal Playground + +* Critical anomaly. Use the query `Hello, can you classify the following metric for machine-005: [{"metric": "mixing_temperature", "value": 175}]` + +### Task 3: Understand root cause with the Fault Diagnosis Agent and Foundry IQ + +The **Fault Diagnosis Agent** is tasked with understanding the actual root cause of the issues raised by the Anomaly Classification Agent. Besides machine data and maintenance history, add a machine wiki as a tool for the agent by using Foundry IQ. + +#### Task 3.1: Examine the machine data wiki + +The machine wiki contains knowledge (common issues, repair instructions, and repair details) about different machine types. The wiki pages are available as markdown files in Azure Blob Storage. + +❢ Navigate to the [Azure portal](https://portal.azure.com) and locate the storage account. + +❷ Select **Storage browser** > **Blob containers** and select the `machine-wiki` container. + +❸ Select a wiki article and select the **Edit** tab to preview the content. + +Storage wiki page + +#### Task 3.2: Expose the machine wiki data as a knowledge base + +Foundry IQ consists of knowledge sources (*what* to retrieve) and knowledge bases (*how* to retrieve). Knowledge sources are created as standalone objects and then referenced in a knowledge base. + +> [!NOTE] +> Foundry Agent Service orchestrates calls to the knowledge base via the MCP tool and synthesizes the final answer. At runtime, the agent calls only the knowledge base, not the data platform (Azure Blob Storage in this case) that underlies the knowledge source. The knowledge base handles all retrieval operations. + +Create a knowledge source and knowledge base by following the steps in [`src/create_knowledge_base.ipynb`](../src/create_knowledge_base.ipynb). + +> [!TIP] +> When running the notebook you will be asked to select an environment. Pick the user-installed Python environment. +> +> Select Python environment + +#### Task 3.3: Create the Fault Diagnosis Agent + +Create the Fault Diagnosis Agent and use the newly created Foundry IQ knowledge base. + +Examine the Python code in [`src/agents/fault_diagnosis_agent.py`](../src/agents/fault_diagnosis_agent.py). Currently only one tool, `machine_data`, is available. Your task is to add the knowledge base MCP tool to the agent so the machine wiki content can be used when diagnosing the root cause of the anomaly. + +❢ Locate the placeholder comment `# TODO: add Foundry IQ MCP tool` in `src/agents/fault_diagnosis_agent.py`. + +❷ Add the knowledge base as an `MCPTool` by replacing the placeholder with: + +```python +MCPTool( + server_label="machine-wiki", + server_url=machine_wiki_mcp_endpoint, + require_approval="never", + project_connection_id="machine-wiki-connection" +) +``` + +A few things to observe: + +* The agent now uses two MCP tools: `knowledge_base` retrieves machine wiki information for root cause analysis, and `machine_data` fetches details about machines. +* The agent is clearly instructed to use the machine knowledge base instead of its own knowledge. + +Run the code: + +```bash +python src/agents/fault_diagnosis_agent.py +``` + +Verify that the agent's answer includes a root cause, severity, and a knowledge base reference from the machine wiki. + +πŸŽ‰ Congratulations! You've successfully built two agents and equipped them with enterprise tools to perform their tasks. + +## πŸš€ Go Further + +> [!NOTE] +> Finished early? These tasks are **optional** extras for exploration. Feel free to move on to the next challenge β€” you can always come back later! + +
+Experiment with prompts + +Try modifying the system instructions in `anomaly_classification_agent_mcp.py` or `fault_diagnosis_agent.py`. Observe how different phrasings affect the agent's responses β€” this is practical prompt engineering in action. + +
+ +
+Test edge cases in the playground + +Use the Foundry portal playground to test boundary conditions: non-existent machines (for example `machine-999`), metrics at exact threshold values, invalid or malformed input data, and multiple anomalies at once. + +
+ +
+Add a new wiki article + +Create a new markdown file for a machine type (copy and modify an existing article), upload it to the `machine-wiki` container in Azure Blob Storage, re-run the indexer to include the new content, and test retrieval with the Fault Diagnosis Agent. + +
+ +## πŸ› οΈ Troubleshooting + +* **`Connection 'xxx-connection' not found` error when running agents:** this is a known intermittent issue with Foundry's MCP connection resolution (the service is in preview) β€” the connections exist but are not always resolved correctly by the agent runtime. Run the script again (the error is transient and often succeeds on retry), test in the Foundry portal playground if the agent was already created, or wait a few minutes for connection state to propagate. Verify connections exist with: + + ```bash + az rest --method GET \ + --url "https://management.azure.com${AZURE_AI_PROJECT_RESOURCE_ID}/connections?api-version=2025-10-01-preview" \ + --query "value[].name" -o tsv + ``` + +* **The agent doesn't show up in the Foundry portal:** there can be a delay before newly created agents are visible. If you don't see the agent after 10 minutes, refresh the browser or run the Python script again. +* **`PermissionDenied` when running agent creation scripts:** confirm you have the *Azure AI Developer* role on the Foundry project. + +## 🧠 Conclusion and Reflection + +In Task 1, you created the Anomaly Classification Agent using a Python script. + +Task 1 recap + +The agent had a system prompt with instructions on how to behave, and two *local* tools to query Cosmos DB data. When running the Python script, the tools executed locally in the Python process β€” if you asked the same questions in the Foundry portal playground, the agent wouldn't be able to answer since the tools are not available there. + +> [!NOTE] +> **When do you need an agent?** This particular example could be solved *without* an agent β€” it's mainly mapping metrics to thresholds. The only thing AI is used for here is generating a human-readable summary of the issue. However, using an agent (besides being a learning exercise) means you can add more tools in the future for more advanced reasoning, plus built-in observability and memory (conversation history), which you'll examine in Challenge 4. +> +> You don't always need a full agent β€” you can call a model directly through the Azure AI Inference SDK when your use case is simpler. And agents and traditional code work well together: an agent doesn't have to perform every step in a workflow. Traditional code is often better suited for deterministic logic like validation, filtering, or data transformation. A good rule of thumb: if your agent's system instructions are becoming very long and detailed with step-by-step rules, that's often a sign that some of that logic belongs in code rather than in the prompt. Use agents for reasoning, interpretation, and decisions that benefit from flexibility, and use code for everything else. + +In Task 2, you published the APIs as MCP servers in API Management and connected them to the Foundry project. + +Task 2 recap + +The Anomaly Classification Agent could then run fully in Agent Service, so the questions asked in the Foundry playground worked. + +Finally, in Task 3, you created the Fault Diagnosis Agent and grounded it with data via AI Search exposed as an MCP server. + +Task 3 recap + +This agent also runs fully in Agent Service and can use the tools when answering questions in the playground. Note that the content from Blob Storage isn't fetched on demand β€” instead, it's indexed ahead of time, and retrieval queries are executed against AI Search. + +If you want to learn more about what you covered in this challenge, check out the links below: + +* [Model Context Protocol](https://modelcontextprotocol.io/) +* [Expose an API as an MCP server in API Management](https://learn.microsoft.com/azure/api-management/export-rest-mcp-server) +* [Foundry IQ overview](https://learn.microsoft.com/azure/ai-foundry/concepts/foundry-iq) +* [Azure AI Search documentation](https://learn.microsoft.com/azure/search/) + +**Next:** [Challenge 3 - Repair Planner Agent and AI-Driven Development](challenge-03.md) diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/challenge-03.md b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/challenge-03.md new file mode 100644 index 000000000..3c9c1b25b --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/challenge-03.md @@ -0,0 +1,382 @@ +# Challenge 3 - Repair Planner Agent and AI-Driven Development + +**[Home](../README.md)** | [Previous challenge](challenge-02.md) | [Next challenge](challenge-04.md) + +> [!TIP] +> Stuck on a step? See the [Challenge 3 walkthrough](../walkthrough/challenge-03/solution-03.md) for exact commands, expected output, and a validation checklist. + +## 🎯 Objective + +The goals for this challenge are: + +* Pair program with GitHub Copilot to build an agent from scratch +* Create a .NET agent using the Foundry Agents SDK +* Understand the similarities between a coding assistant (like GitHub Copilot) and a Foundry Agent β€” both use instructions, tools, and an LLM to accomplish tasks + +## 🧭 Context and Background + +![Challenge 3 scenario](./images/challenge-03-scenario.png) + +The **Repair Planner Agent** is the third component in the multi-agent system. After a fault has been diagnosed, this agent determines what repair tasks need to be performed, finds technicians with the required skills, checks parts inventory, and creates a structured work order. + +You will implement the Repair Planner Agent as a .NET application that reads information about `Technicians` and `PartsInventory` from Cosmos DB. The final work order is also saved in Cosmos DB. The following diagram illustrates the target solution. + +Challenge 3 Target Solution + +❢ The **Repair Planner** is a .NET console application with `Program.cs` as its entry point. + +❷ The `RepairPlannerAgent.cs` class registers the agent and orchestrates calls to other services. + +❸ The `CosmosDbService.cs` class encapsulates Cosmos DB data access. + +❹ In this exercise, the mappings between faults and required skills/parts are done as a static mapping in `FaultMappingService.cs`. In a real-world application, this would be fetched from another system. + +> [!TIP] +> This is just one way to structure the solution β€” there are many valid approaches! If you have experience with .NET, feel free to experiment with a different architecture (for example dependency injection, separate class libraries, or different layering patterns). The goal, though, is to have GitHub Copilot write the majority of the code while you provide guidance and review. + +The following mappings are used in this exercise. The `agentplanning` agent already knows these mappings, so you don't need to copy them manually. + +
+Fault β†’ Required Skills + +* `curing_temperature_excessive` β†’ `tire_curing_press`, `temperature_control`, `instrumentation`, `electrical_systems`, `plc_troubleshooting`, `mold_maintenance` +* `curing_cycle_time_deviation` β†’ `tire_curing_press`, `plc_troubleshooting`, `mold_maintenance`, `bladder_replacement`, `hydraulic_systems`, `instrumentation` +* `building_drum_vibration` β†’ `tire_building_machine`, `vibration_analysis`, `bearing_replacement`, `alignment`, `precision_alignment`, `drum_balancing`, `mechanical_systems` +* `ply_tension_excessive` β†’ `tire_building_machine`, `tension_control`, `servo_systems`, `precision_alignment`, `sensor_alignment`, `plc_programming` +* `extruder_barrel_overheating` β†’ `tire_extruder`, `temperature_control`, `rubber_processing`, `screw_maintenance`, `instrumentation`, `electrical_systems`, `motor_drives` +* `low_material_throughput` β†’ `tire_extruder`, `rubber_processing`, `screw_maintenance`, `motor_drives`, `temperature_control` +* `high_radial_force_variation` β†’ `tire_uniformity_machine`, `data_analysis`, `measurement_systems`, `tire_building_machine`, `tire_curing_press` +* `load_cell_drift` β†’ `tire_uniformity_machine`, `load_cell_calibration`, `measurement_systems`, `sensor_alignment`, `instrumentation` +* `mixing_temperature_excessive` β†’ `banbury_mixer`, `temperature_control`, `rubber_processing`, `instrumentation`, `electrical_systems`, `mechanical_systems` +* `excessive_mixer_vibration` β†’ `banbury_mixer`, `vibration_analysis`, `bearing_replacement`, `alignment`, `mechanical_systems`, `preventive_maintenance` + +
+ +
+Fault β†’ Required Parts + +* `curing_temperature_excessive` β†’ `TCP-HTR-4KW`, `GEN-TS-K400` +* `curing_cycle_time_deviation` β†’ `TCP-BLD-800`, `TCP-SEAL-200` +* `building_drum_vibration` β†’ `TBM-BRG-6220` +* `ply_tension_excessive` β†’ `TBM-LS-500N`, `TBM-SRV-5KW` +* `extruder_barrel_overheating` β†’ `EXT-HTR-BAND`, `GEN-TS-K400` +* `low_material_throughput` β†’ `EXT-SCR-250`, `EXT-DIE-TR` +* `high_radial_force_variation` β†’ (empty array) +* `load_cell_drift` β†’ `TUM-LC-2KN`, `TUM-ENC-5000` +* `mixing_temperature_excessive` β†’ `BMX-TIP-500`, `GEN-TS-K400` +* `excessive_mixer_vibration` β†’ `BMX-BRG-22320`, `BMX-SEAL-DP` + +
+ +### Using the custom GitHub Copilot agent (agentplanning) + +GitHub Copilot custom agents in VS Code are reusable, task-specific chat personas. A custom agent bundles a set of instructions (how Copilot should behave) and an allowed set of tools (what Copilot can do). This makes it easy to switch into a consistent "mode" (for example, planning vs. implementation) without re-explaining context each time. In a workspace, custom agents are typically defined as `.agent.md` files under `.github/agents`. + +This repository includes a specialized GitHub Copilot agent called `agentplanning` that knows: + +* Foundry Agents SDK patterns (`Azure.AI.Projects` + `Microsoft.Agents.AI`) +* .NET and C# best practices +* Cosmos DB integration +* The fault β†’ skills/parts mappings for this microhack + +#### Agent-driven development workflow + +Follow this workflow when using the agent planner: + +1. Ask the agent to plan the component architecture. +2. Request code generation with specific requirements. +3. Review and refine the generated code. +4. Ask for improvements or additional features. +5. Request tests to validate functionality. + +## βœ… Tasks + +> [!IMPORTANT] +> The outcome depends on which model GitHub Copilot uses. Larger models may handle more complex prompts. Smaller models work better with focused, single-file requests. + +### Task 1: Project setup + +Create a new empty .NET application that will host your agent. + +```bash +# Navigate to the src directory +cd src + +# Create a new console application +dotnet new console -n RepairPlanner + +# Navigate into the project +cd RepairPlanner +``` + +### Task 2: Create the RepairPlanner agent with agentplanning + +Open GitHub Copilot Chat (Ctrl+Shift+I or Cmd+Shift+I) and select the `agentplanning` agent in the Agents dropdown. + +agentplanning selection + +#### Task 2.1: Architecture planning + +Start with the following prompt to understand the proposed setup for the Repair Planner Agent. + +πŸ’¬ Ask the agent: + +``` +I need to build a Repair Planner Agent in .NET for Challenge 3 +using the Foundry Agents SDK. Can you explain the architecture? +``` + +#### Task 2.2: Create data models + +Now let the agent create the data models. + +πŸ’¬ Ask the agent: + +``` +Create all data models for the Repair Planner Agent: +- DiagnosedFault (input from previous agent) +- Technician (with skills and availability) +- Part (inventory items) +- WorkOrder (output with tasks) +- RepairTask (individual repair steps) +- WorkOrderPartUsage (parts needed) + +Use dual JSON attributes for Cosmos DB compatibility. +``` + +#### Task 2.3: Create FaultMappingService + +Create a service for mapping between fault/skills and fault/parts. In this exercise, it will be a static mapping of values, but in a real-world scenario this would be fetched from a dedicated system. + +πŸ’¬ Ask the agent: + +``` +Create a FaultMappingService that maps fault types to required skills and parts using hardcoded dictionaries. +``` + +#### Task 2.4: Create CosmosDbService + +Let's create the data access service. + +πŸ’¬ Ask the agent: + +``` +Create a CosmosDbService that: +- Queries available technicians by skills +- Fetches parts by part numbers +- Creates work orders +Include error handling and logging. +``` + +#### Task 2.5: Create the main agent + +πŸ’¬ Ask the agent: + +``` +Create the RepairPlannerAgent class that orchestrates the entire workflow. +It should register the agent, determine required skills, query technicians and parts, +and save the work order. +``` + +#### Task 2.6: Create the main program + +Finally, let the `agentplanning` agent update `Program.cs` to initialize all services and run a sample fault. + +πŸ’¬ Ask the agent: + +``` +Update Program.cs to initialize all services, create a sample fault, +and demonstrate the repair planning workflow. +``` + +Your completed project could look similar to this: + +```text +RepairPlanner/ +β”œβ”€β”€ RepairPlanner.csproj +β”œβ”€β”€ Program.cs +β”œβ”€β”€ RepairPlannerAgent.cs +β”œβ”€β”€ Models/ +β”‚ β”œβ”€β”€ DiagnosedFault.cs +β”‚ β”œβ”€β”€ Technician.cs +β”‚ β”œβ”€β”€ Part.cs +β”‚ β”œβ”€β”€ WorkOrder.cs +β”‚ β”œβ”€β”€ RepairTask.cs +β”‚ └── WorkOrderPartUsage.cs +└── Services/ + β”œβ”€β”€ CosmosDbService.cs + β”œβ”€β”€ CosmosDbOptions.cs + └── FaultMappingService.cs +``` + +### Task 3: Test your agent + +Try out your agent. + +```bash +export $(grep -v '^#' ../../.env | xargs) +dotnet run +``` + +Verify that the agent logs finding available technicians, fetching parts, and saving a work order with a generated work order number. + +πŸŽ‰ Congratulations! You've built a Repair Planner Agent in .NET using GitHub Copilot. + +## πŸš€ Go Further + +> [!NOTE] +> Finished early? These tasks are **optional** extras for exploration. Feel free to move on to the next challenge β€” you can always come back later! + +
+Expose Cosmos DB queries as agent tools + +In the current implementation, the agent orchestrates calls to Cosmos DB directly through the `CosmosDbService` class. An alternative approach is to expose the database queries as **tools** that the agent can invoke, letting the LLM decide when and how to use them. + +**Why consider this?** + +| Direct code orchestration | Tool-based approach | +|---------------------------|---------------------| +| You control the exact sequence of calls | Agent decides which tools to call and when | +| Predictable but rigid | More flexible β€” agent can adapt to different inputs | +| Good for well-defined workflows | Good when reasoning is needed to choose actions | + +**How to implement:** + +1. Define local function tools (similar to `get_thresholds` and `get_machine_data` in Challenge 2) that wrap your Cosmos DB queries: `get_technicians_by_skills`, `get_parts_inventory`, and `create_work_order`. +2. Register these functions as tools when creating the agent. +3. Update the agent's system prompt to explain when to use each tool. +4. Let the agent decide which tools to call based on the diagnosed fault. + +**Taking it further:** once you have local tools working, you could expose them as an MCP server (via API Management or a standalone server). This would allow the same tools to be shared across multiple agents and managed independently from the agent code β€” similar to how you exposed the Machine API in Challenge 2. + +
+ +
+Enhance the Repair Planner Agent + +Once the basic agent works, try adding: + +``` +Add priority calculation based on fault severity +``` + +``` +Add better error handling for when no technicians are available +``` + +``` +Add structured output using AIJsonUtilities.CreateJsonSchema +and ChatResponseFormat.ForJsonSchema for type-safe responses +``` + +
+ +
+Add an MCP server to GitHub Copilot + +In Challenge 2, you saw how agents in Foundry Agent Service can use MCP tools. GitHub Copilot in VS Code also supports MCP servers, letting you extend Copilot's capabilities with custom tools. + +Try adding the Microsoft Learn MCP server to your GitHub Copilot setup: + +1. Open the Command Palette (Ctrl+Shift+P or Cmd+Shift+P). +2. Run **MCP: Add Server**. +3. Select **HTTP** as the server type. +4. Enter `https://learn.microsoft.com/api/mcp` as the URL. +5. Enter `microsoft-learn` as the server ID. +6. Select **Workspace** to add the server to `.vscode/mcp.json`. + +In GitHub Copilot Chat, you can now ask questions that use Microsoft Learn documentation β€” useful for questions about Azure services, .NET APIs, and other Microsoft technologies. + +
+ +
+Create your own custom GitHub Copilot agent + +In this challenge you used the `agentplanning` agent β€” a custom GitHub Copilot agent defined in [`.github/agents/agentplanning.agent.md`](../.github/agents/agentplanning.agent.md). Try creating your own custom agent with a specific persona: + +1. Create a new file in `.github/agents/` with a `.agent.md` extension. +2. Define the agent's persona, instructions, and any tools it should use. +3. Test it by selecting your new agent from the Agents dropdown in GitHub Copilot Chat. + +Two agent personas that would be useful in this microhack: + +**πŸ§ͺ Agent Test Generator** (`agenttester.agent.md`) β€” a test-driven development specialist for AI agents. Helps write unit tests, integration tests, and mock data for agent workflows. Example prompt: "Write unit tests for the CosmosDbService that mock the container responses." + +**πŸ—„οΈ Cosmos DB Helper** (`cosmoshelper.agent.md`) β€” a database specialist focused on Cosmos DB best practices. Helps write efficient queries, design partition keys, and troubleshoot data access. Example prompt: "Write a query to find all technicians with bearing_replacement skill who are available." + +See [Custom agents in VS Code](https://code.visualstudio.com/docs/copilot/customization/custom-agents) for detailed documentation on creating custom agents. + +
+ +## πŸ› οΈ Troubleshooting + +* **Preview API warnings:** add `$(NoWarn);CA2252` to your `RepairPlanner.csproj`. +* **JSON parsing errors with numbers:** LLMs sometimes return `"60"` instead of `60`. Use `NumberHandling = JsonNumberHandling.AllowReadingFromString`. +* **Cosmos DB errors:** ensure you're using both `[JsonPropertyName]` and `[JsonProperty]` attributes on models. +* **Is there a finished example solution available?** Yes β€” see the [Challenge 3 walkthrough](../walkthrough/challenge-03/solution-03.md), which links to a complete reference implementation. Try building the agent yourself with Copilot's guidance first; only consult the reference if you want to compare your work or are stuck, since `.github/copilot-instructions.md` already asks Copilot not to copy from it directly. + +## 🧠 Conclusion and Reflection + +### C# vs Python + +You used .NET (C#) in this challenge and Python in the previous one β€” both are first-class for building agents with modern AI/agent SDKs (including the Foundry Agents SDK patterns used in this microhack). Agent solutions quickly become application development (integration, data access, security, ops), so teams typically choose the language that best fits their existing stack and skills β€” Python often excels for rapid iteration, while .NET is common in larger enterprises for long-lived, well-governed services. + +### GitHub Copilot instructions + +This repository uses VS Code Copilot customization so the agent behaves consistently during the microhack. + +> [!TIP] +> Using guided agents (clear instructions + constrained tools + repeatable steps) is an improvement over pure vibe coding, where solutions can drift, skip requirements, or become hard to review. A lightweight, guided approach keeps changes aligned with the goal and makes agent output easier to validate. + +GitHub Copilot instructions + +The diagram above illustrates how GitHub Copilot combines multiple inputs to generate contextual responses: + +❢ **Your prompt** β€” the primary instruction you provide to the agent (for example "Create the RepairPlannerAgent class..."). This is your direct request that drives the conversation. + +❷ **Custom instructions** β€” two types of instruction files shape Copilot's behavior: + +* [`copilot-instructions.md`](../.github/copilot-instructions.md) β€” general workspace-wide instructions applied to all chat requests (SDK constraints, pinned package versions, environment variables, and so on) +* [`agentplanning.agent.md`](../.github/agents/agentplanning.agent.md) β€” the specific role and persona for the `agentplanning` agent, discovered by VS Code from `.github/agents/*.agent.md` and selectable from the Agents dropdown + +❸ **Workspace context** β€” Copilot examines files in your workspace to understand structure and data, such as [README.md](../README.md), [technicians.json](../labautomation/data/technicians.json), [work-orders.json](../labautomation/data/work-orders.json), and your existing code files. This helps it generate code that fits your project. + +❹ **Tools and data (MCP)** β€” Copilot can be equipped with additional tools exposed via the Model Context Protocol (MCP) to accomplish more complex tasks. This is very similar to how the agents in [Challenge 2](challenge-02.md) were equipped with tools β€” just as you gave the Fault Diagnosis Agent access to Cosmos DB queries and a knowledge base, you can extend Copilot with external data sources and capabilities. + +### Taking it further with Spec Kit + +In this challenge, you used custom instructions and a guided agent to build a single component. But what happens when you need to build entire features or complete applications with AI assistance? + +[Spec Kit](https://github.com/github/spec-kit) is an open-source toolkit that takes AI-assisted development to the next level through Spec-Driven Development (SDD). Instead of generating code directly from prompts, SDD flips the traditional approach: specifications become executable, directly generating working implementations rather than just guiding them. + +**Why this matters for larger projects:** + +| Traditional approach | Spec-Driven Development | +|---------------------|------------------------| +| Code is the source of truth | Specifications are the source of truth | +| Specs drift from implementation | Specs generate implementation | +| One-shot prompt β†’ code | Multi-step: specify β†’ plan β†’ tasks β†’ implement | +| Hard to review AI output | Structured artifacts at each stage | + +Spec Kit provides slash commands that structure AI-assisted development: + +* `/speckit.constitution` β€” establish project principles and architectural guidelines +* `/speckit.specify` β€” create functional specifications (focus on *what* and *why*, not *how*) +* `/speckit.plan` β€” generate technical implementation plans with your chosen tech stack +* `/speckit.tasks` β€” break down into actionable, parallelizable tasks +* `/speckit.implement` β€” execute tasks according to the plan + +The key insight is that specifications become living documents that generate code, tests, and documentation, rather than artifacts that are written once and ignored. When requirements change, you update the spec and regenerate, maintaining alignment between intent and implementation. + +This approach is particularly valuable when building complete features or applications (not just single files), working on teams where specifications need review and approval, maintaining long-lived projects where requirements evolve, or wanting reproducible, auditable AI-assisted development. + +If you want to expand your knowledge on what you covered in this challenge, have a look at the content below: + +* [Custom agents in VS Code](https://code.visualstudio.com/docs/copilot/customization/custom-agents) +* [Custom instructions in VS Code](https://code.visualstudio.com/docs/copilot/customization/custom-instructions) +* [Use MCP servers in VS Code](https://code.visualstudio.com/docs/copilot/customization/mcp-servers) +* [Microsoft Learn MCP server](https://github.com/microsoftdocs/mcp) +* [Spec Kit on GitHub](https://github.com/github/spec-kit) +* [Spec-Driven Development blog post](https://developer.microsoft.com/blog/spec-driven-development-spec-kit) + +**Next:** [Challenge 4 - Maintenance Scheduler and Parts Ordering Agents](challenge-04.md) diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/challenge-04.md b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/challenge-04.md new file mode 100644 index 000000000..9df7afb1c --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/challenge-04.md @@ -0,0 +1,277 @@ +# Challenge 4 - Maintenance Scheduler & Parts Ordering Agents + +**[Home](../README.md)** | [Previous challenge](challenge-03.md) | [Next challenge](challenge-05.md) + +> [!TIP] +> Stuck on a step? See the [Challenge 4 walkthrough](../walkthrough/challenge-04/solution-04.md) for exact commands, expected output, and a validation checklist. + +## 🎯 Objective + +The goals for this challenge are: + +* Use **agent memory** to maintain context across multiple interactions +* Implement **observability** with Azure AI tracing to monitor and debug agent behavior + +## 🧭 Context and Background + +![Challenge 4 scenario](./images/challenge-04-scenario.png) + +The **Maintenance Scheduler Agent** analyzes work orders and historical maintenance data to find optimal maintenance windows that minimize production disruption. The **Parts Ordering Agent** checks inventory levels for required parts, evaluates supplier performance and lead times, and generates optimized parts orders with cost analysis. + +You will use the Azure resources highlighted in the image below. + +Azure Resources + +Both agents interact with **Cosmos DB** as their primary data store. + +
+Containers used + +| Container | Purpose | Agent usage | +|-----------|---------|-------------| +| `WorkOrders` | Work orders from the Repair Planner | Read by both agents to get job details | +| `Machines` | Equipment information | Referenced for machine context | +| `MaintenanceHistory` | Historical maintenance records | Read by the **Maintenance Scheduler Agent** for pattern analysis | +| `MaintenanceWindows` | Available production windows | Read by the **Maintenance Scheduler Agent** to find optimal timing | +| `MaintenanceSchedules` | Generated maintenance schedules | Written by the **Maintenance Scheduler Agent** | +| `PartsInventory` | Current stock levels | Read by the **Parts Ordering Agent** to check availability | +| `Suppliers` | Supplier information | Read by the **Parts Ordering Agent** for sourcing decisions | +| `PartsOrders` | Generated parts orders | Written by the **Parts Ordering Agent** | + +
+ +
+Data flow + +```text +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ MAINTENANCE SCHEDULER AGENT β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + +Input (READ): Output (WRITE): +β”œβ”€ WorkOrders β”œβ”€ MaintenanceSchedules +β”œβ”€ MaintenanceHistory β”‚ └─ scheduled_date +β”œβ”€ MaintenanceWindows β”‚ └─ risk_score (0-100) +└─ Machines β”‚ └─ predicted_failure_probability + β”‚ └─ recommended_action + β”‚ └─ maintenance_window + β”‚ └─ reasoning + └─ WorkOrders (status update to 'Scheduled') + +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ PARTS ORDERING AGENT β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + +Input (READ): Output (WRITE): +β”œβ”€ WorkOrders β”œβ”€ PartsOrders +β”œβ”€ PartsInventory β”‚ └─ supplier_id, supplier_name +β”œβ”€ Suppliers β”‚ └─ order_items (part, qty, cost) +└─ Machines β”‚ └─ total_cost + β”‚ └─ expected_delivery_date + β”‚ └─ reasoning + └─ WorkOrders (status update to 'PartsOrdered' or 'Ready') +``` + +
+ +### Agent memory + +Both agents implement **persistent memory** by storing conversation history in **Cosmos DB**. This enables an agent to maintain context across multiple interactions with the same entity (machine or work order). + +Why use agent memory? + +* **Contextual awareness**: an agent can reference previous interactions when making decisions, leading to more informed recommendations. +* **Consistency**: it maintains a coherent conversation thread even when sessions are interrupted or resumed later. +* **Learning from history**: the model can consider past recommendations and outcomes when generating new responses. + +The conversation history is injected into the AI prompt as context, so the model can see what was previously discussed and provide more relevant responses. + +### Azure AI tracing and observability + +Both agents are instrumented for **end-to-end observability** so you can debug runs and understand model behavior without adding print statements everywhere. + +Why observability matters β€” when running agents in production, you need visibility into what's happening: + +* **Identify bottlenecks**: see which steps take the longest (AI inference vs. database operations). +* **Debug failures**: when an agent fails, traces show exactly which step failed and why. +* **Monitor costs**: token usage metrics help you understand and optimize AI spending. +* **Validate behavior**: confirm that agents are making reasonable decisions by reviewing their reasoning. + +What's included: + +* **Azure Monitor integration** β€” sends traces to **Application Insights**. +* **AI inference instrumentation** β€” automatically traces all AI model calls. +* **OpenTelemetry support** β€” industry-standard distributed tracing. +* **Graceful fallback** β€” agents work even if tracing packages aren't installed. + +What you get in traces: + +* **Agent execution timeline**: each step (read data, reason, write results). +* **AI model calls**: prompts, responses, token usage, and latency. +* **Cosmos DB operations**: reads/writes to containers used by the agents. +* **Failures with context**: exceptions and which step failed. + +Tracing is automatically enabled when you run the agents, as long as: + +1. Tracing packages are installed (already included in the project dependencies). +2. `APPLICATIONINSIGHTS_CONNECTION_STRING` is set (configured in `.env`). + +The agents use **Azure Monitor** exporters to send traces, metrics, and logs directly to **Application Insights**, which integrates with the **Foundry** portal. + +## βœ… Tasks + +### Task 1: Maintenance Scheduler Agent + +The **Maintenance Scheduler Agent** analyzes work orders and determines the optimal time to perform maintenance by balancing equipment reliability needs against production impact. + +**Agent memory in action:** this agent stores chat history per machine using `get_machine_chat_history` and `save_machine_chat_history`. When the agent runs for the same machine multiple times, it can recall previous maintenance discussions and recommendations, allowing it to build on prior analysis rather than starting fresh each time. + +What it does: + +1. **Reads the work order** from the `WorkOrders` container. +2. **Analyzes historical data** from `MaintenanceHistory` to understand failure patterns. +3. **Checks available windows** from `MaintenanceWindows` to find low-impact periods. +4. **Runs AI analysis** using **Microsoft Agent Framework** to assess risk and recommend timing. +5. **Saves the schedule** to `MaintenanceSchedules` with risk scores and recommendations. +6. **Updates the work order** status to `Scheduled`. + +#### Task 1.1: Run the agent + +```bash +# Ensure you are located in the src directory +cd src + +python agents/maintenance_scheduler_agent.py wo-2024-456 +``` + +Verify that the script prints a predictive maintenance schedule with a risk score, failure probability, recommended action, and reasoning, and that the work order status is updated to `Scheduled`. + +### Task 2: Parts Ordering Agent + +The **Parts Ordering Agent** checks inventory availability and generates optimized parts orders by evaluating supplier reliability, lead times, and costs. + +**Agent memory in action:** this agent stores chat history per work order using `get_work_order_chat_history` and `save_work_order_chat_history`. This allows the agent to maintain ordering context for each job β€” if a work order is processed multiple times (for example, due to partial fulfillment or changes), the agent remembers previous ordering decisions and can provide consistent recommendations. + +What it does: + +1. **Reads the work order** from `WorkOrders` to get required parts. +2. **Checks inventory** from `PartsInventory` to determine what's in stock. +3. **Identifies missing parts** that need to be ordered. +4. **Finds suppliers** from `Suppliers` that can provide the parts. +5. **Runs AI analysis** to optimize supplier selection based on reliability, lead time, and cost. +6. **Saves the parts order** to `PartsOrders` with order details. +7. **Updates the work order** status to `PartsOrdered` (or `Ready` if all parts are available). + +#### Task 2.1: Run the agent + +```bash +python agents/parts_ordering_agent.py wo-2024-456 +``` + +Verify that the script finds the parts that need ordering, selects a supplier, and generates an order with a total cost and expected delivery date, updating the work order status to `PartsOrdered`. + +Test with another work order where all required parts are already in stock: + +```bash +python agents/parts_ordering_agent.py wo-2024-468 +``` + +Verify that no parts order is needed this time, and the work order status is updated to `Ready` instead. + +### Task 3: Azure AI tracing and observability + +Now let's generate some trace data and explore it in the **Foundry** portal. + +#### Task 3.1: Generate traces + +Generate some trace data by running the batch script [run-batch.py](../src/run-batch.py), which processes five work orders through each agent, generating ten total agent runs. + +```bash +python run-batch.py +``` + +#### Task 3.2: View traces in Foundry + +1. Go to . +2. Select your project. +3. Select **Agents**, then `MaintenanceSchedulerAgent` or `PartsOrderingAgent`. +4. Go to the **Monitor** tab. + +You should see something similar to this: + +Monitor portal + +You can examine metrics for *token usage*, *agent runs*, *tool calls*, and potential *errors*. + +#### Task 3.3: View traces in Application Insights + +The **Foundry** project is connected to **Application Insights**, where the monitoring data is stored. Select **Open in Azure Monitor** in the upper right corner. + +Open in monitor + +This opens **Application Insights**, where you'll see similar monitoring details as in the **Foundry** portal, but you can also drill further into traces for troubleshooting purposes. + +Application Insights + +πŸŽ‰ Congratulations! You've successfully worked with two agents that integrate with **Cosmos DB** and include production-ready observability. + +## πŸš€ Go Further + +> [!NOTE] +> Finished early? These tasks are **optional** extras for exploration. Feel free to move on to the next challenge β€” you can always come back later! + +
+Inspect conversation history in Cosmos DB + +Open the chat history containers in **Data Explorer** and find the persisted messages for a machine or work order you've run more than once. Notice how prior reasoning is included as context for the next run. + +
+ +
+Compare traces across runs + +Run the same agent against the same work order twice and compare the two traces in Application Insights. Look for differences in token usage, latency, and the reasoning text β€” how much does agent memory change the second response? + +
+ +
+Add a custom trace attribute + +Extend one of the agents to emit a custom OpenTelemetry span attribute (for example, the machine's criticality level) and confirm it shows up on the trace in Application Insights. + +
+ +## πŸ› οΈ Troubleshooting + +* **No traces show up in the Foundry portal:** confirm `APPLICATIONINSIGHTS_CONNECTION_STRING` is set in `.env` and that the Foundry project is linked to an Application Insights resource. Traces can take a minute or two to appear. +* **`Connection 'xxx-connection' not found` error when running agents:** this is a known intermittent issue with Foundry's MCP connection resolution (the service is in preview) β€” the connections exist but are not always resolved correctly by the agent runtime. Run the script again, or wait a few minutes for connection state to propagate. +* **Agent memory doesn't seem to carry over between runs:** verify the chat history container has an entry for the specific machine or work order ID β€” memory is keyed per-entity, so different IDs will not share history. + +## 🧠 Conclusion and Reflection + +In Task 1, you ran the Maintenance Scheduler Agent, which balances equipment reliability against production impact. + +Task 1 recap + +The agent used its memory of prior maintenance discussions for the same machine to inform its recommendation, rather than reasoning about the machine from scratch on every run. + +In Task 2, you ran the Parts Ordering Agent, which evaluates supplier reliability, lead time, and cost to generate an order. + +Task 2 recap + +Notice how the agent produced a different outcome (order parts vs. mark the work order ready) depending purely on the current inventory state read from Cosmos DB. + +Finally, in Task 3, you generated trace data and explored it in the Foundry portal and Application Insights. + +Task 3 recap + +> [!NOTE] +> **Why observability matters for agents:** unlike traditional deterministic code, an agent's behavior depends on the model's reasoning, which can vary between runs. Tracing lets you inspect *why* an agent made a particular decision β€” not just *whether* it succeeded β€” which is essential once you move an agentic solution from a demo into production. + +If you want to learn more about what you covered in this challenge, check out the links below: + +* [Azure Monitor OpenTelemetry](https://learn.microsoft.com/azure/azure-monitor/app/opentelemetry-overview) +* [Trace your application with Azure AI Foundry](https://learn.microsoft.com/azure/ai-foundry/concepts/observability) +* [Azure Cosmos DB documentation](https://learn.microsoft.com/azure/cosmos-db/) + +**Next:** [Challenge 5 - Multi-Agent Orchestration](challenge-05.md) diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/challenge-05.md b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/challenge-05.md new file mode 100644 index 000000000..e1b3b6b8f --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/challenge-05.md @@ -0,0 +1,352 @@ +# Challenge 5 - Multi-Agent Orchestration with Aspire + +**[Home](../README.md)** | [Previous challenge](challenge-04.md) | [Next: Wrap-up](finish.md) + +> [!TIP] +> Stuck on a step? See the [Challenge 5 walkthrough](../walkthrough/challenge-05/solution-05.md) for exact commands, expected output, and a validation checklist. + +## 🎯 Objective + +The goals for this challenge are: + +* Run an Aspire-hosted **Agent Framework** workflow end-to-end +* Understand how different agent types and hosting models work together +* Observe execution via the Aspire dashboard and traces + +## 🧭 Context and Background + +![Challenge 5 scenario](./images/challenge-05-scenario.png) + +At a high level, each agent contributes a specific capability to the workflow: + +* **Anomaly Classification Agent**: determines whether incoming telemetry indicates an anomaly, and classifies severity. +* **Fault Diagnosis Agent**: interprets the anomaly signals and proposes likely root causes and next checks. +* **Repair Planner Agent**: drafts a repair plan (tasks, parts, and recommended technician actions), with direct access to operational data. +* **Maintenance Scheduler Agent**: selects maintenance windows and assigns technicians based on availability. +* **Parts Ordering Agent**: reserves/requests required parts from inventory or suppliers. + +This challenge demonstrates how a "real application" can orchestrate multiple agents in a workflow β€” where agents are not the product UI, but capabilities that enhance the solution. + +Now we put all previously used Azure resources in action. + +Azure Resources + +### Why Aspire (workflow host) + +We use **.NET Aspire** as the host for the workflow. Aspire is an opinionated framework for building distributed applications, and it's a good fit here because: + +* It gives a consistent developer experience for running multi-service apps locally. +* It provides a built-in dashboard for observability. +* It supports polyglot solutions (this project includes both .NET and Python components). + +In practice, that means Aspire can orchestrate both .NET services and Python processes side-by-side, which is exactly what we need for this multi-agent workflow. + +In other words, Aspire is the "glue" that runs the workflow host, API, UI, and agent processes together and gives you one place to see logs, health, and traces. For agentic systems, that observability is especially useful because a single user action can fan out into multiple agent calls and tool invocations. + +### Agent-to-Agent (A2A) invocation + +This solution includes different types of agents: + +* **Anomaly Classification Agent** and **Fault Diagnosis Agent** + * Defined in Python + * Running fully in **Foundry Agent Service** (no local execution) +* **Repair Planner Agent** + * Implemented in C# + * Runs with local logic and direct access to data sources like **Cosmos DB** + * Invoked from the workflow using **Agent-to-Agent (A2A)** +* **Maintenance Scheduler Agent** and **Parts Ordering Agent** + * Python agents with local logic + * Invoked with A2A similar to the **Repair Planner Agent** + +**A2A** is a pattern for letting a workflow invoke an agent through a consistent interface, so you can combine independently implemented agents without tightly coupling everything together. + +This matters in a polyglot setup: the workflow can call an agent implemented in a different language (or hosted in a different process) the same way it calls any other agent. It also gives you a clean boundary between orchestration (the workflow) and capabilities (the agents), which makes the system easier to evolve over time. + +#### Publishing agents via Azure API Management + +In Challenge 2, we explored how to use [API Management as an AI gateway](challenge-02.md#api-management-as-ai-gateway) for governing and securing access to AI services. **Azure API Management** can also be used to publish agents as A2A endpoints, enabling governed agent-to-agent communication at scale. + +With **API Management** as an A2A gateway, you get: + +* **Centralized agent discovery**: agents can be registered and discovered through a unified gateway. +* **Security and authentication**: apply consistent authentication, authorization, and rate limiting policies across all agent endpoints. +* **Observability**: monitor agent invocations, latencies, and errors through APIM's built-in analytics. +* **Protocol translation**: APIM can handle protocol differences and provide a consistent A2A interface regardless of the underlying agent implementation. + +This approach is particularly valuable in enterprise scenarios where you need to expose agents to multiple consumers while maintaining governance and control over how agents are accessed and invoked. + +### Agent Framework workflows + +The **Microsoft Agent Framework** provides *orchestrations* β€” pre-built workflow patterns with specially-built executors that allow developers to quickly create complex workflows by simply plugging in their own AI agents. + +#### Why workflows matter + +Traditional single-agent systems are limited in their ability to handle complex, multi-faceted tasks. By orchestrating multiple agents, each with specialized skills or roles, you can create systems that are more robust, adaptive, and capable of solving real-world problems collaboratively. + +#### Supported orchestrations + +The **Agent Framework** supports several orchestration patterns, each suited to different scenarios: + +| Pattern | Description | Typical use case | +|---------|-------------|-------------------| +| **Concurrent** | A task is broadcast to all agents and processed concurrently | Parallel analysis, independent subtasks, ensemble decision making | +| **Sequential** | Passes the result from one agent to the next in a defined order | Step-by-step workflows, pipelines, multi-stage processing | +| **Group Chat** | Assembles agents in a star topology with a manager controlling the flow of conversation | Iterative refinement, collaborative problem-solving, content review | +| **Magentic** | A variant of group chat with a planner-based manager (inspired by MagenticOne) | Complex, generalist multi-agent collaboration | +| **Handoff** | Assembles agents in a mesh topology where agents can dynamically pass control based on context without a central manager | Dynamic workflows, escalation, fallback, or expert handoff scenarios | + +This challenge uses the **Sequential** pattern β€” see [Conclusion and Reflection](#-conclusion-and-reflection) for why that fits this scenario. + +## βœ… Tasks + +> [!IMPORTANT] +> This workflow expects the **Anomaly Classification** and **Fault Diagnosis** agents to be hosted in **Foundry Agent Service**. Make sure you've completed [Challenge 2](challenge-02.md) before starting the workflow. + +### Task 1: Start Aspire + +```bash +cd src/agent-workflow +aspire run +``` + +### Task 2: Make the workflow API port public (Codespaces) + +The workflow API runs on port `5231`. In VS Code / **GitHub Codespaces**: + +![Port opening](./images/challenge-05-ports.png) + +* Open the *Ports* tab +* Find port `5231` +* Set *Port Visibility* to *Public* + +> This allows the frontend to call the API from the browser. + +### Task 3: Open the frontend + +Once Aspire is running, it provides links in the output. + +* Click the *frontend* link β€” you should see an app similar to the screenshot below + +![Factory Agent Workflow UI overview](./images/challenge-05-frontend-link.png) + +* Click the link (using Alt+Click) + +The Factory Workflow UI opens in your browser: + +![Factory Workflow UI start](./images/challenge-05-agent-workflow-ui-start.png) + +### Task 4: Run the workflow + +Click the *Trigger Anomaly* button. In the frontend, watch the *Agent Workflow* panel as the workflow progresses through: + +* **Anomaly Classification Agent** +* **Fault Diagnosis Agent** +* **Repair Planner Agent** +* **Maintenance Scheduler Agent** +* **Parts Ordering Agent** + +![Factory Workflow UI in progress](./images/challenge-05-agent-workflow-ui-inprogress.png) + +You should see steps move through states (queued/working/done/error). You can expand the tool call details and agent output: + +![Factory Workflow UI toolcall](./images/challenge-05-agent-workflow-ui-toolcall.png) + +When finished, you should see something similar to this: + +![Factory Agent Workflow finished](./images/challenge-05-agent-workflow-ui-end.png) + +### Task 5: View the dashboard + +Aspire includes a dashboard to observe running services. + +* In the Aspire output, click the *dashboard* link (using Alt+Click) + +![Dashboard link](./images/challenge-05-dashboard-link.png) + +The dashboard starts in your browser and shows the resources: + +![Dashboard start](./images/challenge-05-dashboard-resources.png) + +* Click *Console* to see the invocation chain in the workflow + +![Dashboard console](./images/challenge-05-dashboard-console.png) + +* Click *Traces* to see call duration details + +![Dashboard traces](./images/challenge-05-dashboard-traces.png) + +πŸŽ‰ Congratulations! You've successfully completed the final challenge. + +## πŸš€ Go Further + +> [!NOTE] +> Finished early? These tasks are **optional** extras for exploration. + +
+Add an Executive Summary Agent to the workflow + +Create a new **ExecutiveSummaryAgent** that takes the combined output from the final agent in the workflow and produces a concise, business-friendly summary of the entire maintenance operation. + +What to explore: + +* Create the agent in **Foundry Agent Service** (using a Python script or the Foundry portal UI). +* Update the Aspire workflow code to chain this new agent as the **last step** in the sequential orchestration. +* Consider what information from the previous agents would be most valuable in an executive summary. + +This task exercises your understanding of how to extend an existing Aspire-hosted workflow with additional agents. + +
+ +
+Build a declarative workflow in the Foundry portal + +Use the **Foundry portal** to create a declarative workflow that combines multiple agents without writing orchestration code. + +What to explore: + +* Use the existing **AnomalyClassificationAgent** and **FaultDiagnosisAgent** (already deployed in Agent Service). +* Create a new **ExecutiveSummaryAgent** that summarizes the findings from the previous two agents. +* Stitch all three agents together into a single declarative workflow using the Foundry portal UI. + +This task lets you compare the declarative (portal-based) approach to workflow definition versus the programmatic (Aspire-hosted) approach you used in this challenge. + +
+ +
+Deploy to Azure Container Apps + +In this challenge we ran Aspire locally for development, but the same pattern can be deployed as a hosted application. The lab environment includes both an **Azure Container Registry** and a **Container Apps Environment**. + +Try packaging the workflow host and frontend, and deploying them: + +1. Build container images for the workflow services. +2. Push images to **Azure Container Registry**. +3. Deploy to **Azure Container Apps**. +4. Configure environment variables for the deployed services. + +
+ +
+Explore the Aspire dashboard traces + +The Aspire dashboard provides detailed distributed traces. Try: + +* Clicking on individual trace spans to see timing details. +* Following a request through multiple services. +* Identifying bottlenecks in agent invocations. + +
+ +
+Add custom telemetry + +Extend the workflow with custom telemetry: + +* Add custom metrics for agent response times. +* Create custom spans for specific operations. +* Export traces to **Application Insights**. + +
+ +## πŸ› οΈ Troubleshooting + +* **`aspire` / `aspire run` is not found:** install it and restart your shell session: + + ```bash + curl -fsSL https://aspire.dev/install.sh | bash -s + ``` + + If you're using a `.env` file, load it into your current shell session with `export $(cat .env | xargs)`. + +* **HTTP 401 / `PermissionDenied` when calling model endpoints:** this usually means your identity is missing **Azure OpenAI** data-plane permissions on the **Azure OpenAI** resource. Ensure you have **Cognitive Services OpenAI Contributor** (or **Cognitive Services OpenAI User**) assigned at the resource scope. +* **The frontend loads, but calls to `/api` fail:** confirm port `5231` is forwarded and set to *Public* in the *Ports* view, confirm the frontend is pointing at the correct API URL, and check the Aspire dashboard logs for CORS or network errors. +* **The workflow can't find your Foundry project endpoint:** make sure `AZURE_AI_PROJECT_ENDPOINT` is set in the shell that runs `aspire run`: + + ```bash + echo "$AZURE_AI_PROJECT_ENDPOINT" + ``` + +## 🧠 Conclusion and Reflection + +You've now run an end-to-end, polyglot agent workflow hosted with Aspire. Let's reflect on how the different pieces fit together. + +### Sequential orchestration in this challenge + +In this challenge, we used a **sequential orchestration** to process telemetry anomalies. The workflow passes results from one agent to the next in a defined order: + +```text +Anomaly Classification β†’ Fault Diagnosis β†’ Repair Planner β†’ Maintenance Scheduler β†’ Parts Ordering +``` + +This linear flow makes sense for our scenario because: + +1. **Anomaly Classification** must first determine *if* there's a problem and its severity. +2. **Fault Diagnosis** needs the classification result to identify the root cause. +3. **Repair Planner** requires the diagnosis to create an appropriate repair plan. +4. **Maintenance Scheduler** uses the repair plan to schedule the work. +5. **Parts Ordering** needs the scheduled work order to reserve required parts. + +Each step builds on the previous one β€” you can't schedule maintenance without knowing what needs to be fixed, and you can't order parts without knowing when the work will happen. The sequential orchestration ensures this dependency chain is respected while keeping the workflow definition simple and readable. + +### Workflow architecture + +![Challenge 5 workflow architecture](./images/challenge-05-workflow-architecture.png) + +The diagram above illustrates the full workflow in nine steps: + +❢ A user triggers a sample anomaly in the Aspire **Factory Agent UI** (sample machine + sample metrics). + +❷ The **agent framework workflow** (implemented in .NET) is triggered. + +❸ The workflow invokes the **Anomaly Classification Agent** running in **Foundry Agent Service**. + +❹ The **Anomaly Classification Agent** uses remote MCP tools to call **API Management** and fetch required data. + +❺ The workflow invokes the **Fault Diagnosis Agent** running in **Foundry Agent Service**. + +❻ The **Fault Diagnosis Agent** uses remote MCP tools to call **API Management** and query **Azure AI Search**. + +❼ The workflow invokes the **Repair Planner Agent** locally via A2A, running as a .NET application. It still uses the agent registration in **Foundry**, but executes its logic locally. + +❽ The **Repair Planner Agent** queries **Cosmos DB** locally to retrieve operational data. + +❾ The final two agents (**Maintenance Scheduler Agent** and **Parts Ordering Agent**) are implemented in Python and also run locally in a separate process (similar to the **Repair Planner Agent**). They're invoked via A2A and query **Cosmos DB** directly. + +In this challenge, you built and ran a solution where agents are **part of the application**, not the entire application. + +* **Choosing different tech stacks is a feature, not a bug**: you can implement agents in Python or C# depending on what they need to do (data access, performance, existing libraries, team skills). +* **Agents can be polyglots**: different agents can be built in different languages and still work together through standard invocation patterns (like A2A). +* **This is close to traditional application development**: you still assemble services, define interfaces, configure environments, and diagnose issues with logs and traces β€” agents simply enhance the solution's capabilities. + +### Alternative: DevUI for lightweight workflow visualization + +In this challenge we used **Aspire** as the workflow host, which provides comprehensive orchestration and observability for multi-service applications. However, if you only need simple workflow visualization during development β€” without the full Aspire infrastructure β€” the **Agent Framework** also includes **DevUI**. + +**DevUI** is a lightweight development tool that provides: + +* Real-time visualization of agent workflow execution +* Inspection of agent inputs, outputs, and tool calls +* A simpler setup for quick iteration during development + +DevUI is ideal when you're focused on debugging a single agent or workflow and don't need the full distributed tracing and service orchestration that Aspire provides. + +If you want to expand your knowledge on what we've covered in this challenge, have a look at the content below: + +* [Microsoft Agent Framework Workflows](https://learn.microsoft.com/agent-framework/user-guide/workflows/overview) +* [What is .NET Aspire?](https://aspire.dev/get-started/what-is-aspire/) +* [Agent-to-Agent in Foundry](https://learn.microsoft.com/azure/ai-foundry/agents/how-to/tools/agent-to-agent?view=foundry&pivots=python) +* [Agent-to-Agent API in Azure API Management](https://learn.microsoft.com/azure/api-management/agent-to-agent-api) +* [DevUI for Agent Framework](https://learn.microsoft.com/agent-framework/user-guide/devui/?pivots=programming-language-csharp) + +--- + +You've now completed the final challenge of the Predictive Maintenance multi-agent workflow hack. You have a complete, end-to-end agentic system that combines: + +* **πŸ€– AI-powered agents** for anomaly detection, diagnosis, and planning +* **πŸ”Œ MCP integration** for governed, extensible tool connectivity +* **πŸ“Š Observability** with Aspire dashboard and traces to monitor and debug execution +* **πŸ–₯️ A web frontend** for business-user interaction and workflow visualization + +Your system is ready to be extended with additional agents and tools, integrated with other enterprise systems, and deployed to a hosting environment (for example, as a containerized application). Great work! + +**Next:** [Wrap-up](finish.md) diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/finish.md b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/finish.md new file mode 100644 index 000000000..999452baa --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/finish.md @@ -0,0 +1,9 @@ +# Finish + +[Previous Challenge](challenge-05.md) - **[Home](../README.md)** + +## Congratulations + +You finished the Predictive Maintenance MicroHack. We hope you learned how to design and implement a production-ready, multi-agent predictive maintenance solution β€” combining Foundry Agent Service, MCP tooling, Foundry IQ, agent memory and observability, and a polyglot Aspire-hosted workflow. + +Thank you for investing the time, and see you next time! diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/agentic-maintenance-orchestration.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/agentic-maintenance-orchestration.png new file mode 100644 index 000000000..9b9f23b3f Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/agentic-maintenance-orchestration.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-01-azure-portal-resources.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-01-azure-portal-resources.png new file mode 100644 index 000000000..6b5e1920e Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-01-azure-portal-resources.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-01-azure-resources.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-01-azure-resources.png new file mode 100644 index 000000000..c83f01f8f Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-01-azure-resources.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-ai-gateway.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-ai-gateway.png new file mode 100644 index 000000000..bf641b8b8 Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-ai-gateway.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-azure-resources.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-azure-resources.png new file mode 100644 index 000000000..8d6cfa6e8 Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-azure-resources.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-foundry-iq.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-foundry-iq.png new file mode 100644 index 000000000..11bd87967 Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-foundry-iq.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-foundry-portal-anomaly-classification-agent.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-foundry-portal-anomaly-classification-agent.png new file mode 100644 index 000000000..ddfa6d28e Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-foundry-portal-anomaly-classification-agent.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-foundry-portal-build-tab.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-foundry-portal-build-tab.png new file mode 100644 index 000000000..892af20c1 Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-foundry-portal-build-tab.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-foundry-portal-playground.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-foundry-portal-playground.png new file mode 100644 index 000000000..e8cc32f87 Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-foundry-portal-playground.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-mcp-url.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-mcp-url.png new file mode 100644 index 000000000..e57bb0ab1 Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-mcp-url.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-mcp.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-mcp.png new file mode 100644 index 000000000..4c2f94a89 Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-mcp.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-new-foundry-portal.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-new-foundry-portal.png new file mode 100644 index 000000000..f646a5193 Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-new-foundry-portal.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-portal-apis.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-portal-apis.png new file mode 100644 index 000000000..34b336664 Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-portal-apis.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-portal-expose-mcp.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-portal-expose-mcp.png new file mode 100644 index 000000000..84233be1b Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-portal-expose-mcp.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-portal-knowledge-source.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-portal-knowledge-source.png new file mode 100644 index 000000000..642ea045b Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-portal-knowledge-source.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-scenario.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-scenario.png new file mode 100644 index 000000000..0daf2c6bb Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-scenario.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-select-python-environment.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-select-python-environment.png new file mode 100644 index 000000000..fd8a6aa45 Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-select-python-environment.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-task-1.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-task-1.png new file mode 100644 index 000000000..2392af36d Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-task-1.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-task-2.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-task-2.png new file mode 100644 index 000000000..3ee84ee90 Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-task-2.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-task-3.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-task-3.png new file mode 100644 index 000000000..4bc128ec1 Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-task-3.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-wiki-storage-page.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-wiki-storage-page.png new file mode 100644 index 000000000..7cf2bc30d Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-02-wiki-storage-page.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-03-agentplanning-selection.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-03-agentplanning-selection.png new file mode 100644 index 000000000..b5e4d5e50 Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-03-agentplanning-selection.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-03-copilot-instructions.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-03-copilot-instructions.png new file mode 100644 index 000000000..97859a512 Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-03-copilot-instructions.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-03-scenario.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-03-scenario.png new file mode 100644 index 000000000..52bea25bc Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-03-scenario.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-03-target-solution.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-03-target-solution.png new file mode 100644 index 000000000..21da84750 Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-03-target-solution.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-04-application-insights.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-04-application-insights.png new file mode 100644 index 000000000..3c743eeff Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-04-application-insights.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-04-azure-resources.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-04-azure-resources.png new file mode 100644 index 000000000..f285f2dee Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-04-azure-resources.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-04-monitor-portal.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-04-monitor-portal.png new file mode 100644 index 000000000..3ebb87f23 Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-04-monitor-portal.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-04-open-in-monitor.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-04-open-in-monitor.png new file mode 100644 index 000000000..513ae4eab Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-04-open-in-monitor.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-04-scenario.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-04-scenario.png new file mode 100644 index 000000000..8c3dabd22 Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-04-scenario.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-04-task-1.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-04-task-1.png new file mode 100644 index 000000000..4d413da65 Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-04-task-1.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-04-task-2.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-04-task-2.png new file mode 100644 index 000000000..401d8dad9 Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-04-task-2.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-04-task-3.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-04-task-3.png new file mode 100644 index 000000000..550c4ec99 Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-04-task-3.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-05-agent-workflow-ui-end.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-05-agent-workflow-ui-end.png new file mode 100644 index 000000000..efa9a5dd4 Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-05-agent-workflow-ui-end.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-05-agent-workflow-ui-inprogress.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-05-agent-workflow-ui-inprogress.png new file mode 100644 index 000000000..45092a732 Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-05-agent-workflow-ui-inprogress.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-05-agent-workflow-ui-start.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-05-agent-workflow-ui-start.png new file mode 100644 index 000000000..618c498cd Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-05-agent-workflow-ui-start.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-05-agent-workflow-ui-toolcall.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-05-agent-workflow-ui-toolcall.png new file mode 100644 index 000000000..5df949cdc Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-05-agent-workflow-ui-toolcall.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-05-azure-resources.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-05-azure-resources.png new file mode 100644 index 000000000..3c50d29e0 Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-05-azure-resources.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-05-dashboard-console.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-05-dashboard-console.png new file mode 100644 index 000000000..7f2a7fa60 Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-05-dashboard-console.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-05-dashboard-link.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-05-dashboard-link.png new file mode 100644 index 000000000..db331ae27 Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-05-dashboard-link.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-05-dashboard-resources.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-05-dashboard-resources.png new file mode 100644 index 000000000..2c0b7cc8d Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-05-dashboard-resources.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-05-dashboard-traces.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-05-dashboard-traces.png new file mode 100644 index 000000000..636e40aba Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-05-dashboard-traces.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-05-frontend-link.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-05-frontend-link.png new file mode 100644 index 000000000..efe2e3cee Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-05-frontend-link.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-05-ports.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-05-ports.png new file mode 100644 index 000000000..e9ec00a52 Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-05-ports.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-05-scenario.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-05-scenario.png new file mode 100644 index 000000000..61824e78f Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-05-scenario.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-05-workflow-architecture.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-05-workflow-architecture.png new file mode 100644 index 000000000..250ade2c4 Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/challenge-05-workflow-architecture.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/dataexplorer-sample-query.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/dataexplorer-sample-query.png new file mode 100644 index 000000000..96d78072d Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/dataexplorer-sample-query.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/hackathon-architecture.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/hackathon-architecture.png new file mode 100644 index 000000000..b307b57b2 Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/hackathon-architecture.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/maintenance-scenario.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/maintenance-scenario.png new file mode 100644 index 000000000..c72a2b747 Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/challenges/images/maintenance-scenario.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/README.md b/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/README.md new file mode 100644 index 000000000..87861e6c5 --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/README.md @@ -0,0 +1,70 @@ +# Lab automation + +This folder is optional. Delete it if this MicroHack does not need automated Azure provisioning. + +When present, the MicroHack platform reads [lab-defaults.json](lab-defaults.json) to determine how to scope lab environments. It invokes [deploy-lab.ps1](deploy-lab.ps1) once per participant and [shared-deploy-lab.ps1](shared-deploy-lab.ps1) once per subscription before participant deployments begin. + +## Folder layout + +```text +labautomation/ +|-- data/ # Canonical seed JSON and kb-wiki markdown; included in the lab package +|-- lab-defaults.json # Platform-facing configuration +|-- deploy-lab.ps1 # Per-participant deployment hook + data seeding +|-- shared-deploy-lab.ps1 # Resource-provider registration (once per subscription) +|-- main.bicep # Predictive Maintenance infrastructure +`-- get-keys.sh # Participant-run script: resolves resource names/keys into a repo-root .env +``` + +## What `main.bicep` provisions + +- Storage account with a `machine-wiki` blob container (knowledge base articles). +- Cosmos DB account (serverless) with the `FactoryOpsDB` database and its 10 containers + (partition keys and the 30-day TTL on `Telemetry` match the source hack's seeding script). +- Azure AI Search, Microsoft Foundry account/project with the `gpt-5.4-mini`, `gpt-5.4`, and + `text-embedding-3-large` model deployments, and the AI Search/App Insights connections. +- API Management (`Machine API` and `Maintenance API`), including the inline XML policies that + query Cosmos DB directly via APIM's managed identity β€” fully declarative, no post-deployment + script needed for these two APIs. +- Container Registry and a Container Apps managed environment, for participants who choose the + optional stretch goal in Challenge 5 (deploying the Aspire app as a container). No container app + is pre-created; participants build and deploy their own image. +- Role assignments granting the participant (`userObjectId`) the data-plane access they need + (Cosmos DB Data Contributor, Storage Blob Data Contributor, Foundry "Azure AI User") in addition + to the subscription/resource-group Owner access the Hackbox platform already grants. + +## Data seeding + +Cosmos DB documents and the knowledge-base markdown files are **not** part of `main.bicep` β€” they +are actual data content, not infrastructure, so they can't be expressed declaratively. Instead, +`deploy-lab.ps1` seeds them automatically right after the Bicep deployment succeeds, using only +PowerShell and the `az` CLI (no bash, no Python): + +- Cosmos DB documents: read from [data/](./data/) inside this folder and inserted via signed Cosmos DB + REST calls (HMAC-SHA256 over the account's primary key β€” the same authentication scheme the + Cosmos SDKs use internally, reimplemented here in plain PowerShell). +- Knowledge base articles: uploaded from [data/kb-wiki/](./data/kb-wiki/) to the `machine-wiki` blob container + via `az storage blob upload-batch`. + +Blob uploads disable CLI progress reporting because its stderr output is otherwise interpreted +as child-job errors by Hack Console. Actual errors remain visible and nonzero exit codes fail the script. + +Upload the entire contents of `labautomation/`, including its `data/` subfolder, to Hack Console. +These are the only copies of the seed assets; no sibling folder or runtime download is required. +The deployment script checks that all required JSON files and the wiki folder are present before +provisioning. Missing assets, document upsert failures, and blob upload failures terminate the script +instead of reporting a successfully seeded lab. + +This means participants never need to run a manual seeding step β€” by the time they receive their +Hackbox credentials, the data is already loaded. `walkthrough/challenge-01/solution-01.md` +documents a manual fallback re-seed command in case this automatic step needs to be repeated. + +## Authoring guidance + +- Keep each participant deployment isolated. +- Put shared resources and one-time subscription preparation in `shared-deploy-lab.ps1`. +- Use `Get-MhhStableHash` when deterministic per-participant resource names are required. +- Return participant-facing values as `HackboxCredential` hashtables. +- Never return participant-specific secrets from the shared deployment hook. + +For the complete platform contract and helper cmdlet reference, see the [upstream MicroHack template documentation](https://github.com/microsoft/MicroHack/blob/main/99-MicroHack-Template/labautomation/README.md). diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/data/kb-wiki/banbury_mixer.md b/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/data/kb-wiki/banbury_mixer.md new file mode 100644 index 000000000..123623960 --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/data/kb-wiki/banbury_mixer.md @@ -0,0 +1,63 @@ +ο»Ώ--- +title: Banbury Mixer +machineType: banbury_mixer +domain: tire-manufacturing +summary: Troubleshooting and maintenance guidance derived from factory KB +last_reviewed: 2025-12-29 +--- + +# Banbury Mixer + +This page summarizes common issues, diagnostic steps, and fixes for Banbury mixing operations. + +## Operating Thresholds + +- Mixing temperature: normal ≀ 160Β°C (excessive if above 160Β°C) +- Mixer vibration: normal ≀ 5.5 mm/s (excessive if above 5.5 mm/s) + +## Mixing Temperature Excessive (Priority: High) + +- Fault Type: mixing_temperature_excessive +- Symptoms: Temperature above 160Β°C; compound scorching; rotor drive overload; poor compound quality +- Likely Causes: Excessive mixing speed; prolonged mixing time; inadequate cooling water; rotor tip wear; overloaded batch size +- Diagnostics: + - Review mixing recipe parameters + - Check cooling water flow and temperature + - Inspect rotor tip condition + - Verify batch weight accuracy + - Monitor power consumption profile +- Corrective Actions: + - Reduce rotor speed or mixing time + - Increase cooling water flow rate + - Replace worn rotor tips + - Reduce batch size + - Optimize mixing recipe +- Estimated Repair Time: 2–6 hours +- Impact: Compound properties; cure characteristics + +## Excessive Mixer Vibration (Priority: High) + +- Fault Type: excessive_mixer_vibration +- Symptoms: Vibration above 5.5 mm/s; unusual noise; foundation cracking; safety concerns +- Likely Causes: Rotor imbalance from uneven compound buildup; bearing wear; misaligned drive components; loose anchor bolts; gear wear +- Diagnostics: + - Perform vibration frequency analysis + - Inspect rotors for compound buildup + - Check bearing condition + - Verify drive alignment + - Inspect foundation anchor bolts +- Corrective Actions: + - Clean rotors thoroughly + - Replace worn bearings + - Realign motor and gearbox + - Tighten foundation bolts + - Replace worn gears +- Estimated Repair Time: 4–12 hours +- Impact: Safety; equipment damage risk; production downtime + +--- + +## Notes + +- Include rotor cleaning in PM; trend vibration spectrum over time. +- Confirm foundation integrity in audits to prevent progressive damage. diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/data/kb-wiki/tire_building_machine.md b/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/data/kb-wiki/tire_building_machine.md new file mode 100644 index 000000000..31b18a0f1 --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/data/kb-wiki/tire_building_machine.md @@ -0,0 +1,65 @@ +ο»Ώ--- +title: Tire Building Machine +machineType: tire_building_machine +domain: tire-manufacturing +summary: Troubleshooting and maintenance guidance derived from factory KB +last_reviewed: 2025-12-29 +--- + +# Tire Building Machine + +This page summarizes common issues, diagnostic steps, and fixes for tire building machines. + +## Operating Thresholds + +- Drum vibration: normal ≀ 3.0 mm/s (excessive if above 3.0 mm/s) +- Ply tension: normal ≀ 230 N (excessive if above 230 N) + +--- + +## Building Drum Vibration (Priority: High) + +- Fault Type: building_drum_vibration +- Symptoms: Vibration above 3.0 mm/s; uneven tire build; material placement errors; unusual noise +- Likely Causes: Bearing wear on drum shaft; drum imbalance; loose mounting bolts; misaligned drive components; worn coupling +- Diagnostics: + - Perform vibration analysis on drum bearings + - Check drum balance with dial indicator + - Inspect all mounting hardware + - Verify drive alignment + - Examine coupling for wear +- Corrective Actions: + - Replace drum shaft bearings + - Balance drum assembly + - Tighten mounting bolts to specification + - Realign servo motor and drum shaft + - Replace worn coupling +- Estimated Repair Time: 4–8 hours +- Impact: Tire build quality; uniformity issues + +## Ply Tension Excessive (Priority: Medium) + +- Fault Type: ply_tension_excessive +- Symptoms: Tension exceeds 230 N; material stretching; inconsistent tire dimensions; servo alarms +- Likely Causes: Tension roller misalignment; servo motor tuning issues; material property variation; dancer arm malfunction; load cell calibration drift +- Diagnostics: + - Check tension roller alignment + - Review servo motor parameters + - Verify incoming material specifications + - Test dancer arm movement + - Calibrate tension load cells +- Corrective Actions: + - Align tension rollers properly + - Retune servo motor PID parameters + - Adjust for material batch variations + - Repair or replace dancer arm mechanism + - Recalibrate tension measurement system +- Estimated Repair Time: 2–4 hours +- Impact: Tire dimensional accuracy; structural integrity + +--- + +## Notes + +- Keep a maintenance log for alignment checks and PID tune changes. +- Calibrate load cells routinely; verify after any mechanical interventions. diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/data/kb-wiki/tire_curing_press.md b/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/data/kb-wiki/tire_curing_press.md new file mode 100644 index 000000000..456a9a9a6 --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/data/kb-wiki/tire_curing_press.md @@ -0,0 +1,63 @@ +ο»Ώ--- +title: Tire Curing Press +machineType: tire_curing_press +domain: tire-manufacturing +summary: Troubleshooting and maintenance guidance derived from factory KB +last_reviewed: 2025-12-29 +--- + +# Tire Curing Press + +This page summarizes common issues, diagnostic steps, and fixes for tire curing presses. + +## Operating Thresholds + +- Temperature: normal ≀ 178Β°C (excessive if above 178Β°C) +- Curing cycle time: target ≀ 14 minutes (deviation if exceeds 14 min) + +## Curing Temperature Excessive (Priority: High) + +- Fault Type: curing_temperature_excessive +- Symptoms: Temperature exceeds 178Β°C; tire surface scorching; extended cycle times; bladder damage +- Likely Causes: Heating element malfunction; temperature sensor drift; steam pressure too high; thermostat failure; inadequate cooling water flow +- Diagnostics: + - Verify temperature sensor calibration + - Check heating element resistance + - Inspect steam pressure regulator + - Test thermostat response + - Measure cooling water flow rate +- Corrective Actions: + - Calibrate or replace temperature sensors + - Replace faulty heating elements + - Adjust steam pressure settings + - Replace thermostat controller + - Clean or replace cooling system components +- Estimated Repair Time: 2–4 hours +- Impact: Tire quality defects; increased scrap rate + +## Curing Cycle Time Deviation (Priority: High) + +- Fault Type: curing_cycle_time_deviation +- Symptoms: Cycle time exceeds 14 minutes; inconsistent cure; under- or over-cured tires +- Likely Causes: Mold heating inefficiency; steam supply pressure drop; bladder leakage; control system programming error; worn mold seals +- Diagnostics: + - Monitor steam supply pressure throughout the cycle + - Inspect bladder for leaks or damage + - Review PLC program timing sequences + - Check mold seal condition + - Verify heating element power draw +- Corrective Actions: + - Service steam supply system + - Replace damaged bladder + - Reprogram or update PLC parameters + - Replace mold seals + - Clean or replace heating elements +- Estimated Repair Time: 3–6 hours +- Impact: Reduced throughput; tire quality issues + +--- + +## Notes + +- Maintain accurate sensor calibration and log periodic checks. +- Track cycle time and temperature trends to detect drift early. diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/data/kb-wiki/tire_extruder.md b/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/data/kb-wiki/tire_extruder.md new file mode 100644 index 000000000..585e92aa6 --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/data/kb-wiki/tire_extruder.md @@ -0,0 +1,63 @@ +ο»Ώ--- +title: Tire Extruder +machineType: tire_extruder +domain: tire-manufacturing +summary: Troubleshooting and maintenance guidance derived from factory KB +last_reviewed: 2025-12-29 +--- + +# Tire Extruder + +This page summarizes common issues, diagnostic steps, and fixes for tire compound extrusion. + +## Operating Thresholds + +- Barrel temperature: normal ≀ 118Β°C (overheating if above 118Β°C) +- Throughput: target β‰₯ 650 kg/h (low throughput if below 650 kg/h) + +## Extruder Barrel Overheating (Priority: High) + +- Fault Type: extruder_barrel_overheating +- Symptoms: Temperature exceeds 118Β°C; material degradation; smoke from die; discolored compound +- Likely Causes: Cooling system failure; screw wear causing excessive friction; material residence time too long; temperature control malfunction; blocked cooling passages +- Diagnostics: + - Verify cooling water flow and temperature + - Inspect screw and barrel for wear + - Check material feed rate consistency + - Test temperature controller operation + - Inspect cooling jacket for blockages +- Corrective Actions: + - Repair cooling system; increase water flow + - Replace worn screw or barrel + - Increase feed rate or adjust process + - Replace temperature controller + - Clean cooling passages +- Estimated Repair Time: 4–12 hours +- Impact: Material quality; compound properties compromised + +## Low Material Throughput (Priority: Medium) + +- Fault Type: low_material_throughput +- Symptoms: Output below 650 kg/h; production bottleneck; inconsistent extrusion rate +- Likely Causes: Screw wear reducing pumping efficiency; die restriction or blockage; feed throat bridging; motor drive issues; material feed problems +- Diagnostics: + - Measure screw wear with borescope + - Inspect die for carbon buildup + - Check feed throat for material bridging + - Monitor motor current and speed + - Verify material feed system operation +- Corrective Actions: + - Replace worn screw elements + - Clean or replace die + - Install or adjust feed throat heating/cooling + - Service variable frequency drive (VFD) + - Repair material feeding equipment +- Estimated Repair Time: 3–8 hours +- Impact: Production capacity; downstream starvation + +--- + +## Notes + +- Trend temperature vs. motor load to spot friction-related wear. +- Include die cleaning in preventive maintenance to stabilize throughput. diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/data/kb-wiki/tire_uniformity_machine.md b/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/data/kb-wiki/tire_uniformity_machine.md new file mode 100644 index 000000000..ed7e50f4d --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/data/kb-wiki/tire_uniformity_machine.md @@ -0,0 +1,62 @@ +ο»Ώ--- +title: Tire Uniformity Machine +machineType: tire_uniformity_machine +domain: tire-manufacturing +summary: Troubleshooting and maintenance guidance derived from factory KB +last_reviewed: 2025-12-29 +--- + +# Tire Uniformity Machine + +This page summarizes common issues, diagnostic steps, and fixes for tire uniformity testing. + +## Operating Thresholds + +- RFV (Radial Force Variation): target ≀ 100 N (high if exceeds 100 N) + +## High Radial Force Variation (Priority: High) + +- Fault Type: high_radial_force_variation +- Symptoms: RFV exceeds 100 N; tire vibration concerns; customer complaints; failed quality tests +- Likely Causes: Building process inconsistency; curing press issues; material non-uniformity; mold damage; green tire storage conditions +- Diagnostics: + - Analyze RFV waveform for patterns + - Trace tire back to building machine + - Review curing press data for the tire + - Inspect mold condition + - Check material lot numbers +- Corrective Actions: + - Adjust building machine parameters + - Service or repair curing press + - Quarantine affected material lots + - Repair or replace damaged mold + - Improve green tire storage practices +- Estimated Time: 1–2 hours analysis + upstream fixes +- Impact: Customer satisfaction; ride quality + +## Load Cell Drift (Priority: Medium) + +- Fault Type: load_cell_drift +- Symptoms: Inconsistent measurements; calibration check failures; trending data shift +- Likely Causes: Load cell aging; temperature variations; mechanical binding; electrical noise interference; previous mechanical shock +- Diagnostics: + - Run master tire calibration check + - Verify environmental temperature stability + - Check for mechanical binding in load frame + - Test signal cables for noise + - Review maintenance history for impacts +- Corrective Actions: + - Perform full system recalibration + - Replace load cells if out of specification + - Improve temperature control + - Free up mechanical bindings + - Replace damaged cables; add shielding +- Estimated Repair Time: 2–4 hours +- Impact: Measurement accuracy; false accept/reject decisions + +--- + +## Notes + +- Keep calibration artifacts controlled; trend drift vs. ambient temperature. +- Ensure test recipes and tire genealogy remain synchronized with production. diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/data/knowledge-base.json b/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/data/knowledge-base.json new file mode 100644 index 000000000..187621473 --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/data/knowledge-base.json @@ -0,0 +1,322 @@ +[ + { + "id": "kb-curing-overheating", + "category": "troubleshooting", + "machineType": "tire_curing_press", + "issue": "Curing Temperature Excessive", + "faultType": "curing_temperature_excessive", + "symptoms": [ "Temperature exceeds 178Β°C", "Tire surface scorching", "Extended cycle times", "Bladder damage" ], + "possibleCauses": [ + "Heating element malfunction", + "Temperature sensor drift", + "Steam pressure too high", + "Thermostat failure", + "Inadequate cooling water flow" + ], + "diagnosticSteps": [ + "Verify temperature sensor calibration", + "Check heating element resistance", + "Inspect steam pressure regulator", + "Test thermostat response", + "Measure cooling water flow rate" + ], + "solutions": [ + "Calibrate or replace temperature sensors", + "Replace faulty heating elements", + "Adjust steam pressure settings", + "Replace thermostat controller", + "Clean or replace cooling system components" + ], + "priority": "high", + "estimatedRepairTime": "2-4 hours", + "impact": "Tire quality defects, increased scrap rate" + }, + { + "id": "kb-curing-cycle-deviation", + "category": "troubleshooting", + "machineType": "tire_curing_press", + "issue": "Curing Cycle Time Deviation", + "faultType": "curing_cycle_time_deviation", + "symptoms": [ "Cycle time exceeds 14 minutes", "Inconsistent cure", "Under-cured or over-cured tires" ], + "possibleCauses": [ + "Mold heating inefficiency", + "Steam supply pressure drop", + "Bladder leakage", + "Control system programming error", + "Worn mold seals" + ], + "diagnosticSteps": [ + "Monitor steam supply pressure throughout cycle", + "Inspect bladder for leaks or damage", + "Review PLC program timing sequences", + "Check mold seal condition", + "Verify heating element power draw" + ], + "solutions": [ + "Service steam supply system", + "Replace damaged bladder", + "Reprogram or update PLC parameters", + "Replace mold seals", + "Clean or replace heating elements" + ], + "priority": "high", + "estimatedRepairTime": "3-6 hours", + "impact": "Reduced throughput, tire quality issues" + }, + { + "id": "kb-building-vibration", + "category": "troubleshooting", + "machineType": "tire_building_machine", + "issue": "Building Drum Vibration", + "faultType": "building_drum_vibration", + "symptoms": [ "Vibration above 3.0 mm/s", "Uneven tire build", "Material placement errors", "Unusual noise" ], + "possibleCauses": [ + "Bearing wear on drum shaft", + "Drum imbalance", + "Loose mounting bolts", + "Misaligned drive components", + "Worn coupling" + ], + "diagnosticSteps": [ + "Perform vibration analysis on drum bearings", + "Check drum balance with dial indicator", + "Inspect all mounting hardware", + "Verify drive alignment", + "Examine coupling for wear" + ], + "solutions": [ + "Replace drum shaft bearings", + "Balance drum assembly", + "Tighten all mounting bolts to specification", + "Realign servo motor and drum shaft", + "Replace worn coupling" + ], + "priority": "high", + "estimatedRepairTime": "4-8 hours", + "impact": "Tire build quality, uniformity issues" + }, + { + "id": "kb-building-tension", + "category": "troubleshooting", + "machineType": "tire_building_machine", + "issue": "Ply Tension Excessive", + "faultType": "ply_tension_excessive", + "symptoms": [ "Tension exceeds 230N", "Material stretching", "Inconsistent tire dimensions", "Servo alarms" ], + "possibleCauses": [ + "Tension roller misalignment", + "Servo motor tuning issues", + "Material property variation", + "Dancer arm malfunction", + "Load cell calibration drift" + ], + "diagnosticSteps": [ + "Check tension roller alignment", + "Review servo motor parameters", + "Verify incoming material specifications", + "Test dancer arm movement", + "Calibrate tension load cells" + ], + "solutions": [ + "Align tension rollers properly", + "Retune servo motor PID parameters", + "Adjust for material batch variations", + "Repair or replace dancer arm mechanism", + "Recalibrate tension measurement system" + ], + "priority": "medium", + "estimatedRepairTime": "2-4 hours", + "impact": "Tire dimensional accuracy, structural integrity" + }, + { + "id": "kb-extruder-overheating", + "category": "troubleshooting", + "machineType": "tire_extruder", + "issue": "Extruder Barrel Overheating", + "faultType": "extruder_barrel_overheating", + "symptoms": [ "Temperature exceeds 118Β°C", "Material degradation", "Smoke from die", "Discolored compound" ], + "possibleCauses": [ + "Cooling system failure", + "Screw wear causing excessive friction", + "Material residence time too long", + "Temperature control malfunction", + "Blocked cooling passages" + ], + "diagnosticSteps": [ + "Verify cooling water flow and temperature", + "Inspect screw and barrel for wear", + "Check material feed rate consistency", + "Test temperature controller operation", + "Inspect cooling jacket for blockages" + ], + "solutions": [ + "Repair cooling system, increase water flow", + "Replace worn screw or barrel", + "Increase feed rate or adjust process", + "Replace temperature controller", + "Clean cooling passages" + ], + "priority": "high", + "estimatedRepairTime": "4-12 hours", + "impact": "Material quality, compound properties compromised" + }, + { + "id": "kb-extruder-low-throughput", + "category": "troubleshooting", + "machineType": "tire_extruder", + "issue": "Low Material Throughput", + "faultType": "low_material_throughput", + "symptoms": [ "Output below 650 kg/h", "Production bottleneck", "Inconsistent extrusion rate" ], + "possibleCauses": [ + "Screw wear reducing pumping efficiency", + "Die restriction or blockage", + "Feed throat bridging", + "Motor drive issues", + "Material feed problems" + ], + "diagnosticSteps": [ + "Measure screw wear with bore scope", + "Inspect die for carbon buildup", + "Check feed throat for material bridging", + "Monitor motor current and speed", + "Verify material feed system operation" + ], + "solutions": [ + "Replace worn screw elements", + "Clean or replace die", + "Install or adjust feed throat heating/cooling", + "Service variable frequency drive", + "Repair material feeding equipment" + ], + "priority": "medium", + "estimatedRepairTime": "3-8 hours", + "impact": "Production capacity, downstream starvation" + }, + { + "id": "kb-uniformity-high-rfv", + "category": "troubleshooting", + "machineType": "tire_uniformity_machine", + "issue": "High Radial Force Variation", + "faultType": "high_radial_force_variation", + "symptoms": [ "RFV exceeds 100N", "Tire vibration concerns", "Customer complaints", "Failed quality tests" ], + "possibleCauses": [ + "Building process inconsistency", + "Curing press issues", + "Material non-uniformity", + "Mold damage", + "Green tire storage conditions" + ], + "diagnosticSteps": [ + "Analyze RFV waveform for patterns", + "Trace tire back to building machine", + "Review curing press data for that tire", + "Inspect mold condition", + "Check material lot numbers" + ], + "solutions": [ + "Adjust building machine parameters", + "Service or repair curing press", + "Quarantine affected material lots", + "Repair or replace damaged mold", + "Improve green tire storage practices" + ], + "priority": "high", + "estimatedRepairTime": "1-2 hours analysis + upstream fixes", + "impact": "Customer satisfaction, ride quality" + }, + { + "id": "kb-uniformity-calibration", + "category": "maintenance", + "machineType": "tire_uniformity_machine", + "issue": "Load Cell Drift", + "faultType": "load_cell_drift", + "symptoms": [ "Inconsistent measurements", "Calibration check failures", "Trending data shift" ], + "possibleCauses": [ + "Load cell aging", + "Temperature variations", + "Mechanical binding", + "Electrical noise interference", + "Previous mechanical shock" + ], + "diagnosticSteps": [ + "Run master tire calibration check", + "Verify environmental temperature stability", + "Check for mechanical binding in load frame", + "Test signal cables for noise", + "Review maintenance history for impacts" + ], + "solutions": [ + "Perform full system recalibration", + "Replace load cells if out of specification", + "Improve temperature control", + "Free up mechanical bindings", + "Replace damaged cables, add shielding" + ], + "priority": "medium", + "estimatedRepairTime": "2-4 hours", + "impact": "Measurement accuracy, false accept/reject decisions" + }, + { + "id": "kb-mixer-overheating", + "category": "troubleshooting", + "machineType": "banbury_mixer", + "issue": "Mixing Temperature Excessive", + "faultType": "mixing_temperature_excessive", + "symptoms": [ "Temperature above 160Β°C", "Compound scorching", "Rotor drive overload", "Poor compound quality" ], + "possibleCauses": [ + "Excessive mixing speed", + "Prolonged mixing time", + "Inadequate cooling water", + "Rotor tip wear", + "Overloaded batch size" + ], + "diagnosticSteps": [ + "Review mixing recipe parameters", + "Check cooling water flow and temperature", + "Inspect rotor tip condition", + "Verify batch weight accuracy", + "Monitor power consumption profile" + ], + "solutions": [ + "Reduce rotor speed or mixing time", + "Increase cooling water flow rate", + "Replace worn rotor tips", + "Reduce batch size", + "Optimize mixing recipe" + ], + "priority": "high", + "estimatedRepairTime": "2-6 hours", + "impact": "Compound properties, cure characteristics" + }, + { + "id": "kb-mixer-vibration", + "category": "troubleshooting", + "machineType": "banbury_mixer", + "issue": "Excessive Mixer Vibration", + "faultType": "excessive_mixer_vibration", + "symptoms": [ "Vibration above 5.5 mm/s", "Unusual noise", "Foundation cracking", "Safety concerns" ], + "possibleCauses": [ + "Rotor imbalance due to uneven compound buildup", + "Bearing wear", + "Misaligned drive components", + "Loose anchor bolts", + "Gear wear" + ], + "diagnosticSteps": [ + "Perform vibration frequency analysis", + "Inspect rotors for compound buildup", + "Check bearing condition", + "Verify drive alignment", + "Inspect foundation anchor bolts" + ], + "solutions": [ + "Clean rotors thoroughly", + "Replace worn bearings", + "Realign motor and gearbox", + "Tighten foundation bolts", + "Replace worn gears" + ], + "priority": "high", + "estimatedRepairTime": "4-12 hours", + "impact": "Safety, equipment damage risk, production downtime" + } +] diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/data/machines.json b/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/data/machines.json new file mode 100644 index 000000000..2f24a2b13 --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/data/machines.json @@ -0,0 +1,149 @@ +[ + { + "id": "machine-001", + "name": "Tire Curing Press A1", + "type": "tire_curing_press", + "location": "Curing Department, Line A", + "manufacturer": "TireTech Systems Inc", + "model": "TCP-4000", + "serialNumber": "TCP-4000-2023-001", + "installDate": "2023-01-15", + "status": "operational", + "operatingHours": 12450, + "cyclesCompleted": 45680, + "maintenanceHistory": [ + { + "date": "2024-11-01", + "type": "preventive", + "description": "Bladder inspection and heating element check", + "technician": "John Smith" + } + ], + "specifications": { + "maxPressure": 200, + "curingTemperature": 170, + "operatingTemperatureRange": { + "min": 150, + "max": 180 + }, + "cycleTime": 12, + "powerConsumption": 150 + } + }, + { + "id": "machine-002", + "name": "Tire Building Machine B1", + "type": "tire_building_machine", + "location": "Building Department, Station B1", + "manufacturer": "AutoTire Manufacturing Ltd", + "model": "TBM-3000", + "serialNumber": "TBM-3000-2022-045", + "installDate": "2022-06-20", + "status": "operational", + "operatingHours": 18920, + "tiresBuilt": 67840, + "maintenanceHistory": [ + { + "date": "2024-10-15", + "type": "corrective", + "description": "Drum bearing replacement and alignment", + "technician": "Maria Garcia" + } + ], + "specifications": { + "drumDiameter": 800, + "maxTireWidth": 315, + "operatingTemperatureRange": { + "min": 18, + "max": 35 + }, + "buildCycleTime": 4.5, + "powerConsumption": 45 + } + }, + { + "id": "machine-003", + "name": "Tire Extruder C1", + "type": "tire_extruder", + "location": "Mixing Department, Line C", + "manufacturer": "RubberTech Industries", + "model": "EXT-2500", + "serialNumber": "EXT-2500-2023-012", + "installDate": "2023-03-10", + "status": "operational", + "operatingHours": 15230, + "totalOutput": 1245000, + "maintenanceHistory": [], + "specifications": { + "screwDiameter": 250, + "maxThroughput": 800, + "operatingTemperatureRange": { + "min": 80, + "max": 120 + }, + "extrusionSpeed": 15, + "powerConsumption": 180 + } + }, + { + "id": "machine-004", + "name": "Tire Uniformity Machine D1", + "type": "tire_uniformity_machine", + "location": "Quality Control, Station D1", + "manufacturer": "QualityTest Systems Corp", + "model": "TUM-1000", + "serialNumber": "TUM-1000-2021-089", + "installDate": "2021-09-15", + "status": "maintenance_required", + "operatingHours": 24560, + "tiresInspected": 98450, + "maintenanceHistory": [ + { + "date": "2024-11-20", + "type": "preventive", + "description": "Load cell calibration and sensor alignment", + "technician": "David Lee" + } + ], + "specifications": { + "maxLoadCapacity": 1500, + "measurementSpeed": 60, + "operatingTemperatureRange": { + "min": 20, + "max": 30 + }, + "powerConsumption": 35 + } + }, + { + "id": "machine-005", + "name": "Banbury Mixer E1", + "type": "banbury_mixer", + "location": "Mixing Department, Station E1", + "manufacturer": "RubberTech Industries", + "model": "BMX-500", + "serialNumber": "BMX-500-2020-123", + "installDate": "2020-08-10", + "status": "operational", + "operatingHours": 32140, + "batchesCompleted": 15670, + "maintenanceHistory": [ + { + "date": "2024-09-12", + "type": "preventive", + "description": "Rotor blade inspection and seal replacement", + "technician": "Michael Chen" + } + ], + "specifications": { + "chamberVolume": 500, + "maxMixingTemp": 180, + "operatingTemperatureRange": { + "min": 100, + "max": 160 + }, + "mixingCycleTime": 8, + "powerConsumption": 250 + } + } +] diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/data/maintenance-history.json b/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/data/maintenance-history.json new file mode 100644 index 000000000..6c55966a5 --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/data/maintenance-history.json @@ -0,0 +1,146 @@ +[ + { + "id": "mh-001", + "machineId": "machine-001", + "faultType": "Temperature Sensor Failure", + "occurrenceDate": "2025-11-15T14:30:00Z", + "resolutionDate": "2025-11-15T18:45:00Z", + "downtime": 255, + "cost": 1250.00, + "technician": "T-001", + "partsUsed": ["PART-001"], + "description": "Temperature sensor calibration drift detected. Replaced sensor and recalibrated." + }, + { + "id": "mh-002", + "machineId": "machine-001", + "faultType": "Hydraulic Pressure Drop", + "occurrenceDate": "2025-10-22T08:15:00Z", + "resolutionDate": "2025-10-22T16:30:00Z", + "downtime": 495, + "cost": 3200.00, + "technician": "T-002", + "partsUsed": ["PART-009", "PART-010"], + "description": "Hydraulic seal leak causing pressure loss. Replaced seals and tested system." + }, + { + "id": "mh-003", + "machineId": "machine-002", + "faultType": "Excessive Vibration", + "occurrenceDate": "2025-12-01T10:00:00Z", + "resolutionDate": "2025-12-01T14:30:00Z", + "downtime": 270, + "cost": 890.00, + "technician": "T-003", + "partsUsed": ["PART-006"], + "description": "Drum bearing wear detected. Replaced bearing and rebalanced drum." + }, + { + "id": "mh-004", + "machineId": "machine-002", + "faultType": "Ply Tension Sensor Malfunction", + "occurrenceDate": "2025-11-08T13:20:00Z", + "resolutionDate": "2025-11-08T15:45:00Z", + "downtime": 145, + "cost": 450.00, + "technician": "T-004", + "partsUsed": ["PART-005"], + "description": "Tension sensor giving inconsistent readings. Replaced sensor module." + }, + { + "id": "mh-005", + "machineId": "machine-003", + "faultType": "Motor Overheating", + "occurrenceDate": "2025-12-10T16:45:00Z", + "resolutionDate": "2025-12-11T09:30:00Z", + "downtime": 1005, + "cost": 5200.00, + "technician": "T-001", + "partsUsed": ["PART-002", "PART-007"], + "description": "Motor cooling system failure. Replaced cooling fan and thermal sensors." + }, + { + "id": "mh-006", + "machineId": "machine-003", + "faultType": "Screw Wear", + "occurrenceDate": "2025-10-18T07:30:00Z", + "resolutionDate": "2025-10-19T12:00:00Z", + "downtime": 1710, + "cost": 8900.00, + "technician": "T-002", + "partsUsed": ["PART-008"], + "description": "Extruder screw showing significant wear. Replaced screw assembly." + }, + { + "id": "mh-007", + "machineId": "machine-004", + "faultType": "Load Cell Calibration Drift", + "occurrenceDate": "2025-11-25T11:00:00Z", + "resolutionDate": "2025-11-25T13:30:00Z", + "downtime": 150, + "cost": 320.00, + "technician": "T-005", + "partsUsed": [], + "description": "Force measurement accuracy degraded. Recalibrated load cells." + }, + { + "id": "mh-008", + "machineId": "machine-004", + "faultType": "Spindle Bearing Failure", + "occurrenceDate": "2025-12-18T14:15:00Z", + "resolutionDate": "2025-12-19T10:45:00Z", + "downtime": 1230, + "cost": 4500.00, + "technician": "T-001", + "partsUsed": ["PART-011"], + "description": "Spindle bearing seized. Replaced bearing assembly and lubrication system." + }, + { + "id": "mh-009", + "machineId": "machine-005", + "faultType": "Vision System Alignment", + "occurrenceDate": "2025-12-05T09:30:00Z", + "resolutionDate": "2025-12-05T12:00:00Z", + "downtime": 150, + "cost": 280.00, + "technician": "T-006", + "partsUsed": [], + "description": "Camera alignment drift affecting inspection accuracy. Realigned and calibrated." + }, + { + "id": "mh-010", + "machineId": "machine-001", + "faultType": "Pressure Relief Valve Stuck", + "occurrenceDate": "2025-09-30T15:20:00Z", + "resolutionDate": "2025-09-30T19:45:00Z", + "downtime": 265, + "cost": 720.00, + "technician": "T-002", + "partsUsed": ["PART-012"], + "description": "Pressure relief valve not opening properly. Cleaned and replaced valve." + }, + { + "id": "mh-011", + "machineId": "machine-002", + "faultType": "Pneumatic System Leak", + "occurrenceDate": "2025-10-12T08:45:00Z", + "resolutionDate": "2025-10-12T11:30:00Z", + "downtime": 165, + "cost": 340.00, + "technician": "T-003", + "partsUsed": ["PART-013"], + "description": "Air leak in pneumatic control system. Replaced fittings and hoses." + }, + { + "id": "mh-012", + "machineId": "machine-005", + "faultType": "Conveyor Belt Misalignment", + "occurrenceDate": "2025-11-20T13:00:00Z", + "resolutionDate": "2025-11-20T15:30:00Z", + "downtime": 150, + "cost": 210.00, + "technician": "T-004", + "partsUsed": [], + "description": "Conveyor tracking off center. Adjusted alignment and tension." + } +] diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/data/maintenance-windows.json b/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/data/maintenance-windows.json new file mode 100644 index 000000000..26cdbce4a --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/data/maintenance-windows.json @@ -0,0 +1,155 @@ +[ + { + "id": "mw-2026-01-03-night", + "startTime": "2026-01-03T22:00:00Z", + "endTime": "2026-01-04T06:00:00Z", + "productionImpact": "Low", + "isAvailable": true, + "shift": "Night", + "description": "Weekend night shift - minimal production" + }, + { + "id": "mw-2026-01-04-night", + "startTime": "2026-01-04T22:00:00Z", + "endTime": "2026-01-05T06:00:00Z", + "productionImpact": "Low", + "isAvailable": true, + "shift": "Night", + "description": "Weekend night shift - minimal production" + }, + { + "id": "mw-2026-01-06-night", + "startTime": "2026-01-06T22:00:00Z", + "endTime": "2026-01-07T06:00:00Z", + "productionImpact": "Medium", + "isAvailable": true, + "shift": "Night", + "description": "Weekday night shift - reduced production" + }, + { + "id": "mw-2026-01-07-night", + "startTime": "2026-01-07T22:00:00Z", + "endTime": "2026-01-08T06:00:00Z", + "productionImpact": "Medium", + "isAvailable": true, + "shift": "Night", + "description": "Weekday night shift - reduced production" + }, + { + "id": "mw-2026-01-08-night", + "startTime": "2026-01-08T22:00:00Z", + "endTime": "2026-01-09T06:00:00Z", + "productionImpact": "Medium", + "isAvailable": true, + "shift": "Night", + "description": "Weekday night shift - reduced production" + }, + { + "id": "mw-2026-01-09-night", + "startTime": "2026-01-09T22:00:00Z", + "endTime": "2026-01-10T06:00:00Z", + "productionImpact": "Medium", + "isAvailable": true, + "shift": "Night", + "description": "Weekday night shift - reduced production" + }, + { + "id": "mw-2026-01-10-night", + "startTime": "2026-01-10T22:00:00Z", + "endTime": "2026-01-11T06:00:00Z", + "productionImpact": "Low", + "isAvailable": true, + "shift": "Night", + "description": "Weekend night shift - minimal production" + }, + { + "id": "mw-2026-01-11-night", + "startTime": "2026-01-11T22:00:00Z", + "endTime": "2026-01-12T06:00:00Z", + "productionImpact": "Low", + "isAvailable": true, + "shift": "Night", + "description": "Weekend night shift - minimal production" + }, + { + "id": "mw-2026-01-13-night", + "startTime": "2026-01-13T22:00:00Z", + "endTime": "2026-01-14T06:00:00Z", + "productionImpact": "Medium", + "isAvailable": true, + "shift": "Night", + "description": "Weekday night shift - reduced production" + }, + { + "id": "mw-2026-01-14-night", + "startTime": "2026-01-14T22:00:00Z", + "endTime": "2026-01-15T06:00:00Z", + "productionImpact": "Medium", + "isAvailable": true, + "shift": "Night", + "description": "Weekday night shift - reduced production" + }, + { + "id": "mw-2026-01-15-night", + "startTime": "2026-01-15T22:00:00Z", + "endTime": "2026-01-16T06:00:00Z", + "productionImpact": "Medium", + "isAvailable": true, + "shift": "Night", + "description": "Weekday night shift - reduced production" + }, + { + "id": "mw-2026-01-16-night", + "startTime": "2026-01-16T22:00:00Z", + "endTime": "2026-01-17T06:00:00Z", + "productionImpact": "Medium", + "isAvailable": true, + "shift": "Night", + "description": "Weekday night shift - reduced production" + }, + { + "id": "mw-2026-01-17-night", + "startTime": "2026-01-17T22:00:00Z", + "endTime": "2026-01-18T06:00:00Z", + "productionImpact": "Low", + "isAvailable": true, + "shift": "Night", + "description": "Weekend night shift - minimal production" + }, + { + "id": "mw-2026-01-18-night", + "startTime": "2026-01-18T22:00:00Z", + "endTime": "2026-01-19T06:00:00Z", + "productionImpact": "Low", + "isAvailable": true, + "shift": "Night", + "description": "Weekend night shift - minimal production" + }, + { + "id": "mw-2026-01-11-shutdown", + "startTime": "2026-01-11T06:00:00Z", + "endTime": "2026-01-11T18:00:00Z", + "productionImpact": "None", + "isAvailable": true, + "shift": "Day", + "description": "Planned production shutdown - ideal for major maintenance" + }, + { + "id": "mw-2026-01-05-early", + "startTime": "2026-01-05T05:00:00Z", + "endTime": "2026-01-05T08:00:00Z", + "productionImpact": "Low", + "isAvailable": true, + "shift": "Early Morning", + "description": "Early morning window before first shift" + }, + { + "id": "mw-2026-01-12-early", + "startTime": "2026-01-12T05:00:00Z", + "endTime": "2026-01-12T08:00:00Z", + "productionImpact": "Low", + "isAvailable": true, + "shift": "Early Morning", + "description": "Early morning window before first shift" + } +] diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/data/parts-inventory.json b/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/data/parts-inventory.json new file mode 100644 index 000000000..1e4aa385f --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/data/parts-inventory.json @@ -0,0 +1,211 @@ +[ + { + "id": "part-curing-bladder", + "partNumber": "TCP-BLD-800", + "name": "Curing Press Bladder 800mm", + "category": "bladder", + "compatibleMachines": ["tire_curing_press"], + "manufacturer": "TireTech Components", + "quantityInStock": 8, + "reorderLevel": 3, + "unitCost": 1850.00, + "leadTimeDays": 21, + "location": "Warehouse A, Shelf 5" + }, + { + "id": "part-curing-heater", + "partNumber": "TCP-HTR-4KW", + "name": "Mold Heating Element 4kW", + "category": "heating_element", + "compatibleMachines": ["tire_curing_press"], + "manufacturer": "IndustrialHeat Systems", + "quantityInStock": 12, + "reorderLevel": 4, + "unitCost": 385.00, + "leadTimeDays": 14, + "location": "Warehouse A, Shelf 12" + }, + { + "id": "part-curing-seal", + "partNumber": "TCP-SEAL-200", + "name": "Mold Seal Ring 200mm", + "category": "seal", + "compatibleMachines": ["tire_curing_press"], + "manufacturer": "SealPro Industries", + "quantityInStock": 25, + "reorderLevel": 10, + "unitCost": 125.00, + "leadTimeDays": 7, + "location": "Warehouse B, Drawer 8" + }, + { + "id": "part-building-bearing", + "partNumber": "TBM-BRG-6220", + "name": "Drum Shaft Bearing 6220", + "category": "bearing", + "compatibleMachines": ["tire_building_machine"], + "manufacturer": "Precision Bearings Corp", + "quantityInStock": 10, + "reorderLevel": 4, + "unitCost": 420.00, + "leadTimeDays": 10, + "location": "Warehouse A, Shelf 18" + }, + { + "id": "part-building-servo-motor", + "partNumber": "TBM-SRV-5KW", + "name": "Servo Motor 5kW", + "category": "motor", + "compatibleMachines": ["tire_building_machine"], + "manufacturer": "MotionControl Systems", + "quantityInStock": 3, + "reorderLevel": 1, + "unitCost": 2850.00, + "leadTimeDays": 30, + "location": "Warehouse C, Section 2" + }, + { + "id": "part-building-sensor", + "partNumber": "TBM-LS-500N", + "name": "Tension Load Sensor 500N", + "category": "sensor", + "compatibleMachines": ["tire_building_machine"], + "manufacturer": "LoadTech Instruments", + "quantityInStock": 6, + "reorderLevel": 2, + "unitCost": 680.00, + "leadTimeDays": 14, + "location": "Warehouse B, Shelf 15" + }, + { + "id": "part-extruder-screw", + "partNumber": "EXT-SCR-250", + "name": "Extruder Screw 250mm", + "category": "screw", + "compatibleMachines": ["tire_extruder"], + "manufacturer": "ExtraTech Manufacturing", + "quantityInStock": 2, + "reorderLevel": 1, + "unitCost": 8500.00, + "leadTimeDays": 45, + "location": "Warehouse C, Heavy Equipment" + }, + { + "id": "part-extruder-die", + "partNumber": "EXT-DIE-TR", + "name": "Tread Extrusion Die", + "category": "die", + "compatibleMachines": ["tire_extruder"], + "manufacturer": "ExtraTech Manufacturing", + "quantityInStock": 5, + "reorderLevel": 2, + "unitCost": 3200.00, + "leadTimeDays": 35, + "location": "Warehouse C, Section 5" + }, + { + "id": "part-extruder-heater", + "partNumber": "EXT-HTR-BAND", + "name": "Barrel Heater Band", + "category": "heating_element", + "compatibleMachines": ["tire_extruder"], + "manufacturer": "IndustrialHeat Systems", + "quantityInStock": 18, + "reorderLevel": 6, + "unitCost": 285.00, + "leadTimeDays": 12, + "location": "Warehouse A, Shelf 22" + }, + { + "id": "part-uniformity-load-cell", + "partNumber": "TUM-LC-2KN", + "name": "Load Cell 2kN", + "category": "sensor", + "compatibleMachines": ["tire_uniformity_machine"], + "manufacturer": "PrecisionMeasure Inc", + "quantityInStock": 4, + "reorderLevel": 2, + "unitCost": 1850.00, + "leadTimeDays": 20, + "location": "Warehouse A, Shelf 8" + }, + { + "id": "part-uniformity-encoder", + "partNumber": "TUM-ENC-5000", + "name": "Rotary Encoder 5000ppr", + "category": "sensor", + "compatibleMachines": ["tire_uniformity_machine"], + "manufacturer": "EncoderTech Ltd", + "quantityInStock": 3, + "reorderLevel": 1, + "unitCost": 920.00, + "leadTimeDays": 15, + "location": "Warehouse B, Shelf 12" + }, + { + "id": "part-mixer-rotor-tip", + "partNumber": "BMX-TIP-500", + "name": "Mixer Rotor Tip", + "category": "wear_part", + "compatibleMachines": ["banbury_mixer"], + "manufacturer": "MixerParts Intl", + "quantityInStock": 24, + "reorderLevel": 8, + "unitCost": 450.00, + "leadTimeDays": 18, + "location": "Warehouse B, Shelf 28" + }, + { + "id": "part-mixer-seal", + "partNumber": "BMX-SEAL-DP", + "name": "Drop Door Seal", + "category": "seal", + "compatibleMachines": ["banbury_mixer"], + "manufacturer": "SealPro Industries", + "quantityInStock": 6, + "reorderLevel": 2, + "unitCost": 680.00, + "leadTimeDays": 14, + "location": "Warehouse B, Shelf 29" + }, + { + "id": "part-mixer-bearing", + "partNumber": "BMX-BRG-22320", + "name": "Rotor Shaft Bearing 22320", + "category": "bearing", + "compatibleMachines": ["banbury_mixer"], + "manufacturer": "Precision Bearings Corp", + "quantityInStock": 4, + "reorderLevel": 2, + "unitCost": 1250.00, + "leadTimeDays": 21, + "location": "Warehouse A, Shelf 20" + }, + { + "id": "part-temp-sensor", + "partNumber": "GEN-TS-K400", + "name": "K-Type Thermocouple 400C", + "category": "sensor", + "compatibleMachines": ["tire_curing_press", "tire_extruder", "banbury_mixer"], + "manufacturer": "TempSense Tech", + "quantityInStock": 35, + "reorderLevel": 12, + "unitCost": 85.00, + "leadTimeDays": 5, + "location": "Warehouse B, Drawer 5" + }, + { + "id": "part-hydraulic-oil", + "partNumber": "GEN-OIL-HVI46", + "name": "Hydraulic Oil HVI 46", + "category": "fluid", + "compatibleMachines": ["tire_curing_press", "tire_building_machine"], + "manufacturer": "LubeTech Industries", + "quantityInStock": 800, + "unit": "liters", + "reorderLevel": 200, + "unitCost": 12.50, + "leadTimeDays": 3, + "location": "Warehouse D, Tank 2" + } +] diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/data/suppliers.json b/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/data/suppliers.json new file mode 100644 index 000000000..6a86d371b --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/data/suppliers.json @@ -0,0 +1,97 @@ +[ + { + "id": "SUP-001", + "name": "Industrial Parts Co", + "category": "general", + "contactEmail": "orders@industrialparts.com", + "contactPhone": "+1-555-0101", + "address": "123 Industrial Way, Detroit, MI 48201", + "partsSupplied": [ + "TUM-LC-2KN", + "TUM-ENC-5000", + "TBM-BRG-6220", + "TBM-LS-500N", + "GEN-TS-K400" + ], + "leadTimeDays": 5, + "reliability": "high", + "rating": 4.8, + "paymentTerms": "Net 30", + "minimumOrderValue": 100.00 + }, + { + "id": "SUP-002", + "name": "PrecisionMeasure Inc", + "category": "sensors", + "contactEmail": "sales@precisionmeasure.com", + "contactPhone": "+1-555-0202", + "address": "456 Sensor Drive, Cleveland, OH 44101", + "partsSupplied": [ + "TUM-LC-2KN", + "TUM-ENC-5000", + "TBM-LS-500N", + "GEN-TS-K400" + ], + "leadTimeDays": 7, + "reliability": "high", + "rating": 4.9, + "paymentTerms": "Net 45", + "minimumOrderValue": 500.00 + }, + { + "id": "SUP-003", + "name": "TireTech Components", + "category": "tire_equipment", + "contactEmail": "supply@tiretech.com", + "contactPhone": "+1-555-0303", + "address": "789 Rubber Road, Akron, OH 44301", + "partsSupplied": [ + "TCP-BLD-800", + "TCP-HTR-4KW", + "TCP-SEAL-200", + "BMX-TIP-500", + "BMX-SEAL-DP", + "BMX-BRG-22320" + ], + "leadTimeDays": 14, + "reliability": "medium", + "rating": 4.2, + "paymentTerms": "Net 30", + "minimumOrderValue": 250.00 + }, + { + "id": "SUP-004", + "name": "Precision Bearings Corp", + "category": "bearings", + "contactEmail": "orders@precisionbearings.com", + "contactPhone": "+1-555-0404", + "address": "321 Bearing Blvd, Toledo, OH 43601", + "partsSupplied": [ + "TBM-BRG-6220", + "BMX-BRG-22320" + ], + "leadTimeDays": 10, + "reliability": "high", + "rating": 4.7, + "paymentTerms": "Net 30", + "minimumOrderValue": 200.00 + }, + { + "id": "SUP-005", + "name": "ExtraTech Manufacturing", + "category": "extrusion", + "contactEmail": "parts@extratech.com", + "contactPhone": "+1-555-0505", + "address": "555 Extrusion Ave, Pittsburgh, PA 15201", + "partsSupplied": [ + "EXT-SCR-250", + "EXT-DIE-TR", + "EXT-HTR-BAND" + ], + "leadTimeDays": 21, + "reliability": "medium", + "rating": 4.0, + "paymentTerms": "Net 60", + "minimumOrderValue": 1000.00 + } +] diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/data/technicians.json b/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/data/technicians.json new file mode 100644 index 000000000..c1b60118e --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/data/technicians.json @@ -0,0 +1,146 @@ +[ + { + "id": "tech-001", + "employeeId": "EMP-1024", + "name": "John Smith", + "role": "Senior Tire Equipment Technician", + "department": "Maintenance", + "email": "john.smith@tirefactory.com", + "phone": "+1-555-0101", + "skills": [ + "tire_curing_press", + "hydraulic_systems", + "mold_maintenance", + "bladder_replacement", + "plc_troubleshooting" + ], + "certifications": [ + "Tire Manufacturing Equipment Level 3", + "Hydraulics Specialist", + "Lockout/Tagout Certified" + ], + "available": true, + "currentAssignments": [ ], + "shiftSchedule": "day" + }, + { + "id": "tech-002", + "employeeId": "EMP-1087", + "name": "Maria Garcia", + "role": "Building Machine Specialist", + "department": "Maintenance", + "email": "maria.garcia@tirefactory.com", + "phone": "+1-555-0102", + "skills": [ + "tire_building_machine", + "servo_systems", + "precision_alignment", + "tension_control", + "drum_balancing" + ], + "certifications": [ + "Tire Building Systems", + "Servo Motor Specialist", + "Precision Mechanics" + ], + "available": true, + "currentAssignments": [ ], + "shiftSchedule": "day" + }, + { + "id": "tech-003", + "employeeId": "EMP-1145", + "name": "David Lee", + "role": "Quality Systems Technician", + "department": "Maintenance", + "email": "david.lee@tirefactory.com", + "phone": "+1-555-0103", + "skills": [ + "tire_uniformity_machine", + "load_cell_calibration", + "measurement_systems", + "data_analysis", + "sensor_alignment" + ], + "certifications": [ + "Metrology Level 2", + "Calibration Specialist", + "Quality Control Systems" + ], + "available": false, + "currentAssignments": [ "WO-2024-456" ], + "shiftSchedule": "day" + }, + { + "id": "tech-004", + "employeeId": "EMP-1203", + "name": "Sarah Johnson", + "role": "Electrical Technician", + "department": "Maintenance", + "email": "sarah.johnson@tirefactory.com", + "phone": "+1-555-0104", + "skills": [ + "electrical_systems", + "plc_programming", + "motor_drives", + "control_panels", + "instrumentation" + ], + "certifications": [ + "Industrial Electrician", + "PLC Programming Siemens/Allen-Bradley", + "Variable Frequency Drive Specialist" + ], + "available": true, + "currentAssignments": [ ], + "shiftSchedule": "day" + }, + { + "id": "tech-005", + "employeeId": "EMP-1267", + "name": "Michael Chen", + "role": "Mixing & Extrusion Technician", + "department": "Maintenance", + "email": "michael.chen@tirefactory.com", + "phone": "+1-555-0105", + "skills": [ + "banbury_mixer", + "tire_extruder", + "rubber_processing", + "temperature_control", + "screw_maintenance" + ], + "certifications": [ + "Rubber Processing Equipment", + "Extruder Maintenance Level 2", + "Process Control Systems" + ], + "available": true, + "currentAssignments": [ ], + "shiftSchedule": "night" + }, + { + "id": "tech-006", + "employeeId": "EMP-1312", + "name": "Jennifer Rodriguez", + "role": "Mechanical Technician", + "department": "Maintenance", + "email": "jennifer.rodriguez@tirefactory.com", + "phone": "+1-555-0106", + "skills": [ + "bearing_replacement", + "alignment", + "vibration_analysis", + "mechanical_systems", + "preventive_maintenance" + ], + "certifications": [ + "Vibration Analysis Level 2", + "Precision Alignment", + "Mechanical Systems Specialist" + ], + "available": true, + "currentAssignments": [ ], + "shiftSchedule": "day" + } +] diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/data/telemetry-samples.json b/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/data/telemetry-samples.json new file mode 100644 index 000000000..f4ea5ffed --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/data/telemetry-samples.json @@ -0,0 +1,141 @@ +[ + { + "id": "telemetry-001-20241201-143022", + "machineId": "machine-001", + "timestamp": "2024-12-01T14:30:22Z", + "metrics": { + "curing_temperature": 170.5, + "curing_pressure": 178, + "cycle_time": 12.2, + "bladder_pressure": 175, + "heating_element_temp": 192, + "mold_close_force": 1850 + }, + "status": "normal" + }, + { + "id": "telemetry-001-20241201-143522", + "machineId": "machine-001", + "timestamp": "2024-12-01T14:35:22Z", + "metrics": { + "curing_temperature": 179.2, + "curing_pressure": 196, + "cycle_time": 14.5, + "bladder_pressure": 188, + "heating_element_temp": 205, + "mold_close_force": 1920 + }, + "status": "warning", + "alerts": ["curing_temperature_warning", "cycle_time_warning"] + }, + { + "id": "telemetry-002-20241201-144022", + "machineId": "machine-002", + "timestamp": "2024-12-01T14:40:22Z", + "metrics": { + "drum_vibration": 2.1, + "drum_speed": 45, + "ply_tension": 205, + "bead_wire_tension": 380, + "servo_motor_temp": 52 + }, + "status": "normal" + }, + { + "id": "telemetry-002-20241201-144522", + "machineId": "machine-002", + "timestamp": "2024-12-01T14:45:22Z", + "metrics": { + "drum_vibration": 3.2, + "drum_speed": 43, + "ply_tension": 238, + "bead_wire_tension": 395, + "servo_motor_temp": 58 + }, + "status": "warning", + "alerts": ["drum_vibration_warning", "ply_tension_warning"] + }, + { + "id": "telemetry-003-20241201-145022", + "machineId": "machine-003", + "timestamp": "2024-12-01T14:50:22Z", + "metrics": { + "barrel_temperature": 108, + "extrusion_pressure": 105, + "throughput": 780, + "screw_speed": 85, + "die_temperature": 112 + }, + "status": "normal" + }, + { + "id": "telemetry-003-20241201-145522", + "machineId": "machine-003", + "timestamp": "2024-12-01T14:55:22Z", + "metrics": { + "barrel_temperature": 119, + "extrusion_pressure": 132, + "throughput": 640, + "screw_speed": 82, + "die_temperature": 124 + }, + "status": "warning", + "alerts": ["barrel_temperature_warning", "extrusion_pressure_warning", "throughput_low"] + }, + { + "id": "telemetry-004-20241201-150022", + "machineId": "machine-004", + "timestamp": "2024-12-01T15:00:22Z", + "metrics": { + "radial_force_variation": 75, + "lateral_force_variation": 42, + "static_balance": 48, + "conicity": 12, + "load_cell_temp": 25 + }, + "status": "normal" + }, + { + "id": "telemetry-004-20241201-150522", + "machineId": "machine-004", + "timestamp": "2024-12-01T15:05:22Z", + "metrics": { + "radial_force_variation": 105, + "lateral_force_variation": 58, + "static_balance": 62, + "conicity": 18, + "load_cell_temp": 26 + }, + "status": "warning", + "alerts": ["radial_force_variation_warning", "static_balance_warning"] + }, + { + "id": "telemetry-005-20241201-151022", + "machineId": "machine-005", + "timestamp": "2024-12-01T15:10:22Z", + "metrics": { + "mixing_temperature": 148, + "power_consumption": 245, + "vibration": 4.2, + "rotor_speed": 45, + "ram_pressure": 12, + "batch_weight": 485 + }, + "status": "normal" + }, + { + "id": "telemetry-005-20241201-151522", + "machineId": "machine-005", + "timestamp": "2024-12-01T15:15:22Z", + "metrics": { + "mixing_temperature": 162, + "power_consumption": 288, + "vibration": 5.8, + "rotor_speed": 43, + "ram_pressure": 13, + "batch_weight": 490 + }, + "status": "warning", + "alerts": ["mixing_temperature_warning", "power_consumption_warning", "vibration_warning"] + } +] diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/data/thresholds.json b/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/data/thresholds.json new file mode 100644 index 000000000..5b1b63099 --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/data/thresholds.json @@ -0,0 +1,171 @@ +[ + { + "id": "threshold-curing-temp", + "machineType": "tire_curing_press", + "metric": "curing_temperature", + "unit": "celsius", + "normalRange": { + "min": 165, + "max": 175 + }, + "warningThreshold": 178, + "criticalThreshold": 182, + "description": "Tire curing temperature - critical for tire quality" + }, + { + "id": "threshold-curing-pressure", + "machineType": "tire_curing_press", + "metric": "curing_pressure", + "unit": "bar", + "normalRange": { + "min": 150, + "max": 190 + }, + "warningThreshold": 195, + "criticalThreshold": 205, + "description": "Curing press bladder pressure" + }, + { + "id": "threshold-curing-cycle-time", + "machineType": "tire_curing_press", + "metric": "cycle_time", + "unit": "minutes", + "normalRange": { + "min": 11, + "max": 13 + }, + "warningThreshold": 14, + "criticalThreshold": 16, + "description": "Curing cycle duration deviation" + }, + { + "id": "threshold-building-drum-vibration", + "machineType": "tire_building_machine", + "metric": "drum_vibration", + "unit": "mm/s", + "normalRange": { + "min": 0, + "max": 2.5 + }, + "warningThreshold": 3.0, + "criticalThreshold": 4.0, + "description": "Building drum vibration indicating bearing wear" + }, + { + "id": "threshold-building-tension", + "machineType": "tire_building_machine", + "metric": "ply_tension", + "unit": "newtons", + "normalRange": { + "min": 180, + "max": 220 + }, + "warningThreshold": 230, + "criticalThreshold": 250, + "description": "Ply material tension during building process" + }, + { + "id": "threshold-extruder-temp", + "machineType": "tire_extruder", + "metric": "barrel_temperature", + "unit": "celsius", + "normalRange": { + "min": 95, + "max": 115 + }, + "warningThreshold": 118, + "criticalThreshold": 125, + "description": "Extruder barrel temperature" + }, + { + "id": "threshold-extruder-pressure", + "machineType": "tire_extruder", + "metric": "extrusion_pressure", + "unit": "bar", + "normalRange": { + "min": 80, + "max": 120 + }, + "warningThreshold": 130, + "criticalThreshold": 145, + "description": "Extruder head pressure" + }, + { + "id": "threshold-extruder-throughput", + "machineType": "tire_extruder", + "metric": "throughput", + "unit": "kg/h", + "normalRange": { + "min": 700, + "max": 850 + }, + "warningThreshold": 650, + "criticalThreshold": 600, + "description": "Material throughput rate" + }, + { + "id": "threshold-uniformity-force-variation", + "machineType": "tire_uniformity_machine", + "metric": "radial_force_variation", + "unit": "newtons", + "normalRange": { + "min": 0, + "max": 80 + }, + "warningThreshold": 100, + "criticalThreshold": 120, + "description": "Tire radial force variation measurement" + }, + { + "id": "threshold-uniformity-balance", + "machineType": "tire_uniformity_machine", + "metric": "static_balance", + "unit": "gram_cm", + "normalRange": { + "min": 0, + "max": 50 + }, + "warningThreshold": 60, + "criticalThreshold": 75, + "description": "Tire static balance measurement" + }, + { + "id": "threshold-mixer-temp", + "machineType": "banbury_mixer", + "metric": "mixing_temperature", + "unit": "celsius", + "normalRange": { + "min": 130, + "max": 155 + }, + "warningThreshold": 160, + "criticalThreshold": 170, + "description": "Rubber compound mixing temperature" + }, + { + "id": "threshold-mixer-power", + "machineType": "banbury_mixer", + "metric": "power_consumption", + "unit": "kw", + "normalRange": { + "min": 200, + "max": 270 + }, + "warningThreshold": 285, + "criticalThreshold": 300, + "description": "Mixer motor power draw indicating viscosity issues" + }, + { + "id": "threshold-mixer-vibration", + "machineType": "banbury_mixer", + "metric": "vibration", + "unit": "mm/s", + "normalRange": { + "min": 0, + "max": 4.5 + }, + "warningThreshold": 5.5, + "criticalThreshold": 7.0, + "description": "Mixer rotor vibration levels" + } +] diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/data/work-orders.json b/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/data/work-orders.json new file mode 100644 index 000000000..a41dde67e --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/data/work-orders.json @@ -0,0 +1,165 @@ +[ + { + "id": "wo-2024-445", + "workOrderNumber": "WO-2024-445", + "machineId": "machine-001", + "title": "Preventive Maintenance - Bladder Inspection", + "description": "Scheduled bladder inspection and heating element check for Tire Curing Press A1", + "type": "preventive", + "priority": "medium", + "status": "completed", + "assignedTo": "tech-001", + "createdDate": "2024-11-01T08:00:00Z", + "scheduledDate": "2024-11-01T10:00:00Z", + "completedDate": "2024-11-01T13:45:00Z", + "estimatedDuration": 180, + "actualDuration": 225, + "partsUsed": [ + { + "partId": "part-curing-seal", + "quantity": 2 + }, + { + "partId": "part-temp-sensor", + "quantity": 1 + } + ], + "notes": "Bladder condition acceptable. Replaced two mold seals showing minor wear. One temperature sensor replaced due to drift. System tested and operating within specifications.", + "cost": 335.00 + }, + { + "id": "wo-2024-456", + "workOrderNumber": "WO-2024-456", + "machineId": "machine-004", + "faultType": "load_cell_drift", + "title": "Corrective Maintenance - Load Cell Calibration", + "description": "Load cell drift detected during master tire check - recalibration required", + "type": "corrective", + "priority": "high", + "status": "in_progress", + "assignedTechnician": "tech-003", + "assignedTo": "tech-003", + "createdDate": "2024-11-20T09:15:00Z", + "createdAt": "2024-11-20T09:15:00Z", + "scheduledDate": "2024-11-20T14:00:00Z", + "estimatedDuration": 180, + "requiredParts": [ + { + "partNumber": "TUM-LC-2KN", + "partName": "Load Cell 2kN", + "quantity": 1, + "isAvailable": false + }, + { + "partNumber": "TUM-ENC-5000", + "partName": "Rotary Encoder 5000ppr", + "quantity": 1, + "isAvailable": false + } + ], + "partsUsed": [], + "notes": "Running full calibration sequence with master tire. Environmental temperature stable. All mechanical bindings checked and cleared.", + "cost": 0.00 + }, + { + "id": "wo-2024-432", + "workOrderNumber": "WO-2024-432", + "machineId": "machine-002", + "title": "Emergency Repair - Drum Bearing Replacement", + "description": "Urgent drum shaft bearing replacement due to excessive vibration and noise", + "type": "emergency", + "priority": "critical", + "status": "completed", + "assignedTo": "tech-002", + "createdDate": "2024-10-15T11:45:00Z", + "scheduledDate": "2024-10-15T13:00:00Z", + "completedDate": "2024-10-15T21:30:00Z", + "estimatedDuration": 300, + "actualDuration": 510, + "partsUsed": [ + { + "partId": "part-building-bearing", + "quantity": 2 + }, + { + "partId": "part-hydraulic-oil", + "quantity": 15 + } + ], + "notes": "Bearings showed severe wear patterns. Replaced both drum shaft bearings. Drum realigned and rebalanced. Hydraulic system serviced. Machine tested under load - vibration levels now within normal range. Recommend increased monitoring for next 100 operating hours.", + "cost": 1027.50, + "downtime": 510 + }, + { + "id": "wo-2024-468", + "workOrderNumber": "WO-2024-468", + "machineId": "machine-005", + "faultType": "rotor_wear", + "title": "Preventive Maintenance - Rotor Tip Replacement", + "description": "Scheduled rotor tip inspection and replacement for Banbury Mixer E1", + "type": "preventive", + "priority": "medium", + "status": "scheduled", + "assignedTechnician": "tech-005", + "assignedTo": "tech-005", + "createdDate": "2024-11-25T10:00:00Z", + "createdAt": "2024-11-25T10:00:00Z", + "scheduledDate": "2024-12-05T06:00:00Z", + "estimatedDuration": 360, + "requiredParts": [ + { + "partNumber": "BMX-TIP-500", + "partName": "Mixer Rotor Tip", + "quantity": 4, + "isAvailable": true + }, + { + "partNumber": "BMX-SEAL-DP", + "partName": "Drop Door Seal", + "quantity": 1, + "isAvailable": true + } + ], + "partsUsed": [ + { + "partId": "part-mixer-rotor-tip", + "quantity": 4 + }, + { + "partId": "part-mixer-seal", + "quantity": 1 + } + ], + "notes": "Scheduled during planned production downtime. All parts staged and ready.", + "cost": 2480.00 + }, + { + "id": "wo-2024-419", + "workOrderNumber": "WO-2024-419", + "machineId": "machine-003", + "title": "Corrective Maintenance - Extruder Heater Band Replacement", + "description": "Zone 3 heater band failure - temperature control degraded", + "type": "corrective", + "priority": "high", + "status": "completed", + "assignedTo": "tech-005", + "createdDate": "2024-09-28T14:20:00Z", + "scheduledDate": "2024-09-28T18:00:00Z", + "completedDate": "2024-09-28T20:15:00Z", + "estimatedDuration": 120, + "actualDuration": 135, + "partsUsed": [ + { + "partId": "part-extruder-heater", + "quantity": 1 + }, + { + "partId": "part-temp-sensor", + "quantity": 1 + } + ], + "notes": "Heater band showed signs of thermal fatigue. Replaced heater and associated thermocouple. System brought back to operating temperature gradually. Temperature profile now uniform across all zones.", + "cost": 370.00, + "downtime": 135 + } +] diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/deploy-lab.ps1 b/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/deploy-lab.ps1 new file mode 100755 index 000000000..c8ff8132e --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/deploy-lab.ps1 @@ -0,0 +1,237 @@ +#!/usr/bin/env pwsh +<# +.SYNOPSIS +Deploys the lab resources scoped to a subscription or resource group, then +seeds Cosmos DB and Blob Storage with the sample factory-operations data. +.DESCRIPTION +Provides a controlled deployment flow for lab environments, optionally limited to a resource group and specific Entra user IDs. +.PARAMETER DeploymentType +Defines the deployment scope; allowed values are subscription, resourcegroup, or resourcegroup-with-subscriptionowner. +.PARAMETER SubscriptionId +Specifies the Azure subscription that contains the lab resources. +.PARAMETER ResourceGroupName +For resource group deployment, specifies the target resource group name. +.PARAMETER PreferredLocation +Specifies the preferred Azure regions, ordered by preference. +.PARAMETER AllowedEntraUserIds +Optional list of Entra user object IDs permitted to access the lab resources. +#> +param( + [Parameter(Mandatory=$true)] + [ValidateSet('subscription','resourcegroup', 'resourcegroup-with-subscriptionowner')] + [string]$DeploymentType, + + [Parameter(Mandatory=$true)] + [string]$SubscriptionId, + + [string]$ResourceGroupName = "", + + [string[]]$PreferredLocation = @(), + + [string[]]$AllowedEntraUserIds = @() +) + +$scriptPath = Split-Path -Parent $MyInvocation.MyCommand.Definition +$dataRoot = Join-Path $scriptPath "data" + +# Keep seed assets inside the folder packaged by Hack Console. +# Partition key paths must match main.bicep's cosmosContainers. +$dataMappings = [ordered]@{ + "Machines" = @{ File = "machines.json"; PartitionKeyPath = "/type" } + "Thresholds" = @{ File = "thresholds.json"; PartitionKeyPath = "/machineType" } + "Telemetry" = @{ File = "telemetry-samples.json"; PartitionKeyPath = "/machineId" } + "KnowledgeBase" = @{ File = "knowledge-base.json"; PartitionKeyPath = "/machineType" } + "PartsInventory" = @{ File = "parts-inventory.json"; PartitionKeyPath = "/category" } + "Technicians" = @{ File = "technicians.json"; PartitionKeyPath = "/department" } + "WorkOrders" = @{ File = "work-orders.json"; PartitionKeyPath = "/status" } + "MaintenanceHistory" = @{ File = "maintenance-history.json"; PartitionKeyPath = "/machineId" } + "MaintenanceWindows" = @{ File = "maintenance-windows.json"; PartitionKeyPath = "/isAvailable" } + "Suppliers" = @{ File = "suppliers.json"; PartitionKeyPath = "/category" } +} + +foreach ($mapping in $dataMappings.Values) { + $dataFile = Join-Path $dataRoot $mapping.File + if (-not (Test-Path -LiteralPath $dataFile -PathType Leaf)) { + throw "Required seed data file not found: '$dataFile'. Include the data folder when uploading labautomation to Hack Console." + } +} +$kbWikiFolder = Join-Path $dataRoot "kb-wiki" +if (-not (Test-Path -LiteralPath $kbWikiFolder -PathType Container)) { + throw "Required knowledge base folder not found: '$kbWikiFolder'. Include the data folder when uploading labautomation to Hack Console." +} +if (@(Get-ChildItem -LiteralPath $kbWikiFolder -Filter "*.md" -File).Count -eq 0) { + throw "No knowledge base markdown files found in '$kbWikiFolder'." +} + +if($DeploymentType -eq 'resourcegroup' -and [string]::IsNullOrEmpty($ResourceGroupName)) { + throw "ResourceGroupName must be provided when DeploymentType is 'resourcegroup'." +} + +if($PreferredLocation.Count -gt 0) { + $effectiveLocation = $PreferredLocation[0] +} else { + $effectiveLocation = "swedencentral" +} + +if($DeploymentType -eq 'subscription') { + $stableHash = Get-MhhStablehash $AllowedEntraUserIds -Length 24 + $effectiveResourceGroup = "lab-$stableHash" + Write-Host "Deploying lab resources at the subscription level in subscription $SubscriptionId..." + if(-not (Get-AzResourceGroup -Name $effectiveResourceGroup -ErrorAction SilentlyContinue)) { + Write-Host "Creating resource group '$effectiveResourceGroup' in location '$effectiveLocation'..." + New-AzResourceGroup -Name $effectiveResourceGroup -Location $effectiveLocation -Verbose + } else { + Write-Host "Resource group '$effectiveResourceGroup' already exists." + } +} else { + $effectiveResourceGroup = $ResourceGroupName +} + +if($AllowedEntraUserIds.Count -eq 0) { + throw "At least one AllowedEntraUserIds value is required to assign lab access." +} + +$resourceSuffix = Get-MhhStablehash $AllowedEntraUserIds -Length 12 +$storageAccountName = "stpdm$resourceSuffix" +$cosmosAccountName = "cosmos-pdm-$resourceSuffix" +$foundryAccountName = "aipdm$resourceSuffix" +$foundryProjectName = "predictive-maintenance" +$apimName = "apim-pdm-$resourceSuffix" + +@{"HackboxCredential" = @{ name = "ResourceGroupName"; value = $effectiveResourceGroup; note = "The name of the resource group where lab resources are deployed" }} +@{"HackboxCredential" = @{ name = "StorageAccountName"; value = $storageAccountName; note = "The name of the Azure Storage account (machine-wiki blob container)" }} +@{"HackboxCredential" = @{ name = "CosmosAccountName"; value = $cosmosAccountName; note = "The name of the Azure Cosmos DB account (FactoryOpsDB database)" }} +@{"HackboxCredential" = @{ name = "FoundryAccountName"; value = $foundryAccountName; note = "The name of the Microsoft Foundry account" }} +@{"HackboxCredential" = @{ name = "FoundryProjectName"; value = $foundryProjectName; note = "The name of the Microsoft Foundry project" }} +@{"HackboxCredential" = @{ name = "ApiManagementName"; value = $apimName; note = "The name of the API Management instance (Machine API / Maintenance API)" }} +@{"HackboxCredential" = @{ name = "EffectiveLocation"; value = $effectiveLocation; note = "The effective Azure region for the lab deployment" }} + +$template = Join-Path $scriptPath "main.bicep" +$templateParameters = @{ + location = $effectiveLocation + resourceSuffix = $resourceSuffix + userObjectId = $AllowedEntraUserIds[0] +} + +$deployment = Invoke-MhhDeploymentWithRegionFallback ` + -PreferredLocations $PreferredLocation ` + -ResourceGroupName $effectiveResourceGroup ` + -RgOwnerEntraObjectIds $AllowedEntraUserIds ` + -Tag @{ CostControl = "Ignore"; SecurityControl = "Ignore" } ` + -TemplateFile $template ` + -TemplateParameterObject $templateParameters + +@{"HackboxCredential" = @{ name = "Region"; value = $deployment.LocationUsed; note = "The Azure region the lab deployment succeeded in" }} + +# ============================================================================= +# Post-deployment data seeding. +# Runs once per participant, immediately after their resources are created, so +# they never have to run a manual seeding step. Uses only PowerShell + az CLI +# (both available in the provisioning runner) -- no bash, no Python. +# ============================================================================= + +# $deployment.Outputs is already a flattened "name -> value" hashtable (see +# Invoke-MhhDeploymentWithRegionFallback's documented return value). +$outputs = $deployment.Outputs + +$cosmosEndpoint = $outputs["cosmosDbEndpoint"] +$cosmosDatabaseName = $outputs["cosmosDbDatabaseName"] +$storageAccount = $outputs["storageAccountName"] +$machineWikiContainer = $outputs["machineWikiContainerName"] + +Write-Host "Seeding Cosmos DB container documents into '$cosmosAccountName / $cosmosDatabaseName'..." + +$cosmosKey = az cosmosdb keys list --name $cosmosAccountName --resource-group $effectiveResourceGroup --query primaryMasterKey -o tsv +if([string]::IsNullOrEmpty($cosmosKey)) { + throw "Could not retrieve the Cosmos DB primary master key for '$cosmosAccountName'." +} +$cosmosKeyBytes = [Convert]::FromBase64String($cosmosKey) + +function Invoke-CosmosDocumentUpsert { + param( + [string]$Endpoint, + [byte[]]$KeyBytes, + [string]$Database, + [string]$Collection, + [string]$PartitionKeyPath, + [object]$Document + ) + + $verb = "post" + $resourceType = "docs" + $resourceLink = "dbs/$Database/colls/$Collection" + $date = [DateTime]::UtcNow.ToString("r") + $stringToSign = "$verb`n$resourceType`n$resourceLink`n$($date.ToLowerInvariant())`n`n" + + $hmac = New-Object System.Security.Cryptography.HMACSHA256 + $hmac.Key = $KeyBytes + $signatureBytes = $hmac.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($stringToSign)) + $signature = [Convert]::ToBase64String($signatureBytes) + $authHeader = [System.Uri]::EscapeDataString("type=master&ver=1.0&sig=$signature") + + $uri = "$Endpoint$resourceLink/docs" + $body = $Document | ConvertTo-Json -Depth 20 -Compress + + # The Cosmos DB Gateway REST API requires the partition key value(s) to be + # supplied explicitly via x-ms-documentdb-partitionkey, even on create - + # it is not inferred from the request body. Single-level partition key + # paths only (matches this hack's containers, e.g. "/type" -> .type). + $partitionKeyPropertyName = $PartitionKeyPath.TrimStart('/') + $partitionKeyValue = $Document.$partitionKeyPropertyName + $partitionKeyHeader = ConvertTo-Json -InputObject @($partitionKeyValue) -Compress + + Invoke-RestMethod -Uri $uri -Method Post ` + -Headers @{ + "Authorization" = $authHeader + "x-ms-date" = $date + "x-ms-version" = "2018-12-31" + "x-ms-documentdb-is-upsert" = "true" + "x-ms-documentdb-partitionkey" = $partitionKeyHeader + } ` + -ContentType "application/json" ` + -Body ([System.Text.Encoding]::UTF8.GetBytes($body)) | Out-Null +} + +foreach($container in $dataMappings.Keys) { + $mapping = $dataMappings[$container] + $dataFile = Join-Path $dataRoot $mapping.File + + $documents = Get-Content -Raw -LiteralPath $dataFile -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop + $count = 0 + foreach($document in $documents) { + try { + Invoke-CosmosDocumentUpsert -Endpoint $cosmosEndpoint -KeyBytes $cosmosKeyBytes ` + -Database $cosmosDatabaseName -Collection $container ` + -PartitionKeyPath $mapping.PartitionKeyPath -Document $document + $count++ + } catch { + throw "Failed to upsert document '$($document.id)' into '$container': $($_.Exception.Message)" + } + } + Write-Host "Seeded $count document(s) into '$container'." +} + +Write-Host "Uploading knowledge base wiki articles to '$storageAccount / $machineWikiContainer'..." + +$storageKey = az storage account keys list --account-name $storageAccount --resource-group $effectiveResourceGroup --query "[0].value" -o tsv +if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($storageKey)) { + throw "Could not retrieve the storage account key for '$storageAccount'." +} + +# CLI progress is written to stderr and appears as child-job errors in Hack Console. +az storage blob upload-batch ` + --account-name $storageAccount ` + --account-key $storageKey ` + --destination $machineWikiContainer ` + --source $kbWikiFolder ` + --pattern "*.md" ` + --no-progress ` + --overwrite | Out-Null + +if($LASTEXITCODE -ne 0) { + throw "Failed to upload knowledge base wiki articles from '$kbWikiFolder' to '$storageAccount / $machineWikiContainer'." +} else { + Write-Host "Knowledge base wiki articles uploaded successfully." +} + +Write-Host "Lab deployment and data seeding complete for resource group '$effectiveResourceGroup'." diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/get-keys.sh b/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/get-keys.sh new file mode 100755 index 000000000..f285f2e5d --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/get-keys.sh @@ -0,0 +1,217 @@ +#!/bin/bash +# ============================================================================= +# Predictive Maintenance MicroHack - Environment Configuration Script +# ============================================================================= +# Resolves resource names, endpoints, and keys from your Azure deployment +# and creates a clean .env file containing the settings required by the hack. +# ============================================================================= + +set -e + +# Resolve paths relative to this script +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ENV_OUT="$SCRIPT_DIR/../.env" + +# Ensure Azure CLI login +if [ -z "$(az account show 2>/dev/null)" ]; then + echo "User not signed in to Azure. Signing in via device code..." + az login --use-device-code +fi + +# Parse named parameters +resourceGroupName="" +while [[ "$#" -gt 0 ]]; do + case $1 in + --resource-group) resourceGroupName="$2"; shift ;; + *) echo "Unknown parameter passed: $1"; exit 1 ;; + esac + shift +done + +# Prompt for resource group if not passed +if [ -z "$resourceGroupName" ]; then + read -p "Enter the resource group name where your lab resources are deployed: " resourceGroupName +fi + +echo "Retrieving deployment information from resource group '$resourceGroupName'..." + +# Fetch subscription ID and tenant ID +subscriptionId=$(az account show --query id -o tsv) + +# Find the lab deployment (prefers platform mhh* deployments, main.bicep, or the newest deployment) +deploymentName=$(az deployment group list --resource-group "$resourceGroupName" \ + --query "sort_by([?starts_with(name, 'mhh') || contains(name, 'main') || contains(name, 'azuredeploy') || contains(name, 'hack-deployment')], &properties.timestamp)[-1].name" \ + -o tsv 2>/dev/null || echo "") + +if [ -z "$deploymentName" ]; then + deploymentName=$(az deployment group list --resource-group "$resourceGroupName" \ + --query "sort_by(@, &properties.timestamp)[-1].name" \ + -o tsv 2>/dev/null || echo "") +fi + +# Helper to read a deployment output safely +get_output() { + local key="$1" + if [ -n "$deploymentName" ]; then + az deployment group show --resource-group "$resourceGroupName" --name "$deploymentName" \ + --query "properties.outputs.${key}.value" -o tsv 2>/dev/null || echo "" + else + echo "" + fi +} + +echo "Discovering Azure resources..." + +# 1. Storage Account +storageAccountName=$(get_output "storageAccountName") +if [ -z "$storageAccountName" ]; then + storageAccountName=$(az storage account list --resource-group "$resourceGroupName" --query "[0].name" -o tsv 2>/dev/null || echo "") +fi + +if [ -n "$storageAccountName" ]; then + storageAccountKey=$(az storage account keys list --account-name "$storageAccountName" --resource-group "$resourceGroupName" --query "[0].value" -o tsv 2>/dev/null || echo "") + storageConnectionString="DefaultEndpointsProtocol=https;AccountName=${storageAccountName};AccountKey=${storageAccountKey};EndpointSuffix=core.windows.net" +else + storageConnectionString="" +fi + +# 2. Microsoft Foundry / Cognitive Services Account +foundryAccountName=$(get_output "aiFoundryHubName") +if [ -z "$foundryAccountName" ]; then + foundryAccountName=$(az cognitiveservices account list --resource-group "$resourceGroupName" --query "[?kind=='AIServices'].name | [0]" -o tsv 2>/dev/null || echo "") +fi + +foundryProjectName=$(get_output "aiFoundryProjectName") +if [ -z "$foundryProjectName" ]; then + foundryProjectName="predictive-maintenance" +fi + +if [ -n "$foundryAccountName" ]; then + foundryKey=$(az cognitiveservices account keys list --name "$foundryAccountName" --resource-group "$resourceGroupName" --query key1 -o tsv 2>/dev/null || echo "") + aiProjectEndpoint="https://${foundryAccountName}.services.ai.azure.com/api/projects/${foundryProjectName}" + aiProjectResourceId="/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.CognitiveServices/accounts/${foundryAccountName}/projects/${foundryProjectName}" + azureOpenAIEndpoint="https://${foundryAccountName}.openai.azure.com/" +else + foundryKey="" + aiProjectEndpoint="" + aiProjectResourceId="" + azureOpenAIEndpoint="" +fi + +# 3. Cosmos DB +cosmosAccountName=$(get_output "cosmosDbAccountName") +if [ -z "$cosmosAccountName" ]; then + cosmosAccountName=$(az cosmosdb list --resource-group "$resourceGroupName" --query "[0].name" -o tsv 2>/dev/null || echo "") +fi + +if [ -n "$cosmosAccountName" ]; then + cosmosEndpoint=$(get_output "cosmosDbEndpoint") + if [ -z "$cosmosEndpoint" ]; then + cosmosEndpoint=$(az cosmosdb show --name "$cosmosAccountName" --resource-group "$resourceGroupName" --query documentEndpoint -o tsv 2>/dev/null || echo "") + fi + cosmosKey=$(az cosmosdb keys list --name "$cosmosAccountName" --resource-group "$resourceGroupName" --query primaryMasterKey -o tsv 2>/dev/null || echo "") +else + cosmosAccountName="" + cosmosEndpoint="" + cosmosKey="" +fi + +# 4. Azure AI Search +searchServiceName=$(get_output "searchServiceName") +if [ -z "$searchServiceName" ]; then + searchServiceName=$(az search service list --resource-group "$resourceGroupName" --query "[0].name" -o tsv 2>/dev/null || echo "") +fi + +if [ -n "$searchServiceName" ]; then + searchServiceEndpoint="https://${searchServiceName}.search.windows.net/" + searchAdminKey=$(az search admin-key show --resource-group "$resourceGroupName" --service-name "$searchServiceName" --query primaryKey -o tsv 2>/dev/null || echo "") +else + searchServiceEndpoint="" + searchAdminKey="" +fi + +# 5. API Management +apimName=$(get_output "apimName") +if [ -z "$apimName" ]; then + apimName=$(az apim list --resource-group "$resourceGroupName" --query "[0].name" -o tsv 2>/dev/null || echo "") +fi + +if [ -n "$apimName" ]; then + apimGatewayUrl=$(get_output "apimGatewayUrl") + if [ -z "$apimGatewayUrl" ]; then + apimGatewayUrl=$(az apim show --name "$apimName" --resource-group "$resourceGroupName" --query gatewayUrl -o tsv 2>/dev/null || echo "") + fi + apimSubscriptionKey=$(az rest --method post \ + --url "https://management.azure.com/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.ApiManagement/service/${apimName}/subscriptions/master/listSecrets?api-version=2024-05-01" \ + --query "primaryKey" -o tsv 2>/dev/null || echo "") +else + apimGatewayUrl="" + apimSubscriptionKey="" +fi + +# 6. Application Insights +appInsightsName=$(get_output "applicationInsightsName") +if [ -z "$appInsightsName" ]; then + appInsightsName=$(az resource list --resource-group "$resourceGroupName" --resource-type "Microsoft.Insights/components" --query "[0].name" -o tsv 2>/dev/null || echo "") +fi + +if [ -n "$appInsightsName" ]; then + appInsightsConnectionString=$(az resource show --resource-group "$resourceGroupName" --name "$appInsightsName" --resource-type "Microsoft.Insights/components" --query properties.ConnectionString -o tsv 2>/dev/null || echo "") +else + appInsightsConnectionString="" +fi + +# ============================================================================= +# Write .env file +# ============================================================================= +echo "Writing environment settings to $ENV_OUT..." + +cat < "$ENV_OUT" +# Azure Environment +RESOURCE_GROUP="$resourceGroupName" +AZURE_SUBSCRIPTION_ID="$subscriptionId" + +# Microsoft Foundry / Azure AI +AZURE_AI_PROJECT_ENDPOINT="$aiProjectEndpoint" +AZURE_AI_PROJECT_RESOURCE_ID="$aiProjectResourceId" +MODEL_DEPLOYMENT_NAME="gpt-5.4" +EMBEDDING_MODEL_DEPLOYMENT_NAME="text-embedding-3-large" + +# Azure OpenAI (vectorizer in create_knowledge_base.ipynb & Aspire flow) +AZURE_OPENAI_ENDPOINT="$azureOpenAIEndpoint" +AZURE_OPENAI_KEY="$foundryKey" +AZURE_OPENAI_DEPLOYMENT_NAME="gpt-5.4" + +# Azure Cosmos DB (SQL API) +COSMOS_ACCOUNT_NAME="$cosmosAccountName" +COSMOS_ENDPOINT="$cosmosEndpoint" +COSMOS_KEY="$cosmosKey" +COSMOS_DATABASE_NAME="FactoryOpsDB" + +# Azure AI Search +SEARCH_SERVICE_ENDPOINT="$searchServiceEndpoint" +SEARCH_ADMIN_KEY="$searchAdminKey" + +# Azure Storage +AZURE_STORAGE_CONNECTION_STRING="$storageConnectionString" + +# API Management +APIM_GATEWAY_URL="$apimGatewayUrl" +APIM_SUBSCRIPTION_KEY="$apimSubscriptionKey" + +# Application Insights +APPLICATIONINSIGHTS_CONNECTION_STRING="$appInsightsConnectionString" +EOF + +echo "Environment file created: $ENV_OUT" +echo "" +echo "=== Configuration Summary ===" +echo "Resource Group: $resourceGroupName" +echo "Foundry Project URL: $aiProjectEndpoint" +echo "Cosmos DB Endpoint: $cosmosEndpoint" +echo "Cosmos DB Account: $cosmosAccountName" +echo "Search Endpoint: $searchServiceEndpoint" +echo "APIM Gateway URL: $apimGatewayUrl" +echo "Storage Account: $storageAccountName" +echo "Application Insights: ${appInsightsName:-"(none)"}" +echo "=============================" diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/lab-defaults.json b/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/lab-defaults.json new file mode 100644 index 000000000..984780656 --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/lab-defaults.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://raw.githubusercontent.com/microsoft/MicroHack/refs/heads/main/lab-defaults-schema.json", + "groups": ["GHCPUsers"], + "deploymentType": "resourcegroup-with-subscriptionowner", + "labsPerSubscription": 5, + "preferredLocation": "swedencentral, francecentral, eastus2, germanywestcentral", + "estimatedDailyCostsUsd": 8.0, + "estimatedSharedDeploymentDailyCostsUsd": 0.0 +} diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/main.bicep b/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/main.bicep new file mode 100644 index 000000000..81f5e6d78 --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/main.bicep @@ -0,0 +1,723 @@ +@description('Azure region used for all regional resources.') +param location string = resourceGroup().location + +@description('Stable suffix shared by globally unique lab resource names.') +@minLength(12) +@maxLength(12) +param resourceSuffix string + +@description('Microsoft Entra object ID of the participant who receives lab access.') +param userObjectId string + +@description('SKU for the Azure AI Search service.') +@allowed([ + 'basic' + 'standard' +]) +param searchServiceSku string = 'basic' + +var storageAccountName = 'stpdm${resourceSuffix}' +var logAnalyticsWorkspaceName = 'log-pdm-${resourceSuffix}' +var applicationInsightsName = 'appi-pdm-${resourceSuffix}' +var searchServiceName = 'srch-pdm-${resourceSuffix}' +var containerRegistryName = 'acrpdm${resourceSuffix}' +var containerAppsEnvironmentName = 'cae-pdm-${resourceSuffix}' +var cosmosDbAccountName = 'cosmos-pdm-${resourceSuffix}' +var apimName = 'apim-pdm-${resourceSuffix}' +var foundryAccountName = 'aipdm${resourceSuffix}' +var foundryProjectName = 'predictive-maintenance' +var cosmosDatabaseName = 'FactoryOpsDB' +var machineWikiContainerName = 'machine-wiki' + +var cognitiveServicesUserRoleId = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a97b65f3-24c7-4388-baec-2e87135dc908') +var searchServiceContributorRoleId = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7ca78c08-252a-4471-8644-bb5ff32d4ba0') +var searchIndexDataReaderRoleId = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '1407120a-92aa-4202-b7e9-c0e197c71c8f') +var monitoringReaderRoleId = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '43d0d8ad-25c7-4714-9337-8ba259a9fe05') +var foundryUserRoleId = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '53ca6127-db72-4b80-b1b0-d745d6d5456d') +var storageBlobDataContributorRoleId = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe') +var cosmosDbDataContributorRoleId = '00000000-0000-0000-0000-000000000002' + +// The 10 Cosmos containers seeded with sample factory-operations data. +// Matches challenge-0/seed-data.sh's `containers_config` in the source hack. +var cosmosContainers = [ + { name: 'Machines', partitionKeyPath: '/type' } + { name: 'Thresholds', partitionKeyPath: '/machineType' } + { name: 'Telemetry', partitionKeyPath: '/machineId', defaultTtl: 2592000 } // 30 days + { name: 'KnowledgeBase', partitionKeyPath: '/machineType' } + { name: 'PartsInventory', partitionKeyPath: '/category' } + { name: 'Technicians', partitionKeyPath: '/department' } + { name: 'WorkOrders', partitionKeyPath: '/status' } + { name: 'MaintenanceHistory', partitionKeyPath: '/machineId' } + { name: 'MaintenanceWindows', partitionKeyPath: '/isAvailable' } + { name: 'Suppliers', partitionKeyPath: '/category' } +] + +resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' = { + name: storageAccountName + location: location + sku: { + name: 'Standard_LRS' + } + kind: 'StorageV2' + properties: { + allowBlobPublicAccess: true + publicNetworkAccess: 'Enabled' + allowSharedKeyAccess: true + networkAcls: { + defaultAction: 'Allow' + bypass: 'AzureServices' + } + } +} + +resource blobServices 'Microsoft.Storage/storageAccounts/blobServices@2023-05-01' = { + parent: storageAccount + name: 'default' +} + +resource machineWikiContainer 'Microsoft.Storage/storageAccounts/blobServices/containers@2023-05-01' = { + parent: blobServices + name: machineWikiContainerName + properties: { + publicAccess: 'None' + } +} + +resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2021-06-01' = { + name: logAnalyticsWorkspaceName + location: location + properties: { + retentionInDays: 30 + features: { + searchVersion: 1 + } + sku: { + name: 'PerGB2018' + } + } +} + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = { + name: applicationInsightsName + location: location + kind: 'web' + properties: { + Application_Type: 'web' + WorkspaceResourceId: logAnalyticsWorkspace.id + } +} + +resource cosmosDbAccount 'Microsoft.DocumentDB/databaseAccounts@2023-04-15' = { + name: cosmosDbAccountName + location: location + kind: 'GlobalDocumentDB' + identity: { + type: 'SystemAssigned' + } + properties: { + consistencyPolicy: { + defaultConsistencyLevel: 'Session' + } + locations: [ + { + locationName: location + failoverPriority: 0 + isZoneRedundant: false + } + ] + databaseAccountOfferType: 'Standard' + enableAutomaticFailover: false + enableMultipleWriteLocations: false + publicNetworkAccess: 'Enabled' + disableLocalAuth: false + capabilities: [ + { + name: 'EnableServerless' + } + ] + } +} + +resource cosmosDatabase 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2023-04-15' = { + parent: cosmosDbAccount + name: cosmosDatabaseName + properties: { + resource: { + id: cosmosDatabaseName + } + } +} + +resource cosmosContainerResources 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2023-04-15' = [ + for container in cosmosContainers: { + parent: cosmosDatabase + name: container.name + properties: { + resource: { + id: container.name + partitionKey: { + paths: [container.partitionKeyPath] + kind: 'Hash' + } + defaultTtl: contains(container, 'defaultTtl') ? container.defaultTtl : -1 + } + } + } +] + +resource searchService 'Microsoft.Search/searchServices@2023-11-01' = { + name: searchServiceName + location: location + identity: { + type: 'SystemAssigned' + } + sku: { + name: searchServiceSku + } + properties: { + hostingMode: 'default' + replicaCount: 1 + partitionCount: 1 + publicNetworkAccess: 'enabled' + disableLocalAuth: false + authOptions: { + aadOrApiKey: { + aadAuthFailureMode: 'http401WithBearerChallenge' + } + } + } +} + +resource containerRegistry 'Microsoft.ContainerRegistry/registries@2023-07-01' = { + name: containerRegistryName + location: location + sku: { + name: 'Basic' + } + properties: { + adminUserEnabled: true + publicNetworkAccess: 'Enabled' + networkRuleBypassOptions: 'AzureServices' + } +} + +// Stretch-goal target for Challenge 5: participants can optionally push their +// own container image here and deploy it to this environment. No container +// app is pre-created; participants create their own. +resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2024-03-01' = { + name: containerAppsEnvironmentName + location: location + properties: { + appLogsConfiguration: { + destination: 'log-analytics' + logAnalyticsConfiguration: { + customerId: logAnalyticsWorkspace.properties.customerId + sharedKey: logAnalyticsWorkspace.listKeys().primarySharedKey + } + } + } +} + +resource aiFoundry 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' = { + name: foundryAccountName + location: location + identity: { + type: 'SystemAssigned' + } + sku: { + name: 'S0' + } + kind: 'AIServices' + properties: { + allowProjectManagement: true + customSubDomainName: foundryAccountName + disableLocalAuth: false + publicNetworkAccess: 'Enabled' + networkAcls: { + defaultAction: 'Allow' + } + } +} + +resource aiProject 'Microsoft.CognitiveServices/accounts/projects@2025-04-01-preview' = { + parent: aiFoundry + name: foundryProjectName + location: location + identity: { + type: 'SystemAssigned' + } + properties: { + description: 'Predictive Maintenance MicroHack project' + displayName: 'Predictive Maintenance' + } + dependsOn: [ + applicationInsights + ] +} + +resource aiProjectAppInsightsConnection 'Microsoft.CognitiveServices/accounts/projects/connections@2025-04-01-preview' = { + parent: aiProject + name: applicationInsightsName + properties: { + authType: 'ApiKey' + category: 'AppInsights' + target: applicationInsights.id + useWorkspaceManagedIdentity: false + isSharedToAll: true + credentials: { + key: applicationInsights.properties.InstrumentationKey + } + metadata: { + ApiType: 'Azure' + ResourceId: applicationInsights.id + } + } +} + +resource gpt5_4MiniDeployment 'Microsoft.CognitiveServices/accounts/deployments@2025-04-01-preview' = { + parent: aiFoundry + name: 'gpt-5.4-mini' + sku: { + capacity: 50 + name: 'GlobalStandard' + } + properties: { + model: { + name: 'gpt-5.4-mini' + format: 'OpenAI' + version: '2026-03-17' + } + } +} + +resource gpt5_4Deployment 'Microsoft.CognitiveServices/accounts/deployments@2025-04-01-preview' = { + parent: aiFoundry + name: 'gpt-5.4' + sku: { + capacity: 50 + name: 'GlobalStandard' + } + properties: { + model: { + name: 'gpt-5.4' + format: 'OpenAI' + version: '2026-03-05' + } + } + dependsOn: [ + gpt5_4MiniDeployment + ] +} + +resource textEmbeddingDeployment 'Microsoft.CognitiveServices/accounts/deployments@2025-04-01-preview' = { + parent: aiFoundry + name: 'text-embedding-3-large' + sku: { + capacity: 10 + name: 'Standard' + } + properties: { + model: { + name: 'text-embedding-3-large' + format: 'OpenAI' + version: '1' + } + } + dependsOn: [ + gpt5_4Deployment + ] +} + +resource aiSearchConnection 'Microsoft.CognitiveServices/accounts/connections@2025-04-01-preview' = { + parent: aiFoundry + name: '${foundryAccountName}-aisearch' + properties: { + category: 'CognitiveSearch' + target: 'https://${searchServiceName}.search.windows.net' + authType: 'ApiKey' + isSharedToAll: true + credentials: { + key: searchService.listAdminKeys().primaryKey + } + metadata: { + ApiType: 'Azure' + ResourceId: searchService.id + location: location + } + } +} + +resource apim 'Microsoft.ApiManagement/service@2023-03-01-preview' = { + name: apimName + location: location + identity: { + type: 'SystemAssigned' + } + sku: { + name: 'BasicV2' + capacity: 1 + } + properties: { + publisherEmail: 'admin@contoso.com' + publisherName: 'Contoso' + } +} + +// --- Role assignments: system identities needed to run the platform --- + +resource appInsightsMonitoringReaderForProject 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: applicationInsights + name: guid(applicationInsights.id, aiProject.id, monitoringReaderRoleId) + properties: { + roleDefinitionId: monitoringReaderRoleId + principalId: aiProject.identity.principalId + principalType: 'ServicePrincipal' + } +} + +resource searchCognitiveServicesUserForFoundry 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: aiFoundry + name: guid(searchService.id, aiFoundry.id, cognitiveServicesUserRoleId, 'search-to-cognitive') + properties: { + roleDefinitionId: cognitiveServicesUserRoleId + principalId: searchService.identity.principalId + principalType: 'ServicePrincipal' + } +} + +resource searchContributorForFoundry 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: searchService + name: guid(searchService.id, aiFoundry.id, searchServiceContributorRoleId) + properties: { + roleDefinitionId: searchServiceContributorRoleId + principalId: aiFoundry.identity.principalId + principalType: 'ServicePrincipal' + } +} + +resource searchContributorForProject 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: searchService + name: guid(searchService.id, aiProject.id, searchServiceContributorRoleId) + properties: { + roleDefinitionId: searchServiceContributorRoleId + principalId: aiProject.identity.principalId + principalType: 'ServicePrincipal' + } +} + +resource searchIndexDataReaderForProject 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: searchService + name: guid(searchService.id, aiProject.id, searchIndexDataReaderRoleId) + properties: { + roleDefinitionId: searchIndexDataReaderRoleId + principalId: aiProject.identity.principalId + principalType: 'ServicePrincipal' + } +} + +resource apimCosmosDataContributor 'Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments@2025-05-01-preview' = { + parent: cosmosDbAccount + name: guid(apim.id, cosmosDbAccount.id, cosmosDbDataContributorRoleId) + properties: { + roleDefinitionId: '${cosmosDbAccount.id}/sqlRoleDefinitions/${cosmosDbDataContributorRoleId}' + principalId: apim.identity.principalId + scope: cosmosDbAccount.id + } +} + +// --- Role assignments: participant access --- +// The lab resource group is deployed ahead of time by the platform, so the +// participant needs explicit data-plane RBAC for services that don't grant +// data access through subscription/resource-group Owner alone. + +resource participantFoundryUser 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: aiProject + name: guid(aiProject.id, userObjectId, foundryUserRoleId) + properties: { + roleDefinitionId: foundryUserRoleId + principalId: userObjectId + principalType: 'User' + } +} + +resource participantCosmosDataContributor 'Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments@2025-05-01-preview' = { + parent: cosmosDbAccount + name: guid(cosmosDbAccount.id, userObjectId, cosmosDbDataContributorRoleId) + properties: { + roleDefinitionId: '${cosmosDbAccount.id}/sqlRoleDefinitions/${cosmosDbDataContributorRoleId}' + principalId: userObjectId + scope: cosmosDbAccount.id + } +} + +resource participantStorageBlobDataContributor 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: storageAccount + name: guid(storageAccount.id, userObjectId, storageBlobDataContributorRoleId) + properties: { + roleDefinitionId: storageBlobDataContributorRoleId + principalId: userObjectId + principalType: 'User' + } +} + +// --- APIM: Machine API and Maintenance API, proxying Cosmos DB via APIM's managed identity --- +// These are static conversions of the inline XML policies generated by +// challenge-0/seed-data.sh in the source hack (function policy_query_all / +// policy_query_by_id), with the collection and field names substituted in. + +var cosmosResourceAudience = 'https://${cosmosDbAccountName}.documents.azure.com' +var cosmosDataPlaneUrl = 'https://${cosmosDbAccountName}.documents.azure.com/' + +// NOTE: Bicep triple-quoted (''') multi-line strings do NOT support ${...} string +// interpolation - any ${...} placeholders below are emitted as literal text. The +// placeholders are therefore substituted afterwards via nested replace() calls. +func cosmosQueryAllPolicy(resourceAudience string, dataPlaneUrl string, collection string) string => replace(replace(replace(''' + + + + + + + @("${dataPlaneUrl}" + "dbs/FactoryOpsDB/colls/${collection}/docs") + POST + + @("type=aad&ver=1.0&sig=" + (string)context.Variables["msi-access-token"]) + + + @(context.Variables.GetValueOrDefault<string>("requestDateString")) + + 2018-12-31 + true + true + application/query+json + application/json + @{ + return JsonConvert.SerializeObject(new { + query = "SELECT * FROM c", + parameters = new object[0] + }); + } + + + + + + application/json + @{ + var response = ((IResponse)context.Variables["cosmosResponse"]).Body.As<JObject>(); + return response["Documents"].ToString(); + } + + + + + + application/json + @{ return ((IResponse)context.Variables["cosmosResponse"]).Body.As<string>(); } + + + + + + + + +''', '\${resourceAudience}', resourceAudience), '\${dataPlaneUrl}', dataPlaneUrl), '\${collection}', collection) + +func cosmosQueryByFieldPolicy(resourceAudience string, dataPlaneUrl string, collection string, paramName string, field string) string => replace(replace(replace(replace(replace(''' + + + + + + + + @("${dataPlaneUrl}" + "dbs/FactoryOpsDB/colls/${collection}/docs") + POST + + @("type=aad&ver=1.0&sig=" + (string)context.Variables["msi-access-token"]) + + + @(context.Variables.GetValueOrDefault<string>("requestDateString")) + + 2018-12-31 + true + true + application/query+json + application/json + @{ + string v = context.Variables["${paramName}"] as string; + return JsonConvert.SerializeObject(new { + query = "SELECT * FROM c WHERE c.${field} = @${paramName}", + parameters = new object[] { new { name = "@${paramName}", value = v } } + }); + } + + + + + + application/json + @{ + var response = ((IResponse)context.Variables["cosmosResponse"]).Body.As<JObject>(); + return response["Documents"].ToString(); + } + + + + + + application/json + @{ return ((IResponse)context.Variables["cosmosResponse"]).Body.As<string>(); } + + + + + + + + +''', '\${resourceAudience}', resourceAudience), '\${dataPlaneUrl}', dataPlaneUrl), '\${collection}', collection), '\${paramName}', paramName), '\${field}', field) + +resource machineApi 'Microsoft.ApiManagement/service/apis@2023-03-01-preview' = { + parent: apim + name: 'machine-api' + properties: { + displayName: 'Machine API' + description: 'Machines via Cosmos DB (APIM Managed Identity)' + path: 'machine' + protocols: ['https'] + subscriptionRequired: true + } +} + +resource machineApiListOperation 'Microsoft.ApiManagement/service/apis/operations@2023-03-01-preview' = { + parent: machineApi + name: 'list-machines' + properties: { + displayName: 'List Machines' + description: 'Retrieves all machines from the factory operations database.' + method: 'GET' + urlTemplate: '/' + responses: [ + { statusCode: 200, description: 'OK' } + ] + } +} + +resource machineApiListPolicy 'Microsoft.ApiManagement/service/apis/operations/policies@2023-03-01-preview' = { + parent: machineApiListOperation + name: 'policy' + properties: { + format: 'rawxml' + value: cosmosQueryAllPolicy(cosmosResourceAudience, cosmosDataPlaneUrl, 'Machines') + } +} + +resource machineApiGetOperation 'Microsoft.ApiManagement/service/apis/operations@2023-03-01-preview' = { + parent: machineApi + name: 'get-machine' + properties: { + displayName: 'Get Machine' + description: 'Retrieves a specific machine by its unique identifier.' + method: 'GET' + urlTemplate: '/{id}' + templateParameters: [ + { name: 'id', type: 'string', required: true } + ] + responses: [ + { statusCode: 200, description: 'OK' } + { statusCode: 404, description: 'Not Found' } + ] + } +} + +resource machineApiGetPolicy 'Microsoft.ApiManagement/service/apis/operations/policies@2023-03-01-preview' = { + parent: machineApiGetOperation + name: 'policy' + properties: { + format: 'rawxml' + value: cosmosQueryByFieldPolicy(cosmosResourceAudience, cosmosDataPlaneUrl, 'Machines', 'id', 'id') + } +} + +resource maintenanceApi 'Microsoft.ApiManagement/service/apis@2023-03-01-preview' = { + parent: apim + name: 'maintenance-api' + properties: { + displayName: 'Maintenance API' + description: 'Thresholds via Cosmos DB (APIM Managed Identity)' + path: 'maintenance' + protocols: ['https'] + subscriptionRequired: true + } +} + +resource maintenanceApiListOperation 'Microsoft.ApiManagement/service/apis/operations@2023-03-01-preview' = { + parent: maintenanceApi + name: 'list-thresholds' + properties: { + displayName: 'List Thresholds' + description: 'Retrieves all operational thresholds for factory equipment.' + method: 'GET' + urlTemplate: '/' + responses: [ + { statusCode: 200, description: 'OK' } + ] + } +} + +resource maintenanceApiListPolicy 'Microsoft.ApiManagement/service/apis/operations/policies@2023-03-01-preview' = { + parent: maintenanceApiListOperation + name: 'policy' + properties: { + format: 'rawxml' + value: cosmosQueryAllPolicy(cosmosResourceAudience, cosmosDataPlaneUrl, 'Thresholds') + } +} + +resource maintenanceApiGetOperation 'Microsoft.ApiManagement/service/apis/operations@2023-03-01-preview' = { + parent: maintenanceApi + name: 'get-threshold' + properties: { + displayName: 'Get Threshold' + description: 'Retrieves operational thresholds for a specific machine type.' + method: 'GET' + urlTemplate: '/{machineType}' + templateParameters: [ + { name: 'machineType', type: 'string', required: true } + ] + responses: [ + { statusCode: 200, description: 'OK' } + { statusCode: 404, description: 'Not Found' } + ] + } +} + +resource maintenanceApiGetPolicy 'Microsoft.ApiManagement/service/apis/operations/policies@2023-03-01-preview' = { + parent: maintenanceApiGetOperation + name: 'policy' + properties: { + format: 'rawxml' + value: cosmosQueryByFieldPolicy(cosmosResourceAudience, cosmosDataPlaneUrl, 'Thresholds', 'machineType', 'machineType') + } +} + +output storageAccountName string = storageAccountName +output machineWikiContainerName string = machineWikiContainerName +output logAnalyticsWorkspaceName string = logAnalyticsWorkspaceName +output searchServiceName string = searchServiceName +output searchServiceEndpoint string = 'https://${searchServiceName}.search.windows.net/' +output aiFoundryHubName string = foundryAccountName +output aiFoundryProjectName string = foundryProjectName +output aiFoundryHubEndpoint string = 'https://ml.azure.com/home?wsid=${aiFoundry.id}' +output aiFoundryProjectEndpoint string = 'https://ai.azure.com/build/overview?wsid=${aiProject.id}' +output containerRegistryName string = containerRegistryName +output acrName string = containerRegistryName +output applicationInsightsName string = applicationInsightsName +output cosmosDbAccountName string = cosmosDbAccountName +output cosmosDbDatabaseName string = cosmosDatabaseName +output cosmosDbEndpoint string = cosmosDbAccount.properties.documentEndpoint +output containerAppEnvironmentName string = containerAppsEnvironmentName +output apimName string = apimName +output apimGatewayUrl string = apim.properties.gatewayUrl diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/shared-deploy-lab.ps1 b/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/shared-deploy-lab.ps1 new file mode 100755 index 000000000..95fb600a6 --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/labautomation/shared-deploy-lab.ps1 @@ -0,0 +1,55 @@ +#!/usr/bin/env pwsh +<# +.SYNOPSIS +Runs once per subscription before any deploy-lab.ps1 run starts. +.DESCRIPTION +Registers the resource providers used by this hack's main.bicep so that +per-participant deployments do not fail on an unregistered provider. +.PARAMETER SubscriptionId +Specifies the Azure subscription that contains the lab resources. +.PARAMETER PreferredLocation +Specifies the preferred Azure regions, ordered by preference. +.PARAMETER AllowedEntraUserIds +Entra user object IDs of every participant holding a lab in this subscription. +#> +param( + [Parameter(Mandatory=$true)] + [string]$SubscriptionId, + + [Parameter(Mandatory=$true)] + [string[]]$PreferredLocation = @(), + + [Parameter(Mandatory=$false)] + [string[]]$AllowedEntraUserIds = @() +) + +$requiredProviders = @( + "Microsoft.Storage", + "Microsoft.OperationalInsights", + "Microsoft.Insights", + "Microsoft.DocumentDB", + "Microsoft.Search", + "Microsoft.ContainerRegistry", + "Microsoft.App", + "Microsoft.CognitiveServices", + "Microsoft.ApiManagement" +) + +foreach($provider in $requiredProviders) { + $registrationState = (Get-AzResourceProvider ` + -ProviderNamespace $provider ` + -ErrorAction Stop | Select-Object -First 1).RegistrationState + + if($registrationState -eq "Registered") { + Write-Host "[$SubscriptionId] Resource provider $provider is already registered." + continue + } + + Write-Host "[$SubscriptionId] Registering resource provider: $provider" + Register-AzResourceProvider ` + -ProviderNamespace $provider ` + -ErrorAction Stop | Out-Null +} + +# No shared (subscription-level) resources are required by this hack; every +# resource in main.bicep is deployed per-participant resource group. diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/pyproject.toml b/03-Azure/01-04-AI/08_Predictive_Maintenance/pyproject.toml new file mode 100644 index 000000000..90879f81f --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/pyproject.toml @@ -0,0 +1,37 @@ +[project] +name = "predictive-maintenance-hack" +version = "0.1.0" +description = "Predictive maintenance multi-agent MicroHack for a tire factory" +requires-python = ">=3.11" +dependencies = [ + # Microsoft Agent Framework + "agent-framework-foundry==1.12.0", + "agent-framework-core==1.17.0", + "azure-ai-projects==2.3.0", + + # A2A (Agent-to-Agent) Protocol + "a2a-sdk[http-server]==1.1.2", + + # Azure dependencies + "azure-cosmos==4.16.4", + "azure-identity==1.25.3", + "azure-search-documents==12.0.0", + "azure-core==1.41.0", + "aiohttp==3.14.3", + + # Azure AI Tracing & Monitoring + "azure-monitor-opentelemetry==1.8.9", + "opentelemetry-api==1.43.0", + "opentelemetry-sdk==1.43.0", + "opentelemetry-exporter-otlp-proto-grpc==1.43.0", + "opentelemetry-semantic-conventions-ai==0.5.1", + + # Data handling + "dataclasses-json==0.6.7", + + # Development dependencies + "python-dotenv==1.2.3", + "requests==2.34.2", + "ipykernel==7.3.0", + "pytest-asyncio==1.4.0", +] diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/scripts/install-pwsh.sh b/03-Azure/01-04-AI/08_Predictive_Maintenance/scripts/install-pwsh.sh new file mode 100755 index 000000000..27ac51e85 --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/scripts/install-pwsh.sh @@ -0,0 +1,48 @@ +#!/bin/bash +# ============================================================================= +# LOCAL DEVELOPMENT / TESTING ONLY +# ============================================================================= +# This script installs PowerShell 7 (pwsh) for local test simulation of +# deploy-lab.ps1. It has NO RELATION to the production Hack Console / Hackbox +# provisioning platform (which already executes PowerShell natively in its +# runner environment) and is NOT needed by hackathon participants. +# ============================================================================= + +set -e + +echo "πŸ” Detecting architecture and latest PowerShell 7 release..." + +ARCH=$(uname -m) +case "$ARCH" in + x86_64) PWSH_ARCH="x64" ;; + aarch64) PWSH_ARCH="arm64" ;; + *) echo "❌ Unsupported architecture: $ARCH"; exit 1 ;; +esac + +INSTALL_DIR="/usr/local/share/powershell" +BIN_LINK="/usr/local/bin/pwsh" + +# Fetch latest release tag from GitHub API or fallback to 7.4 LTS +LATEST_VERSION=$(curl -s https://api.github.com/repos/PowerShell/PowerShell/releases/latest | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/' || echo "7.4.6") +if [ -z "$LATEST_VERSION" ]; then + LATEST_VERSION="7.4.6" +fi + +TARBALL_URL="https://github.com/PowerShell/PowerShell/releases/download/v${LATEST_VERSION}/powershell-${LATEST_VERSION}-linux-${PWSH_ARCH}.tar.gz" + +echo "πŸ“¦ Downloading PowerShell v${LATEST_VERSION} (${PWSH_ARCH})..." +TMP_DIR=$(mktemp -d) +curl -sSL "$TARBALL_URL" -o "$TMP_DIR/powershell.tar.gz" + +echo "πŸ“‚ Extracting to $INSTALL_DIR..." +sudo mkdir -p "$INSTALL_DIR" +sudo tar -zxf "$TMP_DIR/powershell.tar.gz" -C "$INSTALL_DIR" +sudo chmod +x "$INSTALL_DIR/pwsh" + +echo "πŸ”— Creating symlink at $BIN_LINK..." +sudo ln -sf "$INSTALL_DIR/pwsh" "$BIN_LINK" + +rm -rf "$TMP_DIR" + +echo "βœ… PowerShell installed successfully!" +pwsh --version diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/scripts/test-deploy.ps1 b/03-Azure/01-04-AI/08_Predictive_Maintenance/scripts/test-deploy.ps1 new file mode 100755 index 000000000..0705060c1 --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/scripts/test-deploy.ps1 @@ -0,0 +1,167 @@ +#!/usr/bin/env pwsh +# ============================================================================= +# LOCAL DEVELOPMENT / TESTING ONLY +# ============================================================================= +# Simulates the Hackbox platform runner in a local PowerShell environment. +# Deploys main.bicep and executes the automatic data seeding logic. +# +# This harness is intended solely for local authoring validation and is NOT +# invoked by the Hack Console platform. +# ============================================================================= + +[CmdletBinding()] +param( + [Parameter()] + [string]$ResourceGroupName = "rg-pdm-localtest", + + [Parameter()] + [string]$Location = "swedencentral", + + [Parameter()] + [string]$SubscriptionId = "", + + [Parameter()] + [hashtable]$Tag = @{} +) + +$ErrorActionPreference = "Stop" + +# 1. Verify Azure CLI login +$accountJson = az account show 2>$null +if (-not $accountJson) { + Write-Host "Please log in to Azure with 'az login' before running this test." -ForegroundColor Yellow + az login --use-device-code +} + +if ([string]::IsNullOrWhiteSpace($SubscriptionId)) { + $SubscriptionId = (az account show --query id -o tsv).Trim() +} + +$userObjectId = (az ad signed-in-user show --query id -o tsv 2>$null) +if (-not $userObjectId) { + # If signed in as Service Principal or guest, fetch current user ID or fallback + $userObjectId = (az account show --query "user.name" -o tsv).Trim() +} + +$script:additionalTags = $Tag + +Write-Host "==================================================" -ForegroundColor Cyan +Write-Host "Testing deploy-lab.ps1 locally" -ForegroundColor Cyan +Write-Host "Subscription: $SubscriptionId" -ForegroundColor Cyan +Write-Host "Resource Group: $ResourceGroupName" -ForegroundColor Cyan +Write-Host "Location: $Location" -ForegroundColor Cyan +Write-Host "User Object ID: $userObjectId" -ForegroundColor Cyan +if ($Tag.Count -gt 0) { + $tagSummary = ($Tag.Keys | ForEach-Object { "$_=$($Tag[$_])" }) -join ', ' + Write-Host "Extra RG Tags: $tagSummary" -ForegroundColor Cyan +} +Write-Host "==================================================" -ForegroundColor Cyan + +# 2. Mock Get-MhhStablehash if not provided by platform module +if (-not (Get-Command Get-MhhStablehash -ErrorAction SilentlyContinue)) { + function Get-MhhStablehash { + param( + [Parameter(Mandatory=$true)] + [string[]]$Value, + [int]$Length = 12 + ) + $raw = ($Value -join ',').ToLowerInvariant() + $md5 = [System.Security.Cryptography.MD5]::Create() + $bytes = [System.Text.Encoding]::UTF8.GetBytes($raw) + $hashBytes = $md5.ComputeHash($bytes) + $hex = -join ($hashBytes | ForEach-Object { $_.ToString("x2") }) + if ($Length -gt $hex.Length) { return $hex } + return $hex.Substring(0, $Length) + } +} + +# 3. Mock Invoke-MhhDeploymentWithRegionFallback using Azure CLI +if (-not (Get-Command Invoke-MhhDeploymentWithRegionFallback -ErrorAction SilentlyContinue)) { + function Invoke-MhhDeploymentWithRegionFallback { + param( + [string[]]$PreferredLocations, + [string]$ResourceGroupName, + [string[]]$RgOwnerEntraObjectIds, + [hashtable]$Tag, + [string]$TemplateFile, + [hashtable]$TemplateParameterObject + ) + + $chosenLocation = if ($PreferredLocations.Count -gt 0) { $PreferredLocations[0] } else { "swedencentral" } + + # Merge tags passed in by deploy-lab.ps1 (e.g. CostControl/SecurityControl) + # with any additional tags supplied to test-deploy.ps1 via -Tag, so local + # runs can add tags required by subscription policies (e.g. policy exemptions). + $mergedTags = @{} + foreach ($k in $Tag.Keys) { $mergedTags[$k] = $Tag[$k] } + foreach ($k in $script:additionalTags.Keys) { $mergedTags[$k] = $script:additionalTags[$k] } + + Write-Host "Creating/Ensuring Resource Group '$ResourceGroupName' in '$chosenLocation'..." -ForegroundColor Green + if ($mergedTags.Count -gt 0) { + $tagArgs = $mergedTags.Keys | ForEach-Object { "$_=$($mergedTags[$_])" } + Write-Host "Applying tags: $($tagArgs -join ', ')" -ForegroundColor Green + az group create --name $ResourceGroupName --location $chosenLocation --tags $tagArgs --output none + } else { + az group create --name $ResourceGroupName --location $chosenLocation --output none + } + + # Build parameter arguments for az deployment + $paramArgs = @() + foreach ($k in $TemplateParameterObject.Keys) { + $val = $TemplateParameterObject[$k] + $paramArgs += "$k=$val" + } + + $deploymentName = "mhh-test-" + (Get-Date -Format "yyyyMMddHHmmss") + Write-Host "Deploying Bicep template '$TemplateFile' as '$deploymentName'..." -ForegroundColor Green + + $jsonResult = az deployment group create ` + --resource-group $ResourceGroupName ` + --name $deploymentName ` + --template-file $TemplateFile ` + --parameters $paramArgs ` + --query "{outputs: properties.outputs}" ` + --output json + + $parsed = $jsonResult | ConvertFrom-Json + + # Flatten outputs: { "name": { "value": "val" } } -> { "name": "val" } + $flatOutputs = @{} + if ($parsed.outputs) { + foreach ($prop in $parsed.outputs.PSObject.Properties) { + $flatOutputs[$prop.Name] = $prop.Value.value + } + } + + return @{ + Success = $true + LocationUsed = $chosenLocation + DeploymentName = $deploymentName + Outputs = $flatOutputs + } + } +} + +# 4. Invoke deploy-lab.ps1 +$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition +$deployLabScript = Join-Path (Resolve-Path (Join-Path $scriptDir "..\labautomation")) "deploy-lab.ps1" + +Write-Host "Executing deploy-lab.ps1 at '$deployLabScript'..." -ForegroundColor Green + +# Append a synthetic second entry derived from -ResourceGroupName to the hash +# input. Get-MhhStablehash hashes the whole array to compute globally-unique +# resource names, but only $AllowedEntraUserIds[0] (the real user object ID) is +# used for RBAC/portal access. This lets repeated local test runs target a new +# resource group without colliding on already-provisioned globally-unique +# resource names (storage account, AI Search, Cognitive Services subdomain) +# left over from a previous test resource group. +$testNamingSeed = "test-seed-$ResourceGroupName" + +& $deployLabScript ` + -DeploymentType "resourcegroup" ` + -SubscriptionId $SubscriptionId ` + -ResourceGroupName $ResourceGroupName ` + -PreferredLocation @($Location) ` + -AllowedEntraUserIds @($userObjectId, $testNamingSeed) + +Write-Host "`nβœ… Local deployment & data seeding simulation completed!" -ForegroundColor Green diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/.aspire/settings.json b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/.aspire/settings.json new file mode 100644 index 000000000..7812a6fea --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/.aspire/settings.json @@ -0,0 +1,3 @@ +{ + "appHostPath": "../apphost.cs" +} \ No newline at end of file diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/app/.dockerignore b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/app/.dockerignore new file mode 100644 index 000000000..d9d15981f --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/app/.dockerignore @@ -0,0 +1,7 @@ +# Byte-compiled / optimized / DLL files +**/__pycache__/ +**/*.py[cod] + +# Virtual environment +.env +.venv/ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/app/agents.py b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/app/agents.py new file mode 100644 index 000000000..4118be45c --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/app/agents.py @@ -0,0 +1,568 @@ +from multiprocessing import context + +from agent_framework.foundry import FoundryChatClient, FoundryAgent +from azure.identity.aio import AzureCliCredential +from dotenv import load_dotenv +from agent_framework import Agent, AgentExecutorResponse, WorkflowBuilder, Executor, handler, WorkflowContext + +import os +import sys +import re +import logging +from typing import Any + + +def extract_work_order_id(text: str) -> str | None: + """Extract work order ID (wo-XXXX-XXXXXXXX) from text.""" + match = re.search(r'wo-\d{4}-[a-f0-9]+', text, re.IGNORECASE) + return match.group(0) if match else None + + +# Add the shared src/agents to the Python path for in-place imports +# This path is relative to this file's location: src/agent-workflow/app -> src/agents +AGENTS_PATH = os.path.abspath(os.path.join( + os.path.dirname(__file__), "..", "..", "agents")) +if AGENTS_PATH not in sys.path: + sys.path.insert(0, AGENTS_PATH) + +# Load .env from the hack root to get COSMOS and AI_FOUNDRY credentials +HACK_ROOT = os.path.abspath(os.path.join( + os.path.dirname(__file__), "..", "..", "..")) +ENV_FILE = os.path.join(HACK_ROOT, ".env") +loaded = load_dotenv(ENV_FILE, override=True) +logger = logging.getLogger(__name__) + +# Debug: Log env loading status +logger.info( + f"Loading env from: {ENV_FILE} (exists: {os.path.exists(ENV_FILE)}, loaded: {loaded})") +logger.info(f"COSMOS_ENDPOINT set: {bool(os.getenv('COSMOS_ENDPOINT'))}") +logger.info(f"COSMOS_DATABASE set: {bool(os.getenv('COSMOS_DATABASE'))}") +logger.info( + f"AI_FOUNDRY_PROJECT_ENDPOINT set: {bool(os.getenv('AI_FOUNDRY_PROJECT_ENDPOINT'))}") + + +# ============================================================================= +# A2A Server Wrappers for the Maintenance Scheduler and Parts Ordering Agents +# ============================================================================= + +def create_maintenance_scheduler_a2a_app(): + """Create an A2A Starlette application for the Maintenance Scheduler Agent.""" + from starlette.applications import Starlette + from a2a.server.request_handlers import DefaultRequestHandler + from a2a.server.agent_execution import AgentExecutor, RequestContext + from a2a.server.events.event_queue import EventQueue + from a2a.server.tasks.task_updater import TaskUpdater + from a2a.server.tasks import InMemoryTaskStore + from a2a.server.routes import create_agent_card_routes, create_jsonrpc_routes + from a2a.types import AgentCard, AgentCapabilities, AgentSkill, Message, AgentInterface, Part + + # Get the base URL from environment or use default + # The URL should point to where this agent's RPC endpoint is accessible + # Must use https:// to match what the .NET workflow uses via Aspire + default_url = os.getenv("MAINTENANCE_SCHEDULER_AGENT_SELF_URL", + "https://localhost:8000/maintenance-scheduler/") + + agent_card = AgentCard( + name="MaintenanceSchedulerAgent", + description="Predictive maintenance scheduling agent that analyzes work orders, historical maintenance data, and available windows to recommend optimal maintenance schedules.", + version="1.0.0", + supported_interfaces=[AgentInterface( + protocol_binding="JSONRPC", + protocol_version="1.0", + url=default_url, + )], + capabilities=AgentCapabilities( + streaming=True, push_notifications=False), + default_input_modes=["text/plain"], + default_output_modes=["text/plain"], + skills=[ + AgentSkill( + id="schedule_maintenance", + name="Schedule Maintenance", + description="Predict optimal maintenance schedule for a work order", + tags=["maintenance", "scheduling", "predictive"], + ) + ], + ) + + class MaintenanceSchedulerExecutor(AgentExecutor): + """A2A executor that wraps the MaintenanceSchedulerAgent from src/agents.""" + + async def execute(self, context: RequestContext, event_queue: EventQueue) -> None: + from a2a.helpers import new_text_message + from a2a.types.a2a_pb2 import Task, TaskStatus, TaskState, Role + + user_message = context.message + task_id = context.task_id + context_id = context.context_id + + if not user_message or not task_id or not context_id: + return + + # Create and enqueue task + await event_queue.enqueue_event( + Task( + id=task_id, + context_id=context_id, + status=TaskStatus(state=TaskState.TASK_STATE_SUBMITTED), + history=[user_message], + ) + ) + + updater = TaskUpdater( + event_queue=event_queue, + task_id=task_id, + context_id=context_id, + ) + + query = context.get_user_input() + + try: + from maintenance_scheduler_agent import MaintenanceSchedulerAgent + from services.cosmos_db_service import CosmosDbService + except (ImportError, OSError) as import_err: + logger.exception("Failed to import agents from src/agents") + response_text = f"Error: Failed to import required modules: {str(import_err)}" + await updater.add_artifact( + parts=[Part(text=response_text)], + name='response', + last_chunk=True, + ) + await updater.complete() + return + + try: + # Extract the message text from user_message + logger.info( + f"MaintenanceSchedulerExecutor received message: {query}") + + # Initialize services + cosmos_endpoint = os.getenv("COSMOS_ENDPOINT") + cosmos_key = os.getenv("COSMOS_KEY") + database_name = os.getenv( + "COSMOS_DATABASE_NAME") or os.getenv("COSMOS_DATABASE") + project_endpoint = os.getenv("AI_FOUNDRY_PROJECT_ENDPOINT") or os.getenv( + "AZURE_AI_PROJECT_ENDPOINT") + deployment_name = os.getenv("MODEL_DEPLOYMENT_NAME", "gpt-5.4") + + if not all([cosmos_endpoint, cosmos_key, database_name, project_endpoint]): + response_text = "Error: Missing required environment variables for MaintenanceSchedulerAgent" + else: + cosmos_service = CosmosDbService( + cosmos_endpoint, cosmos_key, database_name) + agent = MaintenanceSchedulerAgent( + project_endpoint, deployment_name, cosmos_service) + + # Parse work order ID from input (default matches src/agents/maintenance_scheduler_agent.py) + work_order_id = extract_work_order_id( + query) if query else None + if not work_order_id: + work_order_id = "wo-2024-468" # fallback default + logger.info(f"Looking up work order: '{work_order_id}'") + + # Get work order and run prediction + work_order = await cosmos_service.get_work_order(work_order_id) + logger.info( + f"Found work order: {work_order.id} for machine: {work_order.machine_id}") + history = await cosmos_service.get_maintenance_history(work_order.machine_id) + windows = await cosmos_service.get_available_maintenance_windows(14) + + schedule = await agent.predict_schedule(work_order, history, windows) + + response_text = ( + f"Maintenance Schedule Created:\n" + f"- Schedule ID: {schedule.id}\n" + f"- Machine: {schedule.machine_id}\n" + f"- Scheduled Date: {schedule.scheduled_date}\n" + f"- Risk Score: {schedule.risk_score}/100\n" + f"- Failure Probability: {schedule.predicted_failure_probability * 100:.1f}%\n" + f"- Recommended Action: {schedule.recommended_action}\n" + f"- Reasoning: {schedule.reasoning}" + ) + + await cosmos_service.save_maintenance_schedule(schedule) + + except Exception as e: + logger.exception("MaintenanceSchedulerAgent error") + response_text = f"Error processing maintenance schedule request: {str(e)}" + + await updater.add_artifact( + parts=[Part(text=response_text)], + name='response', + last_chunk=True, + ) + await updater.complete() + + async def cancel(self, context: RequestContext, event_queue: EventQueue) -> None: + pass + + executor = MaintenanceSchedulerExecutor() + task_store = InMemoryTaskStore() + request_handler = DefaultRequestHandler( + agent_executor=executor, + task_store=task_store, + agent_card=agent_card + ) + + # Create routes for JSONRPC and agent card + routes = [] + routes.extend(create_agent_card_routes(agent_card)) + routes.extend(create_jsonrpc_routes(request_handler, '/')) + + # Create and return Starlette app + app = Starlette(routes=routes) + return app + + +def create_parts_ordering_a2a_app(): + """Create an A2A Starlette application for the Parts Ordering Agent.""" + from starlette.applications import Starlette + from a2a.server.request_handlers import DefaultRequestHandler + from a2a.server.agent_execution import AgentExecutor, RequestContext + from a2a.server.events.event_queue import EventQueue + from a2a.server.tasks.task_updater import TaskUpdater + from a2a.server.tasks import InMemoryTaskStore + from a2a.server.routes import create_agent_card_routes, create_jsonrpc_routes + from a2a.types import AgentCard, AgentCapabilities, AgentSkill, Message, AgentInterface, Part + + # Get the base URL from environment or use default + # Must use https:// to match what the .NET workflow uses via Aspire + default_url = os.getenv("PARTS_ORDERING_AGENT_SELF_URL", + "https://localhost:8000/parts-ordering/") + + agent_card = AgentCard( + name="PartsOrderingAgent", + description="Parts ordering agent that analyzes inventory status and generates optimized parts orders from suppliers.", + version="1.0.0", + supported_interfaces=[AgentInterface( + protocol_binding="JSONRPC", + protocol_version="1.0", + url=default_url, + )], + capabilities=AgentCapabilities( + streaming=True, push_notifications=False), + default_input_modes=["text/plain"], + default_output_modes=["text/plain"], + skills=[ + AgentSkill( + id="order_parts", + name="Order Parts", + description="Generate optimized parts order for a work order", + tags=["parts", "ordering", "inventory", "suppliers"], + ) + ], + ) + + class PartsOrderingExecutor(AgentExecutor): + """A2A executor that wraps the PartsOrderingAgent from src/agents.""" + + async def execute(self, context: RequestContext, event_queue: EventQueue) -> None: + from a2a.helpers import new_text_message + from a2a.types.a2a_pb2 import Task, TaskStatus, TaskState, Role + + user_message = context.message + task_id = context.task_id + context_id = context.context_id + + if not user_message or not task_id or not context_id: + return + + # Create and enqueue task + await event_queue.enqueue_event( + Task( + id=task_id, + context_id=context_id, + status=TaskStatus(state=TaskState.TASK_STATE_SUBMITTED), + history=[user_message], + ) + ) + + updater = TaskUpdater( + event_queue=event_queue, + task_id=task_id, + context_id=context_id, + ) + + query = context.get_user_input() + + try: + from parts_ordering_agent import PartsOrderingAgent + from services.cosmos_db_service import CosmosDbService + except (ImportError, OSError) as import_err: + logger.exception("Failed to import agents from src/agents") + response_text = f"Error: Failed to import required modules: {str(import_err)}" + await updater.add_artifact( + parts=[Part(text=response_text)], + name='response', + last_chunk=True, + ) + await updater.complete() + return + + try: + # Initialize services + cosmos_endpoint = os.getenv("COSMOS_ENDPOINT") + cosmos_key = os.getenv("COSMOS_KEY") + database_name = os.getenv( + "COSMOS_DATABASE_NAME") or os.getenv("COSMOS_DATABASE") + project_endpoint = os.getenv("AI_FOUNDRY_PROJECT_ENDPOINT") or os.getenv( + "AZURE_AI_PROJECT_ENDPOINT") + deployment_name = os.getenv("MODEL_DEPLOYMENT_NAME", "gpt-5.4") + + if not all([cosmos_endpoint, cosmos_key, database_name, project_endpoint]): + response_text = "Error: Missing required environment variables for PartsOrderingAgent" + else: + cosmos_service = CosmosDbService( + cosmos_endpoint, cosmos_key, database_name) + agent = PartsOrderingAgent( + project_endpoint, deployment_name, cosmos_service) + + # Parse work order ID from input (default matches src/agents/parts_ordering_agent.py) + work_order_id = extract_work_order_id( + query) if query else None + if not work_order_id: + work_order_id = "wo-2024-468" # fallback default + + # Get work order and generate order + work_order = await cosmos_service.get_work_order(work_order_id) + part_numbers = [ + p.part_number for p in work_order.required_parts] + inventory = await cosmos_service.get_inventory_items(part_numbers) + + parts_needing_order = [ + p for p in work_order.required_parts if not p.is_available] + + if not parts_needing_order: + response_text = "All required parts are available in stock. No parts order needed." + await cosmos_service.update_work_order_status(work_order.id, "Ready") + else: + needed_part_numbers = [ + p.part_number for p in parts_needing_order] + suppliers = await cosmos_service.get_suppliers_for_parts(needed_part_numbers) + + if not suppliers: + response_text = "Error: No suppliers found for required parts." + else: + order = await agent.generate_order(work_order, inventory, suppliers) + + response_text = ( + f"Parts Order Generated:\n" + f"- Order ID: {order.id}\n" + f"- Work Order: {order.work_order_id}\n" + f"- Supplier: {order.supplier_name}\n" + f"- Expected Delivery: {order.expected_delivery_date}\n" + f"- Total Cost: ${order.total_cost:.2f}\n" + f"- Items: {len(order.order_items)} part(s)" + ) + + await cosmos_service.save_parts_order(order) + await cosmos_service.update_work_order_status(work_order.id, "PartsOrdered") + + except Exception as e: + logger.exception("PartsOrderingAgent error") + response_text = f"Error processing parts order request: {str(e)}" + + await updater.add_artifact( + parts=[Part(text=response_text)], + name='response', + last_chunk=True, + ) + await updater.complete() + + async def cancel(self, context: RequestContext, event_queue: EventQueue) -> None: + pass + + executor = PartsOrderingExecutor() + task_store = InMemoryTaskStore() + request_handler = DefaultRequestHandler( + agent_executor=executor, + task_store=task_store, + agent_card=agent_card + ) + + # Create routes for JSONRPC and agent card + routes = [] + routes.extend(create_agent_card_routes(agent_card)) + routes.extend(create_jsonrpc_routes(request_handler, '/')) + + # Create and return Starlette app + app = Starlette(routes=routes) + return app + + +def _require_env(name: str) -> str: + value = os.getenv(name) + if not value: + raise RuntimeError( + f"Missing required environment variable: {name}. " + "This workflow expects Anomaly/Fault agents to be hosted in Foundry Agent Service and referenced by ID." + ) + return value + + +async def get_a2a_agent(server_url: str) -> Agent: + """Create and return an A2A Agent connected to the specified server URL.""" + try: + from agent_framework.a2a import A2AAgent + import importlib + + a2a = importlib.import_module("agent_framework_a2a") + except ModuleNotFoundError as e: + raise RuntimeError( + "A2A support requires the 'agent-framework-a2a' package. " + "If you're using uv, run `uv sync` in this directory." + ) from e + + resolver_cls = getattr(a2a, "A2ACardResolver", None) + if resolver_cls is not None: + import httpx + + async with httpx.AsyncClient(timeout=60.0) as http_client: + resolver = resolver_cls( + httpx_client=http_client, base_url=server_url) + agent_card = await resolver.get_agent_card(relative_card_path=".well-known/agent-card.json") + + return A2AAgent( + name=agent_card.name, + description=getattr(agent_card, "description", ""), + agent_card=agent_card, + url=server_url, + ) + + # Fallback: older/newer A2A packages may not ship a card resolver. + # Try the most common constructor shapes. + for kwargs in ( + {"url": server_url}, + {"name": "RepairPlannerAgent", "description": "A2A agent", "url": server_url}, + ): + try: + return A2AAgent(**kwargs) + except TypeError: + continue + + raise RuntimeError( + "Unable to construct A2A agent from 'agent-framework-a2a'. " + "Please ensure the package version matches the workflow sample." + ) + + +def extract_text_from_message(msg: Any) -> str: + """Helper to extract text from various message types used in the workflow.""" + text = "" + # Priority 1: Check for AgentExecutorResponse used by framework workflows + if hasattr(msg, 'agent_run_response') and hasattr(msg.agent_run_response, 'text'): + text = msg.agent_run_response.text + # Priority 2: Direct text attribute + elif getattr(msg, 'text', None): + text = msg.text + # Priority 3: Nested response (e.g. wrapper) + elif getattr(msg, 'response', None) and getattr(msg.response, 'text', None): + text = msg.response.text + # Priority 4: Event parameters + elif getattr(msg, 'params', None): + params = msg.params + if isinstance(params, dict): + text = params.get('text', '') or str(params) + elif hasattr(params, 'text'): + text = params.text + else: + text = str(params) + # Priority 5: Fallback string representation + else: + text = str(msg) + return text + +# --- Workflow Executors --- + + +class RequestProcessor(Executor): + @handler + async def process(self, data: dict, ctx: WorkflowContext[str]) -> None: + machine_id = data.get("machine_id") + telemetry = data.get("telemetry") + # Format the initial prompt for the Anomaly Agent + prompt = f'Classify the following anomalies for machine {machine_id}: {telemetry}' + await ctx.send_message(prompt) + + +def diagnosis_condition(message: Any) -> bool: + logger.info( + f"Evaluating diagnosis condition on message type: {message}") + # Defensive guard. If a non AgentExecutorResponse appears, let the edge pass to avoid dead ends. + if not isinstance(message, AgentExecutorResponse): + return True + """Determine if Fault Diagnosis is needed based on Anomaly Agent output.""" + print(f"Received message for diagnosis condition: {message}") + logger.info( + f"Evaluating diagnosis condition on message type: {type(message)}") + + text = message.agent_response.text + print(f"Extracted diagnosis text: {text}") + logger.info(f"Diagnosis text extracted: {text[:200]}...") + + keywords = ["critical", "warning", "high", "alert"] + should_run = any(keyword in text.lower() for keyword in keywords) + logger.info(f"Diagnosis condition result: {should_run}") + return should_run + +# --- Main Workflow Function --- + + +async def run_factory_workflow(machine_id: str, telemetry: list): + """ + Creates and runs the Factory Analysis Workflow. + + AnomalyDetectionAgent + FaultDiagnosisAgent are hosted in Foundry Agent Service. + We reference them by name using FoundryAgent. + """ + + project_endpoint = _require_env("AZURE_AI_PROJECT_ENDPOINT") + deployment_name = os.getenv("MODEL_DEPLOYMENT_NAME", "gpt-5.4") + repair_planner_url = os.getenv("REPAIR_PLANNER_AGENT_URL") + + credential = AzureCliCredential() + try: + # Reference hosted Foundry agents by name + anomaly_agent = FoundryAgent( + project_endpoint=project_endpoint, + name="AnomalyClassificationAgent", + agent_name="AnomalyClassificationAgent", + credential=credential, + default_options={ + "model": deployment_name, + "extra_body": {"model": deployment_name}, + } + ) + + fault_agent = FoundryAgent( + project_endpoint=project_endpoint, + name="FaultDiagnosisAgent", + agent_name="FaultDiagnosisAgent", + credential=credential, + default_options={ + "model": deployment_name, + "extra_body": {"model": deployment_name}, + } + ) + + # Build the workflow + logger.info("Building workflow with Foundry agents...") + + # Init with RequestProcessor as the starting executor + request_processor = RequestProcessor(id="init") + + # Build workflow starting with the Anomaly Agent + builder = WorkflowBuilder(start_executor=request_processor) + builder.add_edge(request_processor, anomaly_agent) + builder.add_edge(anomaly_agent, fault_agent, + condition=diagnosis_condition) + + if repair_planner_url: + repair_planner_agent = await get_a2a_agent(server_url=repair_planner_url) + builder.add_edge(fault_agent, repair_planner_agent) + + workflow = builder.build() + result = await workflow.run({"machine_id": machine_id, "telemetry": telemetry}) + return result.get_outputs() + finally: + await credential.close() diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/app/main.py b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/app/main.py new file mode 100644 index 000000000..2c264afe7 --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/app/main.py @@ -0,0 +1,146 @@ +import contextlib +import datetime +import logging +import os +import random +import json +import re + +import fastapi +import fastapi.responses +import fastapi.staticfiles +from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor +from pydantic import BaseModel +from agents import run_factory_workflow, create_maintenance_scheduler_a2a_app, create_parts_ordering_a2a_app +from agent_framework.observability import configure_otel_providers +from dotenv import load_dotenv + + +@contextlib.asynccontextmanager +async def lifespan(app): + configure_otel_providers() + yield + + +# Configure logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +load_dotenv() + +app = fastapi.FastAPI(lifespan=lifespan) + +# Add middleware to log all requests + + +@app.middleware("http") +async def log_requests(request: fastapi.Request, call_next): + logger.info(f">>> Incoming request: {request.method} {request.url.path}") + response = await call_next(request) + logger.info( + f"<<< Response: {request.method} {request.url.path} - Status: {response.status_code}") + return response + +FastAPIInstrumentor.instrument_app(app, exclude_spans=["send"]) + +# ============================================================================= +# A2A Agent Endpoints +# Mount the A2A Starlette applications for the maintenance scheduler and parts ordering agents (src/agents/) +# These can be called from the dotnet workflow using A2A protocol +# ============================================================================= +try: + # Create and mount the A2A Starlette applications directly + # These are already Starlette apps, no need to build them + maintenance_scheduler_starlette = create_maintenance_scheduler_a2a_app() + parts_ordering_starlette = create_parts_ordering_a2a_app() + + app.mount("/maintenance-scheduler", maintenance_scheduler_starlette) + app.mount("/parts-ordering", parts_ordering_starlette) + logger.info( + "A2A agents mounted successfully at /maintenance-scheduler and /parts-ordering") +except Exception as e: + logger.warning(f"Failed to initialize A2A agents: {e}") + + +if not os.path.exists("static"): + @app.get("/", response_class=fastapi.responses.HTMLResponse) + async def root(): + """Root endpoint.""" + return "API service is running. Navigate to /api/weatherforecast to see sample data or POST to /api/analyze_machine." + + +@app.get("/api/weatherforecast") +async def weather_forecast(): + """Weather forecast endpoint.""" + # Generate fresh data if not in cache or cache unavailable. + summaries = [ + "Freezing", + "Bracing", + "Chilly", + "Cool", + "Mild", + "Warm", + "Balmy", + "Hot", + "Sweltering", + "Scorching", + ] + + forecast = [] + for index in range(1, 6): # Range 1 to 5 (inclusive) + temp_c = random.randint(-20, 55) + forecast_date = datetime.datetime.now() + datetime.timedelta(days=index) + forecast_item = { + "date": forecast_date.isoformat(), + "temperatureC": temp_c, + "temperatureF": int(temp_c * 9 / 5) + 32, + "summary": random.choice(summaries), + } + forecast.append(forecast_item) + + return forecast + + +class AnalyzeRequest(BaseModel): + machine_id: str + telemetry: list[dict] | dict + + +@app.post("/api/analyze_machine") +async def analyze_machine(request: AnalyzeRequest): + logger.info(f"Analyzing machine {request.machine_id}") + + try: + outputs = await run_factory_workflow(request.machine_id, request.telemetry) + + serialized_outputs = [] + for out in outputs: + # Handle AgentRunResponse or similar + if hasattr(out, 'text'): + serialized_outputs.append(out.text) + # AgentRunResponse vs AgentRunEvent + elif hasattr(out, 'params') and hasattr(out.params, 'text'): + serialized_outputs.append(str(out)) + else: + serialized_outputs.append(str(out)) + + return {"results": serialized_outputs} + + except Exception as e: + logger.error(f"Workflow failed: {e}") + return fastapi.responses.JSONResponse(content={"error": str(e)}, status_code=500) + + +@app.get("/health", response_class=fastapi.responses.PlainTextResponse) +async def health_check(): + """Health check endpoint.""" + return "Healthy" + + +# Serve static files directly from root, if the "static" directory exists +if os.path.exists("static"): + app.mount( + "/", + fastapi.staticfiles.StaticFiles(directory="static", html=True), + name="static" + ) diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/app/pyproject.toml b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/app/pyproject.toml new file mode 100644 index 000000000..65f0d1adf --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/app/pyproject.toml @@ -0,0 +1,20 @@ +[project] +name = "FastAPI-starter" +version = "0.1.0" +description = "FastAPI starter" +requires-python = ">=3.13" +dependencies = [ + "fastapi[standard]==0.141.1", + "agent-framework-core==1.17.0", + "agent-framework-foundry==1.12.0", + "a2a-sdk[http-server]==1.1.2", + "azure-identity==1.25.3", + "azure-cosmos==4.16.4", + "python-dotenv==1.2.3", + "azure-monitor-opentelemetry==1.8.9", + "opentelemetry-api==1.43.0", + "opentelemetry-sdk==1.43.0", + "opentelemetry-exporter-otlp-proto-grpc==1.43.0", + "opentelemetry-semantic-conventions-ai==0.5.1", + "aiohttp==3.14.3" +] diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/app/uv.lock b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/app/uv.lock new file mode 100644 index 000000000..d2df3654c --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/app/uv.lock @@ -0,0 +1,2725 @@ +version = 1 +revision = 3 +requires-python = ">=3.13" +resolution-markers = [ + "python_full_version >= '3.14'", + "python_full_version < '3.14'", +] + +[[package]] +name = "a2a-sdk" +version = "1.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core" }, + { name = "googleapis-common-protos" }, + { name = "httpx" }, + { name = "json-rpc" }, + { name = "packaging" }, + { name = "protobuf" }, + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/cc/59b35c518d8289bd59d20d9d216ca29ccb41c4697eb85971efe41d1adaf3/a2a_sdk-1.1.2.tar.gz", hash = "sha256:f928d8bf9a0dc0a473ee8258bd5226c1b8520a85c70e7f233c3b9b0e30a1bd10", size = 379627, upload-time = "2026-07-22T13:40:51.409Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/33/d414a03d5ef5a7d6d09a20dc9f8094e7fb9edb488662ff99c535a2a62cf1/a2a_sdk-1.1.2-py3-none-any.whl", hash = "sha256:eb9a698f526dc45a9ea967d7804e7aa363ff273d2c44fbed699bc68c9399f9c9", size = 245981, upload-time = "2026-07-22T13:40:49.484Z" }, +] + +[package.optional-dependencies] +http-server = [ + { name = "sse-starlette" }, + { name = "starlette" }, +] + +[[package]] +name = "agent-detector" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/92/2c3e2ad5ce9683876729034082af19cfb3fbafd1c298b22a0b0c10ecfdc5/agent_detector-2.0.0.tar.gz", hash = "sha256:19c8ae185dbdca23c24abdcd8ac2cf58b8e84dc284c6f6db536656601f9ffc3f", size = 55094, upload-time = "2026-09-02T14:19:51.88Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/15/504337e302d070bd2ff2f39418bb17d2ad73c662c403a4f37cb4e5c5b237/agent_detector-2.0.0-py3-none-any.whl", hash = "sha256:22c6a1c9c23894a12f55aea625f060b8d3330fba774de72d02d5442bb580417f", size = 9352, upload-time = "2026-09-02T14:19:50.971Z" }, +] + +[[package]] +name = "agent-framework-core" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "msgspec" }, + { name = "opentelemetry-api" }, + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1d/a6/a47518b61275a7b001d3acebae6857ad98b7d10303ef8d7c9b6546b1a450/agent_framework_core-1.17.0.tar.gz", hash = "sha256:c1b69ab7f496d1ec9ab5dcb0afc5a99e60e5c820f03f5a755a9104fd89d01394", size = 593864, upload-time = "2026-09-03T09:53:46.019Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/cb/1fceefa6dc720774fc8180a8651045e583de550b946cc59c291684fcbbf6/agent_framework_core-1.17.0-py3-none-any.whl", hash = "sha256:75958ff692a38bf0c627aaa910bae6c4a89569dfec68e1e79eac8e206d88874d", size = 651715, upload-time = "2026-09-03T09:53:33.222Z" }, +] + +[[package]] +name = "agent-framework-foundry" +version = "1.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "agent-framework-core" }, + { name = "agent-framework-openai" }, + { name = "aiohttp" }, + { name = "azure-ai-inference" }, + { name = "azure-ai-projects" }, + { name = "httpx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/14/a1/2a1a8b7d50d0ab6de9c7170b1f90a9d91f664a40f833913b2d2164dba91e/agent_framework_foundry-1.12.0.tar.gz", hash = "sha256:6361fb0ead699ddc907c95c4ecac7176d688f14a3d60f00e8dfdf06904336356", size = 51220, upload-time = "2026-09-03T09:53:48.254Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/6b/b066715e5ccc2d906920b1859160c54703aacc213f2f01c5e8e121d8555a/agent_framework_foundry-1.12.0-py3-none-any.whl", hash = "sha256:f48362cd01962a526ec8f88f67608fbac13d1c728400c6c6ec8a5bc9b1f42c94", size = 53787, upload-time = "2026-09-03T09:53:36.555Z" }, +] + +[[package]] +name = "agent-framework-openai" +version = "1.14.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "agent-framework-core" }, + { name = "openai" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/89/b2/c784f2c11b03f586a61775b1c5f61710379a1069257698dd803e7fbb03fa/agent_framework_openai-1.14.2.tar.gz", hash = "sha256:a09684d35bc0202b8fffea6fbd187480bc56a95027ed352d699a839d536a12ff", size = 63266, upload-time = "2026-09-03T09:53:52.239Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/de/6bdf51e5671b9f72ba83cc8b9ae3bc616c23bdb7f30365756c7d8baa0e74/agent_framework_openai-1.14.2-py3-none-any.whl", hash = "sha256:3da7d1f81bcd2bdbae8ba833647a4a8c2da1f1250bd3cc7947b2eb4e67e10c61", size = 68397, upload-time = "2026-09-03T09:53:41.964Z" }, +] + +[[package]] +name = "aiohappyeyeballs" +version = "2.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/f4/eec0465c2f67b2664688d0240b3212d5196fd89e741df67ddb81f8d35658/aiohappyeyeballs-2.7.1.tar.gz", hash = "sha256:065665c041c42a5938ed220bdcd7230f22527fbec085e1853d2402c8a3615d9d", size = 24757, upload-time = "2026-07-01T17:11:55.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/43/1947f06babed6b3f1d7f38b0c767f52df66bfb2bc10b468c4a7de9eceff2/aiohappyeyeballs-2.7.1-py3-none-any.whl", hash = "sha256:9243213661e29250eb41368e5daa826fc017156c3b8a11440826b2e3ed376472", size = 15038, upload-time = "2026-07-01T17:11:54.055Z" }, +] + +[[package]] +name = "aiohttp" +version = "3.14.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/d9/22ce5786ac0c1653ae8b6c23bded02c1686d11f0dbb45b31ce128e0df985/aiohttp-3.14.3.tar.gz", hash = "sha256:9491196535a88924a60afd5b5f434b5b203b6cc616250878dbdb223a8f7844bc", size = 7971213, upload-time = "2026-07-23T01:57:27.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/be/5afd201cc0ab139029aadb75392efe85a293403d9dd3a3226161c21ce00c/aiohttp-3.14.3-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:2e9878ae68e4a5f1c0abe4dd497dbc3d51946f5837b56759e2a02e78fa90ef86", size = 506269, upload-time = "2026-07-23T01:54:49.075Z" }, + { url = "https://files.pythonhosted.org/packages/22/09/dec8189d62b45ade009f6792a2264b942a90cb88aeaf181239933cd72c3c/aiohttp-3.14.3-cp313-cp313-android_21_x86_64.whl", hash = "sha256:f3d2669fe7dec7fc359ecdb5984b29b50d85d5d00f8c1cb61de4f4a24ee42627", size = 515166, upload-time = "2026-07-23T01:54:51.894Z" }, + { url = "https://files.pythonhosted.org/packages/28/24/2854869d29ed8a8b19d74f9ec6629515f7e04d02dd329d9d179201e58e47/aiohttp-3.14.3-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:cc7cb243a68167172f48c1fd43cee91ec4b1d40cefd190edd43369d1a6bc9c82", size = 486263, upload-time = "2026-07-23T01:54:54.223Z" }, + { url = "https://files.pythonhosted.org/packages/d4/dd/57187c8be2a35aea65eaee3bd2c3dcbbcf0204f5106c89637e3610380cd1/aiohttp-3.14.3-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:78253b573e6ffab5028924fc98bc281aae05445969982a10864bc360dea2016c", size = 492299, upload-time = "2026-07-23T01:54:56.236Z" }, + { url = "https://files.pythonhosted.org/packages/b9/11/06ae6ed8f0d414edf4068861e233d8fe23ee699bfd4b3ceb8663db948a62/aiohttp-3.14.3-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7041d52c3a7fa20c9e8c182b534704abb19502c8bdcbde7ab23bfda6f642394f", size = 502235, upload-time = "2026-07-23T01:54:58.377Z" }, + { url = "https://files.pythonhosted.org/packages/7e/a3/559639c34a345d2cf7c52dff6838119f2eaf29eb508227b5b83f573af813/aiohttp-3.14.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ac74facc01463f138b0da5580329cfcc82818dea5656e83ddcd11268fc12ff80", size = 750883, upload-time = "2026-07-23T01:55:00.65Z" }, + { url = "https://files.pythonhosted.org/packages/91/cd/41e131f13afd1e7b0172a9d9eda085ef90eb8439f41f0d279db81ed3ae60/aiohttp-3.14.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d6218d92e450824e9b4881f44e8c09f1853b490f9a64130801024a4793b1b3b0", size = 508473, upload-time = "2026-07-23T01:55:02.945Z" }, + { url = "https://files.pythonhosted.org/packages/bc/6b/e7f13410d391c6e55b4c007a8de024355389d7d459e3d64c42b2d33617e5/aiohttp-3.14.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:11fb37ef075669eee52ab1928fbf6e1741fada40409fa309ebde9607a962aebf", size = 509190, upload-time = "2026-07-23T01:55:05.173Z" }, + { url = "https://files.pythonhosted.org/packages/97/21/6464573e53d69672cc1eada3e5c5cb2d2efa82701e8305a0f2047a576967/aiohttp-3.14.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55bdcc472aafe2de4a253045cc128007a64f1e0264fb675791e132ea5edaa3bd", size = 1761478, upload-time = "2026-07-23T01:55:07.383Z" }, + { url = "https://files.pythonhosted.org/packages/1a/81/d217043a4c17fbce360905e3b2bdd20139ebc9a2de836d035d179c4da006/aiohttp-3.14.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c39846c3aad97a8530c89d7a3869a8f8e9e3762c6ac0504481e5c80948f7e807", size = 1735092, upload-time = "2026-07-23T01:55:09.803Z" }, + { url = "https://files.pythonhosted.org/packages/a1/66/e13a02d0eeb1a9a502402a977abb4e4abff9fe4051c26f80558c57a7c975/aiohttp-3.14.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5895ef58c4620afe02fa16044f023dc4dafec08158f9d08874a46a7dbc0341b8", size = 1800546, upload-time = "2026-07-23T01:55:12.012Z" }, + { url = "https://files.pythonhosted.org/packages/26/5e/57d42fca1d18cb5acc1cad945d017fabc5d6ae71d8a08ad66be8dc3ee544/aiohttp-3.14.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fa9467a8113aa69d3d7c55a70ef0b7c636010a40993f3df9d9d0d73b3eb7ef24", size = 1895250, upload-time = "2026-07-23T01:55:14.357Z" }, + { url = "https://files.pythonhosted.org/packages/ca/1c/7da8d08e74d56f00070822f9638ff3f1c563f8ad87d1efa996c87bfc8644/aiohttp-3.14.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7d2deec16eeedf55f2c7cf75b521ea3856a5177e123844f8fd0f114ce252cb5", size = 1789289, upload-time = "2026-07-23T01:55:16.668Z" }, + { url = "https://files.pythonhosted.org/packages/cd/0f/cf16bcf56896981c1a0319f5d5db9337994b5165730c48a8fa07e9b34be6/aiohttp-3.14.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dd54d0e8717de95939766febac482ac0474d8ac3b048115f9f2b1d23a16e7db4", size = 1586706, upload-time = "2026-07-23T01:55:18.913Z" }, + { url = "https://files.pythonhosted.org/packages/fe/6f/76eac12a7f2480e1e304f842efdb07db33256b0d9165b866b6ef0806c202/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:df82f3787c940c94986b34222d59c9e38843fba85139f36e85255a82ad5355a9", size = 1724652, upload-time = "2026-07-23T01:55:21.296Z" }, + { url = "https://files.pythonhosted.org/packages/39/b6/19c8c592baeeb94b75f966547d40c02ac7590902306ec5863d5c027cf506/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:42a67efc36300d052fb4508a53e8b6901b9284b599ae63945c377569c5fcc1e1", size = 1756239, upload-time = "2026-07-23T01:55:23.705Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c9/4e9383150296f97f873b680c4de8fb2cd88608fb9f48c79edcb111611abc/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7a75aa63cbf9b21cfaf60dc2657e19df2c2867d91707d653fee171ffeedd1371", size = 1769161, upload-time = "2026-07-23T01:55:26.082Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1e/147bdc6cc5de5f3ab011be8bf5d6e786633249f22c20bae06f85e45f5387/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e92eb8acc45eb6a9f4935071a77edf5b85cc6f8dfad5cd99e97653c26593cdde", size = 1578759, upload-time = "2026-07-23T01:55:28.846Z" }, + { url = "https://files.pythonhosted.org/packages/fd/31/78388a9d6040ece2e11df62ea229a822cf5e52d238374b220ae9975b2623/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b014a6ed7cf912e787149fdc529166d3ceabac23f26efeea3158c9aba2354e7e", size = 1792025, upload-time = "2026-07-23T01:55:31.457Z" }, + { url = "https://files.pythonhosted.org/packages/03/51/a3d29fdf2c25d796746af8ad6fe56a45d6256c38b0a8a2ed752e1160b3a2/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3d4f72af88ac2474bb5bca640030320e3d38a0163a1d7533500e87be458eef71", size = 1768477, upload-time = "2026-07-23T01:55:33.87Z" }, + { url = "https://files.pythonhosted.org/packages/29/a6/442e18b5afeade534d877a2dc3c3e392aff8d49787890b0cf84790410267/aiohttp-3.14.3-cp313-cp313-win32.whl", hash = "sha256:5f08ec777f35ee70720233b8b9811d3bb5d728137f30ac91b7457709c3261ac0", size = 451069, upload-time = "2026-07-23T01:55:36.121Z" }, + { url = "https://files.pythonhosted.org/packages/9d/69/3d876ac02659f271cf7f6769f14a8e3de5b6e888ed8b5a7e998086a4cec8/aiohttp-3.14.3-cp313-cp313-win_amd64.whl", hash = "sha256:dff9461ec275f22135650d5ba4b4931a11f3958df7dfbb8db630000d4dee0883", size = 476518, upload-time = "2026-07-23T01:55:38.303Z" }, + { url = "https://files.pythonhosted.org/packages/b2/0e/50d6e6471cd31edce8b282bdec59375a3a69124d8a989a0b1313355cae52/aiohttp-3.14.3-cp313-cp313-win_arm64.whl", hash = "sha256:ddcac3c6b382e81f1dd0499199d4136b877beb4cb5ef770bbbfba56c4b8f55d2", size = 447676, upload-time = "2026-07-23T01:55:40.451Z" }, + { url = "https://files.pythonhosted.org/packages/c8/20/887fdcf832326571b370ffc347b3e70abe101096f3720126aac161b1d872/aiohttp-3.14.3-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:49f7325beb0f85ef4aef5f48f490269575f83e6e2acad00a1d80b807eb027062", size = 509067, upload-time = "2026-07-23T01:55:42.618Z" }, + { url = "https://files.pythonhosted.org/packages/ad/a3/92cec936f78cc4bf0fa5554ebe593b73459d94e3c62303e1902a4cccb6f7/aiohttp-3.14.3-cp314-cp314-android_24_x86_64.whl", hash = "sha256:e3be98a7c30b8c25d573dafba7171d66dfb05ee6a9070fc46535464ff97700a6", size = 514774, upload-time = "2026-07-23T01:55:44.937Z" }, + { url = "https://files.pythonhosted.org/packages/29/ba/2a0c38df3fc557620b6a5acd98364af050053b6285b4dc7ee74100c63c18/aiohttp-3.14.3-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:614c61d478b83953e261d02bb2df750f17227cd33ef8002945bf5aebbde21919", size = 488134, upload-time = "2026-07-23T01:55:47.135Z" }, + { url = "https://files.pythonhosted.org/packages/48/d6/d51b7d4bf309af3693940d8ffd2b9ed0b682434ef85959b7c9c137f60cf8/aiohttp-3.14.3-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:1caa7b0d05f3e3a36f87788c59e970a7ee1cefcfcbb924a9f138c4a6551c9cb7", size = 494201, upload-time = "2026-07-23T01:55:49.451Z" }, + { url = "https://files.pythonhosted.org/packages/3f/5a/8f624384e5f1efabb5229b94157eb966b021e97bdb188c62860c2ae243c2/aiohttp-3.14.3-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:dfa68deb2a443bdaa3ea5297b0699c1464f08aef3812b486d1348eee61b07dc0", size = 502766, upload-time = "2026-07-23T01:55:51.656Z" }, + { url = "https://files.pythonhosted.org/packages/a6/26/4ff0164370deec18fb19254ee4ab10b7a73304ac0c860b13f5f84663759b/aiohttp-3.14.3-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:e72ee89e28d907a18f46959b4eb0bb06701cc7f8cf4366e00029e2ccfaaf5924", size = 756557, upload-time = "2026-07-23T01:55:53.964Z" }, + { url = "https://files.pythonhosted.org/packages/97/a3/7056b86dc0d9ec709ea9777eae3b0161428f943372f8b98c01c11593b682/aiohttp-3.14.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ad4c8b7488d745d2ca4838ebd8ae5ba9b56341d30b1da43640e4ce87f9f49646", size = 510168, upload-time = "2026-07-23T01:55:56.22Z" }, + { url = "https://files.pythonhosted.org/packages/85/ed/0357a015892fd68058bf2d39d3fd1958e459b997a7db30aaa6aaa434ae96/aiohttp-3.14.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:db332af25642007330fca8be5c4d194caf2bea7a7fc84415aff3497af5dfee6b", size = 512957, upload-time = "2026-07-23T01:55:58.437Z" }, + { url = "https://files.pythonhosted.org/packages/47/d1/8aba53f15ccb2238405f5e9d30e2a8ca44f93878c26e7165ade00d374b1c/aiohttp-3.14.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25bd2708db6bdf6a6630dd37bdcdfcb47c4434d22ac69c64665b802910140b30", size = 1750149, upload-time = "2026-07-23T01:56:00.856Z" }, + { url = "https://files.pythonhosted.org/packages/49/bd/40c3fee327529284375c6701cbb0fa4600cc2e8432af1378f897e2ef7d3a/aiohttp-3.14.3-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:cef89a58e628c4efcac3275c2d68083f82426dcdc89c1492a6f654f9f7ea6ab9", size = 1707685, upload-time = "2026-07-23T01:56:03.371Z" }, + { url = "https://files.pythonhosted.org/packages/2a/a3/ca0cc6724cca8114b05694abd916060758c79894c3aa5b012cdadc1bc28e/aiohttp-3.14.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c23ec8ee9d5ab2f5421f9c7fffce208435607af27fd46d4a44e031954352838f", size = 1803911, upload-time = "2026-07-23T01:56:05.817Z" }, + { url = "https://files.pythonhosted.org/packages/95/b5/85b099c299c3ffd38ad9b3e43694c8a346934e4a30c88c4fd5a841234f77/aiohttp-3.14.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e2667f0bbe7eb6c74eae5e9691441ad186e5845ca3cff63230fc09c4e7514f5d", size = 1876929, upload-time = "2026-07-23T01:56:08.413Z" }, + { url = "https://files.pythonhosted.org/packages/d5/b7/1da684a04175473fa4cddbf9a2f572e79514c3fd27a74597f43057d4f3da/aiohttp-3.14.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18cb43369747b2ae007bd2655fb8e63a099c2ff1d207962943636dac989b3147", size = 1761112, upload-time = "2026-07-23T01:56:10.918Z" }, + { url = "https://files.pythonhosted.org/packages/d1/16/bc4b55e3e5cb175fd69c53c90d60d2f47797cb343da5106e23863dc4dba4/aiohttp-3.14.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d77640cc618c1d99fc4f8589c0f24a730adfa54eb1e57ef7bf0c8dfb78da898c", size = 1583500, upload-time = "2026-07-23T01:56:13.613Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e8/13a9d957a1ee40837f46aa30f0f4c657e673ad86a2e6362a9f9be20d26d9/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:53e5179d8abb5710f8e83ba207c41c8d1261fcffd4616500e15ca2b7a33be10a", size = 1713940, upload-time = "2026-07-23T01:56:15.969Z" }, + { url = "https://files.pythonhosted.org/packages/38/05/d33c680c1bcf1c7e130f9cbfc1fc02fe8bb0c4af2a94a53dd5fb56131e5c/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:cd817772b2fcf2b8c0905795318485f9ec16eae60b29feb7f4c77085311637f0", size = 1724413, upload-time = "2026-07-23T01:56:18.591Z" }, + { url = "https://files.pythonhosted.org/packages/85/1d/af798d306f7a74b6a632dbcabcf62a4c91391b7582d2a8c6d7712e2cc54e/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:4e3ac92d90e92773b2362d506068e9a948192bd553e743c5b2429e28527c8661", size = 1770748, upload-time = "2026-07-23T01:56:21.074Z" }, + { url = "https://files.pythonhosted.org/packages/a8/92/ad720d472556a995049206867765e9410969684f86ee09423ff9969044c1/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3f42e9b78301f11c8f861746175d8b9c1ccef713fcad9eab396e2f6db8ed4a22", size = 1577564, upload-time = "2026-07-23T01:56:23.475Z" }, + { url = "https://files.pythonhosted.org/packages/60/ad/0ed7586cbef7a884e23a752fa2bb987a122e6a5dd50dab109258d0a95193/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:9d9edccfe496b476db5f398d97b865e9a6752bcf8aec4eef8390ce20fb64bb41", size = 1782080, upload-time = "2026-07-23T01:56:25.994Z" }, + { url = "https://files.pythonhosted.org/packages/97/ea/dbaed0d73e8a69aad653b045dab451c67c2454bb731a37b45a86593e9422/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1c5ec8fb1bcc31a8466f74aaf26c345d5c386fa4bd08a3f0eb9c7a4a3fe8b5bf", size = 1745813, upload-time = "2026-07-23T01:56:28.604Z" }, + { url = "https://files.pythonhosted.org/packages/81/1b/6893d4bc57e434fc93a6c9217c637d967a0b651d989f6e3265179375754a/aiohttp-3.14.3-cp314-cp314-win32.whl", hash = "sha256:38901a84da3ce22249f6e860bf8f90d141bcab7da090cc398f8bb58c0e44b7da", size = 455872, upload-time = "2026-07-23T01:56:31.031Z" }, + { url = "https://files.pythonhosted.org/packages/f5/8b/c7baa1ba1eda4db6989baefe5de6d99834921b84ebd7918624febcb9f290/aiohttp-3.14.3-cp314-cp314-win_amd64.whl", hash = "sha256:8b3b60de05f3dcb6f6a00f818bb2ec781cee4de0645f59ccaf99b1d1823b6100", size = 481030, upload-time = "2026-07-23T01:56:33.365Z" }, + { url = "https://files.pythonhosted.org/packages/22/8c/c29d067df825a2df88ca432db848aa2fe8199598359cc06c12b09320cac9/aiohttp-3.14.3-cp314-cp314-win_arm64.whl", hash = "sha256:1576145bdceeb92382d899751e12743a3a5b8e460a841e3e50543859e54864dc", size = 453669, upload-time = "2026-07-23T01:56:35.731Z" }, + { url = "https://files.pythonhosted.org/packages/6a/a4/9c033beb355d39b6147980597ec9645e4729243f686ee4dc73945de72030/aiohttp-3.14.3-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:8800c996b01c2772a783e3e46f3e1abd5823029adca0df54231960de9bfefa5b", size = 791403, upload-time = "2026-07-23T01:56:37.972Z" }, + { url = "https://files.pythonhosted.org/packages/80/ca/87c32a0a7704583cfc49660bd817889bae5b830bf53b5dcb4e92145ac2da/aiohttp-3.14.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:ebe8e504f058fe91223351cecd2d9d6946c9d241bb0250d898ffbdf584cc72b0", size = 526413, upload-time = "2026-07-23T01:56:40.523Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d8/8ec0e471248c500acdce2be3f46db8fb62b5eb60efef072529cc85ee1d26/aiohttp-3.14.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:30402d03a7c0ff52bce290b57e564e9079fd9d0cb545c8aba73f86a103162d2e", size = 532135, upload-time = "2026-07-23T01:56:42.876Z" }, + { url = "https://files.pythonhosted.org/packages/fe/45/f8919fd936e8b79fcd9bda7b6d8e62613462a713f4f17987fd7c34399142/aiohttp-3.14.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9fc7b5bfec6573f3ae844f457fdde5adeb713f8b8e4a81ad64fc207b49383716", size = 1922742, upload-time = "2026-07-23T01:56:45.528Z" }, + { url = "https://files.pythonhosted.org/packages/f6/ec/9ca76b28a27525b0cc53e20842e0228b022f301ce1f436b7d814b4aaf2df/aiohttp-3.14.3-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8a5fd34f7f7410d1730d5c2ba873cacb2eed3fede366feb268a70ba22581ed8f", size = 1787371, upload-time = "2026-07-23T01:56:48.045Z" }, + { url = "https://files.pythonhosted.org/packages/b1/04/6acdbf17315f7b55f1937e3387acb89a3cddeb4995689553d064af8e92ab/aiohttp-3.14.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:270d3dace9ca2f10f0da5d8ebe519b7a310fc6112ed916e32df5866df0888553", size = 1912623, upload-time = "2026-07-23T01:56:50.605Z" }, + { url = "https://files.pythonhosted.org/packages/86/e6/438b0c79ca6f45eb9fd9817dd4c01a91919a38c0de5ee9e05e2b4dc0ece7/aiohttp-3.14.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3ae5b3a59436d089b5395d910121a390feed4d00578eb95a0fd1a329fe963100", size = 2005515, upload-time = "2026-07-23T01:56:53.153Z" }, + { url = "https://files.pythonhosted.org/packages/bb/6b/62cbd6577758699525f5c712d1ddef57d9875fbab0ae8d5f5a202fd598f8/aiohttp-3.14.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2498f0fe69ead802f9675beca44a7c21c62fdaa4ec5145ea1c3ad6edbee29f85", size = 1879906, upload-time = "2026-07-23T01:56:55.818Z" }, + { url = "https://files.pythonhosted.org/packages/00/95/18bcbf830a21dc3aae24d8f6b6feaf3db1d2090242d00a7868db2ffb0b67/aiohttp-3.14.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a0dc483c00da8b673abbb367eb6f8d8f4bcec30eb58529ea13cb42e7fd2dfa33", size = 1675849, upload-time = "2026-07-23T01:56:58.861Z" }, + { url = "https://files.pythonhosted.org/packages/a9/19/47f4968659c5e23606c3790c80fc624e691c153d036148449ee84d31b287/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c7d3a97c678d34fc5b59da671ee9cd630096ddc643e7b5a30d54a2a6f3574d3f", size = 1843496, upload-time = "2026-07-23T01:57:01.591Z" }, + { url = "https://files.pythonhosted.org/packages/64/af/38c33c4dd82fddcb4e56c4653b6f1072a8edbc6b7fa15809f14932c41e2d/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:f8fb78a83c9e5f741ca3a68cfb455c1f5bb83b4e7249a3848b3cd78d0a8563b0", size = 1827746, upload-time = "2026-07-23T01:57:05.131Z" }, + { url = "https://files.pythonhosted.org/packages/a1/9d/0537cda4885ac8f5b7053d164dd06312f4c483a4edcb8ee5b8aaf2a989bf/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:74ab5b6a9fb13e873e5a90946588baecaf488745e1db1a4a5c433f971f035098", size = 1853810, upload-time = "2026-07-23T01:57:08.043Z" }, + { url = "https://files.pythonhosted.org/packages/19/fe/26f9c5e6458385aa86497836b0dea6fb2f027827d63f37c7856cce9286ee/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:bd52f811e65f6fb634b1047159657c98f52b407f8efec907bcfc09da9a4c0a25", size = 1668895, upload-time = "2026-07-23T01:57:10.837Z" }, + { url = "https://files.pythonhosted.org/packages/ec/4c/618b1db9b9ba079b8875d2cdf78e7c4a3bf72903bd5850fee7dd9544600a/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:f0f177d1b195b9e06376cfd7d308d8a1b920909a609d03ac82a8c73bbb16d3b9", size = 1883833, upload-time = "2026-07-23T01:57:13.672Z" }, + { url = "https://files.pythonhosted.org/packages/94/c6/bd959bd1e4771f9fd944e9e436224c48c77b018b73b519b5aad346335bcc/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:498c6c623134f8e09a3c4e60bcd607a0b4590dd7dbf08dd40851b27cbb520ccb", size = 1844251, upload-time = "2026-07-23T01:57:16.593Z" }, + { url = "https://files.pythonhosted.org/packages/5e/19/08d41839658bdd44a0ed2480f3891705ecb487ce28c0dde62c9040c997e0/aiohttp-3.14.3-cp314-cp314t-win32.whl", hash = "sha256:b304db572b4368edd8dda8a2274f73156fe15558fca4a917cb8a09fc47af5963", size = 474180, upload-time = "2026-07-23T01:57:19.306Z" }, + { url = "https://files.pythonhosted.org/packages/99/5d/3cd6ef0a2b2851f7ab913b5b079334781bd50ff56a323e4454063377a080/aiohttp-3.14.3-cp314-cp314t-win_amd64.whl", hash = "sha256:b20032766aedf6261c7a566585a40867d092ac03a0d81592d5370ef9b054f99b", size = 500528, upload-time = "2026-07-23T01:57:21.762Z" }, + { url = "https://files.pythonhosted.org/packages/a4/37/cfd1ed540a4d318da025590d96b728e63713c09e9377950fc655dadeb856/aiohttp-3.14.3-cp314-cp314t-win_arm64.whl", hash = "sha256:2e1161602f45a54de2ce0905243a95f58cb42dcd378402f3697f5e0b21e9d2e7", size = 469280, upload-time = "2026-07-23T01:57:24.241Z" }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, +] +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.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/8e/38aa427ed5402449e226975b649c5dc73ccadfefeb95e6aecb8f8ea4b6b6/annotated_doc-0.0.5.tar.gz", hash = "sha256:c7e58ce09192557605d8bbd92836d7e1d520ac9580096042c0bfd197efacf1bb", size = 10758, upload-time = "2026-07-28T13:50:58.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/30/e900b21425a860e195f32e37657aa1f7c7f2b1bfb26f03ca209b90933c06/annotated_doc-0.0.5-py3-none-any.whl", hash = "sha256:117bac03a25ede5df5440e855b32d556049ca169ead221505badf432fed4b101", size = 5302, upload-time = "2026-07-28T13:50:57.239Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/56/a8120250d128bed162cd73c76d45f6ef9991f3e068f62a8ee060afa3104a/annotated_types-0.8.0.tar.gz", hash = "sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7", size = 15893, upload-time = "2026-07-23T20:16:13.995Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/91/8acff4f5e50511b911bbccb72b8628a49c68ce14148cd9f6431094859a90/annotated_types-0.8.0-py3-none-any.whl", hash = "sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0", size = 13427, upload-time = "2026-07-23T20:16:12.938Z" }, +] + +[[package]] +name = "anyio" +version = "4.15.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "typing-extensions", marker = "python_full_version < '3.15'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/d2/f4d173e22df740bc37b1db102b386ba719b66e95b0f0d751f556b387e6d2/anyio-4.15.1.tar.gz", hash = "sha256:9f28306018cbd6d329e64a36d58256edff76dd996fe423bc957326e578b82a94", size = 276966, upload-time = "2026-09-05T10:42:39.44Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/b8/4bd346e22b28902df4d651910f5242c28d84e4a5c2435ca5c3f797ed7e2e/anyio-4.15.1-py3-none-any.whl", hash = "sha256:6152fdbbf9a77fdec97731721bebf7c4c44f7c29b424b0065826173efc7ed101", size = 132079, upload-time = "2026-09-05T10:42:37.923Z" }, +] + +[[package]] +name = "asgiref" +version = "3.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e6/26/3b59f2bdae5f640389becb1f673cded775287f5fc4f816309d9ca9a3f93d/asgiref-3.12.1.tar.gz", hash = "sha256:59dcb51c272ad209d59bed5708a64a333083e86017d7fcdd67498eeab7784340", size = 42378, upload-time = "2026-07-14T09:56:18.087Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/1b/54f4ad77cd8a584fa70746c47df988e002cf1ee1eba43364d46f87803647/asgiref-3.12.1-py3-none-any.whl", hash = "sha256:fe386d1c2bff7259ea95929266d12a8cf9a8b5a1c2598402967d8792e7a7c094", size = 25478, upload-time = "2026-07-14T09:56:16.926Z" }, +] + +[[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 = "azure-ai-inference" +version = "1.0.0b9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "azure-core" }, + { name = "isodate" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4e/6a/ed85592e5c64e08c291992f58b1a94dab6869f28fb0f40fd753dced73ba6/azure_ai_inference-1.0.0b9.tar.gz", hash = "sha256:1feb496bd84b01ee2691befc04358fa25d7c344d8288e99364438859ad7cd5a4", size = 182408, upload-time = "2025-02-15T00:37:28.464Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/0f/27520da74769db6e58327d96c98e7b9a07ce686dff582c9a5ec60b03f9dd/azure_ai_inference-1.0.0b9-py3-none-any.whl", hash = "sha256:49823732e674092dad83bb8b0d1b65aa73111fab924d61349eb2a8cdc0493990", size = 124885, upload-time = "2025-02-15T00:37:29.964Z" }, +] + +[[package]] +name = "azure-ai-projects" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "azure-core" }, + { name = "azure-identity" }, + { name = "azure-storage-blob" }, + { name = "isodate" }, + { name = "openai" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/29/ab1a80ce483fcc36ec2ac0a3db8e043aa954700b34b77db43ed5f4f9c488/azure_ai_projects-2.3.0.tar.gz", hash = "sha256:6e3006b7b8aa51c6ff9db61ef4aac3717f8a712cd1a183d5a1d34e2eb33450bd", size = 27563233, upload-time = "2026-07-01T21:09:00.018Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/bd/7e64f2d6e43d19fc8cf464769804f947d997833eb4dce1c86c2ce76146d2/azure_ai_projects-2.3.0-py3-none-any.whl", hash = "sha256:1da20aeac9663740a97644efedbb9f59eb2a332d3e01bfde7aa03027936edf44", size = 349581, upload-time = "2026-07-01T21:09:04.32Z" }, +] + +[[package]] +name = "azure-core" +version = "1.41.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/f3/b416179e408990df5db0d516283022dde0f5d0111d98c1a848e41853e81c/azure_core-1.41.0.tar.gz", hash = "sha256:f46ff5dfcd230f25cf1c19e8a34b8dc08a337b2503e268bb600a16c00db8ad5a", size = 381042, upload-time = "2026-05-07T23:30:54.302Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/db/325c6d7312d2200251c52323878281045aaffcb5586612296484e4280eaa/azure_core-1.41.0-py3-none-any.whl", hash = "sha256:522b4011e8180b1a3dcd2024396a4e7fe9ac37fb8597db47163d230b5efe892d", size = 220920, upload-time = "2026-05-07T23:30:56.357Z" }, +] + +[[package]] +name = "azure-core-tracing-opentelemetry" +version = "1.0.0b13" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "azure-core" }, + { name = "opentelemetry-api" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/ab/a937e4af8afec9d437d55252f2a3a4419fc3fc7d5e5d54022622bd11b2b6/azure_core_tracing_opentelemetry-1.0.0b13.tar.gz", hash = "sha256:6cb2f8dfd5dee6c11843db0205fc92e2434e1a272c169c953afe92483aafc7eb", size = 25832, upload-time = "2026-05-01T00:59:57.941Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/01/8898c2506cae6a57c1b76d930d2af94764a65354bc863feb2684235851ce/azure_core_tracing_opentelemetry-1.0.0b13-py3-none-any.whl", hash = "sha256:4dacd3a9f117f11f98e89305e161c951b8df85b984f3b56130614de9cd9887f9", size = 12112, upload-time = "2026-05-01T00:59:59.149Z" }, +] + +[[package]] +name = "azure-cosmos" +version = "4.16.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "azure-core" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/8a/97b68e849fff7d2a76d85e51acdfbd13072266d581dcb7f6ad32bdddbd27/azure_cosmos-4.16.4.tar.gz", hash = "sha256:5a98d33ce6d539a926f47c67b14587fbb8ba4fd662751ff8b0ebe7f9d3765a2e", size = 2462258, upload-time = "2026-09-02T14:59:54.322Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/cc/5f4b06f49ec0bf26e8a390fb55c67ceb67573e94e83493b6b96837b2ee98/azure_cosmos-4.16.4-py3-none-any.whl", hash = "sha256:89bf558203335efc39551dccef6782aa2f286d94cc87dd9bd98efc37d8c69303", size = 502464, upload-time = "2026-09-02T14:59:56.414Z" }, +] + +[[package]] +name = "azure-identity" +version = "1.25.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "azure-core" }, + { name = "cryptography" }, + { name = "msal" }, + { name = "msal-extensions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c5/0e/3a63efb48aa4a5ae2cfca61ee152fbcb668092134d3eb8bfda472dd5c617/azure_identity-1.25.3.tar.gz", hash = "sha256:ab23c0d63015f50b630ef6c6cf395e7262f439ce06e5d07a64e874c724f8d9e6", size = 286304, upload-time = "2026-03-13T01:12:20.892Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/9a/417b3a533e01953a7c618884df2cb05a71e7b68bdbce4fbdb62349d2a2e8/azure_identity-1.25.3-py3-none-any.whl", hash = "sha256:f4d0b956a8146f30333e071374171f3cfa7bdb8073adb8c3814b65567aa7447c", size = 192138, upload-time = "2026-03-13T01:12:22.951Z" }, +] + +[[package]] +name = "azure-monitor-opentelemetry" +version = "1.8.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "azure-core" }, + { name = "azure-core-tracing-opentelemetry" }, + { name = "azure-monitor-opentelemetry-exporter" }, + { name = "opentelemetry-instrumentation-django" }, + { name = "opentelemetry-instrumentation-fastapi" }, + { name = "opentelemetry-instrumentation-flask" }, + { name = "opentelemetry-instrumentation-logging" }, + { name = "opentelemetry-instrumentation-psycopg2" }, + { name = "opentelemetry-instrumentation-requests" }, + { name = "opentelemetry-instrumentation-urllib" }, + { name = "opentelemetry-instrumentation-urllib3" }, + { name = "opentelemetry-resource-detector-azure" }, + { name = "opentelemetry-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/9f/75f517dd019ebc1a6476187c7380ea440f040b7c933c7a6b7159260e2c37/azure_monitor_opentelemetry-1.8.9.tar.gz", hash = "sha256:9cda4481c52e02cb3e8e11a34d2491fb07000ed32ee655d75de0747e7d24fea7", size = 80026, upload-time = "2026-07-01T17:48:58.369Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/ce/8b871c53ba873a03fb8263ae67048be19b7e0bdd002547ff6ac3712ae9a6/azure_monitor_opentelemetry-1.8.9-py3-none-any.whl", hash = "sha256:63245cf597e00d7ca34d19166f8a24435fd7ab07d4167dd78df119e3433ea781", size = 42713, upload-time = "2026-07-01T17:48:59.456Z" }, +] + +[[package]] +name = "azure-monitor-opentelemetry-exporter" +version = "1.0.0b56" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "azure-core" }, + { name = "azure-identity" }, + { name = "msrest" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-sdk" }, + { name = "psutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/0e/c74f9bf0b071f5e13c93c285d585ddc96de43658076733cab039844c198a/azure_monitor_opentelemetry_exporter-1.0.0b56.tar.gz", hash = "sha256:d645e2dbde68123a1c0ab6a5705424bae5bbda53495f2cd8be1bb2a78e9f31d3", size = 355000, upload-time = "2026-08-05T22:49:43.804Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/09/fa/d56f55dae96b8c7ee675ffedbacdb65fd28c5cf00d5134e0fd69aeaea5d1/azure_monitor_opentelemetry_exporter-1.0.0b56-py2.py3-none-any.whl", hash = "sha256:9d006bef633fed895333c6b632eda21411acf5eedcfc396f2813c6d11515278c", size = 255013, upload-time = "2026-08-05T22:49:45.219Z" }, +] + +[[package]] +name = "azure-storage-blob" +version = "12.30.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "azure-core" }, + { name = "cryptography" }, + { name = "isodate" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/39/7e/834d7bfcf999ab89d1bd3a5d235ece6824686da2f3d315e2162c613fe43d/azure_storage_blob-12.30.1.tar.gz", hash = "sha256:7a24f978c51d56a0375beebffcbe8453e59ae390d2695705848edc75083e4184", size = 624787, upload-time = "2026-08-27T19:12:54.967Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/90/f06915ccf78a6d965901aae093bd7e88e486c52e0773202656fb29c2304a/azure_storage_blob-12.30.1-py3-none-any.whl", hash = "sha256:7dc09c37f4f58508e20532b4b4c178f4763f41b01e0b9063835b994fd9d2a7b3", size = 438131, upload-time = "2026-08-27T19:12:56.796Z" }, +] + +[[package]] +name = "certifi" +version = "2026.7.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112, upload-time = "2026-07-22T03:35:12.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983, upload-time = "2026-07-22T03:35:11.276Z" }, +] + +[[package]] +name = "cffi" +version = "2.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/ef/008a1939e372c06329a3fce4279c02f328488f3526744906eeec3da7ad5f/cffi-2.1.1.tar.gz", hash = "sha256:dd31f52ea1086513bb9df30f8fcee9b8918323ae067a3d5b78bc826a000712be", size = 530807, upload-time = "2026-08-03T21:21:18.939Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/f4/035513d4117049066b4779dc3b7c0c0fdad175fa13731c9f4003f1cd1478/cffi-2.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:b5bdfd1c873d4e093aabc0ca84c4ca6dbc4f752afb5c86f146d9742580c9da2e", size = 194248, upload-time = "2026-08-03T21:19:59.399Z" }, + { url = "https://files.pythonhosted.org/packages/76/af/2aeb4dbb5fc41a04161ae9ff1518de7cec08e164f44a8ce6a4cf7fd2cd1d/cffi-2.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:31348097ff5bbe827ccc41795d4dd099d9f0625e7def00ee653c137a490c2a6c", size = 196908, upload-time = "2026-08-03T21:20:00.746Z" }, + { url = "https://files.pythonhosted.org/packages/a7/46/2e5fdde8555706dd98139a910ca11be02809f3f605ce956f655d0214e100/cffi-2.1.1-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:9d2055050ea716bd38b7f7f1579c275386646b4894c155a3e2f3cd62ed41b7c6", size = 184805, upload-time = "2026-08-03T21:20:02.02Z" }, + { url = "https://files.pythonhosted.org/packages/55/41/4c7042f317b9217502988f0873af87e16ad606dc20f84e546e3e6ce9764c/cffi-2.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19ee6127ee34de7d83ce3d371ebc5ed91addbdcc39f9ab15ce4eb35a4e534971", size = 184764, upload-time = "2026-08-03T21:20:03.141Z" }, + { url = "https://files.pythonhosted.org/packages/43/1f/1c3d90d91811c8f86ced9ed637956c54bfe5b79ca98fe976d7f8c8979f6b/cffi-2.1.1-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:6a8dddef476fab96d066d578fc88526767b836ab5ab21754e1d5bf3879c31c7c", size = 214722, upload-time = "2026-08-03T21:20:04.377Z" }, + { url = "https://files.pythonhosted.org/packages/37/6f/3b5ce4c3b2192d250f04908f2bfd91ef34552ec8f7716a5d4abdb8d67bb2/cffi-2.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f16c709686a78c727bbbf059f92b0bf41c6fc60deec706d2dc19f529175a6125", size = 222369, upload-time = "2026-08-03T21:20:05.544Z" }, + { url = "https://files.pythonhosted.org/packages/02/10/4b3c75dde3d9663c9e02ba05c2668b954f671d4bbe346413ca8c696b295a/cffi-2.1.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:fcd22650c908d7b7da162bbfaab594a1227a15d1643a98c68b122ac642fa2264", size = 210175, upload-time = "2026-08-03T21:20:06.75Z" }, + { url = "https://files.pythonhosted.org/packages/df/62/14f74b9543e605d17701dc797b815958b8bb70b7624ce1b832ddad48ed6c/cffi-2.1.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:aa9511c62d14da7aacc9b4bf51f3f697a621e83b2d6919008243c3aad168eea3", size = 208670, upload-time = "2026-08-03T21:20:08.04Z" }, + { url = "https://files.pythonhosted.org/packages/95/95/86342356ff5953b3fb06f7ef7c5bee212d45e770abc7218d451b9148313c/cffi-2.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a931079504ecc49efed7744c476a5c343a92fabf66dec2db95edb1b2fdc770e2", size = 221824, upload-time = "2026-08-03T21:20:09.274Z" }, + { url = "https://files.pythonhosted.org/packages/eb/ff/7b3429ff53aafe931ed8a5fc69f481bbef7ba6de87ddcbb63d08f483f613/cffi-2.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a2d7755bef5a12ed488f4ef1f1b69ee9191d7396083b755a5d2295f6edb4768b", size = 225148, upload-time = "2026-08-03T21:20:10.7Z" }, + { url = "https://files.pythonhosted.org/packages/34/34/a95870b9221e09cf4f2ce3178b1a210abdfe63a1bd357da940418d7b8d15/cffi-2.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e0bcb7e0f677f543555d2adff3bf19c05f66cdb4796e5ff602442ab2fe3c4ef7", size = 223564, upload-time = "2026-08-03T21:20:12.165Z" }, + { url = "https://files.pythonhosted.org/packages/70/ea/839b50531021a647fb5e929f72cf97bc1ff702b5472166164b5b6e76b851/cffi-2.1.1-cp313-cp313-win32.whl", hash = "sha256:334644fbac4eff73d985a17a91226df55d0f394160c4cfb880e084c8f7161cac", size = 175263, upload-time = "2026-08-03T21:20:13.559Z" }, + { url = "https://files.pythonhosted.org/packages/60/a6/8b149b2c3f2e11aaa1618ef64500b45f50f22c57a977a4dff1aff1f91042/cffi-2.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:1aa5645c30469b09530c4ebca77ebf8f17618293c58f8549cb1a543a50236e7d", size = 185688, upload-time = "2026-08-03T21:20:14.69Z" }, + { url = "https://files.pythonhosted.org/packages/01/9a/11f687cb39d6a3504060d5242f04f48c735afb4d3d533958a20594890cb2/cffi-2.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:63bbfd5ded17c4840ac07cd8f1c21ba9d9708141f840b324f422f41b207e3973", size = 180078, upload-time = "2026-08-03T21:20:15.917Z" }, + { url = "https://files.pythonhosted.org/packages/d3/7b/d6bbf82b8b96e7391438898c42f5bd96dd02030fd5b64937d248220003e2/cffi-2.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:7dbb61fe3a7699468030f71bbe5f8a0e326a151daa91beb11a6fc1f980c55e1c", size = 194064, upload-time = "2026-08-03T21:20:17.148Z" }, + { url = "https://files.pythonhosted.org/packages/94/e6/bcc91b283be94735e268487a054004f0aa19947b6348fa367db53230abc8/cffi-2.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:f24fb43132a4c6b4cb4eb029492919b2db645be6808d738f244fd146c03c32cb", size = 196720, upload-time = "2026-08-03T21:20:18.268Z" }, + { url = "https://files.pythonhosted.org/packages/d9/99/c4b0c17cacdc9c3b8f280026286a9826d6a208c0f047591a3c3ce99b91fd/cffi-2.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d28630f5854ab07ab1fd4aba756de52326c82e6be15d414b12793f1975048b54", size = 184964, upload-time = "2026-08-03T21:20:19.708Z" }, + { url = "https://files.pythonhosted.org/packages/b3/a9/9db617d05d7367c1ad0ab00b3aa6e6f9281edd689b4ee9ea0e5a84e89c97/cffi-2.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:661c298b4821edebead0c91edd2b00374d67ad7c5a1f7a91d4442633b79d6a72", size = 184962, upload-time = "2026-08-03T21:20:20.833Z" }, + { url = "https://files.pythonhosted.org/packages/67/b8/b42132ca113dc567d37684437b46ca1dafc885902b02a110a02d5b511857/cffi-2.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:58acb8ab8e295e6c5ea12f888cbb13cf21511ef2a3303a23f4325c29d17fe5c1", size = 222328, upload-time = "2026-08-03T21:20:22.118Z" }, + { url = "https://files.pythonhosted.org/packages/80/10/c5c0cbf0a657aecf59ef511409734230bf556f05a0d6c9eed7aa5c0a0166/cffi-2.1.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:456a61fa52d579ebf9df2e9552ead5129855dbaff6c1e5a9b1bc408809bdc062", size = 209985, upload-time = "2026-08-03T21:20:23.401Z" }, + { url = "https://files.pythonhosted.org/packages/d5/6c/bfa0b87b03b9238148beca990292843c9396ba069b54496596594173de7b/cffi-2.1.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a4f00aa42f75d6e4595e8866e748cc1705adc0cddfeb2ca86d0d03993d63ba03", size = 208530, upload-time = "2026-08-03T21:20:24.628Z" }, + { url = "https://files.pythonhosted.org/packages/e9/02/4e7d553a7ac4b4238b38b3c1b80d486e9d4436f8d2acbf87a0997fe3f402/cffi-2.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b0431303acaea1089ad4b3e9ce4e6518193def1118d4073ca848635ee4ea2e96", size = 221525, upload-time = "2026-08-03T21:20:25.758Z" }, + { url = "https://files.pythonhosted.org/packages/82/1d/a4aaf9babd75acb4d5f223bff71533bee748dd770a382619a798960ee9ba/cffi-2.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:64faea20f4e2613363a1a9b9c7dd73058f3ecd00133a511e72ad7c511658f527", size = 225053, upload-time = "2026-08-03T21:20:26.985Z" }, + { url = "https://files.pythonhosted.org/packages/81/10/5dc0e7bdd18e22107054288283380fc97a06ae3f1656a106908d666a3c88/cffi-2.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5c58fe613dc5e5336357eff555824a314d8e43282600435c8d1cb6a7a2fedd13", size = 223213, upload-time = "2026-08-03T21:20:28.277Z" }, + { url = "https://files.pythonhosted.org/packages/0b/e9/d0061c364cde06ee43168a0d076ac1da512cbc380d44767b844ba34fe2b6/cffi-2.1.1-cp314-cp314-win32.whl", hash = "sha256:1a18a57b58cfb21fc28d72e876acf10eaed67a1ed96226f92af4df681d571c4c", size = 177682, upload-time = "2026-08-03T21:20:44.288Z" }, + { url = "https://files.pythonhosted.org/packages/a7/06/1c3e01e3ba14c39f6d10bfbac52753b7e22259e38088e5cfe1d704918690/cffi-2.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:3222ba5d678f80a030e6afbcc33dc1ae5cb45facabb61cee2c7016b8432fde48", size = 187949, upload-time = "2026-08-03T21:20:45.623Z" }, + { url = "https://files.pythonhosted.org/packages/87/5b/da4e39efe18eeb89cf580ea9cfc66b6a7c3eadb808fc0cc1d3a295cb5a5d/cffi-2.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:ab36d55f9ed2d067327667c2fea18dda018eb628dd6347aa01dda6cf1f5d3836", size = 182947, upload-time = "2026-08-03T21:20:46.955Z" }, + { url = "https://files.pythonhosted.org/packages/23/59/40338bf421c5accea1d45158170c87006ef1cd371b05c077e76476949728/cffi-2.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7750c6449dff7864bb9bb27ddfb0267756189201a3afc911d82b3caacd70dfc3", size = 188504, upload-time = "2026-08-03T21:20:29.495Z" }, + { url = "https://files.pythonhosted.org/packages/7d/47/5ecf1023850036e674c77ec4de86182d309ae344e39e7cba984b7df5d647/cffi-2.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0beceaabe56af686895136a2de78db54ecd8e4046b236b8fd6d6cb61389e9bf2", size = 188259, upload-time = "2026-08-03T21:20:31.291Z" }, + { url = "https://files.pythonhosted.org/packages/2a/9c/92934c3bea9f785b23eba304538c0b4d37a2a96d2431eb3a1bc87a11aa19/cffi-2.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:49cbc70e6542d4ccccb936558d1064a8012541e78f821f955cff24e357776c94", size = 223864, upload-time = "2026-08-03T21:20:32.571Z" }, + { url = "https://files.pythonhosted.org/packages/4d/45/ba4c93527bc38616a8bd36488acb69a2212d60486794f0c1f318949bbb76/cffi-2.1.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e2d65b31f36619cda3999b78b2aa9632e76b78448e7a56fc4240824200e7c4fc", size = 211538, upload-time = "2026-08-03T21:20:33.808Z" }, + { url = "https://files.pythonhosted.org/packages/80/e9/b6ef565e452acb932fb0cb5443f44a78efbd1233e566f02b5a83855e9115/cffi-2.1.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:28907ab9bfb6aa13184cfc17c6b8e1023c5ab6fd7076d8c20a35e59fe04f8f29", size = 210688, upload-time = "2026-08-03T21:20:34.974Z" }, + { url = "https://files.pythonhosted.org/packages/9a/95/eff5f0cee78d2eabc7eebffec40d3fc1876b5f3c95582e018bb4b99601f2/cffi-2.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:51b31d1c98274844cfd7838ce00bfc27c7423a4dc00fc0772fc3331c2cc90676", size = 223803, upload-time = "2026-08-03T21:20:36.564Z" }, + { url = "https://files.pythonhosted.org/packages/fa/01/579d39fb8bef00a335a23d83757b44feb24cd6345a2c451b64cb67b9c362/cffi-2.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e7cecbaadb83884793e05828cee59b210b24583b9c7425d0ba6a754fe22eb4e", size = 226763, upload-time = "2026-08-03T21:20:37.816Z" }, + { url = "https://files.pythonhosted.org/packages/8d/b0/0b44f47c60b01b57b6e2bbd92343f13a85a1d93bc46ccf6e47e244acd99c/cffi-2.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:25792eac27877609e7bb06d42ff88278a6624fff2ba9bbb523c09616b117e80f", size = 225688, upload-time = "2026-08-03T21:20:38.959Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d2/3b7176cb570a1d3e27faf67b72f591af508036e0d8b2be2ef9af9e8c84bb/cffi-2.1.1-cp314-cp314t-win32.whl", hash = "sha256:8ef53b2de9bcb9197d31854256575d59dbac0cba72ac627bb291ef5eceb74be4", size = 182868, upload-time = "2026-08-03T21:20:40.388Z" }, + { url = "https://files.pythonhosted.org/packages/56/78/31f00c1bcd97c9bbf55f1bfdf5bc809a5de8887473e90bb9960dca825e80/cffi-2.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:616f097f2fe415bc92a247f02e11f634e1f9e9a83d327e3c915c15089c87869e", size = 194104, upload-time = "2026-08-03T21:20:41.725Z" }, + { url = "https://files.pythonhosted.org/packages/7b/1b/58496f2ed0a35de575250c02a43ab3cc2c04d494a88fed31c1cabc0fd176/cffi-2.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ad2c86c495b899d862ea0f4b42891b8713a3bd45dd4105c7fd51c2a72f39f3a5", size = 186402, upload-time = "2026-08-03T21:20:43.042Z" }, + { url = "https://files.pythonhosted.org/packages/c1/8f/9ebe220eab48a093d1a5a5e339ab0dc7316eef3bb04d63c42f0251b61f50/cffi-2.1.1-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:dddad92b554513a31f272570678ba307fb9f618f05e3d4a5eacafff9eae03e1d", size = 194043, upload-time = "2026-08-03T21:20:48.179Z" }, + { url = "https://files.pythonhosted.org/packages/ff/69/844bad3ece306c4782c2ecb93597035b6690d48704b803914c199da1e8b3/cffi-2.1.1-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:da0e573f9f97159390c89d9f1a9e41908b66d408cc5b58d08cf3847d844c531b", size = 196737, upload-time = "2026-08-03T21:20:49.457Z" }, + { url = "https://files.pythonhosted.org/packages/1b/8a/af668013284634733f02d683458a0728739c7d6ddb5e14cb0c20832266fe/cffi-2.1.1-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:fb92203a88b3d3053034db775110081c49d28be6551923805e039924093761e4", size = 184933, upload-time = "2026-08-03T21:20:50.639Z" }, + { url = "https://files.pythonhosted.org/packages/0c/75/2f5207ff6d1a613133b23a5203cc0c2a628313b5eb3974d7956ae3c57950/cffi-2.1.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:2ae64be792b8966f2c69538199728b290e34726562896df1e5dc8ffd8d8188e8", size = 185002, upload-time = "2026-08-03T21:20:52.173Z" }, + { url = "https://files.pythonhosted.org/packages/e2/31/9e1313b0a6e30e91b3b3d3fff51ae99c857c07738e3afcce1f7334e1b7ab/cffi-2.1.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:507a24c282e0f42f8ed737cf048572cbf580468da5555764a8331735e9c736b6", size = 222271, upload-time = "2026-08-03T21:20:53.462Z" }, + { url = "https://files.pythonhosted.org/packages/50/e3/f6234a833e6e08c7007003074723c406559eecf9b48dfc97471e5a8eb7a0/cffi-2.1.1-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:246fa40ce8645a614ff682e0b70f37134e460eaf93a775e0cbe3cca585a67a80", size = 209919, upload-time = "2026-08-03T21:20:54.783Z" }, + { url = "https://files.pythonhosted.org/packages/0d/fc/5f74e293fced6edb51af3a46c4ccf6c23c9943774ecb375ddbd522c76add/cffi-2.1.1-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:471cee653ae88de62096552e6d24ccb4a5adb8c8c9f10b5054d0122c15bf2779", size = 208529, upload-time = "2026-08-03T21:20:56.066Z" }, + { url = "https://files.pythonhosted.org/packages/44/16/29e6d01b388bef055ecd6ca8244b3f4d336bd09e92d5d892187b9601084e/cffi-2.1.1-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aeae0e330c9f6acd681f647d46cefd30c29f93e3392882e792e82080c9691399", size = 221630, upload-time = "2026-08-03T21:20:57.336Z" }, + { url = "https://files.pythonhosted.org/packages/a4/18/fa7f1f6857d5eb88a4ca99ffcbfb7c387a287ccc154c64a73e86314745d7/cffi-2.1.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:42a494cee34437f05546455144f2b5d9ac09b1face62bcfce597d2e521066688", size = 225134, upload-time = "2026-08-03T21:20:58.675Z" }, + { url = "https://files.pythonhosted.org/packages/e0/9f/e8e3dfa04a1b4c241f8c91faacad872b4d4efd051d49764ad4e2fd4b9fea/cffi-2.1.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:cc572dace3f60ef98d7b12ff411d20f5362feb31a0439eab0085bbfd349982d7", size = 223197, upload-time = "2026-08-03T21:20:59.968Z" }, + { url = "https://files.pythonhosted.org/packages/f8/7e/8debeb04f1ab9fe2a6963964cd6f1aaf7192627b83926586a6a4e089c9fa/cffi-2.1.1-cp315-cp315-win32.whl", hash = "sha256:4f42141fc14250de6dde5ee7ea4432be017252d91f19c5ad043c084cea629cac", size = 177683, upload-time = "2026-08-03T21:21:14.901Z" }, + { url = "https://files.pythonhosted.org/packages/e0/31/5158704cc474ab65c1647932e88be78dc0873f47130e253be38bcaf13d01/cffi-2.1.1-cp315-cp315-win_amd64.whl", hash = "sha256:e6e8cff14d6fb0be70a09c0bdc58096f501952d04624ebf867e0e56da2df8960", size = 187897, upload-time = "2026-08-03T21:21:16.108Z" }, + { url = "https://files.pythonhosted.org/packages/cc/4b/b3a2da8570c704ffc0f9762cdc3ec0f02c8573798e0b5cf7f11c82bbb70f/cffi-2.1.1-cp315-cp315-win_arm64.whl", hash = "sha256:27350daa11d4f10c540e6e89dada4c54feb7256ad03e9a4dc075ebad7ba360d1", size = 182935, upload-time = "2026-08-03T21:21:17.271Z" }, + { url = "https://files.pythonhosted.org/packages/d0/ef/5443574510a1207e6f6bc38ba6e1f1de36cb48fef07b2728bb896a21f430/cffi-2.1.1-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:c26608d2222fb1e94487e4a387d85f13eb55d5ed725cb25a0c589ac4ee60e7bc", size = 188464, upload-time = "2026-08-03T21:21:01.163Z" }, + { url = "https://files.pythonhosted.org/packages/7e/ae/a56fa8c4686ad50e148fcbc8d3ae0d03915ff5c30d795058988c24118cef/cffi-2.1.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:4be96343e422f2dfcd12ab5c9f5aebe03f82f737c6bffeca6830b3875cb44aab", size = 188262, upload-time = "2026-08-03T21:21:02.382Z" }, + { url = "https://files.pythonhosted.org/packages/53/b2/6187f46f2912276a3ae284076109cc5c8680482f11f766ccf26db4a86427/cffi-2.1.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:937c0052c05a31ca1daf18de3158eed4dbfcb9cc107adbea227728d647be701e", size = 223779, upload-time = "2026-08-03T21:21:03.553Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f6/c3ad28bd19f77047a03084424fbd4cbe997303267c14423737324be0385d/cffi-2.1.1-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:df423d40ee8654634421812bc3b196da3f9bd7d32929da813f8394c4348a5358", size = 211520, upload-time = "2026-08-03T21:21:04.863Z" }, + { url = "https://files.pythonhosted.org/packages/a0/cd/ccac9013a5bd9fd764de118674ab9c805b5ca10c19270d90ee273f8b2240/cffi-2.1.1-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a730a083190634c65cca36ba5f489531576ebd79bcd5c8e172130f6453127231", size = 210673, upload-time = "2026-08-03T21:21:06.223Z" }, + { url = "https://files.pythonhosted.org/packages/52/86/2976131c639aead931c5bee5aba67e4b09fbeb8018b6f282f70803f923a7/cffi-2.1.1-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:363e05fa78e15116c3c32c210ee36884fd6b9afa6d440e47112c3bd511d64cb6", size = 223835, upload-time = "2026-08-03T21:21:07.539Z" }, + { url = "https://files.pythonhosted.org/packages/ac/0c/33a7aeab2f9c76918c52e084beb39c570db3588133412929e8ec06fab90b/cffi-2.1.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:770de9db11e84213beec501cfcaa013b019820ca881e03344dea5844f7876d94", size = 226705, upload-time = "2026-08-03T21:21:08.774Z" }, + { url = "https://files.pythonhosted.org/packages/e3/26/2cde30fdde421130bfc18f70395731a6e6b2053c6a1978a5258ff04e72fa/cffi-2.1.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:7da0c5eff80f0197f3b3d1232ec5a682a9325f4ae9016a78f5f5ca35f9ced1f5", size = 225539, upload-time = "2026-08-03T21:21:09.911Z" }, + { url = "https://files.pythonhosted.org/packages/6d/cd/a361394c94b2129d604bb846f624a8e88255a3ee33129c434a00d715e64f/cffi-2.1.1-cp315-cp315t-win32.whl", hash = "sha256:06c72bb76605a4b0cd0aad6930b69d4baf7dd5d806cfc409b824191099700e66", size = 182707, upload-time = "2026-08-03T21:21:11.226Z" }, + { url = "https://files.pythonhosted.org/packages/9b/b5/ba2b299993c26577d529b6ae29841f9e15b9fcf004d65f423f4fcf94ade9/cffi-2.1.1-cp315-cp315t-win_amd64.whl", hash = "sha256:d9c275eaacd24aa73f94ffd6de08fc3f932424d8b6c376f4bed7cde376fe7bc3", size = 193772, upload-time = "2026-08-03T21:21:12.39Z" }, + { url = "https://files.pythonhosted.org/packages/aa/29/35e016098c814cd93de9cd320c66b5bfba14dc6ecedd3cb518fa7c408c69/cffi-2.1.1-cp315-cp315t-win_arm64.whl", hash = "sha256:d18e5ac0f2f03f4f518d3e23db0f0cad7faa1da8620e9c09461d443bbf6e6692", size = 186360, upload-time = "2026-08-03T21:21:13.636Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/3f/143b048436775b0f76ac3eec145c019e8173ccc2885c8f20319b996d5e83/charset_normalizer-3.5.1.tar.gz", hash = "sha256:6117b84ea48435e5356dc737f5121485c30920ba43375fa7b434fd753df0eac3", size = 171764, upload-time = "2026-08-15T08:20:44.807Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/61/2cb6ad133dbbb449fa2d37ccae973232f4827e799af258d15e589a3d1e9e/charset_normalizer-3.5.1-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:4f298bdadb8f0b9e5672877f647d1be9373ef5320c9e2f049795e26cad28b6a9", size = 211584, upload-time = "2026-08-15T08:17:33.597Z" }, + { url = "https://files.pythonhosted.org/packages/18/57/a305c968be1ca13f3dd1b32f445877e97addf55d80b65c7cb35fac82b777/charset_normalizer-3.5.1-cp313-cp313-android_24_x86_64.whl", hash = "sha256:88ca277405c2d3b71c4e1c2ee0e7966e807bcba86a69d11e19ba199d18ae4491", size = 223359, upload-time = "2026-08-15T08:17:35.022Z" }, + { url = "https://files.pythonhosted.org/packages/09/0a/d3646670292ce8d8f8cc11ac067d44885e697a5591f57a9221128da5e7b3/charset_normalizer-3.5.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:9362dd90aa7dab48c0054a21187791ccf05473f7dba5d92b8033ae62164675e7", size = 194464, upload-time = "2026-08-15T08:17:36.452Z" }, + { url = "https://files.pythonhosted.org/packages/de/93/d51ec556e01042fed6f993ea859311bc7917b466684182fbbceb6ca24762/charset_normalizer-3.5.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:977cdbd483a9cff38179bea4fd754289a6f2195c7abd414aba85410b3e66cc5e", size = 197676, upload-time = "2026-08-15T08:17:37.819Z" }, + { url = "https://files.pythonhosted.org/packages/a4/a0/562247944386f7d4ef94467e84876600cc1e0f1b93239aaa9213d2bc3cbd/charset_normalizer-3.5.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e90251c0c7bdd54a100a0dce3c07b7e637278c93af29dbf78ebb89a58c4bac7d", size = 340473, upload-time = "2026-08-15T08:17:39.303Z" }, + { url = "https://files.pythonhosted.org/packages/31/e7/1d994be1b93d41e9502b8b0460eaa88a1dd8df335df415db87d6c3e91ab2/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94d78ecec2605a8d0398b0f365d5f12a63248438516f5dac536a5eff7337df4a", size = 240156, upload-time = "2026-08-15T08:17:40.66Z" }, + { url = "https://files.pythonhosted.org/packages/09/53/27923ce5cc6cbccb832037b27dca98882d9c53e9b69e866bbbef4aae7fc8/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d59b75732e9b6f27388e10c14b0259cc5f2e48c78627d185e6a177b58ad3cffe", size = 228246, upload-time = "2026-08-15T08:17:42.003Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/5a97e84d63af1d55c07439cb80e56d99a8efb4295700eb4e18c0d1615d2c/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0d929fc574b4d6fd9e7c0f5c2ede8716a41911923aa7fa5fce38e0818aa4a1ac", size = 263660, upload-time = "2026-08-15T08:17:43.627Z" }, + { url = "https://files.pythonhosted.org/packages/7a/c2/071575791dcc88316c0a9a65ce38897a82e4cfe4a325f0f7fe1b1ac47bcf/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:394fea06235c8543390050ed5f529187074b029fb027213f6c46ac11ab5d950e", size = 260354, upload-time = "2026-08-15T08:17:45.094Z" }, + { url = "https://files.pythonhosted.org/packages/fb/af/63240b0c0248c075c2535a1f1bd992821d8251b9f173abc13329661d09e4/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62b55f6722735a6c472f88361cde6640608773d9443cebdbb51abf436a1fcdd3", size = 250638, upload-time = "2026-08-15T08:17:46.496Z" }, + { url = "https://files.pythonhosted.org/packages/4d/66/70dfad64f15be09c15ccfee81330a7e515895dbe296dd23114e9a231268a/charset_normalizer-3.5.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa48b1b63d639f9483e0633e092f5851e2348c352f1f9bb6c8182f87884ef876", size = 244583, upload-time = "2026-08-15T08:17:47.963Z" }, + { url = "https://files.pythonhosted.org/packages/c0/24/ef36367d38b9ddd4bccbf72888c342e8de1f5ae506fa0b2dcf970e2732a1/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c71fb0d56c920c269cd3e2e3fe7c610e3f1fdb21a6ce60efa6430ff63676cea6", size = 242038, upload-time = "2026-08-15T08:17:49.481Z" }, + { url = "https://files.pythonhosted.org/packages/db/ab/55e683ba0fff2e43adafc10daa3001eac90fdaa419a97227d5a7067eedde/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:485a0d363cafefcd2538a73c7c838daa2035f09b2c9f9b5e3133f80c6aeb84c2", size = 233677, upload-time = "2026-08-15T08:17:50.845Z" }, + { url = "https://files.pythonhosted.org/packages/bd/67/0f40eaf8d1b6e7cf15e82382a2965efaca787fc1c2794b7021d37aaf5036/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c0ea61a470e070686aa30892fed79e297d2c8d0ab46b8bcdf027d38c51da591", size = 264491, upload-time = "2026-08-15T08:17:52.61Z" }, + { url = "https://files.pythonhosted.org/packages/5c/64/12b4c2a11ee8df4fcc518c78b0d93e3a92bd3d5253d1617ce74ff0e8c7ef/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:90b7481fb62fbe172c558bc6fd1c4c98d82004a54a7551f20e11ac9bf0b8708c", size = 245196, upload-time = "2026-08-15T08:17:54.023Z" }, + { url = "https://files.pythonhosted.org/packages/37/2e/651d910af6d0fba325eee1cda37ec5443462ed25360e666c144166eb6091/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:35fe081843b35aad20ffeccec3eeffbe637b15d14f3fb22cc1b59cd8ec17e93c", size = 261660, upload-time = "2026-08-15T08:17:55.491Z" }, + { url = "https://files.pythonhosted.org/packages/90/c6/b09e05e6db7f64338e0dc067c79577b1138da86c1e38369096851d96be88/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fd0350afdc3aabd5576f60ea109228bd5538139713c7b094c5cd27c73a98bc6f", size = 252618, upload-time = "2026-08-15T08:17:57.025Z" }, + { url = "https://files.pythonhosted.org/packages/76/4e/362d4f9fdcdf5556fb2aa3ce7d4a58ebce03ed1ff03aa1d9aca8d02f13f3/charset_normalizer-3.5.1-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:9d9a0dc7cbe9bec24c3f767c9122c41fe5a1bc43f47cd099d00d393e09769de4", size = 140362, upload-time = "2026-08-15T08:17:58.425Z" }, + { url = "https://files.pythonhosted.org/packages/b4/d4/703be739b26acce318bd29eb3b25b7209e1b1f527f9eae3d1f1f01fdde2b/charset_normalizer-3.5.1-cp313-cp313-win32.whl", hash = "sha256:d63600d620ad0064c3a748b950ac5ea38a80190e5498532efefa4b7b3f1da1f3", size = 177755, upload-time = "2026-08-15T08:18:00.037Z" }, + { url = "https://files.pythonhosted.org/packages/8a/33/56d97ade41c8db611e727168c52ae46c9224c362ec28d4b65d7e9869e8da/charset_normalizer-3.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:aea996a6aba25260827c9ea511d1addfde2da9eb686ac961838509086188b7e6", size = 199295, upload-time = "2026-08-15T08:18:01.506Z" }, + { url = "https://files.pythonhosted.org/packages/5b/75/5b20dd1e6573a01a08158fe104104fa2c8abf941745596954185726cd46c/charset_normalizer-3.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:fd0a274c0e5f9a21565cd9d3dd749b61f96b7aa1e20a93aa1ba4029518f2e5c0", size = 179856, upload-time = "2026-08-15T08:18:02.929Z" }, + { url = "https://files.pythonhosted.org/packages/29/cd/2b812ce5e888f1ce69a5350281e58aab07ae64a958ecae8912f30865718e/charset_normalizer-3.5.1-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:774d157f112367ff4abd29019f38f023c24e00e56edc7829c20e358a5a913ad8", size = 212318, upload-time = "2026-08-15T08:18:04.403Z" }, + { url = "https://files.pythonhosted.org/packages/9e/4a/a6ee107430768a5334e6d63f31f148a04a1a491ef161a1ac9415a73f2fa8/charset_normalizer-3.5.1-cp314-cp314-android_24_x86_64.whl", hash = "sha256:26422d45fd13551cf564c58932f7d72b4f58b93b0fcf18c35ba6be12b46bb102", size = 224897, upload-time = "2026-08-15T08:18:05.997Z" }, + { url = "https://files.pythonhosted.org/packages/c3/d9/35ae3f64f29d0179c35c3baefe575904df2913dde519129c7f75995a2b1d/charset_normalizer-3.5.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:09a7bba9f739468c8e78c36a75c33768e53cb1959fc638f510454c14683f00d5", size = 194848, upload-time = "2026-08-15T08:18:07.397Z" }, + { url = "https://files.pythonhosted.org/packages/74/76/f2fc7380f056cc273a53af37f50d08ad54b2c59f61078f31432edcf1c2bd/charset_normalizer-3.5.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:4c9548dc78002099910abaebc0a72ac58b7d30931869e0351c09b507dff4ece3", size = 198163, upload-time = "2026-08-15T08:18:08.989Z" }, + { url = "https://files.pythonhosted.org/packages/e9/40/095ce62fa078483cccc1fa2b36e6bc9580b85422a20ee9f925341c50e44f/charset_normalizer-3.5.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c428c6c31eb5f4277d7f8eccaf767fbd548ddd5ce3c8b4f4cbbfab3d96b5904c", size = 341823, upload-time = "2026-08-15T08:18:10.458Z" }, + { url = "https://files.pythonhosted.org/packages/f1/5a/0e58b1c04a1596e0256f407274a92d5fb2ee21324409d1fab1da48a65b5b/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2f06b7eae9dbe77fe1d644ca244dad508de8d302870a43f3c559b521270938a0", size = 242458, upload-time = "2026-08-15T08:18:11.989Z" }, + { url = "https://files.pythonhosted.org/packages/22/95/b4618ce912e6db0b1aae89ba788e38e8a7eba0f3025cc66e8c0699f977b2/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b7430cf5728e68f6c462254009a6ef4086e1bea43cf2f57aa9c55fb4f50ff96", size = 226717, upload-time = "2026-08-15T08:18:13.401Z" }, + { url = "https://files.pythonhosted.org/packages/8a/76/c681192bbda3d55356db5dadd64381d5202b37c6b598fcda5282e88b5d3d/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab743e9bc90c1f73552ec33e10e3331315acd2c397b36065b591b0181de533cc", size = 266111, upload-time = "2026-08-15T08:18:14.961Z" }, + { url = "https://files.pythonhosted.org/packages/88/be/55127bfca72c0cff6c022488d140d7c5b04c771e3b72e9bdb4836d54979d/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6f7deae3feb4edfa2efaf7c574fe88cbf055038a6abdb40188e4fff66d5699f", size = 263128, upload-time = "2026-08-15T08:18:16.515Z" }, + { url = "https://files.pythonhosted.org/packages/e0/91/39c3af510b0aa32bbda03374259200f28430febfd1bf5e511fe765282ce5/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15f024313246a4ed976c60f440bb8d257815513a681d212ff74fd46f7d715a90", size = 251240, upload-time = "2026-08-15T08:18:18.127Z" }, + { url = "https://files.pythonhosted.org/packages/1c/a5/cbe418bbc6ecdfc3e05a0116002897c4b403a5e838d697e64c78e9f0190d/charset_normalizer-3.5.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:823f82903d189af463d7df250ef1f7f696f3cee08cc8d91deb565e8d425f6506", size = 245282, upload-time = "2026-08-15T08:18:19.625Z" }, + { url = "https://files.pythonhosted.org/packages/cc/a4/689bb42e8e7cd492f3cb64907c6bc00ad247ec9a3628cd3f8eed126e8ae1/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:01e93745f7f219b703b60ba7afead36cfc4242782be5af484673fc500df12da5", size = 244597, upload-time = "2026-08-15T08:18:21.121Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ce/9962938e179cf9f699d3f1e7b3114b5d7642dee6a893745229f9dd04f274/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:329fc3ccb63ad22d867d84c2adea759a64079a37ba4a343433b02c7a2816871e", size = 231376, upload-time = "2026-08-15T08:18:22.57Z" }, + { url = "https://files.pythonhosted.org/packages/85/54/46000450ada53bd9eac5429a2c8c54cd2d9b39c0c255f229aea9af0948a5/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:bb57753e36e4855b8ca375069482250a6246372331a3e4f3407eaebb007443f5", size = 266715, upload-time = "2026-08-15T08:18:24.235Z" }, + { url = "https://files.pythonhosted.org/packages/3d/bb/618749d70f792b44252a777bf89bfb86823b9bbc1ea13fe8ce759b07f38a/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:fce8cbd4997efeb450bd298b54f755dcdff18d496f7a5ddbb4867c6d7c88fdc3", size = 245848, upload-time = "2026-08-15T08:18:25.726Z" }, + { url = "https://files.pythonhosted.org/packages/7e/3f/ffb64458527c7668031d5eb095d978de561958dc9f5b53f8e488a533e603/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:6c9cdde8becb25a7fde49924511aa2644d6f8081cc8df8e9452724303348d8e3", size = 264521, upload-time = "2026-08-15T08:18:27.193Z" }, + { url = "https://files.pythonhosted.org/packages/4f/ab/74a55fd803916a35ac461daf002708191aac19b546b80dc8cabfedc63d98/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9ac4444d8d4fd4c4bd08bf451ed3167aa9e7ec6cdb41b648794f1d1103652e36", size = 253054, upload-time = "2026-08-15T08:18:28.568Z" }, + { url = "https://files.pythonhosted.org/packages/a0/2a/6a9034b7d3c60b17499afb482df5878bf9fa20b50cc3887d5ef017a833db/charset_normalizer-3.5.1-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:f03ac127268b43ef4fe9e6ab6794a6794b49485a0cc0c1db79876d2f33f75bc7", size = 140580, upload-time = "2026-08-15T08:18:30.214Z" }, + { url = "https://files.pythonhosted.org/packages/f3/46/1d362e1a00d035d66b9869e1281eee115907f7e390a16a07824ab5737360/charset_normalizer-3.5.1-cp314-cp314-win32.whl", hash = "sha256:1f5883d77fd409a261abb5dc8ccbe335720d798b1de4abb3b1d47ccbbc76b53b", size = 180325, upload-time = "2026-08-15T08:18:31.877Z" }, + { url = "https://files.pythonhosted.org/packages/7a/7c/4938c329b6a9d446f6a59aa2092ff7118f274209b5ed0e26893d1d30a63c/charset_normalizer-3.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:c658c50ac0c98cd755a2dd50b7977d3bca7df401dcc47fbdfa87db53ef7d4e8b", size = 204175, upload-time = "2026-08-15T08:18:33.466Z" }, + { url = "https://files.pythonhosted.org/packages/ac/33/eeb384dbd8dec570661354592f4f2e1b2fcc92585624d146a000caf53841/charset_normalizer-3.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:4bea7f8ebe90bbd7f0e4a2de42ca6924ba23e3e76418c408ff82f1d46fabd687", size = 184123, upload-time = "2026-08-15T08:18:34.913Z" }, + { url = "https://files.pythonhosted.org/packages/1c/6c/c73fa9d5a85f6ab05395de61c5f6984e0a9ff40bb5ff888d46dff02526c6/charset_normalizer-3.5.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:fbc597639158fd7c14d55e808718848319540f51b0e6746e3eefa59723a4a348", size = 381682, upload-time = "2026-08-15T08:18:36.349Z" }, + { url = "https://files.pythonhosted.org/packages/30/c7/63565f860921457feba93bae6c86fb7746deb4cffeed2f375cb845318146/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e71c909f353863b2b89c83de2ebed71ea6d0df8a6ef65a128193c5e650766bef", size = 240826, upload-time = "2026-08-15T08:18:37.887Z" }, + { url = "https://files.pythonhosted.org/packages/06/ae/7ae8807410dfa33f8e6f1715740adeaafa8a816cc4cb33508f54b1f7c896/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7ac76cf9afd34929d76eb7fcb63be476a4853d8a96f0dcf2d0db68a0cbdf9885", size = 227861, upload-time = "2026-08-15T08:18:39.315Z" }, + { url = "https://files.pythonhosted.org/packages/e9/a3/887c1642f0da26000b0e0652d91071113c0e72cea33952e225cf589f49a9/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a3a370082ce34d0612f421e15fe011c53bb1feff21a26d06ad4fb244dab5a375", size = 260758, upload-time = "2026-08-15T08:18:40.88Z" }, + { url = "https://files.pythonhosted.org/packages/3e/11/e6f5b9a3d0e55b0ef7505cd3765cdd48f22db89994c947b316f52f801fd8/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:256dd4d85d9e4dc595e2bc983c980e73f62ddeb3165c58b4c3dfe78c5c8548c1", size = 259950, upload-time = "2026-08-15T08:18:42.351Z" }, + { url = "https://files.pythonhosted.org/packages/1b/ee/e4e10a94d51cd1ee638aa7e00b65399e6b2a4e8376ab6d2eac9f95586671/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:58d4aa13a59c969dbfdf9e6a9560e242cbfd9e8a8f50c2747714df1a423adf65", size = 249329, upload-time = "2026-08-15T08:18:43.914Z" }, + { url = "https://files.pythonhosted.org/packages/c4/25/d5f4198819e6059735a84e8d0bfb72dc33976da67b97adcd3fb5a5e07ec6/charset_normalizer-3.5.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0c6dfb5ca6723eeed15aa8e564a014d69fcb8812f94eef11fe3631e0508199f5", size = 243137, upload-time = "2026-08-15T08:18:45.368Z" }, + { url = "https://files.pythonhosted.org/packages/a5/e9/e925ca7569cf9fb9701fd82503fee73eea5268fdb856bdd64947092d3daa/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c010f5581d9c612804cc59fcf7b524b707fbcb72828551237ab545bb5c7034af", size = 242820, upload-time = "2026-08-15T08:18:46.842Z" }, + { url = "https://files.pythonhosted.org/packages/34/17/672c251a888ed2aebcdd2fe830ad0104e25ff83c43f5c4f9c15e9fc6853c/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:52ec005752a56ae79547a05c0139ca2501a0c866390b6115008456b9f0e7cde1", size = 230504, upload-time = "2026-08-15T08:18:48.353Z" }, + { url = "https://files.pythonhosted.org/packages/3f/fc/f6a85abebd42ce4da2f1db0aa56cc6a0df1995e318b3875d14401b8381d1/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:2bced4061f000f7187254a02ad3433ae17eaf991747ceea2f478422590a5bba9", size = 263087, upload-time = "2026-08-15T08:18:49.859Z" }, + { url = "https://files.pythonhosted.org/packages/98/66/7c42677e739ba66746b297e2046918d793078094dc239e1e72768cffccc6/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:9eea3ab2597a5e65fe65296e2d6a84570845a6b55532d90333d740d48bbc850a", size = 243269, upload-time = "2026-08-15T08:18:51.601Z" }, + { url = "https://files.pythonhosted.org/packages/de/d8/a50b79237f417af10f8c2a501ce8d1ca87829a22e69117891ca4ba20a69e/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:496846868fea80e479324862fa877f02411f2fd0f83b79ccee2607aa68b2a032", size = 258766, upload-time = "2026-08-15T08:18:53.23Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1d/0fc91aeaeb3c83b748f532399ce67cf84604b48297405d740000f7a9e786/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:85d5855daafc240cc045c026d7a15fd198a09b0fc8ff6f5ecbb5297b509cb11e", size = 250814, upload-time = "2026-08-15T08:18:54.768Z" }, + { url = "https://files.pythonhosted.org/packages/ae/10/3d8c777cf9024615295aa1b808324ad5b4a77855869c00824bad74ffaf8a/charset_normalizer-3.5.1-cp314-cp314t-win32.whl", hash = "sha256:58d3e12c88e0950bca850ae1f7c256055c097639c2edb9eb123af9807d8b15e4", size = 191074, upload-time = "2026-08-15T08:18:56.305Z" }, + { url = "https://files.pythonhosted.org/packages/4d/81/ae557d3c44d1a1d688696d60563413a0866a91b7ebc50f20df838be3d8c8/charset_normalizer-3.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:acaf604462bf330b0d07e7a07c1d6e4adac79e5fb13e9c5140590542cafacc00", size = 216476, upload-time = "2026-08-15T08:18:57.889Z" }, + { url = "https://files.pythonhosted.org/packages/27/e9/61c01fb8b804692569c036b3fc50495814502dcf13a60649c6055390b02c/charset_normalizer-3.5.1-cp314-cp314t-win_arm64.whl", hash = "sha256:fdb8a068947befafba9952162645dc2fecaeb400e64584829ed5e9b2fbe21a7f", size = 194115, upload-time = "2026-08-15T08:18:59.418Z" }, + { url = "https://files.pythonhosted.org/packages/4a/4e/8544831ef59d8f27ce92c80871380fdacc8076a8a56ed62f82e54f991333/charset_normalizer-3.5.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:9085f87b0e38a2b92b8923059b4e8789fe40d9279712d15dcc670048d77079af", size = 342048, upload-time = "2026-08-15T08:19:01.054Z" }, + { url = "https://files.pythonhosted.org/packages/7f/a6/e3b46852424246065355644f4fb6dbccc0239a42a2eee27ecfc8957f0bcd/charset_normalizer-3.5.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2679de311c7946dde5d3b6f44941844133ff5c7cb86099c0061ab1e8901c20a8", size = 242997, upload-time = "2026-08-15T08:19:02.492Z" }, + { url = "https://files.pythonhosted.org/packages/03/3b/0cc9a26777334ab2f2e3089b948bbf4e4fe72ea70b897715ef6415043ec8/charset_normalizer-3.5.1-cp315-cp315-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:baf3775a2635e5a11fbd5e4e64ee69c7e86875d224a5c72aca4c141064589a90", size = 237014, upload-time = "2026-08-15T08:19:03.943Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c2/027335f0aa337a2a2e121bac1ad88c4f02ba6053ea0926802784f3db11af/charset_normalizer-3.5.1-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ac8c94b6539074e0f40899301273ac8402b9b3e01c7b7ba269ff30340aaaf20", size = 266174, upload-time = "2026-08-15T08:19:05.598Z" }, + { url = "https://files.pythonhosted.org/packages/86/d3/e367787febe4e74769dec0f406f2c3c8d1b955fce5aee1fd0f94e8367a45/charset_normalizer-3.5.1-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fe532b3c966d1fb794e0698e4589d0444017ae77fc0b31edea13c0e35bcc449", size = 263361, upload-time = "2026-08-15T08:19:07.251Z" }, + { url = "https://files.pythonhosted.org/packages/af/3d/391b193eb9f3e84b02f9314088c386debdc0debee843535aaea2e2c6715d/charset_normalizer-3.5.1-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5c84bec0ab5ae0c64bfe73a7d2adcb5ce73b467523fc27fd6a28ab2aa6cbe35a", size = 252143, upload-time = "2026-08-15T08:19:08.816Z" }, + { url = "https://files.pythonhosted.org/packages/2e/57/de221f1745a90d418199761967e2776bfe2c275a1194220985e8c1d37833/charset_normalizer-3.5.1-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:854066be00447fa8de2ccbbe893e2ffc4b123ef16d897af794c1e18bd4a714b0", size = 252086, upload-time = "2026-08-15T08:19:10.255Z" }, + { url = "https://files.pythonhosted.org/packages/c8/e3/d119f86a01f9331e8186175f24873b1d74a7ee9e2e4b4d68f9947dae5afd/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:21b82d8082f6f5e7f456ef0bd16323d08de1266efbfeb476e64b2a91d1471a4e", size = 245231, upload-time = "2026-08-15T08:19:11.807Z" }, + { url = "https://files.pythonhosted.org/packages/26/de/d8e48c135ae480879539cdb179c8d3b50c7879497d75dd899b5763b69cee/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:838648accb3a7fd9803fd45c87bce8509648eb0c11bc34e216141300977244f2", size = 241546, upload-time = "2026-08-15T08:19:13.416Z" }, + { url = "https://files.pythonhosted.org/packages/67/c4/217755fd1abc50d326c252922cd642002758095a81ff45010337b8b3ef65/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:195ce897c6153c0700078142cf8efe3e6454ca4cf4357499e4078dfd83396626", size = 267033, upload-time = "2026-08-15T08:19:14.981Z" }, + { url = "https://files.pythonhosted.org/packages/b8/d7/34d8e404e358d2adcc5a228c2134643af00104c8fb0bf525f3688d756f05/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:978eab16f55b4ab2c2a745be9a0a840bf8f09a7f227d9c76eb30214d078865a5", size = 252045, upload-time = "2026-08-15T08:19:16.618Z" }, + { url = "https://files.pythonhosted.org/packages/5e/fa/40414471acf0aa0692ca77305aa00e434fcd8288f0941c93c30e9a5f8f2f/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_s390x.whl", hash = "sha256:cc0329df4caaceb950d2f580b5ac716a377f7059624a0bafaeaf8a218c6ed774", size = 264866, upload-time = "2026-08-15T08:19:18.101Z" }, + { url = "https://files.pythonhosted.org/packages/32/90/fcc850bae791abd2e0c041847f13e270aa08692a79f3e00de6d2dce1cb50/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:687c9ca3035544b113bea2055e180af96fb63c0c476e22a9180f51925186e7b7", size = 253932, upload-time = "2026-08-15T08:19:19.734Z" }, + { url = "https://files.pythonhosted.org/packages/af/af/53afe99068b3c10b4cbae592a52ef72a7c92c0188440e83ee3a078fd8f75/charset_normalizer-3.5.1-cp315-cp315-win32.whl", hash = "sha256:706bfd38730a5ac7a365793269a00f4e988178cec121391f4248d84ad8c972e9", size = 180320, upload-time = "2026-08-15T08:19:21.37Z" }, + { url = "https://files.pythonhosted.org/packages/c9/bc/f46a132041b29e4a8779ed712d3df1bf112e94ca8de58b66d7ec2c0cf8b9/charset_normalizer-3.5.1-cp315-cp315-win_amd64.whl", hash = "sha256:92caef967d287a407085d61176fce4012b1dd62daed4eb6d5ceb26d3d2538712", size = 204174, upload-time = "2026-08-15T08:19:23.088Z" }, + { url = "https://files.pythonhosted.org/packages/a1/5d/9ed554480eda8e447b673648628fdc29574d23dbad01fe11837adedd1cae/charset_normalizer-3.5.1-cp315-cp315-win_arm64.whl", hash = "sha256:5fc45d653ea8c9a20479167e11d4a0f8cb2fa3470737ab6f9c827532313187b7", size = 184126, upload-time = "2026-08-15T08:19:24.471Z" }, + { url = "https://files.pythonhosted.org/packages/3b/32/9b8929bf384061ee1fe5d9c27c6f9776d3d824039ad4e14c88ec00c7808e/charset_normalizer-3.5.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:59171c6e45bf07d0d5cab3b0bf81d945035530f6873398b3b531c31184d46663", size = 381441, upload-time = "2026-08-15T08:19:26.038Z" }, + { url = "https://files.pythonhosted.org/packages/96/10/e9aa7923d3ddac652c99a1c5f7be494e737e151566a44abe018daf757f2c/charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9dbdd9205662134957cf0c324f639bdc5031c0ca056e2369e238db75187c0f11", size = 241742, upload-time = "2026-08-15T08:19:27.532Z" }, + { url = "https://files.pythonhosted.org/packages/28/53/a2d249ebddf47b889a100c0bdcb61a2f9dbb8bc24ef325cc062e4f476877/charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e4b018dc5a0eee4676e38fe84a47a427816c590b93b55d9025274ec4d6ffc2dc", size = 235298, upload-time = "2026-08-15T08:19:29.274Z" }, + { url = "https://files.pythonhosted.org/packages/7d/07/469f78af590f7d5cd48e20d8dbfa3d66deeff9ba37768c04d886b5afd45c/charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ced3fdd71aaa83ce593746c2edb42b7a59cb4c19c8b5c407781c72e493aae55a", size = 262500, upload-time = "2026-08-15T08:19:30.955Z" }, + { url = "https://files.pythonhosted.org/packages/55/66/3bb56a47f7dcba014055b1a1d33c6f08bbe9c1e74dba154cfa25f90ae885/charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:19a3dd5aa73cef1c99687c4fc57db016a9c17104ae1185da88ba566a5d3bebe4", size = 258888, upload-time = "2026-08-15T08:19:32.458Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c1/2adc2800903fb013210349313b710a5376856578d9e33e6b9a1d8b36714a/charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cc5d36d96478aa9c60654bd932525bf32964c62a7281eafdf16d85003a8d6004", size = 250243, upload-time = "2026-08-15T08:19:33.94Z" }, + { url = "https://files.pythonhosted.org/packages/95/b5/a18d0dd1157ab655cc2cb14a545f4a4784bbad70ab3502412e36097502d9/charset_normalizer-3.5.1-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:04368edf83514385ffc3e1cfd4546e595f4f1272dd23ba437a93a9cc3741d47b", size = 249871, upload-time = "2026-08-15T08:19:35.413Z" }, + { url = "https://files.pythonhosted.org/packages/ad/c3/525f508cd1e58d0450ac55ed40ac75bc3a97482c59def5278456a5fbf03c/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:9b5db6052055d34d41230fb78d7c439c23dc536a9896f6cb039e8dd92cfc1263", size = 243580, upload-time = "2026-08-15T08:19:36.886Z" }, + { url = "https://files.pythonhosted.org/packages/7c/c1/49a91fe7e97c8140094ca5c64161ab623a70d9f636bf834eace14048acb5/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:252d099029bcbea642f2a06c4ed5046bdf8b5a8150b64afa5e027e88b106e5ee", size = 239807, upload-time = "2026-08-15T08:19:38.392Z" }, + { url = "https://files.pythonhosted.org/packages/d3/58/56a48c296601274c4689b864a8e2dfb209b81dfcb39472753ce95eea662b/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:6199d5606e2bbf2b096cf64d03f8b6790c91081d5ac866b8e7bb6422738cc60c", size = 264083, upload-time = "2026-08-15T08:19:39.856Z" }, + { url = "https://files.pythonhosted.org/packages/10/4c/dc48409274a1817ff349711d26c62aa0c597df865d4d69ef79160c859193/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:77efcff2b23071c349402ac1066667a3d011f62398d81408c9b88ad991747c9e", size = 250317, upload-time = "2026-08-15T08:19:41.53Z" }, + { url = "https://files.pythonhosted.org/packages/81/58/d325912115caec62d6bdd77bbab5e0b7da5d234a9f20affdffcbcb530d0b/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_s390x.whl", hash = "sha256:a5cbd90ecf0fc62e64726917ad083b73001f0563657a87ec3c0b504e277dc90d", size = 258173, upload-time = "2026-08-15T08:19:43.07Z" }, + { url = "https://files.pythonhosted.org/packages/34/f7/b13b1ccae2c8ec63980d13be1890eb73f8aeabbfce02a24aabc0908788f5/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:4d26f14f041e83dd8edfd61f4cd4fa7285d31798b5bf1f28e70c367ba6c41d61", size = 251960, upload-time = "2026-08-15T08:19:44.587Z" }, + { url = "https://files.pythonhosted.org/packages/1e/25/ed3f9919c5aef8cc818be1f972f565f7610d7b2076b8ebb98839516ffc3c/charset_normalizer-3.5.1-cp315-cp315t-win32.whl", hash = "sha256:ac13b004224fb341e1e25a1ed5e19d32f57cdb2a403e01f003b46f051a550f6f", size = 191186, upload-time = "2026-08-15T08:19:46.293Z" }, + { url = "https://files.pythonhosted.org/packages/69/d5/43c2b3e9d8267092b913eb8b0603f0f71993c395632886bd37a7223f96cf/charset_normalizer-3.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:35aea775dc2bd5f54cd84a1cd2696cc3207c479cb9cf0bd346f0d343e4300ddb", size = 215947, upload-time = "2026-08-15T08:19:47.853Z" }, + { url = "https://files.pythonhosted.org/packages/a8/76/9aad3e9c8865e5e0efa9a7f6f81c37a67635a985145ecd44528a81e088ee/charset_normalizer-3.5.1-cp315-cp315t-win_arm64.whl", hash = "sha256:fb78f6e7fcd8ad785d28cd577168bc1aaee827b25bb8755638f694794ea98f0a", size = 193909, upload-time = "2026-08-15T08:19:49.383Z" }, + { url = "https://files.pythonhosted.org/packages/5b/97/fb4e82231aba271ffd775a1b4993b0defc4e3059f286ae41d9433409fe85/charset_normalizer-3.5.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:41876ee62a3dddf48ff1121ad8f0798032aa03f2fd35f21f34a4cab14f18d8d2", size = 331467, upload-time = "2026-08-15T08:19:50.959Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2f/fe3f187327aac18e2d54e9d2b08e15d27bf9b642d9e51c219f130fc34d1a/charset_normalizer-3.5.1-cp37-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a6dac12ff6b846103483683f60c5f8fee205121adc58ffd87e90a90a3af69e99", size = 253057, upload-time = "2026-08-15T08:19:52.654Z" }, + { url = "https://files.pythonhosted.org/packages/d7/c7/9e48cee5c161fe24da823b61bf381921d77cb994a0a4de148e95018c1984/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cee5dd7c6fb5dd52a0fe2a740f9bc6e3593f5f8b1788bde49de02086f30182b2", size = 240930, upload-time = "2026-08-15T08:19:54.163Z" }, + { url = "https://files.pythonhosted.org/packages/49/e0/716601f3cc69be7b198951150c75ead1ece33c3c8036ff6ffa46029659a0/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:343fb4f2821043bd87095f7b08a1a181febc8e36ac64212143bbfd0a0e1bc235", size = 230822, upload-time = "2026-08-15T08:19:55.807Z" }, + { url = "https://files.pythonhosted.org/packages/d3/05/71bfc5caa0abcc45aea1f6a4d50ac68e59605ddc7666fe8494f4cd229665/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ae4a097991662cd4fff0ddc74e0fe7874f82e00042fa0ea00855645ed0c79598", size = 260037, upload-time = "2026-08-15T08:19:57.312Z" }, + { url = "https://files.pythonhosted.org/packages/c3/92/de7e32ed05341e7a9c4c877c318418197b7f2d66a3b68d561bf2ac57ca3e/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b599739b93b2cbeded49645ae3c8d1405c29ddfbceac1545c87a3f9580a9e96", size = 255097, upload-time = "2026-08-15T08:19:59.056Z" }, + { url = "https://files.pythonhosted.org/packages/f5/7b/ade0a122600319dfa0b1000ab0f9731c94a817904cf3c5de408c73a4ede7/charset_normalizer-3.5.1-cp37-abi3-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b39b69b347e5e47a3b5b8cfc005c68c1ba347474e3960236c4944a8ecd174962", size = 250166, upload-time = "2026-08-15T08:20:00.612Z" }, + { url = "https://files.pythonhosted.org/packages/75/9c/019fbb9f4834491a160951349b1a3714439376f66e5f7cf18b4f18f0c7aa/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a2028475ba855475b8b4d3cfeb4994269c967aea8b9892dfba907f4263a863a3", size = 241821, upload-time = "2026-08-15T08:20:02.321Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b8/11d4840bfc99330cc7fbcc2681ee5a044553a6e77655508d8f9b2bff7b34/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:36047af20e17097c3bb9476c2b7655f2f7aa51322c0ba58c07695bedf755a950", size = 232529, upload-time = "2026-08-15T08:20:04.008Z" }, + { url = "https://files.pythonhosted.org/packages/18/96/2b3a21492d9f65171ac75d872f5018260013d00bfa0ff70ec9f179148cbd/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:4c4fb141a727957c93edfe5c32a26ceb6b5f6461d67146e2d39f51e16170bea8", size = 260348, upload-time = "2026-08-15T08:20:05.877Z" }, + { url = "https://files.pythonhosted.org/packages/d6/aa/a69a2028e8bd052476c245460ab19d7de595de084dd968f2d75cd50c3e25/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:2f293479cce755c75f1697e87c409b7ae4c555c7dfecb6e988ad13abba943031", size = 247234, upload-time = "2026-08-15T08:20:07.487Z" }, + { url = "https://files.pythonhosted.org/packages/35/8a/3d130aeabcaf3d2466af76b7b141c08d9e89c9016ab4b7cdd0f7dc2d1c62/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_s390x.whl", hash = "sha256:3588e376b3ea2eea84976f67273d679f229e24c66dce7b82ae45aef04ff6e072", size = 256917, upload-time = "2026-08-15T08:20:09.142Z" }, + { url = "https://files.pythonhosted.org/packages/80/c2/a7379b840292d0c1ab9fbd17d1f3967aa81794dc95bc74be8999d7fedcf7/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e199fb99720074809a7720f1c0b4d919eea8b87e88713e0f8f602f7bef543d9d", size = 254846, upload-time = "2026-08-15T08:20:10.727Z" }, + { url = "https://files.pythonhosted.org/packages/01/65/d43b714731bb2f40d4053dfa00ecfc1c5a301f8e3316c5db3a09af59fe94/charset_normalizer-3.5.1-cp37-abi3-win32.whl", hash = "sha256:dd732602a7009217f658d5863d12d79d373a4de0eebc111094bcdd3bb8e0a6cc", size = 174216, upload-time = "2026-08-15T08:20:12.334Z" }, + { url = "https://files.pythonhosted.org/packages/35/4f/b911ed898b26a09789eba9c9200c999aff6c61b4bafaf4838e56d1a1e1a3/charset_normalizer-3.5.1-cp37-abi3-win_amd64.whl", hash = "sha256:70055ff39b97c99e7ae40ea3e393fb62aa2e44dbd9b29f8d14f42fb0025c3959", size = 199764, upload-time = "2026-08-15T08:20:13.908Z" }, + { url = "https://files.pythonhosted.org/packages/f0/a7/920baf467bfd9bf689f3b318340f37aee4572a71f162bd8db51da55ba4fa/charset_normalizer-3.5.1-cp37-abi3-win_arm64.whl", hash = "sha256:87e4f41d375c0b9be2fb5251aee4b8a689169e134535aed81bf085c3b647451e", size = 287318, upload-time = "2026-08-15T08:20:15.551Z" }, + { url = "https://files.pythonhosted.org/packages/cc/61/d01fc49b8dea277640b55a9e15960dbca9fdc8c9fde18e572d39c59f4019/charset_normalizer-3.5.1-py3-none-any.whl", hash = "sha256:6df0ec430f9a831772c23ca5a224cba36517a58a84bb32c32bb59a9fa67c47f6", size = 68658, upload-time = "2026-08-15T08:20:43.306Z" }, +] + +[[package]] +name = "click" +version = "8.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/0e/7fa0ef50764b67090eca4114772a2abf8b6148198475e54c660b97caeee6/click-8.5.0.tar.gz", hash = "sha256:ba0d2089de75ea0310e2dde03160e6ca10009947fb95a182f9b54021bb272e34", size = 382235, upload-time = "2026-08-26T13:33:14.56Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/50/6c0d534c5f134586a8e1ba4e330569e32f057e33372ae556463212fb4cd3/click-8.5.0-py3-none-any.whl", hash = "sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360", size = 125251, upload-time = "2026-08-26T13:33:12.928Z" }, +] + +[[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 = "cryptography" +version = "50.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bb/ad/5d6702db60b1e40b41ef513b6967ff5848f307d50f8449baf1634f5908f1/cryptography-50.0.1.tar.gz", hash = "sha256:5dd9bda1c12b4162f6ff568eeb5e0ff956c28d14406e875cfe8a63a2d414ff20", size = 880381, upload-time = "2026-08-25T19:45:45.499Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/19/797e2aaac9df6a66f1550f49979dc1b1e39ecd2077501c30efa81e8d5d67/cryptography-50.0.1-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:b8f852c65863251b9e3a1b8c150ce21e59b522dbb6a7d4bc80e680d38388e986", size = 4010153, upload-time = "2026-08-25T19:44:03.155Z" }, + { url = "https://files.pythonhosted.org/packages/90/34/9ce9a62ed9dc82ca9fd6a34445b6904af56e5f38b3eae2ed32e49c36053d/cryptography-50.0.1-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:53e279950892dc102c6b4e52af03ae5ea92fac572a1ddab78ca73a997f62b69f", size = 4723133, upload-time = "2026-08-25T19:44:05.461Z" }, + { url = "https://files.pythonhosted.org/packages/57/26/e6d4fc8512a51a5f9ee7bfdbfb853bce1197087df40c9ad993ad370b846f/cryptography-50.0.1-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ff838d62ec1bfce4f9ba7fa16f4a7b554cd8d0c299e6be37502161a660c84eef", size = 4712478, upload-time = "2026-08-25T19:44:07.375Z" }, + { url = "https://files.pythonhosted.org/packages/e6/de/d3cdc2815697aae84126cbd6a030ca7b6b452e28a88b501b836bd3aa7a86/cryptography-50.0.1-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e74591e283fe6eb956416c929eb58262a719fe0311fd9054c62c3350ed8760d8", size = 4730726, upload-time = "2026-08-25T19:44:09.294Z" }, + { url = "https://files.pythonhosted.org/packages/55/32/38c0d344b98c06d34b5df8946565a9c0d6dbf32c8e0730a7f05f0a3c6cab/cryptography-50.0.1-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5fe002589592ed749ce77fe0695fcbd3500dd61d7d6db5858a7544c612fa8e45", size = 5353524, upload-time = "2026-08-25T19:44:11.96Z" }, + { url = "https://files.pythonhosted.org/packages/e1/1b/82f0f0d8858d4432be1af790477edf62aef90324041aa07c57e57bef1af7/cryptography-50.0.1-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:51593d180cf6d179bde5c5d065bed81386b1f381656ae7d042b7ffc87a9895ad", size = 4746720, upload-time = "2026-08-25T19:44:14.051Z" }, + { url = "https://files.pythonhosted.org/packages/29/ba/042ca458b8c64348c768284b5d23e69b92ed53d057ab779fee628564676d/cryptography-50.0.1-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:359e62deae718bce96170e223fdcb6357e4fbd3bb7a3a75f4430763532560e49", size = 4361866, upload-time = "2026-08-25T19:44:16.167Z" }, + { url = "https://files.pythonhosted.org/packages/39/3b/e96c1ef71edef71057c7e3c3d982ce8fda554e0c52d0cc19c18845cde3eb/cryptography-50.0.1-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e2ca8fd1b6b4b82a1c4cb02841d0837e3c12336c2e24b520ab8ab3b969733d8f", size = 4730028, upload-time = "2026-08-25T19:44:18.085Z" }, + { url = "https://files.pythonhosted.org/packages/e3/38/45abd72ef63f2e7d0754a6cacf97bd8b69512ace7f6130d24c39ece65da2/cryptography-50.0.1-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:76de83fbd91ac49c0feaaa983d0748fd7a53176afac5fb3bf7478d244f0eb527", size = 5308405, upload-time = "2026-08-25T19:44:20.197Z" }, + { url = "https://files.pythonhosted.org/packages/85/66/6ccca4722987ddedaa7fc9c3f4708af7431f5535666c174350830888c6b7/cryptography-50.0.1-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:51afcfceb15597cf2635068e4ac9a56b2abde622edde17f37d85fd7b5306497a", size = 4746230, upload-time = "2026-08-25T19:44:22.376Z" }, + { url = "https://files.pythonhosted.org/packages/13/0e/b1f92e013228111413f2e6743948b80bc24dfd3c1b87ba98ceea16f5df89/cryptography-50.0.1-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:be224a65493ec5b74a158ff22a5522ce4a5ca1e543c647a3a4730d4a09e5f959", size = 4862596, upload-time = "2026-08-25T19:44:24.472Z" }, + { url = "https://files.pythonhosted.org/packages/7e/22/c3654cccc856e9d682817b04ac3ee79731cb09ca6f95996a95c904de2883/cryptography-50.0.1-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9ebcdd5519be9b652a46f507817a74591774fc3d6923ac364e4dfa64e36b291b", size = 5014082, upload-time = "2026-08-25T19:44:26.709Z" }, + { url = "https://files.pythonhosted.org/packages/42/8b/cb12b1b60c91b074ca6bf0fdd59aa8f10d8bc5f73af8faece86ef0421b37/cryptography-50.0.1-cp311-abi3-win_amd64.whl", hash = "sha256:aed8db4f6d71c51efb89530e12d9464e7bf2923d46c3205dc794a2a93f8c0648", size = 3842826, upload-time = "2026-08-25T19:44:28.784Z" }, + { url = "https://files.pythonhosted.org/packages/5b/f0/424cb557d99aa86ac55da5e2add02e2882e44047b6264f93ade1b975a993/cryptography-50.0.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:30a125032e5642a21ff816e021152bd4e7e94f03eff3f4b7fca41cd22bc3110f", size = 3973525, upload-time = "2026-08-25T19:44:30.7Z" }, + { url = "https://files.pythonhosted.org/packages/4d/72/3a2711d967977ab5fc80b782837c7e8d1ac7445e764c20c381a265c57ef3/cryptography-50.0.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a0b1a59e3a089064a0ec309e9428c8e3ae4e161419d20ac33600767e83fc658a", size = 4708817, upload-time = "2026-08-25T19:44:32.773Z" }, + { url = "https://files.pythonhosted.org/packages/b4/f2/bb1f56e10815b789df0b409a69fa4992ff3d3fef9c72747f4a6b26fed38e/cryptography-50.0.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8921d58f426793c5f1b47f0b59575780de9a095214958d0eb37d909593db8367", size = 4697300, upload-time = "2026-08-25T19:44:35.144Z" }, + { url = "https://files.pythonhosted.org/packages/08/bd/ed5396be499ffcf8807a585bfe38b71a1fbdd1c342b4f9b6d0ef5162a946/cryptography-50.0.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:a8f40ea47330e71b594a7e246898f93177c259490c63183dbaf9e571d71ed9a5", size = 4716039, upload-time = "2026-08-25T19:44:37.192Z" }, + { url = "https://files.pythonhosted.org/packages/f6/6e/1cf405c5c8e8df7545378048e954792f00b7f2367af8863ce8b8f3e10607/cryptography-50.0.1-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:a255449073358275b64b67d3f595f268bbef70e72b6edb65e0c70c735bf739c9", size = 5332388, upload-time = "2026-08-25T19:44:39.16Z" }, + { url = "https://files.pythonhosted.org/packages/47/92/b4317e8c32c4f47b062f5398bd79106b220a124546f42be83bf32b761e2a/cryptography-50.0.1-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:8df2de9102026855887e4587084f6eabd80ed0f345b8ad8a7ac27ab9bf4723e0", size = 4730293, upload-time = "2026-08-25T19:44:41.298Z" }, + { url = "https://files.pythonhosted.org/packages/39/0d/a1e7633e2c744d0f2983320a27e924ef2264c79c56e1a58d5fb0a1cfd413/cryptography-50.0.1-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:ac02b07824d4d1001bd4367599f839c19cb171924c796e52c23508ac14c2c0cc", size = 4346031, upload-time = "2026-08-25T19:44:43.245Z" }, + { url = "https://files.pythonhosted.org/packages/88/dd/b215616f9bab3fc18510c78a4e5c9f362d77838503c363dc747c7d4f5c6f/cryptography-50.0.1-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:cbf74a81765ee67413503ca6e26dcc4f6f5a519822436cc0a1b97aab6c1b8a17", size = 4715344, upload-time = "2026-08-25T19:44:45.291Z" }, + { url = "https://files.pythonhosted.org/packages/b1/1b/ec3ebd31741d0e963612c4fe43caa39341b9b1e031e469820e42e4c83918/cryptography-50.0.1-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:16c5ecd954b3330ebfb6605eca4fd952da8bef376551d5cc264534e3770a9ee6", size = 5287201, upload-time = "2026-08-25T19:44:47.297Z" }, + { url = "https://files.pythonhosted.org/packages/1a/01/0127d11a762b31a9ee0221894f540318761783f3fdc4bc5d057698caebd5/cryptography-50.0.1-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:79bf008d1f9af6071c797ad133e39915dfee7614f18f18f4db9072eb715064a3", size = 4730023, upload-time = "2026-08-25T19:44:49.435Z" }, + { url = "https://files.pythonhosted.org/packages/9e/b9/e7425ebfb599241a0c1d7000f1b466c3062da66c19d9525031315dff7213/cryptography-50.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:330fbb252391c596f1ae42c5754449dc924e6ad012dca8efe0d703f9f2d12ec6", size = 4847362, upload-time = "2026-08-25T19:44:51.94Z" }, + { url = "https://files.pythonhosted.org/packages/2d/fd/60d0ddf4defa12e482c9d5e0f554384d6e8ab25341fd15f060028fd92e6a/cryptography-50.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:42be3bb70596b3abe4ac097b75be223e8b3ab614a0e5de068e3dcc54d71d6149", size = 4999247, upload-time = "2026-08-25T19:44:53.876Z" }, + { url = "https://files.pythonhosted.org/packages/4d/56/bc4f2b209e766c93372cfcd59b781a0b2b59700f62a969580415b699c2b2/cryptography-50.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:f74455bb086a85d5e81246412602aaa97ed095e504cd40dd261ef50be42205bf", size = 3825806, upload-time = "2026-08-25T19:44:56.209Z" }, + { url = "https://files.pythonhosted.org/packages/84/a9/ee16a903f13755e914d1eecc482fe64d1f10761c3960e5d8fa6837377aff/cryptography-50.0.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ca83d00d9e69cd5eb63f2e69c3a5a59e0cecae5ae14c6ae0b35830fe3b37bad0", size = 4035307, upload-time = "2026-08-25T19:44:58.305Z" }, + { url = "https://files.pythonhosted.org/packages/5e/a5/9ec7e81e8526c0d7a387d73386b2daed3f39e10d81a85930bd1b6bfba65c/cryptography-50.0.1-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:05ba322c4da95b262a212c345af888ef2c37c88c0509756ea00a0e6d68850f23", size = 4751900, upload-time = "2026-08-25T19:45:00.401Z" }, + { url = "https://files.pythonhosted.org/packages/7e/3c/0e77bd5ffcf078e9dd27d3074aad6c030d9b10d0bf69329d573c927a188c/cryptography-50.0.1-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e22dfed744bd4002e909464cb23d2f0b05c6f3113a79ef2e9864a53db737c733", size = 4738357, upload-time = "2026-08-25T19:45:02.786Z" }, + { url = "https://files.pythonhosted.org/packages/27/3a/3c5f80daa4dcd47323c7af8a2fcb90de27a33564d4fcac69846c0972691a/cryptography-50.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:4c4188f7c0cf655be5c06342b817ed0f9595b69ffa2b12026e5353eed29dea88", size = 4758474, upload-time = "2026-08-25T19:45:04.889Z" }, + { url = "https://files.pythonhosted.org/packages/6e/2b/214cf0cf93db9628c3c20c896b229f327f6fb1b20e4b3743d8ad3f00af8b/cryptography-50.0.1-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:2ebbfb0f1fed745e91796e3e1080a1440423fdae8ece1b995a1d80883a409054", size = 5375862, upload-time = "2026-08-25T19:45:07.163Z" }, + { url = "https://files.pythonhosted.org/packages/d6/51/3f9701867a46b6c1740c9b52fc4d3bed6cbdcfedcc9b6e64305c07f39cff/cryptography-50.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:407fe2b6db00939c05c0e945e9914238f2f0a430974839429dafc82b1ee6bee5", size = 4772942, upload-time = "2026-08-25T19:45:09.396Z" }, + { url = "https://files.pythonhosted.org/packages/0d/5c/13ea642e08e2544d0f5396122055f4820cfacb3203562197b5967125ea97/cryptography-50.0.1-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:2b34d76a652ea2b6faf777c35df230c5637842cd904e04f16230c3f9f03e4361", size = 4383347, upload-time = "2026-08-25T19:45:11.659Z" }, + { url = "https://files.pythonhosted.org/packages/84/d5/7d1fe1cb93f91c428093ff234e128c89ba8ea61a6f26aab406081f9b996e/cryptography-50.0.1-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:01f41478cf33fc605a6a089cd56d28b45c6c0b45a1928b61797f2621a04bac71", size = 4758050, upload-time = "2026-08-25T19:45:13.745Z" }, + { url = "https://files.pythonhosted.org/packages/dd/04/557fc5ead96a829e0bc812a3b9dc4a52a2f27e4f7f5950da7ff27653a805/cryptography-50.0.1-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:fc3ed7ebd2a8c96f5b166de0ab9b624996bef3b07bbeb19364dfb78222c22c80", size = 5332955, upload-time = "2026-08-25T19:45:16.193Z" }, + { url = "https://files.pythonhosted.org/packages/8c/eb/5d7124083e8d8cda8f5b348f544b71ad6f707ad63193758ef4d8e569da02/cryptography-50.0.1-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9dde0a357190eb3b1da1bb9ab750e9c85cba82ca5977aa0836cbb94e92611239", size = 4772694, upload-time = "2026-08-25T19:45:18.315Z" }, + { url = "https://files.pythonhosted.org/packages/63/8e/f1f955e0921dd2b6d22eae7e8d24a4c4b638d10735ffbf6a71f99eb0fcb8/cryptography-50.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd3718b960d0b5dd213cdf03f3bcb7000e69dda0de8b956061947ff6bcff5558", size = 4888413, upload-time = "2026-08-25T19:45:20.4Z" }, + { url = "https://files.pythonhosted.org/packages/1f/ab/89e2b798d2c3925f82e2bb72d5979f3d2f6da2dd22ef4a8cd8b70d920039/cryptography-50.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2a93d05e34d5f67fba6f891fe85d929999baa7195e853923ea6d7576c9e68c5e", size = 5044355, upload-time = "2026-08-25T19:45:22.353Z" }, + { url = "https://files.pythonhosted.org/packages/99/89/87ef49ffe383ef4e147d27b7bf2088fb0b54ea409dd87b5a89442e5828a5/cryptography-50.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:55d16b1ef3ee0958d893a977b19777887e546c9954ea81b200c3301a864013f2", size = 3875429, upload-time = "2026-08-25T19:45:24.418Z" }, +] + +[[package]] +name = "detect-installer" +version = "0.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/eb/77b0cc7fc0235b0495b32410edebe14cf6757b826135a7f92bb0f5d843b9/detect_installer-0.2.1.tar.gz", hash = "sha256:85f889d4d19c1caf5bef89ef389eb920cd8c6c2a868e1eeceec24527b93021e7", size = 3634, upload-time = "2026-09-02T14:14:38.961Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/fe/a245e1d579e7abf47208f7c1ce5684caa2cd956ede5ff646d66d0299d9e8/detect_installer-0.2.1-py3-none-any.whl", hash = "sha256:87e2ce7d05cb108b178f8e45bb80d3d88c0cb4b397983345c18ad6562e02e620", size = 5371, upload-time = "2026-09-02T14:14:38.106Z" }, +] + +[[package]] +name = "dnspython" +version = "2.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/57666417c0f90f08bcafa776861060426765fdb422eb10212086fb811d26/dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f", size = 368251, upload-time = "2025-09-07T18:58:00.022Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" }, +] + +[[package]] +name = "email-validator" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dnspython" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/22/900cb125c76b7aaa450ce02fd727f452243f2e91a61af068b40adba60ea9/email_validator-2.3.0.tar.gz", hash = "sha256:9fc05c37f2f6cf439ff414f8fc46d917929974a82244c20eb10231ba60c54426", size = 51238, upload-time = "2025-08-26T13:09:06.831Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl", hash = "sha256:80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4", size = 35604, upload-time = "2025-08-26T13:09:05.858Z" }, +] + +[[package]] +name = "fastapi" +version = "0.141.1" +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/8a/02/91e3416a8fdd715abb903a952a6bec7cdd8d14eed55d415fc8595524c319/fastapi-0.141.1.tar.gz", hash = "sha256:e8822fc40db1e1858054d7a949a888695bc9bdce70139178e33bd2871a453ca1", size = 425799, upload-time = "2026-07-29T17:18:05.568Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/03/10388a42375ee7e4ac9b94eb2c5c569c8b5795e377e701c9ac3ad63de890/fastapi-0.141.1-py3-none-any.whl", hash = "sha256:bfb91aa2d334c61cb35ba9a116fc123b3d3df31640b801cf57a7a78ec3f603b3", size = 131954, upload-time = "2026-07-29T17:18:04.364Z" }, +] + +[package.optional-dependencies] +standard = [ + { name = "email-validator" }, + { name = "fastapi-cli", extra = ["standard"] }, + { name = "fastar" }, + { name = "httpx" }, + { name = "jinja2" }, + { name = "pydantic-extra-types" }, + { name = "pydantic-settings" }, + { name = "python-multipart" }, + { name = "uvicorn", extra = ["standard"] }, +] + +[[package]] +name = "fastapi-cli" +version = "0.0.32" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "rich-toolkit" }, + { name = "typer" }, + { name = "uvicorn", extra = ["standard"] }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/eb/3b534c6f8e157f9ddbf2a153512307c886cad0b258739c200dd8ff8c4452/fastapi_cli-0.0.32.tar.gz", hash = "sha256:38024d2345275e1b37ce8848727a580d84901b570e96b3256d9d36a9a5039424", size = 26636, upload-time = "2026-07-16T12:16:58.678Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/53/56ae5ae17bb0a5d89d1d31e5320eb1865553ebbfbde91cdc4c221245f2a8/fastapi_cli-0.0.32-py3-none-any.whl", hash = "sha256:8dcc286fa32f01bbd3f65dd09cfd5a2540ed5f2230b77db7fd30978d6165f3c4", size = 14670, upload-time = "2026-07-16T12:16:57.297Z" }, +] + +[package.optional-dependencies] +standard = [ + { name = "fastapi-cloud-cli" }, + { name = "uvicorn", extra = ["standard"] }, +] + +[[package]] +name = "fastapi-cloud-cli" +version = "0.25.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "agent-detector" }, + { name = "detect-installer" }, + { name = "fastar" }, + { name = "httpx" }, + { name = "pydantic", extra = ["email"] }, + { name = "rich-toolkit" }, + { name = "rignore" }, + { name = "sentry-sdk" }, + { name = "typer" }, + { name = "uvicorn", extra = ["standard"] }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/60/a1cd8453204a8e9b04a4c9a6eec03d1a8947f1a837205db157509b9b273f/fastapi_cloud_cli-0.25.0.tar.gz", hash = "sha256:98f6fffc5059c536435eb2ec547add43061a53879a1ac2271001b9e5dca4ba9b", size = 121016, upload-time = "2026-09-02T16:09:07.403Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/f8/6626e21bf9764c09367ed9a558f6505207c61498b1dc2508bb055194c7b5/fastapi_cloud_cli-0.25.0-py3-none-any.whl", hash = "sha256:799868535a7a0716bd50bfb42dacc87aac570224e45052406840ff9244cc5841", size = 107254, upload-time = "2026-09-02T16:09:06.441Z" }, +] + +[[package]] +name = "fastapi-starter" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "a2a-sdk", extra = ["http-server"] }, + { name = "agent-framework-core" }, + { name = "agent-framework-foundry" }, + { name = "aiohttp" }, + { name = "azure-cosmos" }, + { name = "azure-identity" }, + { name = "azure-monitor-opentelemetry" }, + { name = "fastapi", extra = ["standard"] }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-otlp-proto-grpc" }, + { name = "opentelemetry-sdk" }, + { name = "opentelemetry-semantic-conventions-ai" }, + { name = "python-dotenv" }, +] + +[package.metadata] +requires-dist = [ + { name = "a2a-sdk", extras = ["http-server"], specifier = "==1.1.2" }, + { name = "agent-framework-core", specifier = "==1.17.0" }, + { name = "agent-framework-foundry", specifier = "==1.12.0" }, + { name = "aiohttp", specifier = "==3.14.3" }, + { name = "azure-cosmos", specifier = "==4.16.4" }, + { name = "azure-identity", specifier = "==1.25.3" }, + { name = "azure-monitor-opentelemetry", specifier = "==1.8.9" }, + { name = "fastapi", extras = ["standard"], specifier = "==0.141.1" }, + { name = "opentelemetry-api", specifier = "==1.43.0" }, + { name = "opentelemetry-exporter-otlp-proto-grpc", specifier = "==1.43.0" }, + { name = "opentelemetry-sdk", specifier = "==1.43.0" }, + { name = "opentelemetry-semantic-conventions-ai", specifier = "==0.5.1" }, + { name = "python-dotenv", specifier = "==1.2.3" }, +] + +[[package]] +name = "fastar" +version = "0.12.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/52/5bee9a672f418008d34c708d66e89e8f6fed8f0812f406a1c92fb5e393a8/fastar-0.12.0.tar.gz", hash = "sha256:bba71522eae6a7627a5514ffdd4ac9645ef27d82e23931d79fd974bb49c3f2ad", size = 89647, upload-time = "2026-08-20T09:11:25.212Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/ee/bfce95bdf2bd61a1e311c7181e0ff99c39a6eebe4ca2bfd2d04eb403970b/fastar-0.12.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:654165090cdcac7ff13d43ee4012c366f0f2061ddf46658bc0ad248c8aa3960a", size = 697138, upload-time = "2026-08-20T09:09:53.682Z" }, + { url = "https://files.pythonhosted.org/packages/87/b0/dd24d87b58b4e99257ff0b0a53b60c89b0a32e89bd33c1f966e9bda58ad3/fastar-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:685d3d45943b43c32c71c8470552a615c90e06ca532db1b7a5633f01aa108f0d", size = 623873, upload-time = "2026-08-20T09:09:36.361Z" }, + { url = "https://files.pythonhosted.org/packages/5a/00/abacbfa2e94c1ff4b717e07ae2e5521ba84804884ff51f18f01706d024cf/fastar-0.12.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f8da75b5eca0d3b540e50ab6b7dfe4b148d7ae8a0b444a9781fd1219392859a0", size = 855719, upload-time = "2026-08-20T09:09:01.612Z" }, + { url = "https://files.pythonhosted.org/packages/8a/27/2781690ebabbce0d2a25b9efd359a13a5df2b0098120167e58b013c9f65a/fastar-0.12.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13c2df8db1b7a4d783429ffa1125c53f7dd9534baae7218eb49273797691e2a8", size = 754159, upload-time = "2026-08-20T09:07:31.365Z" }, + { url = "https://files.pythonhosted.org/packages/1b/9b/1e6262fe31b2e8efc90feacf3f2213c9d2dff5fcb6e88f9084f26f18d62e/fastar-0.12.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:042fd43c4e0c3f3ba3f7b8a083694d1c4bd77d60ce266090d4eb96cb8a8021c2", size = 748002, upload-time = "2026-08-20T09:07:48.724Z" }, + { url = "https://files.pythonhosted.org/packages/4f/8e/b4792568d3e544b4e2b00b918744e4edb7e8c6be3c4fce268514febeb844/fastar-0.12.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6958a332c55052dd8090b03de238ca59190d625f4ba9c292b34e938ac64105ea", size = 915919, upload-time = "2026-08-20T09:08:07.102Z" }, + { url = "https://files.pythonhosted.org/packages/03/3f/0460223969f5dae9a49d09a29a93d5ccc359e701b0a18c0972a0ebce29b1/fastar-0.12.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6b05c0c739c43b9228e57fc59eb68c38660c62030bcdea3a032b269df71f5bb8", size = 807788, upload-time = "2026-08-20T09:08:42.967Z" }, + { url = "https://files.pythonhosted.org/packages/0c/7b/68e681a12232ca2dc46d7d6de0c0fbe770137a204e4a3c0b5864a9548d67/fastar-0.12.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2bfad69679111e4567d4bad41fd795071c9335cd94bc0f26e24b7d19e95c9b1", size = 807587, upload-time = "2026-08-20T09:09:19.304Z" }, + { url = "https://files.pythonhosted.org/packages/81/8f/f93e981114034eb901d301690ad8e21edca6a2307b380d5e20e4e4862c7d/fastar-0.12.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:1155e1dd9c60cf636b6b3d35edfe242348f47286724fa84b5b4055c03d7fdbf6", size = 874095, upload-time = "2026-08-20T09:08:25.707Z" }, + { url = "https://files.pythonhosted.org/packages/2c/8e/74671e6cc6d9056347806ed9eefd0ea0bda25292848b32cc8cc321f956d8/fastar-0.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:29b87474b2e7c9e64549b87aeb2c1d68a94e78c887a3a8d88bf7b804ddbcc0dc", size = 965017, upload-time = "2026-08-20T09:10:12.048Z" }, + { url = "https://files.pythonhosted.org/packages/7f/55/44d5c532bfdbff48f3f6ce957744a612f7a169af9e4a673f00e21f8c83ed/fastar-0.12.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:4587a08d6de2e62611278fc4cd36186a3ebbf6609d9e49df34d79a407d66f599", size = 1024377, upload-time = "2026-08-20T09:10:31.936Z" }, + { url = "https://files.pythonhosted.org/packages/a8/35/73d04733a06175211fef985f4e89604882b849cf1125c4a792ef022aa85c/fastar-0.12.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e7d7512b5c747edfce129448a72c8f6223323748ca3e98cc54401241bff70ee5", size = 1061994, upload-time = "2026-08-20T09:10:52.014Z" }, + { url = "https://files.pythonhosted.org/packages/f5/d2/c841b941fad02b5fc6e2277fc8ca35d963685fafc9335b76925bd166d11a/fastar-0.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ce9a7e7757074a3d0920a8bc3936cf4164f63786d8e1b64e425d2957ddcceace", size = 1018409, upload-time = "2026-08-20T09:11:11.495Z" }, + { url = "https://files.pythonhosted.org/packages/8d/c8/59a1791df4f128e6f8fffa03fd46d9b6c8ca24bcb6b7e9071973648edab3/fastar-0.12.0-cp313-cp313-win32.whl", hash = "sha256:61f1eec258b328182c6b6258641d33264ac3080fb7fcbf40ea1e326fc855d917", size = 450302, upload-time = "2026-08-20T09:12:00.704Z" }, + { url = "https://files.pythonhosted.org/packages/a7/9e/483982c1e60e3d9332c3b870230b34404b5b44340d647a97fe8f91d4fa0f/fastar-0.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:84caa362865cac75807c51afbeff2e9b313fc45f89e0865f7c8bf627ea721f4b", size = 482104, upload-time = "2026-08-20T09:11:43.655Z" }, + { url = "https://files.pythonhosted.org/packages/1e/e6/ec8ab1d44d73c0cad4f9e0ae4ce7b9330f2d504f835774802845dd465a42/fastar-0.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:a3de985d942247fa924e185ff2744a6da0005dcedbb39ae1b811bec11710e572", size = 458292, upload-time = "2026-08-20T09:11:29.693Z" }, + { url = "https://files.pythonhosted.org/packages/cf/f9/cf4b63a3b8bfa7dba8de8db364246d9e8390108a789de3f6a9628bb75c8a/fastar-0.12.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:7116a770a4e47262734fafe06d3e835a23b833e81b45b3054558301385a0f2a7", size = 698042, upload-time = "2026-08-20T09:09:55.257Z" }, + { url = "https://files.pythonhosted.org/packages/be/3f/c0ef2beffeeb01f9ec27b0ba8ce23697919b2247316d923f5a3bed6fc26b/fastar-0.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ebe324ffcb3e8efb2308255f60de911bfa4ccf10f087b6e60c62606f091f1807", size = 626452, upload-time = "2026-08-20T09:09:37.853Z" }, + { url = "https://files.pythonhosted.org/packages/e5/32/87c1887bcbe311a913a2b5b2f4481cce9bae7dd05115575c9f6cbbc892f5/fastar-0.12.0-cp314-cp314-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4af6be78f7ec6ef8e6da7d162361e4198be10fe81bfc95112f635c2c14e12922", size = 856780, upload-time = "2026-08-20T09:09:03.181Z" }, + { url = "https://files.pythonhosted.org/packages/89/d3/d4299d3c73df485d2bf095cca7e3829036f6a3fce26728ccfe4c3db3798b/fastar-0.12.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad38b27e93ba9c7de076ad694d224153bf8d66695acc27a114cba8087078fd54", size = 755380, upload-time = "2026-08-20T09:07:32.822Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ff/768ec3c6898fb20710ab222b8bc7caedcb9765a4c78122bb0267373f8255/fastar-0.12.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:968d64c203d10d257c2f2268cfc97d94f273fdfc64b37a4739fdb6e2cf2c3f03", size = 748975, upload-time = "2026-08-20T09:07:50.575Z" }, + { url = "https://files.pythonhosted.org/packages/36/7c/0ea040fdd20fe90c39e008f5c8b54b54058afb78faf261484c67b4ea5e94/fastar-0.12.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21256ccb3946730e3d601b6a9c9de61a127855957a7f0852cb14b0dbe15f8aeb", size = 917534, upload-time = "2026-08-20T09:08:08.782Z" }, + { url = "https://files.pythonhosted.org/packages/ce/5d/5a46751dff921b344ce995ee364801a867d80331406a96215336140182ed/fastar-0.12.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d89116b102fb7d4c47b6b723dc32d5c12122e7ffeb41b7a8b22cc777c9eca5", size = 808943, upload-time = "2026-08-20T09:08:44.785Z" }, + { url = "https://files.pythonhosted.org/packages/f3/67/6336def57f2b4701f93289a999a42de7743dfb78320a6b8931e9c0472da4/fastar-0.12.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1450e0325897e87594c29837fdd9e338b1d281f6c81d14d7a75b6776285b5b6e", size = 809624, upload-time = "2026-08-20T09:09:20.899Z" }, + { url = "https://files.pythonhosted.org/packages/6e/75/bd2540d70c6e4deeae3693cd41725dc42d4a96ced0394a9f845ac6afe148/fastar-0.12.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:ca63fee43f07408efec09e1c0ae34a1b29ae52b8c1adc31bd6434ccc9e1741cb", size = 874787, upload-time = "2026-08-20T09:08:27.198Z" }, + { url = "https://files.pythonhosted.org/packages/6e/5c/13d20ec4d2c1e5ee15bf3b01fa43b282ff93f481d162e5ab507d8a341fbf/fastar-0.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:3fa5fd057b4f4537f04dd4e4f13be92433bf47bc479309335755531c5af34560", size = 966646, upload-time = "2026-08-20T09:10:13.697Z" }, + { url = "https://files.pythonhosted.org/packages/4a/6e/fcecbb90c6784ca0d5a54a9c3f7edf78ae5393c71b8dd0088130c3a4c82a/fastar-0.12.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:4b395c3d4375809d0d55b5ae297f6bc037b90b382f35f056e453732e4f6f523c", size = 1025855, upload-time = "2026-08-20T09:10:33.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/8f/e865bf29f54c93a6fee55c7248ae64de91e0c68c6ce7f9a6d8c4adff471d/fastar-0.12.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:20d5e52c45e75a55ed27e7952487506269e9a64559d6cbfaf6977529db81298f", size = 1063126, upload-time = "2026-08-20T09:10:53.804Z" }, + { url = "https://files.pythonhosted.org/packages/42/19/4010752bcd7f7f290476fd4eec5f9611324b78a00575ad7224b4375fa124/fastar-0.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4497098ebdf3c1f89dbeafe44ebcd7f143f8a774641bbfa13d1d793104464c41", size = 1019854, upload-time = "2026-08-20T09:11:13.219Z" }, + { url = "https://files.pythonhosted.org/packages/ad/fe/9dc92104a021396d2cef2d7d3a8a8db2f3409bc19f41a7e8370642e980f8/fastar-0.12.0-cp314-cp314-win32.whl", hash = "sha256:56cb3b3c46edf26f054f2420573c7c69c03997203a7a867ca835bc305b4a0f30", size = 451451, upload-time = "2026-08-20T09:12:02.371Z" }, + { url = "https://files.pythonhosted.org/packages/02/a7/b2f55b50aa446958225360a20a16a87a4717c2deeda4ebc85edc62b58f64/fastar-0.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:81534df96e775ccaa37fcd1f45e06f48c245ff77e30d3bcb0d3c1101da9399e8", size = 483614, upload-time = "2026-08-20T09:11:45.439Z" }, + { url = "https://files.pythonhosted.org/packages/ae/0b/eb965694e157e09aa92857db73dd46d7cb10c24dae261a50dc46e0d361e2/fastar-0.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:fce60bd91fd982bf52e9a4c87820a44f92ac0d896bd64544891d6995fa6b8b98", size = 459422, upload-time = "2026-08-20T09:11:31.659Z" }, + { url = "https://files.pythonhosted.org/packages/31/21/58b9e84b20c50d8cbd31d25aa99ce852a463e60c8f88b3a44bfcdecb46da/fastar-0.12.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:22c882f1096199d5e63f3ea4d9313e10799fcbc70166d315aa07576148601326", size = 696156, upload-time = "2026-08-20T09:09:56.953Z" }, + { url = "https://files.pythonhosted.org/packages/05/84/227ad56548f2de419a5fb948cedc866ae0aec6fa0cf057b50e96e50aebb8/fastar-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:74c928183d7ca19056bc0eb24d8c1907c115cfb2382ad4a7c32ccbfb04ca0a0b", size = 624449, upload-time = "2026-08-20T09:09:39.39Z" }, + { url = "https://files.pythonhosted.org/packages/c7/51/8b05253149a568bc62b9a2d774f26a4b963dd91dae3da4e843a2cb7c5b40/fastar-0.12.0-cp314-cp314t-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f1f3e70447f45e54b488eea8bd401cd3774b7b688d5b507915ae415058d8ac86", size = 855891, upload-time = "2026-08-20T09:09:04.968Z" }, + { url = "https://files.pythonhosted.org/packages/02/77/e62ef58301d2d79d624fc17fdaaee61c1a827d5f4000a8482be6a20bd10c/fastar-0.12.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96ae27bbb807e39e05097bdc2a6cba6820f31dd053aaf8cf0a045e5969041778", size = 754529, upload-time = "2026-08-20T09:07:34.489Z" }, + { url = "https://files.pythonhosted.org/packages/af/87/c35f3c3effae445d1b51b56ad6629df6d2001aeb5051646d081904be2081/fastar-0.12.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f0d60682ed24fc6063b18e76a947f1cc0fcd0777709357b801b3e3458a87c2e5", size = 747923, upload-time = "2026-08-20T09:07:52.301Z" }, + { url = "https://files.pythonhosted.org/packages/3e/85/e9556fbaa8183db72c6d3cffdb56384299b2bd4d7a5e0eb990401ea221a0/fastar-0.12.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:644f6d06e312bc47dd315aa36fba723854859601e184113de7b887613ed9a5ad", size = 917040, upload-time = "2026-08-20T09:08:10.474Z" }, + { url = "https://files.pythonhosted.org/packages/53/8f/9a53202c1dcb3a5cd5661c04c201bff358919f23f6506e73533411cd96d4/fastar-0.12.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea6c5addb01f206bd75f784540a8a11bbddc451293318a823e3fecaf0d62cc3c", size = 808402, upload-time = "2026-08-20T09:08:46.871Z" }, + { url = "https://files.pythonhosted.org/packages/07/aa/6a083aa6f7089f5ee7cfa99f15f299658953074035a6b7ed4dd52e21b8fe/fastar-0.12.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc892486eb242ac55dde185d547d2723289da50c6cdc06614868416576ba5c4f", size = 809412, upload-time = "2026-08-20T09:09:22.5Z" }, + { url = "https://files.pythonhosted.org/packages/d4/3e/1c45d7da2e67134f161c25f733ba231df67fdcdd018ceb9aecc50feb01d6/fastar-0.12.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:a47a68d1b9bd59062af41d9809a340739ab1ba13cb5b4beb23466a621d6479f2", size = 874673, upload-time = "2026-08-20T09:08:28.874Z" }, + { url = "https://files.pythonhosted.org/packages/22/7f/c56ddd4e7c9035170b4513c2cd24673a4ce7a2a6b6cb9a18970672e14455/fastar-0.12.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a7d9fd762e7eab2262ec004aa9c714b53d303e11cca7c814b7b634d9d2424691", size = 965952, upload-time = "2026-08-20T09:10:15.447Z" }, + { url = "https://files.pythonhosted.org/packages/f8/5a/329cca10ae74a8b40791dd59868276e66d9e9321d1c1c4f1284e6c864f9d/fastar-0.12.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:3766bf0aeeb6a03d114b185472593a2a3f0ff43f1b56c40fe0cda4283f9f4351", size = 1024392, upload-time = "2026-08-20T09:10:35.46Z" }, + { url = "https://files.pythonhosted.org/packages/42/ec/3c6bda956cd88d22ed9cb3fdc15e43a6fccf58ac64b8856f5aebd23117c8/fastar-0.12.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:860516a52a3bdcaa746be42fc1bdbbbd48e05c7579f9f61e30d78a7e065835bd", size = 1062506, upload-time = "2026-08-20T09:10:55.553Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e1/bfb903579672ae213f5c81a5d289e3b9ca92ff2b3f789b08577de5f51050/fastar-0.12.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:eb75898c166ff6d232bceb68a5a7dc4a8f17239fb40925d169649a3967b76c4a", size = 1019468, upload-time = "2026-08-20T09:11:14.932Z" }, + { url = "https://files.pythonhosted.org/packages/61/46/c0f6a5b18981425879938fdf8ae38dfe7ef031d7164cb5b40eec29e5a4a3/fastar-0.12.0-cp314-cp314t-win32.whl", hash = "sha256:c5755332572756061b29766a9ceff2c837d52d96828e58f908cb46cc49123bd7", size = 449931, upload-time = "2026-08-20T09:12:08.865Z" }, + { url = "https://files.pythonhosted.org/packages/d9/8f/832c7ca0d28642d5869a1aa194e4fb644bae2013101a1e9357168e6e1383/fastar-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:07d861c7ddf31bccb9615a0ba4c9f06987d1373a6b357183526d68cf9c3f5552", size = 482560, upload-time = "2026-08-20T09:11:52.086Z" }, + { url = "https://files.pythonhosted.org/packages/ad/10/17b9b24e129dcf3f4b4eb57b148888b128930180e9babd0e166cc828b2b6/fastar-0.12.0-cp314-cp314t-win_arm64.whl", hash = "sha256:b1d56e2a52bebd3e379d0cacc2b018b819a0b99f0dc19b4453f304c4e2fce5b3", size = 458209, upload-time = "2026-08-20T09:11:35.086Z" }, + { url = "https://files.pythonhosted.org/packages/0a/b7/21ec24e28f98554f727ba78a8703009d44ddc8946a91715743e574ed3f01/fastar-0.12.0-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:6109ec55528a975ab3644dc1c9ccccb2c2315daa66ca34f54e1e3dca60afa757", size = 698704, upload-time = "2026-08-20T09:09:58.576Z" }, + { url = "https://files.pythonhosted.org/packages/a7/f2/9e570204757c36d3ef1c94012294b1921a4c51af55da4684f206d88c09df/fastar-0.12.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:6e94e2881c3aa53da5d9161e2e64c0d698c66506a62024d5900943098220ebc7", size = 626785, upload-time = "2026-08-20T09:09:40.933Z" }, + { url = "https://files.pythonhosted.org/packages/c4/d1/dfaf600497cdd4ddf3942c188d27171689f370d6ea2156b7646c606be8a0/fastar-0.12.0-cp315-cp315-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:682c531ac174d63919374eaab6fca91f92432cbf6a2262acb72719ba2e2a694d", size = 857172, upload-time = "2026-08-20T09:09:06.417Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d8/551b13387ae7ce88f165e7201757f031309a0cc9226663245096fba40554/fastar-0.12.0-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cbf3d5bfd73516b506a916f6809b90a4ad73ff5840c0af6cbe0142417a03b014", size = 755384, upload-time = "2026-08-20T09:07:36.044Z" }, + { url = "https://files.pythonhosted.org/packages/0e/cb/b8f2f3572e0ec95ca9f0eaf6889e6d7da65782461b5a983379f833d6a453/fastar-0.12.0-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fbeb5fce858248d6b9b1fbef12c3e14d174432728c6e6eb1e2a63447432571c2", size = 750165, upload-time = "2026-08-20T09:07:53.779Z" }, + { url = "https://files.pythonhosted.org/packages/34/66/71d24540a462eb5dbff8993dcf674844ae8355b8f5ea63195c10207c10a0/fastar-0.12.0-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45164f7138613b76918101ea28842cb69c10ff441c1ba2d56d7c6b28053f28e2", size = 916891, upload-time = "2026-08-20T09:08:12.202Z" }, + { url = "https://files.pythonhosted.org/packages/84/b6/fc72480b7771ee14541b04ece4f94327a6522883ca51ebf46d9112723486/fastar-0.12.0-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e71715c64695bc80fd8fed2a82af30acb49b6bf085c06876cbfc2116b53cc7c", size = 808828, upload-time = "2026-08-20T09:08:48.413Z" }, + { url = "https://files.pythonhosted.org/packages/88/1e/0cb98e45845e442cb04b4dcc5d3a149184b188ec41425960fec62ee140b9/fastar-0.12.0-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8056fb0f3ff2213eb00234d32b2d701cd288b763d3430b67033a393b8d33b47", size = 809399, upload-time = "2026-08-20T09:09:23.995Z" }, + { url = "https://files.pythonhosted.org/packages/9b/00/e568dfd06fbb14a70f165a9972672dc21418edbf25590229e8fb2176cd19/fastar-0.12.0-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:a843704912dc3b20e152743bd5fa3e225bf9cc23c34fea0debeceefead477e78", size = 874588, upload-time = "2026-08-20T09:08:30.455Z" }, + { url = "https://files.pythonhosted.org/packages/6e/8a/83ebe531a4a6fc93511c719ce8e505a2b23711e9ac4bed627cd7950eeb60/fastar-0.12.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:b4a7b6ca4a04e269aa26533ca8bfd0c674e4ee7328b0d3d80d45ab979a7e613c", size = 966301, upload-time = "2026-08-20T09:10:17.137Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7c/8c37e19cc9248b35d1b13cb6d27828755e2b23be9dd06437b77b572c0a01/fastar-0.12.0-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:883f1e06c0d9649a2e54b767b3384b47306098ea73be3ca288d562c4d73dbcaa", size = 1026601, upload-time = "2026-08-20T09:10:37.159Z" }, + { url = "https://files.pythonhosted.org/packages/de/ac/4fb738d3ab7ede5545ebf8205beae5c9e3fb97088b68b2c2103357349efd/fastar-0.12.0-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:74858c4648bdc4450a66f3d6ec16a4e61ba48c16c9898a88a061d3272f82c65b", size = 1063319, upload-time = "2026-08-20T09:10:57.318Z" }, + { url = "https://files.pythonhosted.org/packages/e3/8e/c31c84446226f20a217bad3921528bd8214b714ab1aa634afb183c1d852d/fastar-0.12.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:b16451d5b50579e4eb7dc1761946bc6f6186df44fa84310a06f455c26eb4442e", size = 1019807, upload-time = "2026-08-20T09:11:16.731Z" }, + { url = "https://files.pythonhosted.org/packages/02/46/a44dee8cbc14601a91fc1818ea3e172cd5db2cd9af55f29c866bcee4c22d/fastar-0.12.0-cp315-cp315-win32.whl", hash = "sha256:ad8185a7b379e5cd81ef65209d21db4c63e8a62bcaa2d17fe40a8e50fcb28427", size = 451911, upload-time = "2026-08-20T09:12:04.003Z" }, + { url = "https://files.pythonhosted.org/packages/4a/e2/1feebd0c4e7ea7839f250a4ab77663d3a6149124a5ff6ee01859d788b6a3/fastar-0.12.0-cp315-cp315-win_amd64.whl", hash = "sha256:a2819b9061cee89da560156b77230d4ffe81e75b8f64b9732ca34d7bc546e49e", size = 483441, upload-time = "2026-08-20T09:11:47.052Z" }, + { url = "https://files.pythonhosted.org/packages/fc/32/a2eaddc9b4f63d1560d4467df586d427da52717e01e88d9a0202439c07c9/fastar-0.12.0-cp315-cp315-win_arm64.whl", hash = "sha256:a8a8130f236a5dc2ceab88486f77bbdd516d08dc949d0f04194305845cf44c19", size = 459131, upload-time = "2026-08-20T09:11:33.448Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7f/cffe7bae35e9e80789396f03cae4e8c4c761c3bee6003d2e622f6c9d8d6b/fastar-0.12.0-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:6f25c1aa6d55a457d95dc2163bbc27942e1541ca6792d4bf323a922688b8597e", size = 696600, upload-time = "2026-08-20T09:10:00.071Z" }, + { url = "https://files.pythonhosted.org/packages/ab/75/5e28ef81c3fac04a8d04a1068399069e28e4dbc6df221254c3a75b43af4d/fastar-0.12.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:aeb69fe64537deec4902f45ad9634b85d44ebb42ee1a33725d6584e8d9b33927", size = 625371, upload-time = "2026-08-20T09:09:42.547Z" }, + { url = "https://files.pythonhosted.org/packages/ad/aa/234c34a70d5e9e30420f43a9bad7a0046593ce5d65c2452f147f0a65e86a/fastar-0.12.0-cp315-cp315t-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6a83ae278bcc718dd155219fbbd552a16bd8c178effc5021600c3be2806a01cf", size = 856582, upload-time = "2026-08-20T09:09:08.082Z" }, + { url = "https://files.pythonhosted.org/packages/79/99/bd327da80d86309f6f5806cb3af70f91473d26fafe53f7cb646aaf35a4e5/fastar-0.12.0-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c048c732e3ca28a132732f83130ccdab58d9b27dd36bb26bdeb42c2d48827da9", size = 754890, upload-time = "2026-08-20T09:07:37.76Z" }, + { url = "https://files.pythonhosted.org/packages/fa/01/a72bf87d4a26ef1bc94fde3b4c37c97500c1db5e2431bbfba865fabd8eb2/fastar-0.12.0-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:613466f628667af03de8f914de58a07bfb3ee1bd0347e3532ec9790df92a1e72", size = 748828, upload-time = "2026-08-20T09:07:55.27Z" }, + { url = "https://files.pythonhosted.org/packages/fb/63/2880890777d680271115ff6b2e8b891069610463308e7779e0fa9c8d9e75/fastar-0.12.0-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f59d3243d8913db385ab822be8f111f188218ea73f9f14f5d70c869a33ff4d1", size = 917262, upload-time = "2026-08-20T09:08:14.243Z" }, + { url = "https://files.pythonhosted.org/packages/08/06/b3293a2a8bf7bac81e848fcc1c67411a0770b186a3bfe5232ea6dac5e929/fastar-0.12.0-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b362e5404dab262e85f0d93bd950933a0935dac6a9f5f0516bba6c703c440ec", size = 808819, upload-time = "2026-08-20T09:08:49.914Z" }, + { url = "https://files.pythonhosted.org/packages/45/a4/0c0e9c1bc422272df07414d137d25eeb4bec8b3a8966c8e2b179967390fe/fastar-0.12.0-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d1e50c423cd064f29f11c98f6d995b8fa7df7bbc19f3fdb9f081859afd8e00b", size = 809620, upload-time = "2026-08-20T09:09:25.495Z" }, + { url = "https://files.pythonhosted.org/packages/18/34/f10ca8db20176ee9e685da80fcdad2c79e66485ed025ee16881e8113d97f/fastar-0.12.0-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:6857a79691c5c033a31d76c62ad02f2c92d173a0e1fb2fac7fcb7ac686108bd3", size = 874739, upload-time = "2026-08-20T09:08:31.946Z" }, + { url = "https://files.pythonhosted.org/packages/fd/a8/547881f35496d5b5b64f3e29552475d92d55d7bc213244bd7fb59938a116/fastar-0.12.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:3053bb800b5375fbca8f96d256654ae3489c439f1b55766896d1c703d8281804", size = 965821, upload-time = "2026-08-20T09:10:18.83Z" }, + { url = "https://files.pythonhosted.org/packages/3d/57/a4376b6e70e6ca8909b8788b6e0ccc55cb9f0ada4c2bf7f95490d06659fe/fastar-0.12.0-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:067094312cbea82ef2efa3999dc31318ac539805965c9505d99654f01775cac1", size = 1025655, upload-time = "2026-08-20T09:10:38.892Z" }, + { url = "https://files.pythonhosted.org/packages/9a/ab/48e90600f5c08e7869c8cbd346cb9cf846414f85ca1f9e7aa6976222c6b5/fastar-0.12.0-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:f69400ebb83a8d754aa7735165c12f8029ac577c3c08eb6d174eedc5901b7cb2", size = 1063028, upload-time = "2026-08-20T09:10:58.989Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/e113f8aebfd874d3c78945a094fc803ca295b92e12dc0d80ec81e2a7701f/fastar-0.12.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:7d875d99946a11538f7ecb183f0a885d1d0a0495a6f1d6d2aa1de9b5fe6e5e9d", size = 1019546, upload-time = "2026-08-20T09:11:18.523Z" }, + { url = "https://files.pythonhosted.org/packages/51/45/72c2bf5ae3386407009fe51c40e548e41f70181bbe34a6477bb6078be047/fastar-0.12.0-cp315-cp315t-win32.whl", hash = "sha256:39dad3351f1399cd28e2e649b6651299ef857df6c745e598b00b4167dcf93dbc", size = 449932, upload-time = "2026-08-20T09:12:10.589Z" }, + { url = "https://files.pythonhosted.org/packages/7f/74/0adeb47b838c62ebd78bdf11ee2255e6e205ea176f9147978efa022ef855/fastar-0.12.0-cp315-cp315t-win_amd64.whl", hash = "sha256:00cda9a3f11871261a4e77a3b8f0eede85c9730fb7516811bcbcf96a2bb3b75b", size = 482858, upload-time = "2026-08-20T09:11:53.714Z" }, + { url = "https://files.pythonhosted.org/packages/bf/b9/b2ce5a79c57150d36aa8e0514dd65999ea76f091242aba93c403cd20e491/fastar-0.12.0-cp315-cp315t-win_arm64.whl", hash = "sha256:e8e0fb057b5c271f46f3300b539b0d3dab8c8cb2515205a37c818c2f68d16806", size = 458287, upload-time = "2026-08-20T09:11:36.952Z" }, +] + +[[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/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 = "google-api-core" +version = "2.34.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/7b/7c/9be3903e3d45415e8ca493c75f8990a0f6f579d168015d44c379350d0ab0/google_api_core-2.34.0.tar.gz", hash = "sha256:98a779fe72de956eb1c9c2f47ff4c4432a668ece1a002ec38bed07ec2698ae59", size = 187953, upload-time = "2026-08-06T06:23:58.128Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/c1/a8a92ae1bc4b1a8f804c776d7d3f0c771b78a62c3ad4df1be41b3fd8c767/google_api_core-2.34.0-py3-none-any.whl", hash = "sha256:cdf9c67e7ca2402d86ccbfde5f2503fc83e3cc3f58cc78456ae96cad24a6d2de", size = 180545, upload-time = "2026-08-06T06:22:47.502Z" }, +] + +[[package]] +name = "google-auth" +version = "2.57.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "pyasn1-modules" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/53/3a/d3982b28d267880b5641f9a32c55d8062a9d2bad2d28d0274285391c89c2/google_auth-2.57.1.tar.gz", hash = "sha256:eb47b230fc6707eed4aee1c9cef55ec05bc1785eecba74ff8b572d531e921b1e", size = 372426, upload-time = "2026-09-04T00:50:27.593Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/27/0f7247b8002a1404fdb5412aeb15fb0fe70288eb8f88a5873d1c0d8262cf/google_auth-2.57.1-py3-none-any.whl", hash = "sha256:ab439dee60a6856412bc058f13a68eb6a59c5b81526bb89cfdcf89ce9c0a48c9", size = 259974, upload-time = "2026-09-04T00:50:21.693Z" }, +] + +[[package]] +name = "googleapis-common-protos" +version = "1.75.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/c5/4353a188e2c335aee33269e8b654af228278cca8e5f0b4b5f11e5d0e9adb/googleapis_common_protos-1.75.3.tar.gz", hash = "sha256:57c435ac2c68b108999b6db075d9053e4d7a936ba57b4a3d45667b1346f1738a", size = 153905, upload-time = "2026-09-03T22:31:21.869Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/7a/7d79170c6ce6f12e109df2b3879d6b934010cf4f99aea8de8b7e5408c174/googleapis_common_protos-1.75.3-py3-none-any.whl", hash = "sha256:a018d2bf098ca9fb6faa08d5bb780e2a2c2f73c566f069761331386c9596d3f2", size = 306984, upload-time = "2026-09-03T22:30:45.133Z" }, +] + +[[package]] +name = "grpcio" +version = "1.83.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e2/b1/46539f5050d7c316a13396d185451f95084a74ddc68b12d818595bef0377/grpcio-1.83.1.tar.gz", hash = "sha256:9cee6fcbf2eb57c4b49451787bfa87be8efc1ca02a0b327dd4b54d44502e362b", size = 13445033, upload-time = "2026-08-28T07:09:11.464Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/fd/d1fc58933bf88c9209f89dc570c810f1aa57cb04b3459cf2b26f61e32112/grpcio-1.83.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:8d228e253b77865efcbdd7b5894ca882c9e0ea98c02b7d20582e61ded8dfd4b5", size = 6305628, upload-time = "2026-08-28T07:08:27.872Z" }, + { url = "https://files.pythonhosted.org/packages/c4/49/0b40bae059c619505c9b751cee6caa208e4904e290aaefa1728c4c2c67a5/grpcio-1.83.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:0468b627f2987c9a77f7580030207cbd85457ffe52998beff4f0b5c38c58a72c", size = 12156839, upload-time = "2026-08-28T07:08:30.191Z" }, + { url = "https://files.pythonhosted.org/packages/61/4b/e8c0d635da0ee5ddd9950c8d540f5dcdd0ef1854a382cc55496a487a8d31/grpcio-1.83.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a6a282e81530cead60bbd752cc04950a57f224379e9821495d6a35bd5ce9b1f4", size = 6877036, upload-time = "2026-08-28T07:08:32.285Z" }, + { url = "https://files.pythonhosted.org/packages/c5/d4/760a33f339a7dd3d5f4b3e0e9bec5472d95592a80f887b2e9dab4e41cfbc/grpcio-1.83.1-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:947d945f52e8ecf3cafd2bb7113502a16ccfda3e12c854443094de32d83ad432", size = 7624404, upload-time = "2026-08-28T07:08:34.194Z" }, + { url = "https://files.pythonhosted.org/packages/54/ec/bd798654b06fb42a92b57d1dc1b530084fa89ed442806fcd0a833a36f9b3/grpcio-1.83.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:55656318d5dd387077396dffb929171ca3966e24bfead9a6c5dba9f889062cb4", size = 7042942, upload-time = "2026-08-28T07:08:36.208Z" }, + { url = "https://files.pythonhosted.org/packages/08/b0/c00f86614566dd0961825cf0f43d4f96a74371d9d95f952bcbc4b86d9a27/grpcio-1.83.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9daf5acf4fc9d5f5627229969c2580a91e511779d76e4ccdeb9f4770f05d8bc2", size = 7576937, upload-time = "2026-08-28T07:08:38.041Z" }, + { url = "https://files.pythonhosted.org/packages/b1/38/85eff43a5c89dc666a252b5c9f8e9ab03f89e11c95b6263d2933f08fdbe7/grpcio-1.83.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7b94174cbca93316888f805efbeb08f1c020f7b7493d2d50cc4f6b64ebb7e8bd", size = 8608391, upload-time = "2026-08-28T07:08:40.092Z" }, + { url = "https://files.pythonhosted.org/packages/35/4e/82835483e2f812494be865e7965c0d626cb9e71ab0d83a420d75aea4ad67/grpcio-1.83.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:65c5a7210911ffe0f67b1cdc5308f9854b6d1f1b345e3e49ab7cac1ba50fa346", size = 7980060, upload-time = "2026-08-28T07:08:42.434Z" }, + { url = "https://files.pythonhosted.org/packages/b2/b7/68a98bef733fef704fbcfb3957c8dba67e3e38ca7a7fea851195bc97c648/grpcio-1.83.1-cp313-cp313-win32.whl", hash = "sha256:179368d9361854616ce6f397d4716e07480129652752fcbcfc5a7260455ad6f2", size = 4395226, upload-time = "2026-08-28T07:08:44.463Z" }, + { url = "https://files.pythonhosted.org/packages/85/a0/df4de3b51d37ac8fb0320bb9668381ce2bd3b7aa990880bfc56a8a26f665/grpcio-1.83.1-cp313-cp313-win_amd64.whl", hash = "sha256:2e57af456385491a76e13c4aada8c8f43a8e47051e06ea97a9dbe2a49654e6db", size = 5160273, upload-time = "2026-08-28T07:08:46.216Z" }, + { url = "https://files.pythonhosted.org/packages/42/9c/484d981d8b90c4e6abf3030bd2ed747e84d1eb192b3ec9cbb41e0b73e4bf/grpcio-1.83.1-cp314-cp314-linux_armv7l.whl", hash = "sha256:8b3c87ca908296bf125f841d3e1a2225a2b39aaa8ed7a57e7ccde465ee519bab", size = 6306089, upload-time = "2026-08-28T07:08:48.379Z" }, + { url = "https://files.pythonhosted.org/packages/84/01/0afec1c92e4f292f74a44ecf75eabbf40903125b8c4df103c9868d6338da/grpcio-1.83.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:c0f3f20c90e72a171917ae65706500b096a1c3eb5f162c3ce702a2e25635f132", size = 12170381, upload-time = "2026-08-28T07:08:50.653Z" }, + { url = "https://files.pythonhosted.org/packages/c7/5a/e9a2383804433a0a61d6d93777ad321c7f36ac1cfdaa4c6d1a7c9ac846b7/grpcio-1.83.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:81bbf35a46bf8cad2dfbb2eccc19c711befb58b288acb534bbcd0d74283202a6", size = 6883286, upload-time = "2026-08-28T07:08:53.654Z" }, + { url = "https://files.pythonhosted.org/packages/63/e7/f8ca8f76994e14c70b9a0052e82f10de497a23db450c36379c9716ebfc4d/grpcio-1.83.1-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:215cec07d11176507387bda4bf2751816e880f9bff8dc1ca524bfbb8ed8f2fad", size = 7624293, upload-time = "2026-08-28T07:08:55.709Z" }, + { url = "https://files.pythonhosted.org/packages/bb/7a/4b672814b0cd0fe63bdd735379d88b165759f3144ab023ad8ec5fc4d53ac/grpcio-1.83.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:abce7d43ec29cd39230fa8339de1a07643b55adc412a454850fbd875349950ff", size = 7044346, upload-time = "2026-08-28T07:08:57.802Z" }, + { url = "https://files.pythonhosted.org/packages/50/b8/d89fe60e4239ad51be333dd9cc703741d449a35064e51f8a0b5bfa755432/grpcio-1.83.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e256f95a40e3b0183a98556fb7164d24b97eeb353123ccabfcba94712b35ee2a", size = 7584187, upload-time = "2026-08-28T07:08:59.867Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b2/b290d7402633d9166e4dd47e6f5f74a24ce10a8340b84455896ebc349f85/grpcio-1.83.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:2110059146fb0ea216e1ffddb29377b5cc2fd412a5b0a92e102616bd5edf18c2", size = 8608730, upload-time = "2026-08-28T07:09:02.592Z" }, + { url = "https://files.pythonhosted.org/packages/f5/44/fa89e44d1b5cf5b9fa71b2fd7abf506f182fd43917231a92fbf1ea326b02/grpcio-1.83.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20d944d967843f8183f9f23d5916388362e5f8eeeae855bbe4354d906dc9f31b", size = 7983283, upload-time = "2026-08-28T07:09:05.087Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b8/9db73ed1f35ffa76124ac574bf296d06a359798dfd6b50d382f2b8a060a1/grpcio-1.83.1-cp314-cp314-win32.whl", hash = "sha256:623c87c6d4a1cb30d82c4e896f95477050f2e01b4a1f8cf91ff2b1abdf89c457", size = 4474327, upload-time = "2026-08-28T07:09:07.179Z" }, + { url = "https://files.pythonhosted.org/packages/65/22/fc9a622d885a7a37ff972a12faaef443d74e47407181da70d0ab62ab41f0/grpcio-1.83.1-cp314-cp314-win_amd64.whl", hash = "sha256:47e6934ad38779271e2e7cc5f78a63a407cf3d98114c65c1fdbcd3f5a716f29b", size = 5302032, upload-time = "2026-08-28T07:09:09.285Z" }, +] + +[[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 = "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 = "httpcore2" +version = "2.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "h11" }, + { name = "truststore" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/be/ad/f4f0e57345f1870f3e8cb624e058d7eca6e5a27d33bcc3311d9b618734cd/httpcore2-2.12.0.tar.gz", hash = "sha256:9293522bba0aa7c4c8e9e3f040c16575bd8868e155a77fa30c7a9085a5eae648", size = 67548, upload-time = "2026-08-18T13:22:08.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/74/d370e55600d9bcfa0d9794b0166126d49291a3d2b20c268fc98c453a4948/httpcore2-2.12.0-py3-none-any.whl", hash = "sha256:7e04258ce01013d7d615e5b910a3b27fac937d7a95038227e79652b4ba3b4ceb", size = 83074, upload-time = "2026-08-18T13:22:05.854Z" }, +] + +[[package]] +name = "httptools" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/e5/d471fcb0e14523fe1c3f4ba58ca52480e7bd70ad7109a3846bc75892f7fb/httptools-0.8.0.tar.gz", hash = "sha256:6b2a32f18d97e16e90827d7a819ffa8dbd8cc245fc4e1fa9d1095b54ef4bd999", size = 271342, upload-time = "2026-05-25T22:17:48.841Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/e5/8cfcabc5546e8022f168be28bcdaa128a240a0befdd03b59d558b4f18bd6/httptools-0.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:614ceea8ea606848bece2338ac03b3ce5324bcb4be8dc7d377ed708012fa4db8", size = 205148, upload-time = "2026-05-25T22:17:16.333Z" }, + { url = "https://files.pythonhosted.org/packages/2a/0e/0fb14848c19a686c8062ff9067c1a48793e3224b47bc5b201535b6036fce/httptools-0.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2d689918c15a013c65ef52d9fd495d766893ab831a2c8d89f2ac5940a5df847c", size = 111368, upload-time = "2026-05-25T22:17:17.586Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1b/46f1cecf06b9bbde8e4b8c88034ac7908989e5ff7a3a388ef38392949c1f/httptools-0.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:eb3028cca2fc0a6d720e52ef61d8ebb62fcbfeb1de56874546d858d3f25a26b7", size = 486447, upload-time = "2026-05-25T22:17:18.564Z" }, + { url = "https://files.pythonhosted.org/packages/77/00/258bfc0837221f81d9725c45f9b948a6a6b2994a147a4fb66e85100c668f/httptools-0.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:88bdd940f2b5d487b4d032c6afa5489a7dc4694410d43de3c38c4fb3af0dc45d", size = 482448, upload-time = "2026-05-25T22:17:19.912Z" }, + { url = "https://files.pythonhosted.org/packages/04/ab/d1cef3b5523f4d272a70f42a776c3169a2dddfe3a54de4b2ce4a36341528/httptools-0.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6a43c9dd399758ccc0531acb0a3c4a6c299ee893ee9400e9c893b7bdcfae0681", size = 464460, upload-time = "2026-05-25T22:17:20.882Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/5d1d072442277bb2b3434e0e60690b8e8c23840ef7de8b6ea54040a536d3/httptools-0.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0770728beb05094c809b98e814edff5fef69d26ad7d21185f2f6d5884a0ba683", size = 471312, upload-time = "2026-05-25T22:17:22.085Z" }, + { url = "https://files.pythonhosted.org/packages/0d/66/b96623b27e51a68199ef4efdda0613cced9233fe3062ac74e50749c5ad37/httptools-0.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:7685df791fad561384bfb139e77fde27a1ffd93134e016f95a0db424ffbf77b1", size = 90117, upload-time = "2026-05-25T22:17:23.074Z" }, + { url = "https://files.pythonhosted.org/packages/1a/12/fa3fbf5f9517b273edea2dc982aa82a8c634091e67c590792b729017bc6f/httptools-0.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:de242a49b5d18e0a8776e654e9f6bf6d89f3875a5c35b425a0e7ce940feb3fd6", size = 206183, upload-time = "2026-05-25T22:17:24.004Z" }, + { url = "https://files.pythonhosted.org/packages/30/fc/5e7c4cb443370f2090a3aba0453a07384d29ff66b7435bb90e77e1037599/httptools-0.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:159e9ab5f701ccd42e555a12f1ad8ff69702910fc1c996cf2bb66e5fcb7a231b", size = 112079, upload-time = "2026-05-25T22:17:25.216Z" }, + { url = "https://files.pythonhosted.org/packages/ba/53/771bd891eb0f236f32145d6a1775777ec85745f3cc983a1f23d1a3b8ddfe/httptools-0.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c4a9f1707e4823d54dfec6c33fa3697d302aed536ed352a7ebb5a061ddb869d0", size = 481596, upload-time = "2026-05-25T22:17:26.186Z" }, + { url = "https://files.pythonhosted.org/packages/62/42/94e15bc68ce3d423243c45d7f1b0c7561f13844f97dc52ae23182fb65628/httptools-0.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d76ad7b951387e3632c8716a9bb03ac5b45c5f16119aa409db0459520887944e", size = 480865, upload-time = "2026-05-25T22:17:27.542Z" }, + { url = "https://files.pythonhosted.org/packages/1c/7c/fe2980fc03723272e30f135b62360b075f513dfe7cc73aef36c7f04012bd/httptools-0.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a3b7387147361c3fd47a0bde763c5c91b5b4cd4dc9989b8ece84ff436c99843b", size = 463189, upload-time = "2026-05-25T22:17:28.546Z" }, + { url = "https://files.pythonhosted.org/packages/15/1b/47fc5fff68acd1bfa20b4734059c9a06cadb88119dcd5258b5b0d21d91c8/httptools-0.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f256d6ce930c52ca1cb2a960b7da03548c454e7d28b06059ad41bfe789036ce0", size = 466610, upload-time = "2026-05-25T22:17:29.816Z" }, + { url = "https://files.pythonhosted.org/packages/60/bd/07b13c93ffd9bec9546e0d43f8e19378dd696dbd278511406bc07371ef1f/httptools-0.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:19d1ee275bb59ba2643ba9a3a1e51cc0c788caf2b8df506368e03f56fdd08527", size = 92705, upload-time = "2026-05-25T22:17:31.133Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c4/121648f68ce066d7bd762d6b6d97e620847642d38d54f3d90ff11d947629/httptools-0.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:de1ed58a974e75d56560acc7e7fed01a454994429456f65209789992e41f2568", size = 215023, upload-time = "2026-05-25T22:17:32.401Z" }, + { url = "https://files.pythonhosted.org/packages/b9/b0/312a062ae741ae3e8baa8c8bf20be81b2e67337b259ab4349bebc7b6142e/httptools-0.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e93c227b595c6926c1acee96891dd9da4be338cfbe82e5cd3bb9d8dd7dc4ac0b", size = 117405, upload-time = "2026-05-25T22:17:33.742Z" }, + { url = "https://files.pythonhosted.org/packages/fc/37/fccd705f795386bb05bf413012fecff2a33e5aa8c2f069096de3e9fd8702/httptools-0.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2a021c3a8e65cc125390d72f59b968afca3bdcaff25bd67965e0a055a14946ca", size = 558497, upload-time = "2026-05-25T22:17:34.732Z" }, + { url = "https://files.pythonhosted.org/packages/bd/39/f172e8003576de35f5ba77ff417cf0e34429d35dc014deef15afa337a72c/httptools-0.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48774d39cbb70e2b1f71f88852a3087ae1d3a1eb80482bb48c13067ab080c14f", size = 571585, upload-time = "2026-05-25T22:17:35.813Z" }, + { url = "https://files.pythonhosted.org/packages/3e/b9/f5564760af99f3dbbf3f9104dc00e5da27e96cf433c6bdcf77617f70bf3f/httptools-0.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:88eead8ec8680a9f146c655bc88445a325bd7921cfd8194c7337e9467282427d", size = 543297, upload-time = "2026-05-25T22:17:37.08Z" }, + { url = "https://files.pythonhosted.org/packages/99/67/8d9f2c313618e161b82f3873188e7196126da1d6e29688df40eb3997c77a/httptools-0.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2c032fa028f46871ec7e1fc59fc15e8023eab3e6bbe6ece786a1611719a5d081", size = 539535, upload-time = "2026-05-25T22:17:38.032Z" }, + { url = "https://files.pythonhosted.org/packages/48/63/b906c01e53f50d432c0defe43ce52764a111dc1bdd028bafbeb54dcfd008/httptools-0.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:384c17174464c8e873398b7af24f0b1f44d992c820328413951a625323155d77", size = 108209, upload-time = "2026-05-25T22:17:39.473Z" }, +] + +[[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 = "httpx2" +version = "2.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio", marker = "sys_platform != 'emscripten'" }, + { name = "httpcore2", marker = "sys_platform != 'emscripten'" }, + { name = "httpx2-jsfetch", marker = "sys_platform == 'emscripten'" }, + { name = "idna" }, + { name = "truststore", marker = "sys_platform != 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7f/f8/579a8b51e42e38ee32647df9f08aa25643ae788e275cc625b199829c4671/httpx2-2.12.0.tar.gz", hash = "sha256:7631fe9887a8a2275f4a2540e053aa670fcc50742864a9ae7c66e609fdcf12cf", size = 100040, upload-time = "2026-08-18T13:22:09.086Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/95/411ba65569158e862368917aaf56597f3e5fa3b91b0502919638465a08f3/httpx2-2.12.0-py3-none-any.whl", hash = "sha256:cc8b6eecb8661c146b8f89a60e97456ee086e91a784ed31ac450c3a9e613dd36", size = 95427, upload-time = "2026-08-18T13:22:06.834Z" }, +] + +[[package]] +name = "httpx2-jsfetch" +version = "1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/c4/0e5636363151a2a1795e0a77617168b9ca438e1748ec05fc9b5687f93d64/httpx2_jsfetch-1.0.tar.gz", hash = "sha256:70a0e3eabfef7cce5ad9c629f7d01ca05e418f586646f4ddf14782e4c1454c60", size = 6872, upload-time = "2026-08-07T00:13:07.492Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/43/832f631d32e4f1211caa2ba368317739fe71f0b8530e4c9d15dc454bac2a/httpx2_jsfetch-1.0-py3-none-any.whl", hash = "sha256:cb916b707601e69a07721aabc8f3f6659be3a6893bc1ff5c6f9e02241df2da32", size = 6382, upload-time = "2026-08-07T00:13:06.567Z" }, +] + +[[package]] +name = "idna" +version = "3.19" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/f7/abb373e5757eaec4b922b92f97ec8d6d7e057cf06778247604fbc4e7c3f3/idna-3.19.tar.gz", hash = "sha256:5e0811a4383b21dc5838069f801c4fb62113b7447663d2530d2bd6e77b49bf15", size = 215237, upload-time = "2026-08-18T05:14:24.27Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" }, +] + +[[package]] +name = "isodate" +version = "0.7.2" +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" } +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" }, +] + +[[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.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/1f/10936e16d8860c70698a1aa939a46aa0224813b782bce4e000e637da0b2d/jiter-0.16.0.tar.gz", hash = "sha256:7b24c3492c5f4f84a37946ad9cf504910cf6a782d6a4e0689b6673c5894b4a1c", size = 176431, upload-time = "2026-06-29T13:05:13.657Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/c0/555fc60473d30d66894ba825e63615e3be7524fac23858356afa7a38906c/jiter-0.16.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:41977aa5654023948c2dae2a81cbf9c43343954bef1cd59a154dd15a4d84c195", size = 306203, upload-time = "2026-06-29T13:03:36.243Z" }, + { url = "https://files.pythonhosted.org/packages/d0/2b/c3eaf16f5d7c9bad66ea32f40a95bd169b29a91217fcc7f081375157e99c/jiter-0.16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d28bb3c26762358dadf3e5bf0bccd29ae987d65e6988d2e6f49829c76b003c09", size = 306489, upload-time = "2026-06-29T13:03:37.846Z" }, + { url = "https://files.pythonhosted.org/packages/96/3f/02fdfc6705cad96127d883af5c34e4867f554f29ec7705ec1a46156400a9/jiter-0.16.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0542a7189c26920778658fc8fcf2af8bae05bae9924577f71804acef37996536", size = 335453, upload-time = "2026-06-29T13:03:39.221Z" }, + { url = "https://files.pythonhosted.org/packages/b2/a6/e4bda5920d4b0d7c5dfb7174ce4a6b2e4d3e11c9162c452ef0eab4cdbdbd/jiter-0.16.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8fb8de1e23a0cb2a7f53c335049c7b72b6db41aa6227cdcc0972a1de5cb39450", size = 361625, upload-time = "2026-06-29T13:03:40.597Z" }, + { url = "https://files.pythonhosted.org/packages/b7/97/4e6b59b2c6e55cbb3e183595f81ad65dcfb21c915fee5e19e335df21bc55/jiter-0.16.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b72d0b2990ca754a9102779ac98d8597b7cb31678958562214a007f909eab78e", size = 456958, upload-time = "2026-06-29T13:03:42.074Z" }, + { url = "https://files.pythonhosted.org/packages/15/e0/97e9557686d2f94f4b93786eccb7eed28e9228ad132ea8237f44727314a7/jiter-0.16.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5f91b1c27fc22a57993d5a5cb8a627cb8ed4b10502716fac1ffbfe1d19d84e8", size = 372017, upload-time = "2026-06-29T13:03:43.658Z" }, + { url = "https://files.pythonhosted.org/packages/0f/94/db768b6938e0df35c86beeba3dfbbb025c9ee5c19e1aa271f2396e50864d/jiter-0.16.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c682bea068a90b764577bdb78a60a4c1d1606daf9cd4c893832a37c7cc9d9026", size = 343320, upload-time = "2026-06-29T13:03:45.226Z" }, + { url = "https://files.pythonhosted.org/packages/c1/d6/5a59d938244a30735fe62d9433fd325f9021ea29d89780ea4596ea93bc89/jiter-0.16.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:8d031aabecc4f1b6276adfb42e3aabb77c89d468bf616600e8d3a11328929053", size = 350520, upload-time = "2026-06-29T13:03:46.671Z" }, + { url = "https://files.pythonhosted.org/packages/67/f8/c4a857f49c9af125f6bbcac7e3eee7f7978ed89682833062e2dbf62576b1/jiter-0.16.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eab2cd170150e70153de16896a1774e3a1dca80154c56b54d7a812c479a7165e", size = 387550, upload-time = "2026-06-29T13:03:48.361Z" }, + { url = "https://files.pythonhosted.org/packages/8b/d6/5fbc2f7d6b67b754caa61a993a2e626e815dec47ffc2f9e35f01adfebec7/jiter-0.16.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:6edb63a46e65a82c26800a868e49b2cac30dd5a4218b88d74bc2c848c8ad60bb", size = 515424, upload-time = "2026-06-29T13:03:49.881Z" }, + { url = "https://files.pythonhosted.org/packages/ed/54/284f0164b64a5fed915fea6ba7e9ba9b3d8d37c67d59cf2e3bb99d45cdfe/jiter-0.16.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:659039cc50b5addcc35fcc87ae2c1833b7c0a8e5326ef631a75e4478447bcf84", size = 546981, upload-time = "2026-06-29T13:03:51.363Z" }, + { url = "https://files.pythonhosted.org/packages/13/c5/2a467585a576594384e1d2c43e1224deaafc085f24e243529cf98beef8e1/jiter-0.16.0-cp313-cp313-win32.whl", hash = "sha256:c9c53be232c2e206ef9cdbad81a48bfa74c3d3f08bcf8124630a8a748aad993e", size = 202853, upload-time = "2026-06-29T13:03:53.015Z" }, + { url = "https://files.pythonhosted.org/packages/88/6a/de61d04b9eec69c71719968d2f716532a3bc121170c44a39e14979c6be81/jiter-0.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:baad945ed47f163ad833314f8e3288c396118934f94e7bbb9e243ce4b341a4fd", size = 196160, upload-time = "2026-06-29T13:03:54.447Z" }, + { url = "https://files.pythonhosted.org/packages/19/4b/b390ed59bafb3f31d008d1218578f10327714484b334439947f7e5b11e7f/jiter-0.16.0-cp313-cp313-win_arm64.whl", hash = "sha256:3c1fd2dbe1b0af19e987f03fe66c5f5bd105a2229c1aff4ab14890b24f41d21a", size = 189862, upload-time = "2026-06-29T13:03:55.754Z" }, + { url = "https://files.pythonhosted.org/packages/a7/89/bc4f1b57d5da938fd344a466396541e586d161320d70bffd929aaafcd8f4/jiter-0.16.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:b2c61484666ad42726029af0c00ef4541f0f3b5cdc550221f56c2343208018ee", size = 308239, upload-time = "2026-06-29T13:03:57.205Z" }, + { url = "https://files.pythonhosted.org/packages/65/7a/c415453e5213001bf3b411ff65dec3d303b0e76a4a2cfea9768cd4960994/jiter-0.16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:63efadc657488f45db1c676d81e704cac2abf3fdb892def1faea61db053127e2", size = 308928, upload-time = "2026-06-29T13:03:58.643Z" }, + { url = "https://files.pythonhosted.org/packages/11/fc/1f4fb7ebf9a724c7741994f4aae18fba1e2f3133df14521a79194952c34a/jiter-0.16.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf0d73f50e7b6935677854f6e8e31d499ca7064dd24734f703e060f5b237d883", size = 336998, upload-time = "2026-06-29T13:04:00.071Z" }, + { url = "https://files.pythonhosted.org/packages/a0/8d/72cadaac05ccfa7cc3a0a2232862e6c72443ca40cf300ba8b57f9f18b69b/jiter-0.16.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf3ea07d9bc8e7d03a9fbc051295462e6dbc295b894fd72457c3136e3e43d898", size = 362112, upload-time = "2026-06-29T13:04:01.52Z" }, + { url = "https://files.pythonhosted.org/packages/58/4a/c4b0d5f651fda90a24ffce9f8d56cde462a2e09d31ae3de3c68cef34c04e/jiter-0.16.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:26798522707abb47d767db536e4148ceac1b14446bf028ee85e579a2e043cfe5", size = 459807, upload-time = "2026-06-29T13:04:03.214Z" }, + { url = "https://files.pythonhosted.org/packages/80/58/ef77879ea9aa56b50824edc5a445e226422c7a8d211f3fd2a56bcb9493cf/jiter-0.16.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bc837c1b9631be10abfe0191537fe8009838204cec7e44827401ace390ddb567", size = 373181, upload-time = "2026-06-29T13:04:04.629Z" }, + { url = "https://files.pythonhosted.org/packages/49/2e/ffbc3f254e4d8a66da3062c624a7df4b7c2b2cf9e1fe43cf394b3e104041/jiter-0.16.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49060fd70737fad59d33ba9dcc0d83247dc9e77187de26053a19c16c9f32bd69", size = 344927, upload-time = "2026-06-29T13:04:06.067Z" }, + { url = "https://files.pythonhosted.org/packages/9a/f6/0be5dc6d64a89f80aa8fec984f94dedb2973e251edcae55841d60786d578/jiter-0.16.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:adbb8edeadd431bc4477879d5d371ece7cb1334486584e0f252656dd7ffada29", size = 352754, upload-time = "2026-06-29T13:04:07.477Z" }, + { url = "https://files.pythonhosted.org/packages/da/6e/7d31243b3b91cd261dd19e9d3557fc3251a80883d3d8049c86174e7ab7af/jiter-0.16.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:31aaee5b80f672c1dc21272bcfb9cbdcfc1ea04ff50f00ed5af500b80c44fa93", size = 390553, upload-time = "2026-06-29T13:04:08.92Z" }, + { url = "https://files.pythonhosted.org/packages/25/33/51ae371fde3c88897520f62b4d5f8b27ad7103e2bb10812ff52195609853/jiter-0.16.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:6722bcef4ffc86c835574b1b2fac6b33b9fb4a889c781e67950e891591f3c55a", size = 516900, upload-time = "2026-06-29T13:04:10.407Z" }, + { url = "https://files.pythonhosted.org/packages/a0/45/6449b3d123ea439ba79507c657288f461d55049e7bcbdc2cf8eb8210f491/jiter-0.16.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:5ab4f50ff971b611d656554ea10b75f80097392c827bc32923c6eeb6386c8b00", size = 548754, upload-time = "2026-06-29T13:04:12.046Z" }, + { url = "https://files.pythonhosted.org/packages/9b/e7/fd2fb11ae3e2649333da3aa170d04d7b3000bbdc3b270f6513382fdf4e04/jiter-0.16.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:710cc51d4ebdcd3c1f70b232c1db1ea1344a075770422bbd4bede5708335acbe", size = 122381, upload-time = "2026-06-29T13:04:13.413Z" }, + { url = "https://files.pythonhosted.org/packages/26/80/f0b147a62c315a164ed2168908286ca302310824c218d3aae52b06c0c9a9/jiter-0.16.0-cp314-cp314-win32.whl", hash = "sha256:57b37fc887a32d44798e4d8ebfa7c9683ff3da1d5bf38f08d1bb3573ccb39106", size = 204578, upload-time = "2026-06-29T13:04:14.813Z" }, + { url = "https://files.pythonhosted.org/packages/5e/e6/4758a14304b4523a6f5adb2419340086aa3593bd4327c2b25b5948a90548/jiter-0.16.0-cp314-cp314-win_amd64.whl", hash = "sha256:cbd18dd5e2df96b580487b5745adf57ef64ad89ba2d9662fc3c19386acce7db8", size = 198154, upload-time = "2026-06-29T13:04:16.272Z" }, + { url = "https://files.pythonhosted.org/packages/26/be/41fa54a2e7ea41d6c99f1dc5b1f0fd4cb474680304b5d268dd518e81da3a/jiter-0.16.0-cp314-cp314-win_arm64.whl", hash = "sha256:a32d2027a9fa67f109ff245a3252ece3ccc32cc56703e1deab6cc846a59e0585", size = 191458, upload-time = "2026-06-29T13:04:17.707Z" }, + { url = "https://files.pythonhosted.org/packages/81/6b/59127338b86d9fe4d99418f5a15118bea778103ee0fe9d9dd7e0af174e95/jiter-0.16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2577196f4474ef3fc4779a088a23b0897bbf86f9ea3679c372d45b8383b43207", size = 316739, upload-time = "2026-06-29T13:04:19.663Z" }, + { url = "https://files.pythonhosted.org/packages/2d/95/49461034d5388196d3dabf98748935f017b7785d8f3f5349f834bcc4ed0d/jiter-0.16.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:616e89e008a93c01104161c75b4988e58716b01d62307ebfe161e52a56d2a818", size = 340911, upload-time = "2026-06-29T13:04:21.257Z" }, + { url = "https://files.pythonhosted.org/packages/cd/97/a4369f2fb82cb3dda13b98622f31249b2e014b223fe64ee534413ad72294/jiter-0.16.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e2e9efbe042210df657bade597f66d6d75723e3d8f45a12ea6d8167ff8bbce3", size = 361747, upload-time = "2026-06-29T13:04:22.677Z" }, + { url = "https://files.pythonhosted.org/packages/28/51/49b6ed456261646e1906016a6760367a28aacd3c24805e4e5fe64116c1db/jiter-0.16.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f4d9e473a5ce7d27fef8b848df4dc16e283893d3f53b4a585e72c9595f3c284", size = 460225, upload-time = "2026-06-29T13:04:24.441Z" }, + { url = "https://files.pythonhosted.org/packages/33/b5/5689aff4f66c5b60be63106e591dbfcba2190df97d2c9c7cf052361ddb98/jiter-0.16.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d30a4a1c87713060c8d1cc59a7b6c8fb6b8ef0a6900368014c76c87922a2929", size = 373169, upload-time = "2026-06-29T13:04:25.884Z" }, + { url = "https://files.pythonhosted.org/packages/a2/96/3ae1b85ee0d6d6cab254fb7f8da018272b932bbf2d69b07e98aa2a96c746/jiter-0.16.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bae96332410f866e5900d809298b1ed82735932986c672495f9701daacd80620", size = 350332, upload-time = "2026-06-29T13:04:27.302Z" }, + { url = "https://files.pythonhosted.org/packages/15/32/c99d7bafd78986556c95bf60ce84c6cc98786eac56066c12d7f828bb6747/jiter-0.16.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:da3d7ec75dc83bb18bca888b5edfae0656a26849056c59e05a7728badd17e7af", size = 353377, upload-time = "2026-06-29T13:04:28.731Z" }, + { url = "https://files.pythonhosted.org/packages/0e/4b/f99a8e571287c3dec766bcc18528bbe8e8fb5365522ab5e6d64c93e87066/jiter-0.16.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ee6162b77d49a9939229df666dfa8af3e656b6701b54c4c84966d740e189264e", size = 387746, upload-time = "2026-06-29T13:04:30.319Z" }, + { url = "https://files.pythonhosted.org/packages/75/69/c78a5b3f71040e34eb5917df26fb7ae9a2174cad1ccbf277512507c53a6e/jiter-0.16.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:63ffdbdae7d4499f4cda14eadc12ddcabef0fc0c081191bdc2247489cb698077", size = 517292, upload-time = "2026-06-29T13:04:31.709Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f7/095b38eda4c70d03651c403f29a5590f16d12ddc5d544aac9f9cddf72277/jiter-0.16.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a111256a7193bea0759267b10385e5870949c239ed7b6ddbaaf57573edb38734", size = 549259, upload-time = "2026-06-29T13:04:33.721Z" }, + { url = "https://files.pythonhosted.org/packages/2e/c5/6a0207d90e5f656d95af98ebd0934f382d37674416f215aeda2ff8063e51/jiter-0.16.0-cp314-cp314t-win32.whl", hash = "sha256:de5ba8763e56b793561f43bed197c9ea55776daa5e9a6b91eed68a909bc9cdbf", size = 206523, upload-time = "2026-06-29T13:04:35.068Z" }, + { url = "https://files.pythonhosted.org/packages/a5/31/c757d5f30a8980fd945ce7b98be10be9e4ff59c7c42f5fd86804c2e87db8/jiter-0.16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b8a3f9a6008048fe9def7bf465180564a6e458047d2ce499149cfbe73c3ae9db", size = 200366, upload-time = "2026-06-29T13:04:36.61Z" }, + { url = "https://files.pythonhosted.org/packages/7c/a2/d88de6d313d734a544a7901353ad5db67cb38dcfcd91713b7979dafc345d/jiter-0.16.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0fa25b09b13075c46f5bc174f2690525a925a4fc2f7c82969a2bbabff22386ce", size = 190516, upload-time = "2026-06-29T13:04:38.004Z" }, +] + +[[package]] +name = "json-rpc" +version = "1.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/9e/59f4a5b7855ced7346ebf40a2e9a8942863f644378d956f68bcef2c88b90/json-rpc-1.15.0.tar.gz", hash = "sha256:e6441d56c1dcd54241c937d0a2dcd193bdf0bdc539b5316524713f554b7f85b9", size = 28854, upload-time = "2023-06-11T09:45:49.078Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/9e/820c4b086ad01ba7d77369fb8b11470a01fac9b4977f02e18659cf378b6b/json_rpc-1.15.0-py2.py3-none-any.whl", hash = "sha256:4a4668bbbe7116feb4abbd0f54e64a4adcf4b8f648f19ffa0848ad0f6606a9bf", size = 39450, upload-time = "2023-06-11T09:45:47.136Z" }, +] + +[[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/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 = "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 = "msal" +version = "1.38.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "pyjwt", extra = ["crypto"] }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b8/1f/10f9d47a63d3a2e61b2c43e15bee6b95682aab827018f9a1b97a80787e25/msal-1.38.0.tar.gz", hash = "sha256:4f10ff1257bacfd1781f22e85bd2b8d43ad1b490f3b6aafd7906671cadedd464", size = 203411, upload-time = "2026-08-24T10:22:46.053Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/ca/d768f77a27d81ed0a6884f2458f8613c31c79b2eb95defbeca2273fd0754/msal-1.38.0-py3-none-any.whl", hash = "sha256:765b9b98b6aa380ee8b8f1c75636e08863edaf0a953498955bd668650dde5d49", size = 131057, upload-time = "2026-08-24T10:22:47.485Z" }, +] + +[[package]] +name = "msal-extensions" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "msal" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/01/99/5d239b6156eddf761a636bded1118414d161bd6b7b37a9335549ed159396/msal_extensions-1.3.1.tar.gz", hash = "sha256:c5b0fd10f65ef62b5f1d62f4251d51cbcaf003fcedae8c91b040a488614be1a4", size = 23315, upload-time = "2025-03-14T23:51:03.902Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/75/bd9b7bb966668920f06b200e84454c8f3566b102183bc55c5473d96cb2b9/msal_extensions-1.3.1-py3-none-any.whl", hash = "sha256:96d3de4d034504e969ac5e85bae8106c8373b5c6568e4c8fa7af2eca9dbe6bca", size = 20583, upload-time = "2025-03-14T23:51:03.016Z" }, +] + +[[package]] +name = "msgspec" +version = "0.21.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/60/f79b9b013a16fa3a58350c9295ddc6789f2e335f36ea61ed10a21b215364/msgspec-0.21.1.tar.gz", hash = "sha256:2313508e394b0d208f8f56892ca9b2799e2561329de9763b19619595a6c0f72c", size = 319193, upload-time = "2026-04-12T21:44:50.394Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/74/f11ede02839b19ff459f88e3145df5d711626ca84da4e23520cebf819367/msgspec-0.21.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:764173717a01743f007e9f74520ed281f24672c604514f7d76c1c3a10e8edb66", size = 196176, upload-time = "2026-04-12T21:44:17.613Z" }, + { url = "https://files.pythonhosted.org/packages/bb/40/4476c1bd341418a046c4955aff632ec769315d1e3cb94e6acf86d461f9ed/msgspec-0.21.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:344c7cd0eaed1fb81d7959f99100ef71ec9b536881a376f11b9a6c4803365697", size = 188524, upload-time = "2026-04-12T21:44:18.815Z" }, + { url = "https://files.pythonhosted.org/packages/ca/d9/9e9d7d7e5061b47540d03d640fab9b3965ba7ae49c1b2154861c8f007518/msgspec-0.21.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48943e278b3854c2f89f955ddc6f9f430d3f0784b16e47d10604ee0463cd21f5", size = 218880, upload-time = "2026-04-12T21:44:20.028Z" }, + { url = "https://files.pythonhosted.org/packages/74/66/2bb344f34abb4b57e60c7c9c761994e0417b9718ec1460bf00c296f2a7ea/msgspec-0.21.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9aa659ebb0101b1cbc31461212b87e341d961f0ab0772aaf068a99e001ec4aa", size = 225050, upload-time = "2026-04-12T21:44:21.577Z" }, + { url = "https://files.pythonhosted.org/packages/1a/84/7c1e412f76092277bf760cef12b7979d03314d259ab5b5cafde5d0c1722d/msgspec-0.21.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7b27d1a8ead2b6f5b0c4f2d07b8be1ccfcc041c8a0e704781edebe3ae13c484", size = 222713, upload-time = "2026-04-12T21:44:22.83Z" }, + { url = "https://files.pythonhosted.org/packages/4e/27/0bba04b2b4ef05f3d068429410bc71d2cea925f1596a8f41152cccd5edb8/msgspec-0.21.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:38fe93e86b61328fe544cb7fd871fad5a27c8734bfda90f65e5dbe288ae50f61", size = 227259, upload-time = "2026-04-12T21:44:24.11Z" }, + { url = "https://files.pythonhosted.org/packages/b0/2d/09574b0eea02fed2c2c1383dbaae2c7f79dc16dcd6487a886000afb5d7c4/msgspec-0.21.1-cp313-cp313-win_amd64.whl", hash = "sha256:8bc666331c35fcce05a7cd2d6221adbe0f6058f8e750711413d22793c080ac6a", size = 189857, upload-time = "2026-04-12T21:44:25.359Z" }, + { url = "https://files.pythonhosted.org/packages/46/34/105b1576ad182879914f0c821f17ee1d13abb165cb060448f96fe2aff078/msgspec-0.21.1-cp313-cp313-win_arm64.whl", hash = "sha256:42bb1241e0750c1a4346f2aa84db26c5ffd99a4eb3a954927d9f149ff2f42898", size = 175403, upload-time = "2026-04-12T21:44:26.608Z" }, + { url = "https://files.pythonhosted.org/packages/5a/ad/86954e987d1d6a5c579e2c2e7832b65e0fff194179fdac4f581536086024/msgspec-0.21.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fab48eb45fdbfbdb2c0edfec00ffc53b6b6085beefc6b50b61e01659f9f8757f", size = 196261, upload-time = "2026-04-12T21:44:27.807Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a1/c5e46c3e42b866199365e35d11dddfd1fbd8bba4fdb3c52f965b1607ce94/msgspec-0.21.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3cb779ea0c35bc807ff941d415875c1f69ca0be91a2e907ab99a171811d86a9a", size = 188729, upload-time = "2026-04-12T21:44:28.99Z" }, + { url = "https://files.pythonhosted.org/packages/85/7d/1e29a319d678d6cb962ae5bdf32a6858ebdf38f73bc654c0e9c742a0c2c8/msgspec-0.21.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:68604db36b3b4dd9bf160e436e12798a4738848144cea1aca1cb984011eb160f", size = 219866, upload-time = "2026-04-12T21:44:31.104Z" }, + { url = "https://files.pythonhosted.org/packages/25/1f/cca084ca2572810fff12ea9dbdcbe39eac048f40daf4a9077b49fcbe8cee/msgspec-0.21.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3d6b9dc50948eaf65df54d2fd0ff66e6d8c32f116037209ee861810eb9b676cb", size = 224993, upload-time = "2026-04-12T21:44:32.649Z" }, + { url = "https://files.pythonhosted.org/packages/71/94/d2120fc9d419a89a3a7c13e5b7078798c4b392a96a02a6e2b3ce43a8766c/msgspec-0.21.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:52c5e21930942302394429c5a582ce7e6b62c7f983b3760834c2ce107e0dd6df", size = 223535, upload-time = "2026-04-12T21:44:33.839Z" }, + { url = "https://files.pythonhosted.org/packages/75/17/42418b66a3ad972a89bab73dd78b79cc6282bb488a25e73c853cee7443b9/msgspec-0.21.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:abbb39d65681fa24ed394e01af3d59d869068324f900c61d06062b7fb9980f2f", size = 227222, upload-time = "2026-04-12T21:44:35.093Z" }, + { url = "https://files.pythonhosted.org/packages/c4/33/265c894268cca88ff67b144ca2b4c522fc8b9a6f1966a3640c70516e78e1/msgspec-0.21.1-cp314-cp314-win_amd64.whl", hash = "sha256:5666b1b560b97b6ec2eb3fca8a502298ebac56e13bbca1f88523538ce83d01ea", size = 193810, upload-time = "2026-04-12T21:44:36.612Z" }, + { url = "https://files.pythonhosted.org/packages/3b/8f/a6d35f25bf1fc63c492fdd88fdce01ba0875ead48c2b91f90f33653b4131/msgspec-0.21.1-cp314-cp314-win_arm64.whl", hash = "sha256:d8b8578e4c83b14ceea4cef0d0b747e31d9330fe4b03b2b2ad4063866a178f93", size = 179125, upload-time = "2026-04-12T21:44:38.198Z" }, + { url = "https://files.pythonhosted.org/packages/c6/39/74839641e64b99d87da55af0fc472854d42b46e2183b9e2a67fe1bb2a512/msgspec-0.21.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:15f523d51c00ebad412213bfe9f06f0a50ec2b93e0c19e824a2d267cabb48ea2", size = 200171, upload-time = "2026-04-12T21:44:39.414Z" }, + { url = "https://files.pythonhosted.org/packages/70/9b/ce0cca6d2d87fcd4b6ff97600790494e64f26a2c55d61507cd2755c16193/msgspec-0.21.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:4e47390360583ba3d5c6cb44cf0a9f61b0a06a899d3c2c00627cedebb2e2884b", size = 192879, upload-time = "2026-04-12T21:44:40.882Z" }, + { url = "https://files.pythonhosted.org/packages/a7/08/673a7bb05e5702dc787ddd3011195b509f9867927970da59052211929987/msgspec-0.21.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f60800e6299b798142dc40b0644da77ceac5ea0568be58228417eae14135c847", size = 226281, upload-time = "2026-04-12T21:44:42.181Z" }, + { url = "https://files.pythonhosted.org/packages/7d/45/86508cf57283e9070b3c447e3ab25b792a7a0855a3ea4e0c6d111ac34c97/msgspec-0.21.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5f8e9dfcd98419cf7568808470c4317a3fb30bef0e3715b568730a2b272a20d7", size = 229863, upload-time = "2026-04-12T21:44:43.442Z" }, + { url = "https://files.pythonhosted.org/packages/2c/62/e7c9367cd08d590559faacd711edbae36840342843e669440363f33c7d36/msgspec-0.21.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:92d89dfad13bd1ea640dc3e37e724ed380da1030b272bdf5ecafb983c3ad7c75", size = 230445, upload-time = "2026-04-12T21:44:44.806Z" }, + { url = "https://files.pythonhosted.org/packages/42/b4/c0f54632103846b658a10930025f4de41c8724b5e4805a5f3b395586cb7e/msgspec-0.21.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0d03867786e5d7ba25d666df4b11320c27170f4aeafcb8e3a8b0a50a4fb742ca", size = 231822, upload-time = "2026-04-12T21:44:46.343Z" }, + { url = "https://files.pythonhosted.org/packages/ea/1d/0d85cc79d0ccf5508e9c846cc66552a6a16bf92abd1dbd8362617f7b35cd/msgspec-0.21.1-cp314-cp314t-win_amd64.whl", hash = "sha256:740fbf1c9d59992ca3537d6fbe9ebbf9eaf726a65fbf31448e0ecbc710697a63", size = 206650, upload-time = "2026-04-12T21:44:47.601Z" }, + { url = "https://files.pythonhosted.org/packages/90/91/56c5d560f20e6c20e9e4f55bd0e458f7f162aa689ee350346c04c48eac0b/msgspec-0.21.1-cp314-cp314t-win_arm64.whl", hash = "sha256:0d2cc73df6058d811a126ac3a8ad63a4dfa210c82f9cf5a004802eaf4712de90", size = 183149, upload-time = "2026-04-12T21:44:48.833Z" }, +] + +[[package]] +name = "msrest" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "azure-core" }, + { name = "certifi" }, + { name = "isodate" }, + { name = "requests" }, + { name = "requests-oauthlib" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/77/8397c8fb8fc257d8ea0fa66f8068e073278c65f05acb17dcb22a02bfdc42/msrest-0.7.1.zip", hash = "sha256:6e7661f46f3afd88b75667b7187a92829924446c7ea1d169be8c4bb7eeb788b9", size = 175332, upload-time = "2022-06-13T22:41:25.111Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/cf/f2966a2638144491f8696c27320d5219f48a072715075d168b31d3237720/msrest-0.7.1-py3-none-any.whl", hash = "sha256:21120a810e1233e5e6cc7fe40b474eeb4ec6f757a15d7cf86702c369f9567c32", size = 85384, upload-time = "2022-06-13T22:41:22.42Z" }, +] + +[[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/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 = "oauthlib" +version = "3.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/5f/19930f824ffeb0ad4372da4812c50edbd1434f678c90c2733e1188edfc63/oauthlib-3.3.1.tar.gz", hash = "sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9", size = 185918, upload-time = "2025-06-19T22:48:08.269Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/9c/92789c596b8df838baa98fa71844d84283302f7604ed565dafe5a6b5041a/oauthlib-3.3.1-py3-none-any.whl", hash = "sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1", size = 160065, upload-time = "2025-06-19T22:48:06.508Z" }, +] + +[[package]] +name = "openai" +version = "3.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "httpx2" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/24/4e/45c33ece533f9e4252de537c802e2efb68b066befc7b7bbbbc59c4865341/openai-3.10.0.tar.gz", hash = "sha256:c2dc841b944c4720a222f9a92f66a9a122e46968d87ed7857104655054c687ba", size = 1486030, upload-time = "2026-09-09T00:18:25.046Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dd/50/0d796ada2d1222cfe609cf435ce7a688450735cee401b24737882c9b9612/openai-3.10.0-py3-none-any.whl", hash = "sha256:d1f307d866c7d12872809aeb68b7d1a085b49c2656787eae7c39ba381a41ad0d", size = 1746707, upload-time = "2026-09-09T00:18:22.498Z" }, +] + +[[package]] +name = "opentelemetry-api" +version = "1.43.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/cc/e4c9584181f86494df0f6bdec1a4f3280c50db44704dc2a407e994fc87bb/opentelemetry_api-1.43.0.tar.gz", hash = "sha256:107d0d03857ea8fc7c5fcbbbd83f800c281f0d560553d61c1d675fccfd1761c1", size = 73476, upload-time = "2026-06-24T15:19:55.323Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/83/6dba32b85f31868400440dc7ad2ca1eab94cbbf3a7b0459ed39f8311a9e2/opentelemetry_api-1.43.0-py3-none-any.whl", hash = "sha256:20acf45e9b21851926835292e4045d290acade1edd2ff3de86d2f069687ba1fd", size = 61912, upload-time = "2026-06-24T15:19:35.434Z" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-proto-common" +version = "1.43.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-proto" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/c1/e8098490ab15abf116dcaf9fa89ededcb35547c7d08d4b5a62f573dc1e63/opentelemetry_exporter_otlp_proto_common-1.43.0.tar.gz", hash = "sha256:c4e32ba6d6b13bdb2b8f6764c4fd28d00192826561aa04f6d14eedfce7ac076f", size = 20197, upload-time = "2026-06-24T15:20:00.247Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/b2/41ebc74ae1d5859901f1b69305de58724bf043381103d6ef413521cbc35a/opentelemetry_exporter_otlp_proto_common-1.43.0-py3-none-any.whl", hash = "sha256:123c3f9cc87218562490c63b36f497bf3a722faf174a515d1443f31ababa6264", size = 17048, upload-time = "2026-06-24T15:19:41.264Z" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-proto-grpc" +version = "1.43.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "googleapis-common-protos" }, + { name = "grpcio" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-otlp-proto-common" }, + { name = "opentelemetry-proto" }, + { name = "opentelemetry-sdk" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e2/1d/6336453716ca0a240d4417d19e6d5b77a5e7163e5670ec4f7ec4d3ede7bf/opentelemetry_exporter_otlp_proto_grpc-1.43.0.tar.gz", hash = "sha256:1b3e0627daa9bc21884d4a13946807c255eb558bfe5bdd543dffb6f4c9faee0d", size = 27213, upload-time = "2026-06-24T15:20:00.907Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/74/2700b5d5c946bf2dba87073fce3dfc198c46bc92ea3d5693f54bc51c90b1/opentelemetry_exporter_otlp_proto_grpc-1.43.0-py3-none-any.whl", hash = "sha256:6a10d1feacffffda19acacbf277b736094b1e2f4dbb98c90ccb2c6e1962e2ec6", size = 19626, upload-time = "2026-06-24T15:19:42.233Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation" +version = "0.64b0" +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/7e/97/02fe6e1c8b1ffac42d0b429c18080edb24e0e0d18c86612edf72b5752382/opentelemetry_instrumentation-0.64b0.tar.gz", hash = "sha256:b47d528dead6271d7743114417eb67fc915bd9258111c48dbf9a4951d2efa88d", size = 41935, upload-time = "2026-06-24T15:19:12.951Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/0c/cb9fe342de5299c7af24582eb7d788661cc53a1c4b904da92309caaa9417/opentelemetry_instrumentation-0.64b0-py3-none-any.whl", hash = "sha256:133ab7ffca796557aec059bf6be3190a34b6dea987f25be3d9409e230cbdad8b", size = 35880, upload-time = "2026-06-24T15:18:17.277Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation-asgi" +version = "0.64b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asgiref" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "opentelemetry-util-http" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/0c/71c696fccb86d37af383ea1604af4729fabad0af2fabaf203e4c79c1e859/opentelemetry_instrumentation_asgi-0.64b0.tar.gz", hash = "sha256:4dd3eee566a4303f8e6b9b84f2a0a7abc57a6640df768926c68a3868bf5b2090", size = 26136, upload-time = "2026-06-24T15:19:17.003Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/20/218b65a63d847a7ed28d1bea84c39234689160b74480b8702272e37f4240/opentelemetry_instrumentation_asgi-0.64b0-py3-none-any.whl", hash = "sha256:e0840b66e15303a9254b0540946010bd008aa0504f4d89b8e1b7fb63490a36f0", size = 15906, upload-time = "2026-06-24T15:18:23.107Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation-dbapi" +version = "0.64b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/9f/3c6ce6f4394faa1540f48b7d442f455582ca76449952506ce767a54f09bf/opentelemetry_instrumentation_dbapi-0.64b0.tar.gz", hash = "sha256:8e3a1528fc9753a04190a7e7bf0180c5abd059f3aa68ec3857edb363c9847c44", size = 20138, upload-time = "2026-06-24T15:19:24.097Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/1b/77972514bcc046741339b76e2cc104bef536c1433b45a2b39adbdadc9889/opentelemetry_instrumentation_dbapi-0.64b0-py3-none-any.whl", hash = "sha256:1caa96d438bf37f71dd778c9f1e03ba1f50b8ab54aeef68b2a630c93c05cddc6", size = 14812, upload-time = "2026-06-24T15:18:33.541Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation-django" +version = "0.64b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-instrumentation-wsgi" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "opentelemetry-util-http" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/2e/8f6084eab186533b0574683a8c39e51803e6c34c21fc33e194f58352d704/opentelemetry_instrumentation_django-0.64b0.tar.gz", hash = "sha256:3d2673b4f77156b15b1b119c8849b50e3644cfc325f7a24c03602596de2dbba4", size = 25387, upload-time = "2026-06-24T15:19:24.795Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/ef/7e7857fae434e0eb0395d8a5b399c21efd23fe0f3c166ecdcb8596902541/opentelemetry_instrumentation_django-0.64b0-py3-none-any.whl", hash = "sha256:c1f628e53c22aa8f9cc9cbe6e61e781940da6eda5f910d26720ef8ac73e25afe", size = 18938, upload-time = "2026-06-24T15:18:34.449Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation-fastapi" +version = "0.64b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-instrumentation-asgi" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "opentelemetry-util-http" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/db/a1/52282e2cc5c08f4df13b087896d9907258fe2ff4f34035d3b21aa92684c3/opentelemetry_instrumentation_fastapi-0.64b0.tar.gz", hash = "sha256:05f75149929e433c1630de381688e650bf651c1e1cce7f9a7b649a807dac8a98", size = 26236, upload-time = "2026-06-24T15:19:27.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/2d/4f48dc2d6f289f2febc5b871940dbea9f3b04ab9186d23342581b49cb984/opentelemetry_instrumentation_fastapi-0.64b0-py3-none-any.whl", hash = "sha256:43cbbfb2d3079dc81104478a2950ae93ac6d0e90a5020fa3987a236f8f2bdef1", size = 13263, upload-time = "2026-06-24T15:18:38.553Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation-flask" +version = "0.64b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-instrumentation-wsgi" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "opentelemetry-util-http" }, + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4a/6d/cf9a9cf18603e3aaebba96633ec3c1ced463706f545d8672419b715c1e59/opentelemetry_instrumentation_flask-0.64b0.tar.gz", hash = "sha256:74d07edba426beae1214bd0aec69bd31a9d0d22c29747b497d5d94c516d1c860", size = 24150, upload-time = "2026-06-24T15:19:27.847Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/64/42c6ef303e2c6b773debc2621148532c6e5ac5050c8fb6347f125c884757/opentelemetry_instrumentation_flask-0.64b0-py3-none-any.whl", hash = "sha256:bad53249bb68ab62c993dc9a3cb6be13f6ec0934ff658fea426e4d800a1310a2", size = 15079, upload-time = "2026-06-24T15:18:39.423Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation-logging" +version = "0.64b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-semantic-conventions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/88/8e/3b7191ba5817f139867612e960fbe06a69fe0d522d3b2c5e9c8e89b3ef8c/opentelemetry_instrumentation_logging-0.64b0.tar.gz", hash = "sha256:6435b4d215bf8183e15f7e3416a10ebe1c411810d3411fbfc62667daae3abacb", size = 20000, upload-time = "2026-06-24T15:19:30.946Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/07/9f97b0ccbb38363d12c7a2bfe44912ae7440c97aae250e2eedb32739f662/opentelemetry_instrumentation_logging-0.64b0-py3-none-any.whl", hash = "sha256:9b56f19648798ddb331c206b74d36c5f2a71e5c6b771e96298f167b2b590e40d", size = 16062, upload-time = "2026-06-24T15:18:44.019Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation-psycopg2" +version = "0.64b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-instrumentation-dbapi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/2d/0062ecf86b6bd407398820ae34d8acf7259f2b32531fa22e1c3b7748c6d0/opentelemetry_instrumentation_psycopg2-0.64b0.tar.gz", hash = "sha256:57df449718297b93e7bb57c76d3439c475eb5a5451600fcd6a9024d74822d972", size = 12065, upload-time = "2026-06-24T15:19:33.994Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/e8/961677ce65cd5065646d90dfa8a7d780b782fefe3c8effdf4b91691e339d/opentelemetry_instrumentation_psycopg2-0.64b0-py3-none-any.whl", hash = "sha256:9a41b5f094f19b87e7dddf1d6f03f1bf956620faaa918fc1987dbd53e73f9b98", size = 10790, upload-time = "2026-06-24T15:18:48.693Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation-requests" +version = "0.64b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "opentelemetry-util-http" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/97/d5fb7b3f329c24ff2f6478f1a0c0c516ba942d3df2acbb197d276af31816/opentelemetry_instrumentation_requests-0.64b0.tar.gz", hash = "sha256:8213a20b6578c41aa05cc5b48419aea75e1584924a4904dfcf36524597e7cc96", size = 18106, upload-time = "2026-06-24T15:19:39.522Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/b2/f0f32ec327bdd76d245d28975dfb446d62eea2d267c06b6cb6c1a9b7e7d4/opentelemetry_instrumentation_requests-0.64b0-py3-none-any.whl", hash = "sha256:dcad4324ad97d785a5a3b9500acf3af3965b4c1d1b95776fa5e275b173d9541c", size = 13385, upload-time = "2026-06-24T15:18:56.163Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation-urllib" +version = "0.64b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "opentelemetry-util-http" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/87/20/547126ab1706e65629c2079cdbb956ef06ccc58a7cbad520c3505886e4cc/opentelemetry_instrumentation_urllib-0.64b0.tar.gz", hash = "sha256:954ab9908f08dc9127ca3d259f88a37b52f54c03f6fa4c384fd9fcfe9c85cd76", size = 16668, upload-time = "2026-06-24T15:19:45.096Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/7d/b0fbd641f5de3b05ab888fa649164a10d8e7d36969ea07090581c35c20a5/opentelemetry_instrumentation_urllib-0.64b0-py3-none-any.whl", hash = "sha256:712b091e16fe72b6b1f3f2a7638d3db78f2ce83ae8bfa515ecbfcdcac6efb97f", size = 13138, upload-time = "2026-06-24T15:19:03.412Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation-urllib3" +version = "0.64b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "opentelemetry-util-http" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/30/e2/9e6f747018f639b9ffed97f715f1eb16c95dd87ba572c880ce451a65a18f/opentelemetry_instrumentation_urllib3-0.64b0.tar.gz", hash = "sha256:647c6d1b012e14168323b18d4e60fca9a5d280764821b99ea0a5639450fbd74e", size = 18945, upload-time = "2026-06-24T15:19:45.738Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/74/01536fcafb60dbb2d1fec925e849663e66596e072f4badea5e4ec9879774/opentelemetry_instrumentation_urllib3-0.64b0-py3-none-any.whl", hash = "sha256:d2bd0de75a4c3e60da3ba375139cfbc6f9f95c26f8d6ef3a54d52437d43c2f87", size = 13513, upload-time = "2026-06-24T15:19:04.491Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation-wsgi" +version = "0.64b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "opentelemetry-util-http" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/4f/0b5232a0d635eee5da7c85af503d2f00fdcac61cc6d4d4a69aadf1fa7a0e/opentelemetry_instrumentation_wsgi-0.64b0.tar.gz", hash = "sha256:1cce6ea28d1800c154e6ccdf52f05bae2f05c5e28ee44f0dddb17ada26208986", size = 19667, upload-time = "2026-06-24T15:19:46.407Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/23/5d/4da612cfe1ad96730dba19c9ab97f531be0558a918998e11478734cb36b2/opentelemetry_instrumentation_wsgi-0.64b0-py3-none-any.whl", hash = "sha256:5a3b0174f39072c0abb749be4ae7e469c7585e87cdfaf63665657b1a7058c105", size = 13787, upload-time = "2026-06-24T15:19:05.417Z" }, +] + +[[package]] +name = "opentelemetry-proto" +version = "1.43.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e0/b9/d357faefb40bda1d4799913e6af611171ff22a2dedcb93576bc92242d056/opentelemetry_proto-1.43.0.tar.gz", hash = "sha256:224778df17e1f3fafeaaa21d874236ca5f6ffc2f86e0899298ec7351aac27924", size = 46481, upload-time = "2026-06-24T15:20:07.625Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/a7/3e5308cf548b8f72529c7db1afdb3a404211982376a12927fd7759f77bf3/opentelemetry_proto-1.43.0-py3-none-any.whl", hash = "sha256:c58f1f7ef84bc7dc2834016c0c37fe0081dde7ca9f6339be1970fbf9cdaaa90d", size = 72489, upload-time = "2026-06-24T15:19:51.164Z" }, +] + +[[package]] +name = "opentelemetry-resource-detector-azure" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fd/39/d42e29076bad36b07ac0894701b87a81b36b68a92359ccec50f01b3305d2/opentelemetry_resource_detector_azure-0.2.0.tar.gz", hash = "sha256:7a82d1c1794a3b252de9b0cb393116ae260f8414a085010e1c334cea34c4677e", size = 11676, upload-time = "2026-08-20T18:36:56.93Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/54/56bf2c4936ab482bef76a54603119c53c0ea4bdee0912966f2da07d8d43f/opentelemetry_resource_detector_azure-0.2.0-py3-none-any.whl", hash = "sha256:0a8368e91356464bd3746848a8ef952e89a5e8c83673c7d4b722ad5f3716b45e", size = 12265, upload-time = "2026-08-20T18:36:55.846Z" }, +] + +[[package]] +name = "opentelemetry-sdk" +version = "1.43.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3e/eb/5041074274ac0956b03637cc039d434569112468e875eddfcc9a0674ce06/opentelemetry_sdk-1.43.0.tar.gz", hash = "sha256:d8187c81c162df9913e4003dd6485f7390d9a24fc17026ec7387b8b8218b08e9", size = 254744, upload-time = "2026-06-24T15:20:08.467Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/e3/b17be23af124201c9f52eececd4cc8ddfed1597d37b4ee771895d325805c/opentelemetry_sdk-1.43.0-py3-none-any.whl", hash = "sha256:d1323a547c1ce69d6a069a17a44b7da82bb8b332051ecb074041f87642c86823", size = 178852, upload-time = "2026-06-24T15:19:52.169Z" }, +] + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.64b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/30/5f26df29509eccd86b99b481ac9ffa39da49ba9577cc69071c552ae30447/opentelemetry_semantic_conventions-0.64b0.tar.gz", hash = "sha256:72f76fb2d1582d9d033dd1fcd84532e961e6ff3d90d24ba6fabc72975a83864c", size = 148340, upload-time = "2026-06-24T15:20:09.267Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/ca/23ba87a221b574a7c5a99d48849d80bfe8b047624681357e2b002e566187/opentelemetry_semantic_conventions-0.64b0-py3-none-any.whl", hash = "sha256:ea77e85e354b8f604ddbe5f3d9135216f982fa4d77e5859ac30f6d8a50505aa6", size = 203713, upload-time = "2026-06-24T15:19:53.339Z" }, +] + +[[package]] +name = "opentelemetry-semantic-conventions-ai" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-sdk" }, + { name = "opentelemetry-semantic-conventions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/24/02/10aeacc37a38a3a8fa16ff67bec1ae3bf882539f6f9efb0f70acf802ca2d/opentelemetry_semantic_conventions_ai-0.5.1.tar.gz", hash = "sha256:153906200d8c1d2f8e09bd78dbef526916023de85ac3dab35912bfafb69ff04c", size = 26533, upload-time = "2026-03-26T14:20:38.73Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/22/41fb05f1dc5fda2c468e05a41814c20859016c85117b66c8a257cae814f6/opentelemetry_semantic_conventions_ai-0.5.1-py3-none-any.whl", hash = "sha256:25aeb22bd261543b4898a73824026d96770e5351209c7d07a0b1314762b1f6e4", size = 11250, upload-time = "2026-03-26T14:20:37.108Z" }, +] + +[[package]] +name = "opentelemetry-util-http" +version = "0.64b0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/1b/1029a805fd7242f7dfce91633b244c3b14a94d703232878f71e01ce862b1/opentelemetry_util_http-0.64b0.tar.gz", hash = "sha256:8a86a220dbfc56d736f47f1e5c4e7932a21fcf69052312e1bcf166444dc79322", size = 11102, upload-time = "2026-06-24T15:19:48.974Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1c/c7/5f8ec5b30546f2dc22cd5fc5759bce2ab5be6e89a2e710a405ac9ef64ed3/opentelemetry_util_http-0.64b0-py3-none-any.whl", hash = "sha256:c1e5350d25507c1afcd6076cf9ac062485a0a4f79cd9971366996fd3056bacdb", size = 8204, upload-time = "2026-06-24T15:19:09.02Z" }, +] + +[[package]] +name = "packaging" +version = "26.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" }, +] + +[[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/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 = "proto-plus" +version = "1.28.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/40/a6/4fbadcc2044034449b3f8f0ce82dcf3005d53f37c136642103fd4836a31c/proto_plus-1.28.4.tar.gz", hash = "sha256:5ff7ecad828e032a491fcb86947801768e32237f99dd049b649965b892ae9a63", size = 58679, upload-time = "2026-08-25T19:19:15.102Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/5d/0f04b85dafdc3250ced7f2592efc17dce7f40712e941e9632202481e600d/proto_plus-1.28.4-py3-none-any.whl", hash = "sha256:4b01341272f8a348db3f003b6143109f83ab43091019d5181b3fcdf500ab32aa", size = 50797, upload-time = "2026-08-25T19:18:12.338Z" }, +] + +[[package]] +name = "protobuf" +version = "6.33.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/70/e908e9c5e52ef7c3a6c7902c9dfbb34c7e29c25d2f81ade3856445fd5c94/protobuf-6.33.6.tar.gz", hash = "sha256:a6768d25248312c297558af96a9f9c929e8c4cee0659cb07e780731095f38135", size = 444531, upload-time = "2026-03-18T19:05:00.988Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/9f/2f509339e89cfa6f6a4c4ff50438db9ca488dec341f7e454adad60150b00/protobuf-6.33.6-cp310-abi3-win32.whl", hash = "sha256:7d29d9b65f8afef196f8334e80d6bc1d5d4adedb449971fefd3723824e6e77d3", size = 425739, upload-time = "2026-03-18T19:04:48.373Z" }, + { url = "https://files.pythonhosted.org/packages/76/5d/683efcd4798e0030c1bab27374fd13a89f7c2515fb1f3123efdfaa5eab57/protobuf-6.33.6-cp310-abi3-win_amd64.whl", hash = "sha256:0cd27b587afca21b7cfa59a74dcbd48a50f0a6400cfb59391340ad729d91d326", size = 437089, upload-time = "2026-03-18T19:04:50.381Z" }, + { url = "https://files.pythonhosted.org/packages/5c/01/a3c3ed5cd186f39e7880f8303cc51385a198a81469d53d0fdecf1f64d929/protobuf-6.33.6-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:9720e6961b251bde64edfdab7d500725a2af5280f3f4c87e57c0208376aa8c3a", size = 427737, upload-time = "2026-03-18T19:04:51.866Z" }, + { url = "https://files.pythonhosted.org/packages/ee/90/b3c01fdec7d2f627b3a6884243ba328c1217ed2d978def5c12dc50d328a3/protobuf-6.33.6-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:e2afbae9b8e1825e3529f88d514754e094278bb95eadc0e199751cdd9a2e82a2", size = 324610, upload-time = "2026-03-18T19:04:53.096Z" }, + { url = "https://files.pythonhosted.org/packages/9b/ca/25afc144934014700c52e05103c2421997482d561f3101ff352e1292fb81/protobuf-6.33.6-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:c96c37eec15086b79762ed265d59ab204dabc53056e3443e702d2681f4b39ce3", size = 339381, upload-time = "2026-03-18T19:04:54.616Z" }, + { url = "https://files.pythonhosted.org/packages/16/92/d1e32e3e0d894fe00b15ce28ad4944ab692713f2e7f0a99787405e43533a/protobuf-6.33.6-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:e9db7e292e0ab79dd108d7f1a94fe31601ce1ee3f7b79e0692043423020b0593", size = 323436, upload-time = "2026-03-18T19:04:55.768Z" }, + { url = "https://files.pythonhosted.org/packages/c4/72/02445137af02769918a93807b2b7890047c32bfb9f90371cbc12688819eb/protobuf-6.33.6-py3-none-any.whl", hash = "sha256:77179e006c476e69bf8e8ce866640091ec42e1beb80b213c3900006ecfba6901", size = 170656, upload-time = "2026-03-18T19:04:59.826Z" }, +] + +[[package]] +name = "psutil" +version = "7.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" }, + { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" }, + { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" }, + { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" }, + { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" }, + { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" }, + { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" }, + { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" }, + { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" }, + { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" }, + { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, + { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, + { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, + { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, + { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, + { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, + { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, +] + +[[package]] +name = "pyasn1" +version = "0.6.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a4/9a/23310166d960def5897e91fe20e5b724601b02a22e84ba1f94232c0b7f67/pyasn1-0.6.4.tar.gz", hash = "sha256:9c447d8431c947fe4c8febc4ed9e760bc29011a5b01e5c74b67025bd9fb8ce81", size = 151262, upload-time = "2026-07-09T01:12:33.988Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3b/6163796d69c3977d1e4287bea4a6979161cbbdd170ebb430511e8e1999ce/pyasn1-0.6.4-py3-none-any.whl", hash = "sha256:deda9277cfd454080ec40b207fb6df82206a3a2688735233cdcd8d3d565f088b", size = 84410, upload-time = "2026-07-09T01:12:32.92Z" }, +] + +[[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" +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.5" +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/53/ef/fc4f868f4e2cee79f863883abffceff107875f569b848507319842d2a681/pydantic-2.13.5.tar.gz", hash = "sha256:51a9c5f7b2f8e636f04c6cada605d9b6a3bf1348fdf945a3d8869b19bba0ee08", size = 845750, upload-time = "2026-08-28T14:04:00.916Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/47/c95ffc2009878c7aac0c5e08528022dcb885933252a88b5f170058014464/pydantic-2.13.5-py3-none-any.whl", hash = "sha256:346a034f080da3755d8e9cb5e00e8b07de1d39e4f6e2c87d8ab7cafa0b269a73", size = 472589, upload-time = "2026-08-28T14:03:59.136Z" }, +] + +[package.optional-dependencies] +email = [ + { name = "email-validator" }, +] + +[[package]] +name = "pydantic-core" +version = "2.46.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/af/f9/8a06bea35ef8daf588f707784c973a7046e0034c8d8cfb08828eeffb8b75/pydantic_core-2.46.5.tar.gz", hash = "sha256:10416c15b8839ecc4ef4d0885da76da6fd0f67333a0eb8aff6d93c4b8f2910fc", size = 472262, upload-time = "2026-08-28T10:01:31.677Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/37/5abe39a8372a61d3dc3c1338fc504281c01b32fdb3169cd7187153b56d3e/pydantic_core-2.46.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:b7ca9034437b6022f941f4857459562ee00a560b97e7cce8a0ec5a74fc6766e0", size = 2075885, upload-time = "2026-08-28T09:58:47.856Z" }, + { url = "https://files.pythonhosted.org/packages/21/43/6323b1f8b217780454c61304bcd2b38ae4762f50754414124603ccc90bb2/pydantic_core-2.46.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f332f0e72a5a0400141f830744e141bf9f97917878dbe968669e8a7fefea78ff", size = 1922768, upload-time = "2026-08-28T09:58:49.58Z" }, + { url = "https://files.pythonhosted.org/packages/0f/a3/c05ca796e1197618a774b01e596aeedfefc2f7d8c01ae3054e910b120e8a/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:193375f3548919d3f0b60936ca113ada3e38f264f91b9b8e0508efaad57be931", size = 1951241, upload-time = "2026-08-28T09:58:51.511Z" }, + { url = "https://files.pythonhosted.org/packages/68/32/33bc39ac705c52cffc908e8389f9754fdb208aea5c69cceddf4eb3ce99af/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:79bdfa52f843137045b2d081cc05c120ba6665d29b7559c2c47690906f39279f", size = 2031975, upload-time = "2026-08-28T09:58:53.166Z" }, + { url = "https://files.pythonhosted.org/packages/b0/70/2333e885c0f6a67bc105c5916965dac9b57f2718ee20d81d1a06a4ebdc13/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:24922243639cbdac66c75fcb6fd6495a9cb52b213d62f9a0d16f0310b1ff8038", size = 2208542, upload-time = "2026-08-28T09:58:55.017Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ea/296debfb4264207bbda5936133892e027c0a58875ad53ebd512fba8ec3a2/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c76fe65e607be28c7fd4d56fc3c42b1583aa058ce3408b7ad0fd540171d31f9f", size = 2264692, upload-time = "2026-08-28T09:58:56.767Z" }, + { url = "https://files.pythonhosted.org/packages/d3/f2/9e4de77a6271e07a76d2d58b11c091a979c191ed2939bf80067568b369d2/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f7b393a8b3da82f5c1fc0751e6d01ac6c55b93c18226a60bdfba4a724efafd1", size = 2066633, upload-time = "2026-08-28T09:58:58.531Z" }, + { url = "https://files.pythonhosted.org/packages/8d/db/f9e9d0c97445987b2084823d5c240de88087338f04fc2cfaa2df186b8049/pydantic_core-2.46.5-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:7ac031912d54f3d83ef3b3eb98dfabc1608802e2202263d25957eeed40b94761", size = 2105235, upload-time = "2026-08-28T09:59:00.421Z" }, + { url = "https://files.pythonhosted.org/packages/07/c5/79169b047b3b2c3e99e04bc76372af9637e0bf6db638274fa927df96369e/pydantic_core-2.46.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:837b396ca3d7b74091ca623f6cbd8351bd42d670a79c2683e79fb089f06a2de5", size = 2157367, upload-time = "2026-08-28T09:59:02.442Z" }, + { url = "https://files.pythonhosted.org/packages/26/b5/ba6057afb7c291bd449f51b867f95aef2072941c4ce4e5c31d6ffd132d3b/pydantic_core-2.46.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:5ee239d575f80b08eca11f6e20f90c4c695de7825c67eefe6091fbf20dda648e", size = 2158420, upload-time = "2026-08-28T09:59:04.2Z" }, + { url = "https://files.pythonhosted.org/packages/6e/28/2057abecaafdc22912afa819603a51f0a62d40643b7c4871c51721fea9be/pydantic_core-2.46.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:e80675d75ae2cd14372cb65cad5400d9347a3d3f6c13000183f22dfd027283ed", size = 2309588, upload-time = "2026-08-28T09:59:06.048Z" }, + { url = "https://files.pythonhosted.org/packages/71/9d/881156dc404e27479c4246128d73538464cab4a239bec61995e227644c30/pydantic_core-2.46.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:9c4b71f10dd532fb7a5cbc8f58707779e64f03a258c2bf8bfbaecfcd9970b519", size = 2341866, upload-time = "2026-08-28T09:59:08.539Z" }, + { url = "https://files.pythonhosted.org/packages/5a/38/d66f443a259f84d13babdceae568e572b0ed26da17ca5d0a649ebb110a67/pydantic_core-2.46.5-cp313-cp313-win32.whl", hash = "sha256:97bf8de4d541598c94a59344eeb988a94c08ff76b5723c41f6567ec18c7892ea", size = 1938580, upload-time = "2026-08-28T09:59:10.402Z" }, + { url = "https://files.pythonhosted.org/packages/2c/1e/1d5371213f4cc9a7ed70c0bfcc7911de22311ee99a662a56077d7292d2ac/pydantic_core-2.46.5-cp313-cp313-win_amd64.whl", hash = "sha256:15f4a94963c95accac15b7b657bb177d3ad82bb90b0d0526d9a9b85079925db5", size = 2041980, upload-time = "2026-08-28T09:59:12.396Z" }, + { url = "https://files.pythonhosted.org/packages/5a/48/4222d90b1c67568bace4dec6dca6271449c66de3595d72b6d098f5fde597/pydantic_core-2.46.5-cp313-cp313-win_arm64.whl", hash = "sha256:d22a945598fb91236b4dd793a6e42e4f3dd7740bb5aace5ebd7d4c08d13bb575", size = 1997213, upload-time = "2026-08-28T09:59:14.245Z" }, + { url = "https://files.pythonhosted.org/packages/8e/8a/14596f2a8367da50cf7cbac48169ee5d9c8e11d486a3b527082384630c72/pydantic_core-2.46.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:c1c43ad4339643d70ebb8124e1305a7dab423001eff58bb41a0f731adbc98355", size = 2074081, upload-time = "2026-08-28T09:59:16.141Z" }, + { url = "https://files.pythonhosted.org/packages/ae/d5/d8a4eb6d6c7f66b91dd37c576d76e9e60fba900caf5372c17bcf949febc2/pydantic_core-2.46.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1a353f84de772f423b5ffb11d7ae352fbbef0f446f3c0b0af0f8236d7233606e", size = 1920497, upload-time = "2026-08-28T09:59:18.065Z" }, + { url = "https://files.pythonhosted.org/packages/8e/26/092079428f86e927e030b2c0ced87df69dbb1c875cdeaa67bf42ea2be746/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5086029a57366b8cf81b130a43908738095c270c21a8d7f0e8bdfdb89718e2f3", size = 1952130, upload-time = "2026-08-28T09:59:20.476Z" }, + { url = "https://files.pythonhosted.org/packages/08/c3/8ec0e290a9ebaebd64047bf5fda94be835c6b1551b02437e4b76778fbcd7/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:46c25dda9d092a06c08db76ffe0a197107904d0dfac653f7d5306bbcd6d6119c", size = 2026371, upload-time = "2026-08-28T09:59:22.227Z" }, + { url = "https://files.pythonhosted.org/packages/01/72/4fd20ad520fb8da0157f95b27a7eb05a72790ef08138e7701ac972c342ea/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:37ea7b83c935e5b0d68c9449b82651accf78a10828b2c02b2f2d9e9496446c21", size = 2202822, upload-time = "2026-08-28T09:59:24.277Z" }, + { url = "https://files.pythonhosted.org/packages/31/b0/d16e0771206b29314f0d52198b720be21e8a99ab2bf11e3bc0d7c9cebdff/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e64e88d5585bea9ce95861079de72006c7fa6d3df4e3a3b65ba31eb979c15c9f", size = 2262756, upload-time = "2026-08-28T09:59:26.608Z" }, + { url = "https://files.pythonhosted.org/packages/2c/9b/59634b7ac631c63b2a37760eb6943af3e29573d6b59a4abc5e7f019d4cee/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54d510bac3ee52247af28ed4bb18a1e799f040ac60fd2bf5ccd4c92f1fbe786f", size = 2068352, upload-time = "2026-08-28T09:59:29.044Z" }, + { url = "https://files.pythonhosted.org/packages/08/7c/570abb1ad2155348dc754ea91be22e5aaa18eb6d69a6068f7c6f2679a6ed/pydantic_core-2.46.5-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:a2a5e1d0ff29adddc9f6d6821a66302e4493f8ca898b715b6b1182c2c201ea0a", size = 2104777, upload-time = "2026-08-28T09:59:30.95Z" }, + { url = "https://files.pythonhosted.org/packages/8e/25/5bf74adc65a1ac5b7be3f6cb0bcb5433615c1598a801c19d830d84c98ded/pydantic_core-2.46.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:03b9666e41e35d8909852ba191a0607520f81b74eaf12ccf8737005dbb313821", size = 2156312, upload-time = "2026-08-28T09:59:32.604Z" }, + { url = "https://files.pythonhosted.org/packages/90/6a/2ef38830675e050121040618135564ed56b860b45433b02d9b4ebece46f3/pydantic_core-2.46.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:a91c17edf6eea2402cb5457b4c89e99bc5ed1004aa34c4adf1d4258c1a5c22c2", size = 2150067, upload-time = "2026-08-28T09:59:34.453Z" }, + { url = "https://files.pythonhosted.org/packages/90/ef/a7dbb03a14a64c2a4621f989c615ed9a892535a6cad938fc27079f919d80/pydantic_core-2.46.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b49924c73a235e969511bf2aabdff3beebf9820931f646c80274d5d780010c47", size = 2304516, upload-time = "2026-08-28T09:59:36.194Z" }, + { url = "https://files.pythonhosted.org/packages/68/f8/6bb4c4b80e8a6fde1904c64a51c62a1d04fcdfa3ea521a66b2ddefa1d885/pydantic_core-2.46.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:2cbd9a5eff05e51c447c34dfa4632145b26b09120cf04bd0c871e44c1a5e1c9a", size = 2335223, upload-time = "2026-08-28T09:59:37.931Z" }, + { url = "https://files.pythonhosted.org/packages/2a/80/f46b8c681195190b2c1f1c7c0a81abce60663e987613e09ef64d433dd96b/pydantic_core-2.46.5-cp314-cp314-win32.whl", hash = "sha256:2d5d76654becf5efd62c9e51c3756c67b49498b0c9a40884934c40807adbd074", size = 1934827, upload-time = "2026-08-28T09:59:39.836Z" }, + { url = "https://files.pythonhosted.org/packages/f7/3c/60674207246bc0a4009d2391b7c7251c7159f279c8d2ab8aae8ef46f3dee/pydantic_core-2.46.5-cp314-cp314-win_amd64.whl", hash = "sha256:fa10ef4112775900e7a0661068635eb67b2ab824fbde764de6e0e21982a93db0", size = 2042648, upload-time = "2026-08-28T09:59:41.792Z" }, + { url = "https://files.pythonhosted.org/packages/69/0c/117c562c7c1babdf44576b72a5e496906506c93690387ecfbca7c729ae2e/pydantic_core-2.46.5-cp314-cp314-win_arm64.whl", hash = "sha256:045ab3b6d308439e32b81cc173bba5b9018bc6ed896afd0c65b3b009b1699af5", size = 1989652, upload-time = "2026-08-28T09:59:43.702Z" }, + { url = "https://files.pythonhosted.org/packages/e8/66/9336ae58f9eb68c41d121894e52c4c89eccb07eb8f602a04ee9c3f37736a/pydantic_core-2.46.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8816f3d218beb4b787de5c9759c259b8fa61f9dec42dc7811f320a33771778b7", size = 2065829, upload-time = "2026-08-28T09:59:45.364Z" }, + { url = "https://files.pythonhosted.org/packages/c5/02/bc19b47a96c2d3109760711acf22369e56bd7e405ca52f7ade164d2ead57/pydantic_core-2.46.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:bce57638e08ac148e5778cce7feb968307a727d66f8e2274a543d0cf0c9ad6a3", size = 1905716, upload-time = "2026-08-28T09:59:47.18Z" }, + { url = "https://files.pythonhosted.org/packages/52/a4/70b47c0509923dd98ccfed04fb3e32ea3849c82a0ff2205bb41009b43c00/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:976e1128455aa595ea04c79ccfedff1aaeab96ee013fcc916bed120c4f0ad94f", size = 1934216, upload-time = "2026-08-28T09:59:49.241Z" }, + { url = "https://files.pythonhosted.org/packages/52/ab/aa03b65f7bb198585edf806b906c3223ecf1795543e39e23aec4cce27ad2/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b891faeedeafba41b2983e5001a81b6a915b69544c7e7570d1989ce1c36ac7", size = 2010635, upload-time = "2026-08-28T09:59:51.692Z" }, + { url = "https://files.pythonhosted.org/packages/3c/8b/0da06343f30b84ec549aafd309c6456223d5dc8bd36af504c573faad561d/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f194189415698233dd1114a093a9b56e61e2c57e11b469be3b0506f46f0771c", size = 2209369, upload-time = "2026-08-28T09:59:53.582Z" }, + { url = "https://files.pythonhosted.org/packages/d6/5b/844c4defaa34a3df66eb9257087d121d70c201298b96abdf9f492fc2f1bf/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:82a36973cf8a2ef5406f4fe2edbf8ed0c99629535d959e0b100c76a32535a111", size = 2253238, upload-time = "2026-08-28T09:59:55.484Z" }, + { url = "https://files.pythonhosted.org/packages/f4/64/a4e536cb16d7f61a7fd3120b46c577fc7fa7325992f69c4f52bc786d77d8/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdbb78909f52b981d3b2d56b97328d71eb0b974c36bd77c920123a7ebb192829", size = 2065740, upload-time = "2026-08-28T09:59:58.038Z" }, + { url = "https://files.pythonhosted.org/packages/5f/75/aaa38c6bc2d085f6605b34eabdc6a8a4e0b2e61fc9c8e6e52b28e97b3125/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:52e24eacdb536cade636aa90fb851835222becff8484b7001fdc78cb0290f2aa", size = 2087425, upload-time = "2026-08-28T09:59:59.898Z" }, + { url = "https://files.pythonhosted.org/packages/55/ae/fcab4cfc39aba3689e1d20c8b5250ad280957022c09af2ed9cd585602a5e/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:37ae34309d7bd8c0d61ab839668058f2a7962ea1fc51d105d2db228fe0618034", size = 2139306, upload-time = "2026-08-28T10:00:03.057Z" }, + { url = "https://files.pythonhosted.org/packages/2d/f4/f1d03a4bc9d9acbc62f4d742b8a319af52f71885079868b2ff8e48a651ee/pydantic_core-2.46.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:0cdbada856a1c69a7624a64d3d9aefe79300bd6ef827b43a4f265010b9b55184", size = 2144589, upload-time = "2026-08-28T10:00:05.645Z" }, + { url = "https://files.pythonhosted.org/packages/83/f3/7a53bb1356de514a4cd295f25b6ac39237895620c0462d2592b76c16e114/pydantic_core-2.46.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:545f26c504b27c3758439a5e6d9349931f0a04f855668d5fe323c89e82300a38", size = 2288882, upload-time = "2026-08-28T10:00:07.931Z" }, + { url = "https://files.pythonhosted.org/packages/cd/94/5a81583660c175c59d49ffb09f4b3a44debeaf86a19fca664ae1cdd9ee32/pydantic_core-2.46.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:ff218293c9c806138dca139765e3b067621be52bcd93cdc14c7711be7ddc90a9", size = 2335210, upload-time = "2026-08-28T10:00:10.177Z" }, + { url = "https://files.pythonhosted.org/packages/5a/9f/5d685c2693b972d1a59c998586e8823712b66603aeff47ee60a4bdaafd37/pydantic_core-2.46.5-cp314-cp314t-win32.whl", hash = "sha256:97cf3eb53a8cccacf9d46686a0926186c9bfb5574f2ed66d3639d5fe117cd3a9", size = 1921180, upload-time = "2026-08-28T10:00:12.35Z" }, + { url = "https://files.pythonhosted.org/packages/70/12/5c94ee16d65a37a15f9e869f5e6256df111154491173801a4c5e800ab548/pydantic_core-2.46.5-cp314-cp314t-win_amd64.whl", hash = "sha256:d2f9fc07a8042a8f95925b35c4f04f469707c981fc33245b6ca187cf5d2dd290", size = 2020515, upload-time = "2026-08-28T10:00:14.774Z" }, + { url = "https://files.pythonhosted.org/packages/63/19/67830dda664e6bdf9285ee2e40f355d0d7d6b92aa0c42e8d217bb8d33d36/pydantic_core-2.46.5-cp314-cp314t-win_arm64.whl", hash = "sha256:acf8a67ba51f4ca9ddbd0e6b3000a65ac51ab734661778b3e7ba64d99a710f2f", size = 1989276, upload-time = "2026-08-28T10:00:16.984Z" }, +] + +[[package]] +name = "pydantic-extra-types" +version = "2.11.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/71/dba38ee2651f84f7842206adbd2233d8bbdb59fb85e9fa14232486a8c471/pydantic_extra_types-2.11.1.tar.gz", hash = "sha256:46792d2307383859e923d8fcefa82108b1a141f8a9c0198982b3832ab5ef1049", size = 172002, upload-time = "2026-03-16T08:08:03.92Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/c1/3226e6d7f5a4f736f38ac11a6fbb262d701889802595cdb0f53a885ac2e0/pydantic_extra_types-2.11.1-py3-none-any.whl", hash = "sha256:1722ea2bddae5628ace25f2aa685b69978ef533123e5638cfbddb999e0100ec1", size = 79526, upload-time = "2026-03-16T08:08:02.533Z" }, +] + +[[package]] +name = "pydantic-settings" +version = "2.15.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/ca/31c57507b13119d7d3cfa1576dad2911a4861e3be07b579395f4e9d393f9/pydantic_settings-2.15.0.tar.gz", hash = "sha256:694b793e84f766ba76a90ebdefc01d0a9a045dab0382bee70393da93712ad117", size = 261253, upload-time = "2026-08-07T09:24:57.419Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/a4/2bffa9f8e804325a09867f0e9d30795c80ea9f8d62560bd1b6ad6220eb2f/pydantic_settings-2.15.0-py3-none-any.whl", hash = "sha256:0ba092c291c94baceb5eff768aa0d56400a457585bc0175925a5a5510303da42", size = 69413, upload-time = "2026-08-07T09:24:55.839Z" }, +] + +[[package]] +name = "pygments" +version = "2.21.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329, upload-time = "2026-08-17T08:02:48.824Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" }, +] + +[[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 = "python-dotenv" +version = "1.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/53/ed9d74092561d4b01a2ef1349d52cdbc135e526c245f366b089cfca6de49/python_dotenv-1.2.3.tar.gz", hash = "sha256:a20a594dabeaa385725aa239d5244871c143ecb356add8a20fcf23773a6c3a35", size = 58945, upload-time = "2026-08-16T16:54:54.067Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/17/c5c6b53ddc18f297992099b3d9ec16c855c0ccc83263a21fe4d1c625ec6c/python_dotenv-1.2.3-py3-none-any.whl", hash = "sha256:904552145e8bfed22162c09dab1c2b9b54fefa7b23ba780f4f26ca0316b0f0d9", size = 22780, upload-time = "2026-08-16T16:54:52.473Z" }, +] + +[[package]] +name = "python-multipart" +version = "0.0.32" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/42/55c32bb9b12693c092ad250a0e82edb5b31ddeda6eb772de5f308b3804ad/python_multipart-0.0.32.tar.gz", hash = "sha256:be54b7f3fa167bb83e4fcd936b887b708f4e57fe75911c02aebf53efaf8d938e", size = 46881, upload-time = "2026-06-04T16:18:58.647Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/04/e8135ebd1ad02c56ec633277529b2602ff99ff634be76cdba5744cf554fd/python_multipart-0.0.32-py3-none-any.whl", hash = "sha256:ff6d3f776f16878c894e52e107296ffc890e913c611b1a4ec6c44e2821fe2e23", size = 30042, upload-time = "2026-06-04T16:18:57.319Z" }, +] + +[[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/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 = "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-oauthlib" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "oauthlib" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/f2/05f29bc3913aea15eb670be136045bf5c5bbf4b99ecb839da9b422bb2c85/requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9", size = 55650, upload-time = "2024-03-22T20:32:29.939Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/5d/63d4ae3b9daea098d5d6f5da83984853c1bbacd5dc826764b249fe119d24/requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36", size = 24179, upload-time = "2024-03-22T20:32:28.055Z" }, +] + +[[package]] +name = "rich" +version = "15.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" }, +] + +[[package]] +name = "rich-toolkit" +version = "0.20.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "rich" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/1c/f134352beb393cc17e6241ecf0bf4dd41a6759e2e3971a69a6ad185b87a2/rich_toolkit-0.20.5.tar.gz", hash = "sha256:0c9e1c414ffb0720be26285d472e263d1e704b71d31a7e13274b9996db4969e1", size = 213207, upload-time = "2026-09-08T16:13:12.007Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/5f/ee8c39750ed9837f54021a8a0eb96f971cd788c1a7d40931867d6d0518bd/rich_toolkit-0.20.5-py3-none-any.whl", hash = "sha256:e21fd616db9c0539d3c50f3433fb1173db95c19d3cbf215615e224182fad6c18", size = 39370, upload-time = "2026-09-08T16:13:10.885Z" }, +] + +[[package]] +name = "rignore" +version = "0.8.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ff/7e/aa0640d74f6b4bb68466f5899bd5ed1680480732344c31a408504e215801/rignore-0.8.1.tar.gz", hash = "sha256:2b6cf58501e9ff1b6a71c3fd66c8a105311e1f23237626fd4c9c00606bb3d30f", size = 55535, upload-time = "2026-08-04T22:27:08.237Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/b7/ecb7c0b91c8d68b313e3c41796860450b3f99d70ba2473d35f0cd3d1fc28/rignore-0.8.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:16b17b02d3e6ff3b88583af4809b64e14c77cf4de195763f063e5e77d70cd112", size = 847304, upload-time = "2026-08-04T22:23:54.247Z" }, + { url = "https://files.pythonhosted.org/packages/c2/78/71063269a004ab6759682cdc2c8553d1a42bab56aa4bf7f4ffb1468913a7/rignore-0.8.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:58d7172259fd45d8ba00f3e02af5af2113f547f4a7fefab42cab567aa7c999fc", size = 815633, upload-time = "2026-08-04T22:23:55.584Z" }, + { url = "https://files.pythonhosted.org/packages/83/94/d87afacf13f5e32a844ffdd2d906a3c8d5ce24de0a91ff02a605322f34bc/rignore-0.8.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c5ce3b10ce4b716abc535bc0fdd66b0b9fa9f5b1987b36d3f5ece7d0e2a9a81", size = 884581, upload-time = "2026-08-04T22:23:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/cf/ca/ea796f9ad84f8b671d682e27bd1a60a7679b844faaf582b11f7e527967f1/rignore-0.8.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c199fa2d4a898e9b686d846371ead3c8e08d3e29f78e2ecabf4c580820a8f764", size = 856803, upload-time = "2026-08-04T22:23:58.456Z" }, + { url = "https://files.pythonhosted.org/packages/75/71/0f3e6d0c421c7a5f998a21d3314f76f26793bc3af851a5e43b1e82d290aa/rignore-0.8.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7136b7ff29c37c8ec8effa3c59e27839482ba542af94fdc2581a59405e99037", size = 1133897, upload-time = "2026-08-04T22:23:59.923Z" }, + { url = "https://files.pythonhosted.org/packages/20/7a/0861528542be468f493e809064842fbb595706c0adce02d3a6e56f15ad2e/rignore-0.8.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32b0fcd01495cc4f4d10b5307f9f15818c9db752cfbd7a9ecb00b0f129a70dc2", size = 914178, upload-time = "2026-08-04T22:24:01.316Z" }, + { url = "https://files.pythonhosted.org/packages/1d/19/769c0a832d0f0a8d368f09ef7dee4caf53d46f391de489532bf44e1eee1d/rignore-0.8.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ca91c91a53135889945e77b286215ecd41c8f1090398db97ca3459c5a290eb6", size = 928035, upload-time = "2026-08-04T22:24:02.735Z" }, + { url = "https://files.pythonhosted.org/packages/50/e9/76cf08722a4d93836f7d416877f1a0d8e9a456f2e145ab57f12b301ccf70/rignore-0.8.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:67e30c0883f9aef3bcc45e5dc6980bc41f161da6c1b1a460f0bd87064c1d6594", size = 893393, upload-time = "2026-08-04T22:24:04.306Z" }, + { url = "https://files.pythonhosted.org/packages/d4/42/ede8c973b1f979b81a4cc48530107538aa9ec7df97c98759415f958bb0eb/rignore-0.8.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3012fc79b19953f76a67b2e6bae5f456742e96f0ed33d3c6c9390b0586fc2da9", size = 964677, upload-time = "2026-08-04T22:24:05.965Z" }, + { url = "https://files.pythonhosted.org/packages/57/22/70af384dde865285d71f5deccacedc89232a7e04fed558d035dd31d001af/rignore-0.8.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3a437c870f1465aba36eb4ac7108c09d5097c436123fdaec980e7a26a4595141", size = 1060291, upload-time = "2026-08-04T22:24:07.354Z" }, + { url = "https://files.pythonhosted.org/packages/9e/93/1c661799fb7270c55122efa0ac6211fb49b066579095765db90faed06af5/rignore-0.8.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:59f4f92ec5165619b3bc58130ab558bc618f9c5e9df06807339a85d965143fd5", size = 1131079, upload-time = "2026-08-04T22:24:08.708Z" }, + { url = "https://files.pythonhosted.org/packages/59/62/95655b6d2c6d2e82414f431b06fd7a3e6198adedfaf60de2b924c8f8d344/rignore-0.8.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:304ddf5f807c788c08a8210f9977131e7bfa9aa94704668d127689c00279ef41", size = 1140860, upload-time = "2026-08-04T22:24:10.149Z" }, + { url = "https://files.pythonhosted.org/packages/3e/bd/efd83ece6d828f908c751422849ef219847e084fb749f08aeb294e3a48d8/rignore-0.8.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1ce5c6d8f02badc55112b014d4fc9af662f0649869913d25785d7b3676ac19ed", size = 1139561, upload-time = "2026-08-04T22:24:11.633Z" }, + { url = "https://files.pythonhosted.org/packages/49/ae/7609946e2f17f4d820f53a50e683702806a21d313939a6bb2cb5478aebd6/rignore-0.8.1-cp313-cp313-win32.whl", hash = "sha256:711369d017f8959e3a253dedb76f6d2f43eb669a4228b2c1b796e30a5370ca36", size = 637370, upload-time = "2026-08-04T22:24:13.165Z" }, + { url = "https://files.pythonhosted.org/packages/ca/46/b14c894ddc21feda8ca21810c7227de567441d90602f6e4620d623b958be/rignore-0.8.1-cp313-cp313-win_amd64.whl", hash = "sha256:0585eafcacf15f8f2764b8d03a579761296e8c51dd0558f8eb59040810ba8404", size = 727769, upload-time = "2026-08-04T22:24:14.487Z" }, + { url = "https://files.pythonhosted.org/packages/99/49/cdcd08577d5ddfc31c32f5b14528eba9dcb361ae652eed7f2d0207e313c0/rignore-0.8.1-cp313-cp313-win_arm64.whl", hash = "sha256:a8ddf078c4b5da0665a83f7b558dee2d746293ffcce3dfe8786015aed0aac4c2", size = 664478, upload-time = "2026-08-04T22:24:15.804Z" }, + { url = "https://files.pythonhosted.org/packages/3c/13/7ceb0fe7a5a5a67e7f1cee65d8a9b09c52df53a4ff406d14940130a420b7/rignore-0.8.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3054fab93e2d2ad483cb89417f7b883cee025cb22286eb824af0073f2ffd5f7e", size = 849303, upload-time = "2026-08-04T22:24:17.126Z" }, + { url = "https://files.pythonhosted.org/packages/ba/87/fe151f1a3d93483b36e22e8a0e84382cff3b7fe9d7347cfb586727054c56/rignore-0.8.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:60f1ae9eaa51b50d5afe99bd25ffb2cfc10729701ca08c179489555d2614f3ae", size = 817293, upload-time = "2026-08-04T22:24:18.584Z" }, + { url = "https://files.pythonhosted.org/packages/d4/08/cb82a725f040cc5d106a1fe33dec4031fb9d8863a4874a8dfb60c4db79f9/rignore-0.8.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6012766ea3a5a635d9b79f3e8c3797d5e47ce5e5dd81993c9f92a3ea4ff68b4c", size = 885260, upload-time = "2026-08-04T22:24:20.236Z" }, + { url = "https://files.pythonhosted.org/packages/06/77/6ba8d24fd151513347d9420fb383ec732b54886c6a5500e13f0bd7ec4072/rignore-0.8.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:90e60f0073caae0f1d59c993adedafa3a57bc6fac551669677cb25aa7fa9d9b8", size = 857548, upload-time = "2026-08-04T22:24:21.769Z" }, + { url = "https://files.pythonhosted.org/packages/47/66/c54f448b077e89de99e85746677425fbb3a62d3721aeb9afa556dc3ca10f/rignore-0.8.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:121ab7ac93e39fd1d70098461c1ed9a6fb89d54e7bf8c60ae351b23b05cbb8c1", size = 1135817, upload-time = "2026-08-04T22:24:23.179Z" }, + { url = "https://files.pythonhosted.org/packages/c3/92/4b47da84a36687f10c09b43de7600d7dde8f56f2981e68b8822cfadd26aa/rignore-0.8.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:befb772556c8463c640b290f632b57440182edd39996708a33c50bcf437796f9", size = 913513, upload-time = "2026-08-04T22:24:24.719Z" }, + { url = "https://files.pythonhosted.org/packages/2b/a4/8428734c0217b5c0adbe752afc9603ca34434c6033584f63d6ca7ba50331/rignore-0.8.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a184fa45db8cdc7a8604d2df020a107be3fc0adc2e522b4e4eb5cd5b57d5f84", size = 929324, upload-time = "2026-08-04T22:24:26.059Z" }, + { url = "https://files.pythonhosted.org/packages/da/f6/ab3738e42d9d351033b8e8fb46ac4b8eb193545aaf3ed2f52f58fb8d5212/rignore-0.8.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:e68a572efc126aa45195f1581a5ea97c4e36eeba6874a6635ae44daa4fbec7a4", size = 891130, upload-time = "2026-08-04T22:24:27.374Z" }, + { url = "https://files.pythonhosted.org/packages/1a/ba/9f374cf332eeea0e26cca6b6550678d0c69208e7ba4aea8ece0dc6b205eb/rignore-0.8.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c75ac1952ca8892422de328a925f4804120b149543042313bb3af7cbfdd65d64", size = 964769, upload-time = "2026-08-04T22:24:28.688Z" }, + { url = "https://files.pythonhosted.org/packages/37/46/7ee48265e34cb90ef4bcf85c63874da6d6a31af1c63fb7bca84fd5dbc188/rignore-0.8.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:3538084cef4a66ba3fee7c453d17db7cfb32a6653456381b62afd8d53090d6fa", size = 1061779, upload-time = "2026-08-04T22:24:30Z" }, + { url = "https://files.pythonhosted.org/packages/01/23/51c725ba23f06b2809a0a676127ca3613e852bcab0532e58cab9f09b6fbb/rignore-0.8.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:28a3baab3d1b7ea42c38e492d4a100de3e5c67217432364c62b5719e0f04e96a", size = 1132595, upload-time = "2026-08-04T22:24:31.472Z" }, + { url = "https://files.pythonhosted.org/packages/0e/a0/671192e2cc8711694cf564f77e9e59d5e8c41f100bce67fbb384f2befec5/rignore-0.8.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:74996d8ed1a494ff8e61d9194a0dd3637e6b19a582cbed6fe0e1a4cc60e7b266", size = 1141117, upload-time = "2026-08-04T22:24:33.148Z" }, + { url = "https://files.pythonhosted.org/packages/01/df/b3a9a821b37361debc29fd009d21dc4af7f8313b76371c1640aff2765a59/rignore-0.8.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a2e5df9ba53e502f676b054c4d12371273204e2b9c6ec29afad3a155b9ad3399", size = 1140515, upload-time = "2026-08-04T22:24:34.708Z" }, + { url = "https://files.pythonhosted.org/packages/0c/69/bcf0f38713c86d462002c47b0ad14d655f8766a3b9235d8f6813e8245695/rignore-0.8.1-cp314-cp314-win32.whl", hash = "sha256:69db47c2fa51d88e93b4e4de4f44220ce7a9337e4b7526a16f5660b19974a8ca", size = 637894, upload-time = "2026-08-04T22:24:36.081Z" }, + { url = "https://files.pythonhosted.org/packages/44/f8/0c0e0bb59aea492e288d5c058ad873599d43dd5674495033fe9e8444b80d/rignore-0.8.1-cp314-cp314-win_amd64.whl", hash = "sha256:1c668a541ecd8af3d7bd06c48c62e1c3e0a755028d57c07cee697a25d9b0678d", size = 728394, upload-time = "2026-08-04T22:24:37.496Z" }, + { url = "https://files.pythonhosted.org/packages/29/7c/c23926385e01d9b99fdf7bb7b86aaa60c2c1eb4de2a6303549f08497a0fa/rignore-0.8.1-cp314-cp314-win_arm64.whl", hash = "sha256:d63559131efa9cbba82494ec8698e38bea403f214969066c661b3eb4da4ad4f9", size = 667896, upload-time = "2026-08-04T22:24:38.826Z" }, + { url = "https://files.pythonhosted.org/packages/38/9d/a70a895b47fa30446ffadd0a7add23494e18335ddc85c27cbc5a740374c7/rignore-0.8.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:b60f3751f681a12798927d51d1d6a46b81fe7f9923a513dd8fcc7947d785e12f", size = 847278, upload-time = "2026-08-04T22:24:40.329Z" }, + { url = "https://files.pythonhosted.org/packages/d9/cc/24fe37a08fd2083882422509c31bb58c14d85f2c7ab3b4ef3cc6f430d807/rignore-0.8.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d7d56bf138418e31991ce17b738c491fa1ac098bfcc8c3ba67fc0faeecfed357", size = 815968, upload-time = "2026-08-04T22:24:41.975Z" }, + { url = "https://files.pythonhosted.org/packages/a9/3d/1a260407a17e062859995d9f557b4ccbb36e6a0ef44a0d99409c836e5942/rignore-0.8.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f020b018577d0081a2b23da199d22c58f4cbd63935c8e991adffbb1a755b467", size = 884941, upload-time = "2026-08-04T22:24:43.414Z" }, + { url = "https://files.pythonhosted.org/packages/b3/80/70291634f2f2257fcabe1156926d7a10a87b0da1a8ba9ab2cafac37a4ded/rignore-0.8.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4d4a70c9b857657c3a542fd367e16ae106fb6e3e4448cddb30e4edc604d5a025", size = 856667, upload-time = "2026-08-04T22:24:44.769Z" }, + { url = "https://files.pythonhosted.org/packages/1d/84/3811795d0b6fb3e8865d6a4f4eb3d3184e68a8557d7ee5b6d5a463196f54/rignore-0.8.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ab668664e6388afc08fec50186ec21daf17c738ee71bb039d12cf5f14e964dde", size = 1134272, upload-time = "2026-08-04T22:24:46.213Z" }, + { url = "https://files.pythonhosted.org/packages/59/51/6a60ea8291346459e3f788bac4ecc9711b13271804c6eef47dc2ec59e6e7/rignore-0.8.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a3454cdd8bc145fd055dd45f650ccf1509e7b4edfc720152eb2be594b230f03", size = 914263, upload-time = "2026-08-04T22:24:47.604Z" }, + { url = "https://files.pythonhosted.org/packages/50/72/620d4baa44e44ee53ae516efcb714183fd592ba923dc7990bf57947ff610/rignore-0.8.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:425b962f3d68b86ebb785409153708a38b0658542c918ee706ea433e2547c805", size = 928135, upload-time = "2026-08-04T22:24:48.987Z" }, + { url = "https://files.pythonhosted.org/packages/50/60/06495efc96c2edcda647dbf7db770ab9307459d3b2f8598d9ae1f1ce9ea5/rignore-0.8.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:5094965794d163e1c4a71dccf3d1c3b3df86802e3afbfa461b300aa923cd7ea5", size = 890677, upload-time = "2026-08-04T22:24:50.318Z" }, + { url = "https://files.pythonhosted.org/packages/ee/92/396f751e69d543bfb7e9faa5b0ae3ee988fcd76d9c743857fdb26db0651b/rignore-0.8.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1467756e8454d3f816131bbad8b0efc52b7f6924e9b151f2962cec9e2f4af706", size = 964291, upload-time = "2026-08-04T22:24:51.836Z" }, + { url = "https://files.pythonhosted.org/packages/4e/0b/642483058dfbcbfc319d9cf48a05d8dbf9f7f6939b410eebdbc73dd9414c/rignore-0.8.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b5acf12993258f0eac4db81c30bed5469ebadd1b5bb2863c985fe0d802d94e0f", size = 1060972, upload-time = "2026-08-04T22:24:53.921Z" }, + { url = "https://files.pythonhosted.org/packages/64/2d/69b54a53d94baf2fb16ea1246f96bf093aae6a0e5d5c5cf5099c1fee4fb8/rignore-0.8.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:329ae0bea9598541cb818bbecb1e1f56fc1faae77828ebb3846ef2b24a050d05", size = 1130938, upload-time = "2026-08-04T22:24:55.395Z" }, + { url = "https://files.pythonhosted.org/packages/bf/36/c213d838c3c2237da8c51163a6c55acaeedc59759d4884d81ab6d7250721/rignore-0.8.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:4069fcdff01999cd2d5a426eccda45a8b31a3e0eeee8b5a9da5a452714cbb2c7", size = 1140646, upload-time = "2026-08-04T22:24:56.741Z" }, + { url = "https://files.pythonhosted.org/packages/96/70/28125520727d72e2878c1a6a3dbd333e57f27730b463d629d170e2da3b62/rignore-0.8.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cf28097a83e1237f87e11e33fb12def022e103cab2a20340a19753e8e222aebf", size = 1139989, upload-time = "2026-08-04T22:24:58.127Z" }, + { url = "https://files.pythonhosted.org/packages/7d/e6/8c0d1f2fa6d9b2f15e6a4bd20e7bfc8f4af436b8328ced782deda0a46057/rignore-0.8.1-cp314-cp314t-win32.whl", hash = "sha256:c978f26c25e66c118da1e6218f34cc5ce2bf9c5cc3969011cde47bacbfe4a072", size = 640012, upload-time = "2026-08-04T22:24:59.597Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c4/ef25f5b8d65ddb0e5266b13892c0c996dc221faac5b76a7b6fff4295ce44/rignore-0.8.1-cp314-cp314t-win_amd64.whl", hash = "sha256:790e14dba9e1f8a532071d64e1c2fa7529c71db75996e95519d06d3378cbbcd5", size = 728195, upload-time = "2026-08-04T22:25:01.358Z" }, + { url = "https://files.pythonhosted.org/packages/e8/6a/603b522d76a3acd66952e20e2ad7fa84c09d89436d707d6bb9ecb5ae55d3/rignore-0.8.1-cp314-cp314t-win_arm64.whl", hash = "sha256:484c0a4803a1eb9097d875df3200e60e58cbf316d4d40357f92e8e757ce529dd", size = 666282, upload-time = "2026-08-04T22:25:02.75Z" }, + { url = "https://files.pythonhosted.org/packages/ac/fb/b8de8a872bdb9150aa55783273f73e9aa253951d8f82856fe916fce3cae8/rignore-0.8.1-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:1c2a29e7f54e879fa54982887de480b2cfa6e3cb51f01d70dd4e4035f5b79670", size = 849669, upload-time = "2026-08-04T22:25:04.116Z" }, + { url = "https://files.pythonhosted.org/packages/b2/24/4ca7ce428378e7ef938cb36573f22e842dcdb2655e266b32711eea08d16f/rignore-0.8.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:58449c827c36dba68133a5ee4137b67d7d6e050234a95c3a8f5e3cefbd6970ec", size = 817738, upload-time = "2026-08-04T22:25:05.489Z" }, + { url = "https://files.pythonhosted.org/packages/38/5a/78311079595c96e01a2404eff725468e25fad16d2836408046ffbdb33a69/rignore-0.8.1-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e250726b08957aabcf4e28aef4ea18bcccbd95f240cebb75a74b424f067dce4b", size = 885906, upload-time = "2026-08-04T22:25:06.956Z" }, + { url = "https://files.pythonhosted.org/packages/fe/57/7d456d2dcc881e6bf08275222ae36fff58343ee0fc47e1b26722f97b38ea/rignore-0.8.1-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:62470f4d70d83f124381975614ed7c7db6f5c5fb4d777ee88f7bdb9a7d14b65c", size = 858240, upload-time = "2026-08-04T22:25:08.371Z" }, + { url = "https://files.pythonhosted.org/packages/64/3c/0f9bd7b9b3af37b216096e1dc81f9368d64c13625ddd34b4d595b316b47c/rignore-0.8.1-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d886d6bbdd0a1a3ef73bd38d6768cabc9b30bfd1be7b157b27a4ac7ec6c5244b", size = 1135145, upload-time = "2026-08-04T22:25:09.81Z" }, + { url = "https://files.pythonhosted.org/packages/83/06/f0087a520e3bf3d9532aa823ea141e5a076432b9d036bb506a99ddb534c2/rignore-0.8.1-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7ed31657a20df59b0bd63283d278e8678749aa0154ba98fae0838a9845599695", size = 914331, upload-time = "2026-08-04T22:25:11.369Z" }, + { url = "https://files.pythonhosted.org/packages/f0/65/8ac37040f162cb965520286ef48472a87f47cb137df4068a0d32634166dc/rignore-0.8.1-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e425a42f6601bfed266f76f3294bca48efcfdb10c3c0c279fb2f977b1e2cc2bc", size = 929202, upload-time = "2026-08-04T22:25:13.057Z" }, + { url = "https://files.pythonhosted.org/packages/e0/93/19b752fda8a56424ee156ef6a9799c582eb6d2ffb233c7b91e103b79d54e/rignore-0.8.1-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:ff09db59f672d929bca88ee7089d3697256967df76a2bab8b187208f2b517bc0", size = 891752, upload-time = "2026-08-04T22:25:14.773Z" }, + { url = "https://files.pythonhosted.org/packages/a9/94/a5db364ddb136360c7c4ba75cb56ba2f7cd8321b296602f6ea7438fc1a83/rignore-0.8.1-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8dbc5898945c027dc0ae451de0c983e20bd8fa8297f40ee38d0ce1a3ed924d4c", size = 964714, upload-time = "2026-08-04T22:25:16.416Z" }, + { url = "https://files.pythonhosted.org/packages/f6/11/63316cd5a87402a42c75b6ada0331745975f0274cabb6a8aa85c043468ab/rignore-0.8.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:1160abcd855964a9dd69f3d603ef57be14b1ea51ebaf50b07f737a3f3a8b89a6", size = 1062141, upload-time = "2026-08-04T22:25:18.094Z" }, + { url = "https://files.pythonhosted.org/packages/91/6e/19b4bdde3f53bb0c9427d65544f92f864a892d695559423ce01fd14c1a31/rignore-0.8.1-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:be994859b2cbbc69338351bc9908dd7d049de232e6eab5e998ec1feac3faf785", size = 1133487, upload-time = "2026-08-04T22:25:19.855Z" }, + { url = "https://files.pythonhosted.org/packages/1d/d3/fc41ea3164001fe84a2ec8b84d7bb48e7a73ebdcac6d68961c80f874dd4e/rignore-0.8.1-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:f52098f3245c7557229d8253ea3a53a03de03704685aba8a1ccd24a5f004db70", size = 1141434, upload-time = "2026-08-04T22:25:21.316Z" }, + { url = "https://files.pythonhosted.org/packages/34/35/b9d415a7e90c37b28b2d5fd6a074a4a3d572d5a0e0b596df0b4b85af8a78/rignore-0.8.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:c676080bb70cbd5429052bb5a3827b35543876a42c792c6d713d9e162bdaa00d", size = 1140492, upload-time = "2026-08-04T22:25:22.763Z" }, + { url = "https://files.pythonhosted.org/packages/81/9b/49ac5f66862c796a04f0b7aad47ace4dea0a3ef52d313d2fb154821dd88a/rignore-0.8.1-cp315-cp315-win32.whl", hash = "sha256:ec800546b960d5044d2468d22dd0689210e846350c42cc9f02519c2a2528b3ef", size = 638365, upload-time = "2026-08-04T22:25:24.34Z" }, + { url = "https://files.pythonhosted.org/packages/38/76/90568f3af555074c5e1936b80573539658cda1dce4e89dd87f3174d88aa8/rignore-0.8.1-cp315-cp315-win_amd64.whl", hash = "sha256:4225f2d2f0b3e3c39a815f15dbe551f39f0d7f82e2a92d16bdbdb07ff4b6718e", size = 728711, upload-time = "2026-08-04T22:25:25.815Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e0/3afb8bb7c6c538615d58c97f6e3b9ff7071f19442cd9cfb6f3ae6bbe0bee/rignore-0.8.1-cp315-cp315-win_arm64.whl", hash = "sha256:42480707cdd5f92d6b1252faada600e03ab22fbfd53b124c6f7b33c21e06b6cf", size = 668102, upload-time = "2026-08-04T22:25:27.251Z" }, + { url = "https://files.pythonhosted.org/packages/26/08/ce533f5c0b677dd7e82a3461e86afdf0cd99db9a73f3f561d1fabcd03fc8/rignore-0.8.1-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:51e608acd3c1aa834f2b7d956ff5380a0a6b6b595df80e665f2a2889982f1855", size = 846662, upload-time = "2026-08-04T22:25:28.718Z" }, + { url = "https://files.pythonhosted.org/packages/9b/43/4bb308ac5751881d7ba600e93d7f2e4a2c884af5028ac0ced4f1274c09c3/rignore-0.8.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:6602011b6e6ecd157f3b0a0a25b1777b40c541ca21de6ebe2137af713d0efa15", size = 816657, upload-time = "2026-08-04T22:25:30.177Z" }, + { url = "https://files.pythonhosted.org/packages/b3/f1/10a11bf9ebf03f7a8d9876ff023634cd4ce61845be9929f816ece4dfff11/rignore-0.8.1-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8db2be0d49175d5db4cd503f8dc62cbe687b9ebc71af4cede1c8b40bab8f4f4a", size = 884975, upload-time = "2026-08-04T22:25:31.906Z" }, + { url = "https://files.pythonhosted.org/packages/1b/97/c8dddb2dab738bd1b3a0b1639bc42d696bdd2765515a302620a35795c475/rignore-0.8.1-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:601ff49d8458a21d745908e35f792c02b45ecae4a37fa3cd1fc03fa065962bc0", size = 856951, upload-time = "2026-08-04T22:25:33.354Z" }, + { url = "https://files.pythonhosted.org/packages/a5/1d/ddcc1cddfb0a79c82eedbcd25ba0b014d346251cc08c1938b90951660cf8/rignore-0.8.1-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb58e93997d546fad4cfcd730a54d7c12cd7a61fe7a9b31bfc75402403dc559f", size = 1134401, upload-time = "2026-08-04T22:25:34.845Z" }, + { url = "https://files.pythonhosted.org/packages/13/0a/519d2e081e0b89c705bb4b282ed7540c185accc748c97c40dd80a333eb3a/rignore-0.8.1-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0378cf77b8d64560e0cb433deada18438b36ef7933dd284dd65347d03c56c429", size = 914389, upload-time = "2026-08-04T22:25:36.658Z" }, + { url = "https://files.pythonhosted.org/packages/ed/aa/c1e857c4b8f9dd388c38c8ca430917f24f1de44d9279d4f5d9edbcc94a0c/rignore-0.8.1-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0097a8c35106997d2b52851f0888777efe10e34772140a9fdb018b2f99238159", size = 928845, upload-time = "2026-08-04T22:25:38.421Z" }, + { url = "https://files.pythonhosted.org/packages/5f/9c/a9651d49fbf2a336074df0a09d01ee2d968dae902e88e51e32d822f5e9fc/rignore-0.8.1-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:2a809a250f1532b93dcc52e173ee71adcafdb536a125532687666047c4537ac0", size = 891090, upload-time = "2026-08-04T22:25:39.966Z" }, + { url = "https://files.pythonhosted.org/packages/53/98/d3242c45edfdc059fb80b73be9e27dac34639da40002ebbcd5c9b9acb821/rignore-0.8.1-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:580e019787032b430b857335a66db53d7ae0a200586b3a6b5d3ef227648300d6", size = 965283, upload-time = "2026-08-04T22:25:41.796Z" }, + { url = "https://files.pythonhosted.org/packages/56/37/e19e70395c2e5a06ba67e0a51e86ede1b4269b8d3ba091963c4f0a950f63/rignore-0.8.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:f922dcf01e6a7ad26adfcb7f42635ae63066b883d409876129db62297061327e", size = 1061223, upload-time = "2026-08-04T22:25:43.782Z" }, + { url = "https://files.pythonhosted.org/packages/bc/bd/24b737f97c542e4883cfcc557b08565adc7c698e30836b8bc9855676d749/rignore-0.8.1-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:4900bd1ba8938e5a5e601306504c7b8169783a2d44a8396c8fda4a9b659eed51", size = 1131772, upload-time = "2026-08-04T22:25:45.391Z" }, + { url = "https://files.pythonhosted.org/packages/aa/8d/bedc51ca98696998797af67163c039e0adc7835035fb929f10d33fb3d997/rignore-0.8.1-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:3d581bf107490abae4d40a9aa823a79e18c88a21658514ccaf95e42e85d278f7", size = 1141631, upload-time = "2026-08-04T22:25:46.875Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9c/946e55a7e5bc7b165faed3475451683580ddf8b45d63e67c39a0cdee5ce2/rignore-0.8.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:78490b93aec14a87fa4c23e634b1d1dcb2bf5dcb90923f3b03a3428892a13d9f", size = 1140328, upload-time = "2026-08-04T22:25:48.447Z" }, + { url = "https://files.pythonhosted.org/packages/b9/02/211daf0a7f957386d2223ef7a1601d36b42a6c1a6b93aae32db712868390/rignore-0.8.1-cp315-cp315t-win32.whl", hash = "sha256:e8315151a83b982fe972420372e80c550616095181d1549faaa1ec31f363f5ae", size = 640253, upload-time = "2026-08-04T22:25:50.025Z" }, + { url = "https://files.pythonhosted.org/packages/ca/0e/ccb682455ca380cc640c2a9536390fd907cc5ac0396118643f9d386e4af5/rignore-0.8.1-cp315-cp315t-win_amd64.whl", hash = "sha256:69be37202a052d9e13affed6b774f86d8bf3b417b15d54ad737033e56da07ec0", size = 728332, upload-time = "2026-08-04T22:25:51.937Z" }, + { url = "https://files.pythonhosted.org/packages/eb/ec/a2b634938c39600205dcd37015343e779dc8f112c234d905666dec2a66fd/rignore-0.8.1-cp315-cp315t-win_arm64.whl", hash = "sha256:4268e83bcb88f240ed4f43d73e7de9e4bcd8026bdfcfa836567d6c29863e5361", size = 666611, upload-time = "2026-08-04T22:25:53.857Z" }, +] + +[[package]] +name = "sentry-sdk" +version = "2.69.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/10/16/85874f5e51f8d0767ee8c4b4c460c5ea2bc8a1b613d641d9d9577ba39d3a/sentry_sdk-2.69.1.tar.gz", hash = "sha256:f9284b417540b0784b994fa021eb6f1e30ae1cce593d83541274d03c93966eff", size = 1043599, upload-time = "2026-09-08T14:18:19.505Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/eb/17040f8a60c300fe4cc349088d3e351b88812f478b57a0af05a49af7cb7c/sentry_sdk-2.69.1-py3-none-any.whl", hash = "sha256:2d2556d9a14db548982b914cbed2a2dc07b6e55d941a620752f89a2afccfd78d", size = 528587, upload-time = "2026-09-08T14:18:18.151Z" }, +] + +[[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 = "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 = "sse-starlette" +version = "3.4.11" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "starlette" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2b/54/6767bb789b2f2fed6e0f953df949cd39dc263a384c1b65a95232598621d6/sse_starlette-3.4.11.tar.gz", hash = "sha256:1bae716c02f3e6f294be41ff333220692dae7c3cbab077c900f159676719dade", size = 34972, upload-time = "2026-09-05T12:11:04.607Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/6a/2ba3ed4a69babf3afdddf7d8314a48d87562c0a442206bbc2a1b50d5efc0/sse_starlette-3.4.11-py3-none-any.whl", hash = "sha256:c7b2244bdff016fe7f64e10075e89a3e6bbf899649cc89b0fe884b5545042453", size = 17122, upload-time = "2026-09-05T12:11:03.195Z" }, +] + +[[package]] +name = "starlette" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b5/b4/205b0d5241d934e8add0c38aa924c4f9fb7330834ff11e5444db964ec3f9/starlette-1.6.0.tar.gz", hash = "sha256:d4e3ac5e546444960c710297a3c9fc3f7ebae1b7e963f3d36173b49da535be9b", size = 2716969, upload-time = "2026-08-08T18:27:57.512Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/cb/6a6a47d5b464bd08695d254f3da6e7986cc70c9fa5d778eda57538edfe56/starlette-1.6.0-py3-none-any.whl", hash = "sha256:a86dd39d14bb45f85a3d18525215a9ef0cfd1f192ac793220e72598c90335f0c", size = 75969, upload-time = "2026-08-08T18:27:56.196Z" }, +] + +[[package]] +name = "truststore" +version = "0.10.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/a3/1585216310e344e8102c22482f6060c7a6ea0322b63e026372e6dcefcfd6/truststore-0.10.4.tar.gz", hash = "sha256:9d91bd436463ad5e4ee4aba766628dd6cd7010cf3e2461756b3303710eebc301", size = 26169, upload-time = "2025-08-12T18:49:02.73Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/97/56608b2249fe206a67cd573bc93cd9896e1efb9e98bce9c163bcdc704b88/truststore-0.10.4-py3-none-any.whl", hash = "sha256:adaeaecf1cbb5f4de3b1959b42d41f6fab57b2b1666adb59e89cb0b53361d981", size = 18660, upload-time = "2025-08-12T18:49:01.46Z" }, +] + +[[package]] +name = "typer" +version = "0.27.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "rich" }, + { name = "shellingham" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/16/f7/57713ba479fd405eb76de31404b2c744c289e336b2d999511ebf51e496f7/typer-0.27.2.tar.gz", hash = "sha256:269b7eb9d3c202ca84b4bc9618cb04ebb43d3d4d1e567e4c768607232c05f945", size = 204045, upload-time = "2026-08-28T10:26:55.046Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/bf/205d0004930ede8f542fb58f601526fccf4ae7626075ca1e6c4de5d3d652/typer-0.27.2-py3-none-any.whl", hash = "sha256:b3a5fc4342d5fc8fda8fc3010b1cf117e9249aab7fae800c2eff62fd3842d97d", size = 123130, upload-time = "2026-08-28T10:26:53.752Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/26/b09b8010994eccc3c09092e6b34058f36a460eea2d4c3e8b910c695975a0/typing_inspection-0.4.4.tar.gz", hash = "sha256:547274fa6b0a561ccf549cc9524b999a578e737d015d8709d021f9d0d13bea47", size = 76928, upload-time = "2026-08-12T12:37:25.997Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/81/4add07e5172b7ac40d8ed5ff580409a7801a4fe26d529bdd915401dabfbe/typing_inspection-0.4.4-py3-none-any.whl", hash = "sha256:65b8397ba37ccbce054456aaccddfc91e6e3083c92824df348d96ca832f3f147", size = 14750, upload-time = "2026-08-12T12:37:24.648Z" }, +] + +[[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 = "uvicorn" +version = "0.52.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f2/0f/3f86e61397dd33bf2ccf28188c40db6a740658aeebbbf6e7dbc101a1f487/uvicorn-0.52.4.tar.gz", hash = "sha256:73acfee47a0b133c5de13d219492d62d8a31e935f4fe6e41a232451a15379f86", size = 100627, upload-time = "2026-08-19T06:27:41.821Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/79/4a20b54ab0491485ccd8c077db2d39187c7f12b3e15485d38a7be37c81b4/uvicorn-0.52.4-py3-none-any.whl", hash = "sha256:f86e41a149d7d05a9969337e3946a9c171c06a5d42680896daaba624aeac8da1", size = 79871, upload-time = "2026-08-19T06:27:40.36Z" }, +] + +[package.optional-dependencies] +standard = [ + { name = "httptools" }, + { name = "python-dotenv" }, + { name = "pyyaml" }, + { name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" }, + { name = "watchfiles" }, + { name = "websockets" }, +] + +[[package]] +name = "uvloop" +version = "0.22.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/06/f0/18d39dbd1971d6d62c4629cc7fa67f74821b0dc1f5a77af43719de7936a7/uvloop-0.22.1.tar.gz", hash = "sha256:6c84bae345b9147082b17371e3dd5d42775bddce91f885499017f4607fdaf39f", size = 2443250, upload-time = "2025-10-16T22:17:19.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/8c/182a2a593195bfd39842ea68ebc084e20c850806117213f5a299dfc513d9/uvloop-0.22.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:561577354eb94200d75aca23fbde86ee11be36b00e52a4eaf8f50fb0c86b7705", size = 1358611, upload-time = "2025-10-16T22:16:36.833Z" }, + { url = "https://files.pythonhosted.org/packages/d2/14/e301ee96a6dc95224b6f1162cd3312f6d1217be3907b79173b06785f2fe7/uvloop-0.22.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cdf5192ab3e674ca26da2eada35b288d2fa49fdd0f357a19f0e7c4e7d5077c8", size = 751811, upload-time = "2025-10-16T22:16:38.275Z" }, + { url = "https://files.pythonhosted.org/packages/b7/02/654426ce265ac19e2980bfd9ea6590ca96a56f10c76e63801a2df01c0486/uvloop-0.22.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e2ea3d6190a2968f4a14a23019d3b16870dd2190cd69c8180f7c632d21de68d", size = 4288562, upload-time = "2025-10-16T22:16:39.375Z" }, + { url = "https://files.pythonhosted.org/packages/15/c0/0be24758891ef825f2065cd5db8741aaddabe3e248ee6acc5e8a80f04005/uvloop-0.22.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0530a5fbad9c9e4ee3f2b33b148c6a64d47bbad8000ea63704fa8260f4cf728e", size = 4366890, upload-time = "2025-10-16T22:16:40.547Z" }, + { url = "https://files.pythonhosted.org/packages/d2/53/8369e5219a5855869bcee5f4d317f6da0e2c669aecf0ef7d371e3d084449/uvloop-0.22.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bc5ef13bbc10b5335792360623cc378d52d7e62c2de64660616478c32cd0598e", size = 4119472, upload-time = "2025-10-16T22:16:41.694Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ba/d69adbe699b768f6b29a5eec7b47dd610bd17a69de51b251126a801369ea/uvloop-0.22.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1f38ec5e3f18c8a10ded09742f7fb8de0108796eb673f30ce7762ce1b8550cad", size = 4239051, upload-time = "2025-10-16T22:16:43.224Z" }, + { url = "https://files.pythonhosted.org/packages/90/cd/b62bdeaa429758aee8de8b00ac0dd26593a9de93d302bff3d21439e9791d/uvloop-0.22.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3879b88423ec7e97cd4eba2a443aa26ed4e59b45e6b76aabf13fe2f27023a142", size = 1362067, upload-time = "2025-10-16T22:16:44.503Z" }, + { url = "https://files.pythonhosted.org/packages/0d/f8/a132124dfda0777e489ca86732e85e69afcd1ff7686647000050ba670689/uvloop-0.22.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4baa86acedf1d62115c1dc6ad1e17134476688f08c6efd8a2ab076e815665c74", size = 752423, upload-time = "2025-10-16T22:16:45.968Z" }, + { url = "https://files.pythonhosted.org/packages/a3/94/94af78c156f88da4b3a733773ad5ba0b164393e357cc4bd0ab2e2677a7d6/uvloop-0.22.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:297c27d8003520596236bdb2335e6b3f649480bd09e00d1e3a99144b691d2a35", size = 4272437, upload-time = "2025-10-16T22:16:47.451Z" }, + { url = "https://files.pythonhosted.org/packages/b5/35/60249e9fd07b32c665192cec7af29e06c7cd96fa1d08b84f012a56a0b38e/uvloop-0.22.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1955d5a1dd43198244d47664a5858082a3239766a839b2102a269aaff7a4e25", size = 4292101, upload-time = "2025-10-16T22:16:49.318Z" }, + { url = "https://files.pythonhosted.org/packages/02/62/67d382dfcb25d0a98ce73c11ed1a6fba5037a1a1d533dcbb7cab033a2636/uvloop-0.22.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b31dc2fccbd42adc73bc4e7cdbae4fc5086cf378979e53ca5d0301838c5682c6", size = 4114158, upload-time = "2025-10-16T22:16:50.517Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/f1171b4a882a5d13c8b7576f348acfe6074d72eaf52cccef752f748d4a9f/uvloop-0.22.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:93f617675b2d03af4e72a5333ef89450dfaa5321303ede6e67ba9c9d26878079", size = 4177360, upload-time = "2025-10-16T22:16:52.646Z" }, + { url = "https://files.pythonhosted.org/packages/79/7b/b01414f31546caf0919da80ad57cbfe24c56b151d12af68cee1b04922ca8/uvloop-0.22.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:37554f70528f60cad66945b885eb01f1bb514f132d92b6eeed1c90fd54ed6289", size = 1454790, upload-time = "2025-10-16T22:16:54.355Z" }, + { url = "https://files.pythonhosted.org/packages/d4/31/0bb232318dd838cad3fa8fb0c68c8b40e1145b32025581975e18b11fab40/uvloop-0.22.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b76324e2dc033a0b2f435f33eb88ff9913c156ef78e153fb210e03c13da746b3", size = 796783, upload-time = "2025-10-16T22:16:55.906Z" }, + { url = "https://files.pythonhosted.org/packages/42/38/c9b09f3271a7a723a5de69f8e237ab8e7803183131bc57c890db0b6bb872/uvloop-0.22.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:badb4d8e58ee08dad957002027830d5c3b06aea446a6a3744483c2b3b745345c", size = 4647548, upload-time = "2025-10-16T22:16:57.008Z" }, + { url = "https://files.pythonhosted.org/packages/c1/37/945b4ca0ac27e3dc4952642d4c900edd030b3da6c9634875af6e13ae80e5/uvloop-0.22.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b91328c72635f6f9e0282e4a57da7470c7350ab1c9f48546c0f2866205349d21", size = 4467065, upload-time = "2025-10-16T22:16:58.206Z" }, + { url = "https://files.pythonhosted.org/packages/97/cc/48d232f33d60e2e2e0b42f4e73455b146b76ebe216487e862700457fbf3c/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:daf620c2995d193449393d6c62131b3fbd40a63bf7b307a1527856ace637fe88", size = 4328384, upload-time = "2025-10-16T22:16:59.36Z" }, + { url = "https://files.pythonhosted.org/packages/e4/16/c1fd27e9549f3c4baf1dc9c20c456cd2f822dbf8de9f463824b0c0357e06/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6cde23eeda1a25c75b2e07d39970f3374105d5eafbaab2a4482be82f272d5a5e", size = 4296730, upload-time = "2025-10-16T22:17:00.744Z" }, +] + +[[package]] +name = "watchfiles" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/41/5e1a4bb12aac5f1493fa1bdc11154eca3b258ca4eba65d39c473fe19d8e9/watchfiles-1.2.0.tar.gz", hash = "sha256:c995fba777f1ea992f090f9236e9284cf7a5d1a0130dd5a3d82c598cacd76838", size = 108252, upload-time = "2026-05-18T04:32:04.251Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/4d/70a7feced9f87e2ff26dba42667290f41694fc64646c67261fbb8cab5d5c/watchfiles-1.2.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:01ea8d66f0693b9b60a6541c8d10263091ca9a9060d242f3c1f3143f9aad2c98", size = 399730, upload-time = "2026-05-18T04:31:38.162Z" }, + { url = "https://files.pythonhosted.org/packages/31/3a/0da302f2307aee316922806ebd5726c542cbd787c938271cf14a074c7daf/watchfiles-1.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7ba0480b9a74af058f43b337e937a451e109295c420916d68ad24e3dc02f5e44", size = 392842, upload-time = "2026-05-18T04:30:27.051Z" }, + { url = "https://files.pythonhosted.org/packages/db/ef/d5bdb705c224dbc256aa0c1ec47bf4e61ec52558f2afb44a71a1fe4d7015/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f34e26a19f91f710c08e0183429f0d1d15df734e6bc78c31e77b9ea9c433658", size = 452989, upload-time = "2026-05-18T04:31:11.945Z" }, + { url = "https://files.pythonhosted.org/packages/71/29/5495f2c1661949ef7a35e4d71111d129cfe7606414a26887a919d0a55406/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b4e77f6a55f858504069abd35d336a637555c09bca453dde1ee1e5ada8a6a1fb", size = 458978, upload-time = "2026-05-18T04:30:52.606Z" }, + { url = "https://files.pythonhosted.org/packages/d5/8c/7f9c07c433811c2fffd93e13fdfb7135de9aab5f2ae41be08960fa0047dc/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0cb4d80e212f116474a545c21c912b445f16bb0cef9e6a73a498164223e14e2f", size = 490248, upload-time = "2026-05-18T04:31:36.003Z" }, + { url = "https://files.pythonhosted.org/packages/3c/11/d93632febc52fbc21be90231bb7c17fd5387f46c9076fd40a5f9c2ae6910/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b974946a10af379d425e2eef5b62f5c6ebeaccf91d45eaad6f5b27ecd4f91aa0", size = 571847, upload-time = "2026-05-18T04:31:10.862Z" }, + { url = "https://files.pythonhosted.org/packages/55/b4/383173e73aabb07ad1d9c7aa859d95437ac46a6d6a1e11005facda0c9d19/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86bc13c25a8d1fcd70b51d0ce7c9b65e90de5666fcbfd3e34957cc73ee19aeb5", size = 465974, upload-time = "2026-05-18T04:30:17.006Z" }, + { url = "https://files.pythonhosted.org/packages/a7/6c/89b1a230a78f57c52dd8893adb1f92f94411721b6ec12596c56d98c74356/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca148d73dea36c9763aaa351e4d7a51780ec1584217c45276f4fe8239c768b71", size = 454782, upload-time = "2026-05-18T04:30:35.656Z" }, + { url = "https://files.pythonhosted.org/packages/24/62/1732118367cfff0a9fce3bf62ff4bfded09ef5df21d9d446b858b3f70a96/watchfiles-1.2.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:c525543d91961c6955b2636b308569e84a1d1c5f5f2932041ab9ef46422f43e3", size = 465182, upload-time = "2026-05-18T04:30:20.846Z" }, + { url = "https://files.pythonhosted.org/packages/28/96/716f7e5f51339bf22963f3345f9f27d7f3b30e2eadc597e257c881dd3c53/watchfiles-1.2.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:a204794696ffb8f9b10fba6f7cb5216d42f3b2b71860ccac6b6e42f5f10973b0", size = 629841, upload-time = "2026-05-18T04:31:05.397Z" }, + { url = "https://files.pythonhosted.org/packages/4c/fe/c40783950fd771ccf66ab3ec2722d188a9af1c7f96c6e811f36e40c6e03f/watchfiles-1.2.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:10d86db20695afe7997ac9e1717637d6714a8d0220458c33f3d2061f54cec427", size = 658028, upload-time = "2026-05-18T04:31:48.22Z" }, + { url = "https://files.pythonhosted.org/packages/71/72/4508db1856d1d87fcbb3b63f4839bab1b5682cb0e8d224d122263c09654a/watchfiles-1.2.0-cp313-cp313-win32.whl", hash = "sha256:eb283ee99e21ad6443c8cdb06ac5b34b1308c329cbdf03fa02b445363714c799", size = 275183, upload-time = "2026-05-18T04:30:59.57Z" }, + { url = "https://files.pythonhosted.org/packages/f9/36/14b76ca57652e5cc5fd1c11f32a261292c08a0d19a00351013c2549cbfb2/watchfiles-1.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:a0f27f01bee51861392bb6b7c4fdb290b27d1eb194e9e28788d68102a0e898d9", size = 288059, upload-time = "2026-05-18T04:32:07.937Z" }, + { url = "https://files.pythonhosted.org/packages/1b/8d/0a85e395398d8d20fadfe5c5d32c726eee17a519e78fb356f2cf7531bffe/watchfiles-1.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:3651aa7058595e9cfb75d35dd5ada2bf9f48a5b8a0f3562821d3e210c507e077", size = 280186, upload-time = "2026-05-18T04:31:54.484Z" }, + { url = "https://files.pythonhosted.org/packages/37/68/36db056f1fdcc5f07302f56e631774d6835bcd6fa3ace402304621d5f9e5/watchfiles-1.2.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:faea288b6f0ab1902ef08f4ca6de005dccf856c4e0c4f21b8c5fce02d90a1b08", size = 399031, upload-time = "2026-05-18T04:30:44.576Z" }, + { url = "https://files.pythonhosted.org/packages/c1/64/01a9d6f66a82a5c101ce939274106cc72759d62427e153f01edd2b9f87c2/watchfiles-1.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:01859b11fd9fbca670f4d5da00fbac282cfea9bd67a2125d8b2833a3b5617ea9", size = 391205, upload-time = "2026-05-18T04:30:25.413Z" }, + { url = "https://files.pythonhosted.org/packages/84/2c/0a44fe058cb4bb7b8ede6b6670698bbb7c0400740e378d00022189b7b31d/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fff610d7bb2256a317bb1e96f0d7862c7aa8076733ee5df0fd41bbe76a24a4f4", size = 451892, upload-time = "2026-05-18T04:32:14.005Z" }, + { url = "https://files.pythonhosted.org/packages/67/a1/351e0d56cd35e6488b5c8b4fb11a809a5bc923e8fe8fed9faf8920be0c89/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b141a4891c995a039cd89e9a49e62df1dc8a559a5d1a6e4c7106d16c12777a55", size = 458867, upload-time = "2026-05-18T04:31:22.279Z" }, + { url = "https://files.pythonhosted.org/packages/d5/7d/9d09605187f1b838998624049fcf8bf47b73c1a3b76901fcac1782f62277/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f22943b7770483f6ea0721c6b11d022947a98eb0acae14694de034f4d0d38925", size = 490217, upload-time = "2026-05-18T04:31:43.657Z" }, + { url = "https://files.pythonhosted.org/packages/60/5d/a17a16eccb182f04188cd308ec24b1a71a9b5c4e7098269cf35d9fa56d02/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1bc6195825b7dcd217968bb1f801a60fd4c16e8eeab5bedc7fe917d7d5995ab4", size = 571458, upload-time = "2026-05-18T04:32:11.875Z" }, + { url = "https://files.pythonhosted.org/packages/d3/3d/4dd457062083ab1938e5dfd45032eb425cee2ac817287ca8ff4356183e5d/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4a4b147f5dca2a5d325a06a832fb43f345751adfbc63204aec30e0d9ca965a2", size = 464707, upload-time = "2026-05-18T04:30:43.492Z" }, + { url = "https://files.pythonhosted.org/packages/c6/71/ea8c57b128f5383de74d0c7d2d9c57ad7c9a65a930c451bd25d524b295b7/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4543579a9bdb0c9560039b4ffddbdb39545707659fbc430ce4c10f3f68d557f9", size = 454663, upload-time = "2026-05-18T04:30:16.061Z" }, + { url = "https://files.pythonhosted.org/packages/53/fd/2e812bf938406d7db351f0703ddd3fc6c061cf30d96153a77bc79a943a44/watchfiles-1.2.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:20aa0e708b920bde876a4aa82dc7dd6ebea228a63a67cda6632c2fc87b787efa", size = 463537, upload-time = "2026-05-18T04:31:44.9Z" }, + { url = "https://files.pythonhosted.org/packages/86/56/d17a7f1dd1bc3035f1072694a551301272f1739c2d8e319c927cb9e29b38/watchfiles-1.2.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:d413349d565dab74297f2a63e84a097936be69bf8f3b3801f27f380e32040f44", size = 629194, upload-time = "2026-05-18T04:31:14.141Z" }, + { url = "https://files.pythonhosted.org/packages/be/06/f1ff66bf5cae50aa4062779a0ecd0bbaf15e466195719074078947d9a17d/watchfiles-1.2.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:f28b2725eb8cce327b9b3ab02415c853011dc55c95832fe90de6bc56f5315f72", size = 656194, upload-time = "2026-05-18T04:31:47.14Z" }, + { url = "https://files.pythonhosted.org/packages/e7/54/a9c7ea9a82a4ac65e7004c0a03920b5cdd2f9c3b678757d9cd425aa51d53/watchfiles-1.2.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:b8c8358484d5fa12ef34f05b7f4168eaf1932f408725ff6d023c33ec17bd79d4", size = 400205, upload-time = "2026-05-18T04:32:05.153Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5d/c9ab3534374a4a67450696905d6ef16a04405448b8dc52bd752ae50423d4/watchfiles-1.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f04b092229ad2c50126dd3c922c8822e51e605993764a33058d4a791ab42281", size = 392508, upload-time = "2026-05-18T04:30:54.849Z" }, + { url = "https://files.pythonhosted.org/packages/26/ca/1ad30103535cf0cecd7b993e8d50edc5351b1820e38f2d22e3df58962feb/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a7ce236284f002a156f70add88efe5c70879cccbb658be0822c54b1306fc09d", size = 452448, upload-time = "2026-05-18T04:30:53.727Z" }, + { url = "https://files.pythonhosted.org/packages/37/a1/ceee2cdf2afbd715fa07758d39c9859513eae411b23196f7fd039e5feedd/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b9909cc2b48468b575eefa944919e1fe8a36c5849d5c7c168f80a8c1db69398e", size = 459605, upload-time = "2026-05-18T04:30:23.312Z" }, + { url = "https://files.pythonhosted.org/packages/e8/f6/421e30fd1cb3907a84ed92ab3f1983e37ba2dca015e9a894a048418417a2/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a37faaed405c67e28e6be45a1fa4f206ef5a2860f27c237db9fa30704c38242", size = 490757, upload-time = "2026-05-18T04:30:47.358Z" }, + { url = "https://files.pythonhosted.org/packages/41/b0/55ed1b97ed08be7bba6f9a541cac15f2a858e1d74d2b07b6da70a82aab00/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9649193aa27bd9ff2e80ff29bfaa93085496c7a3a377592823cc58b77ee88add", size = 568672, upload-time = "2026-05-18T04:30:38.915Z" }, + { url = "https://files.pythonhosted.org/packages/d1/cf/d8ae8a80dd7bafab395ea7681c10237311bbf34d37704a8c744e7cf31fc7/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e4ff8e37f99cf1da89e255e07c9c4b37c214038c4283707bdec308cb1b0ea1f", size = 464197, upload-time = "2026-05-18T04:30:09.914Z" }, + { url = "https://files.pythonhosted.org/packages/7c/8a/3076c496ca8dafe0e8cd03fcebdfc47be4b1174b4e5b24ff6e396e6b3af2/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:054dc20fd2e3132b4c3883b4a00d72fd6e1f56fdaf89fccd12e8057d74cd74d7", size = 453181, upload-time = "2026-05-18T04:30:14.829Z" }, + { url = "https://files.pythonhosted.org/packages/e5/10/9745e17c98e7b8a86454df0a3c7b5686bd650383f1e9f26e4ebcbd6cc0c0/watchfiles-1.2.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:e140ed30ebde76796b686e67c182cff10ea2fbab186fafd1560f74bb5a473a6e", size = 465109, upload-time = "2026-05-18T04:30:28.123Z" }, + { url = "https://files.pythonhosted.org/packages/8f/95/8ef4a95481d3e0cb52d62a06fa6e972e81424be2d9698b91a2fecca9904c/watchfiles-1.2.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:bb7e52ecf68ba46d22df23467b87cffeb2146908aa523ebfe803019618cfda06", size = 630653, upload-time = "2026-05-18T04:31:49.304Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e4/3b3bf36b0f829b50c6ebcb8d031583863c59f923d6a6af3d485e470d0fac/watchfiles-1.2.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:23282a321c8baf9b3a3c4afff673f9fe65eb7fdc2338d765ccad9d3d1916a5ba", size = 657838, upload-time = "2026-05-18T04:31:06.497Z" }, + { url = "https://files.pythonhosted.org/packages/21/b1/6cbbb50c1f3002ab568777d44aa21206dfb8807a840990c4037523b51812/watchfiles-1.2.0-cp314-cp314-win32.whl", hash = "sha256:c0db965c5f79aa49fe672d297cf1febc5ad149b658594944f49a54a2b96270a7", size = 275108, upload-time = "2026-05-18T04:30:06.891Z" }, + { url = "https://files.pythonhosted.org/packages/92/45/190ce6db8dcb4536682cf75d3889ff1a27182a58cb519d343cb6d9ea63d8/watchfiles-1.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:71283b39fd17e5408eb123bd37aeecfd9d54c81fc184421943208aadb879d103", size = 288441, upload-time = "2026-05-18T04:32:12.901Z" }, + { url = "https://files.pythonhosted.org/packages/74/0d/3eae1c2313ab08378431d907c3f8095ecca00f3eda33111cf4f0f2591799/watchfiles-1.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:c5c19526f4e54a00f2666a6c0e9e40d582c09e865055ea7378bf0009aab857b3", size = 280684, upload-time = "2026-05-18T04:31:26.902Z" }, + { url = "https://files.pythonhosted.org/packages/b1/75/fb64e6c25d6b5ca636d03df34ffb1c6e9873303e76d27967e045f8df088f/watchfiles-1.2.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:d73a585accffa5ae39c17264c36ec3166d2fad7000c780f5ef83b2722afb9dd2", size = 398857, upload-time = "2026-05-18T04:32:17.108Z" }, + { url = "https://files.pythonhosted.org/packages/73/4e/9f7adf01754cbf81843722ccfec169d8f26c69778281a302855cecd2ee08/watchfiles-1.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ae99b14c5f21e026e0e9d96f40e07d8570ebee6cafd9d8fc318354606daa7a28", size = 392413, upload-time = "2026-05-18T04:31:07.911Z" }, + { url = "https://files.pythonhosted.org/packages/47/c8/bec626bcc2d69f44b9acb24ce7d60ed7b16b73628eea747fcbd169d8edda/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4429f3b105524a10b72c3a819b091c495d2811d419c1e1e8df773a5a5974f831", size = 452409, upload-time = "2026-05-18T04:31:20.142Z" }, + { url = "https://files.pythonhosted.org/packages/00/b7/b6362068e81e7c556d155a34c35d40ac3ef42d747b06d7f6e5bf58e359c2/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:43d818978d06062d9b22c4fab2ebe44cf5213d42dc8e62bda8c2760cfa2eeb33", size = 458827, upload-time = "2026-05-18T04:32:06.219Z" }, + { url = "https://files.pythonhosted.org/packages/67/f8/9a813fa42afb1e0b4625e75f0479826644d3ee8dc287e093799bc01f390c/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9f732dc58b2dbe69e464ccf8fff7a03b0dd0be439da4c0720d3558527d3d6b4", size = 490104, upload-time = "2026-05-18T04:31:56.034Z" }, + { url = "https://files.pythonhosted.org/packages/2f/bf/27dfb6094ca4c9aad21298b5525b6c53cb36121ee454331d05161e58d130/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f200104103feb097de4cab8fe4f5dd18a2026934c7dea98c55a2f5fd6d5a33b", size = 571360, upload-time = "2026-05-18T04:31:57.133Z" }, + { url = "https://files.pythonhosted.org/packages/fb/39/44a096d67270ea93df91d33877dbe91fbda3aa4f8ec2edf799d93eda8736/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:63ac26eefbf4af1741247d6fb68b11c49a25b2f7413fbd318a83a12aaa9cf666", size = 464644, upload-time = "2026-05-18T04:30:57.33Z" }, + { url = "https://files.pythonhosted.org/packages/0e/80/c7472203bad6268e3ef1ad260739704847898938ad7ea8b63a5131f46b50/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c4997d4e4a55f0d02b6cde327322daf3a0400e5df6c6b15948994bf72497925", size = 454771, upload-time = "2026-05-18T04:30:48.736Z" }, + { url = "https://files.pythonhosted.org/packages/51/cf/3b10b268b4b7f0fc26e9debb5eef1998b515887840f444cd3ec80c688755/watchfiles-1.2.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:4c887eba18b7945ac73067a8b4a66f21cd46c2539b2bc68588f7be6c7eb6d26b", size = 463494, upload-time = "2026-05-18T04:31:33.826Z" }, + { url = "https://files.pythonhosted.org/packages/3d/3e/a4302545cd589262a0dc7d140e86f7688eba3f9c72776c27f7e23b8864c4/watchfiles-1.2.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:3416ff151bb6b5a8d8d11664974fbef4d9305b9b2957839ab5a270468fd8df30", size = 629383, upload-time = "2026-05-18T04:31:15.596Z" }, + { url = "https://files.pythonhosted.org/packages/db/99/d5649df0a9a410d45b7c882304d0b790903ac9b6e8f2cfd12114e0c6b9f2/watchfiles-1.2.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:0e831a271c035d89789cffc386b6aa1375f39f1cd25eb7ca0997e4970d152fc5", size = 656093, upload-time = "2026-05-18T04:31:58.707Z" }, + { url = "https://files.pythonhosted.org/packages/92/b9/362702539275019a54dd2e94511b31a9b89c5f9e6a21966de7eb692549fc/watchfiles-1.2.0-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:37a6721cdf3f65dbb13aa9503510ccb4451603ac837e44d265d7992a597e1374", size = 400109, upload-time = "2026-05-18T04:31:16.879Z" }, + { url = "https://files.pythonhosted.org/packages/8f/75/71d5ba62db781e5587bded1d944c675374bc4aa37ff33d5018d98e8b6538/watchfiles-1.2.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:2b37d10b5a63bd4d87e18472d80fa525bd670586fae62e5dd580452764879b65", size = 392167, upload-time = "2026-05-18T04:31:28.058Z" }, + { url = "https://files.pythonhosted.org/packages/3c/01/c66dd95d0423fe30d31820e2d1d5bda773764131bbb6ac0cb1cf303ac328/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a105bc2283f67e8fbec74253ec2d94925de92ed72c0393f1206bf326b7b7b69", size = 452372, upload-time = "2026-05-18T04:31:00.836Z" }, + { url = "https://files.pythonhosted.org/packages/91/15/2fe99557e72f85627c6a8eed50d889e8d101623e060a22ad75b875cb932d/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5327989a465505f05cfe06f04fa9d0c2fd5432bb243e10e6f012b1bdca3c8579", size = 459596, upload-time = "2026-05-18T04:31:34.96Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/d4acfa0023367428ed48351b3b9b267893037b6cadae55620c61c24bcfd4/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ecb47f183a8025b2aa18b546725c3657e542112ae9c0613a2af79b4fa8d04ad7", size = 490869, upload-time = "2026-05-18T04:31:59.923Z" }, + { url = "https://files.pythonhosted.org/packages/a4/5f/3164cbdce06c9fb95c4f7b9e2f9760b5e2797af43a9ecc317ef42a23a278/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8520a4ab0e37f770afc34459c4f8f7019e153f9124dc101c15538365875d1ab2", size = 571641, upload-time = "2026-05-18T04:32:00.948Z" }, + { url = "https://files.pythonhosted.org/packages/41/e6/85d3731c55e65cd7690f3f803d24c139588aaf863e4bf2148fe7a7fa1a19/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:71cd71740ed2c15211ebb237ced4e39a1cdf6f80566e5fe95428da1626f4fde6", size = 464444, upload-time = "2026-05-18T04:30:34.298Z" }, + { url = "https://files.pythonhosted.org/packages/f4/7d/562641012b8b09872742c3b8adf9629ec479fd78f8d68ae4a0c13da8add6/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f88af53d6ddaf72179ef613ddc905e6f4785f712b49b80b3bef9f3525e6194b4", size = 453593, upload-time = "2026-05-18T04:31:23.464Z" }, + { url = "https://files.pythonhosted.org/packages/56/fe/cb8ef3d6f929d14158fdaaad9925985b7310abc9384dcd4d82dd0016fb59/watchfiles-1.2.0-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:cee9d5efd929efdac5f7e58f72b3376f676b64050a91c5b99a7094c5b2317488", size = 465096, upload-time = "2026-05-18T04:31:30.384Z" }, + { url = "https://files.pythonhosted.org/packages/25/91/80908e835e100527a9267147b08c0eee1fa6ab0ffec15edc04d1d44885f7/watchfiles-1.2.0-cp315-cp315-musllinux_1_1_aarch64.whl", hash = "sha256:b718bf356bbc15e559bd8ef41782b573b8ae0e3f177ab244b440568d7ea02cfb", size = 630638, upload-time = "2026-05-18T04:30:49.89Z" }, + { url = "https://files.pythonhosted.org/packages/46/4b/95ab2f256bb4af3cb2eb23b9317bda984ee6e0f11733a5c004a6c95b06e3/watchfiles-1.2.0-cp315-cp315-musllinux_1_1_x86_64.whl", hash = "sha256:922c0e019fe68b3ae392965a766b02a71ba1168c932cebc3733cd52c5fe5b377", size = 657684, upload-time = "2026-05-18T04:31:32.027Z" }, +] + +[[package]] +name = "websockets" +version = "17.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/72/fba934cb3dff7a85d811820efffcd141ddd52b5a2a01637f64551373ff4d/websockets-17.1.tar.gz", hash = "sha256:acfea4c20bf54384883ea33b1240fc1db4f52e190823a4e2b334bc3e8bfca96a", size = 187520, upload-time = "2026-08-26T17:25:33.063Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/31/5f6450a7879f4f063ef08897cc385ea3ce3f1fe17f08b11e3fd959abdf27/websockets-17.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2a0162a6372110a5601cb5c9fd826635cedf69f3e110c545dd19774e040b970e", size = 217006, upload-time = "2026-08-26T14:56:10.509Z" }, + { url = "https://files.pythonhosted.org/packages/d0/2a/c1b006fc861695d2aa4e35327b842015ce1d98cf8f99241829b3d6460bfc/websockets-17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:829dba1bc049779de9b332088c1a6a9858e96bd67e50b6b644a95e02b67836bc", size = 214690, upload-time = "2026-08-26T14:56:11.681Z" }, + { url = "https://files.pythonhosted.org/packages/46/69/66e5b7d01445e0eeb1d4ab419c30315f2c90cf7a8a8cd4ecc47f894dba54/websockets-17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fd8f47dbf2e8adb15c847215f83436de3fdb120b51fdae0fbbdf69fd97a3ad80", size = 214947, upload-time = "2026-08-26T14:56:12.923Z" }, + { url = "https://files.pythonhosted.org/packages/07/ce/033cafe2d2538562efa876b9149a2c7a0f7787870a4b1bb6e28adc9ceb6b/websockets-17.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9f4c0377a83e163a303514fdfab501dbe379bdc13e5b9312a91d112658b29dce", size = 224329, upload-time = "2026-08-26T14:56:14.212Z" }, + { url = "https://files.pythonhosted.org/packages/34/c7/e1c2e8a67f6cc0aa43abe0046fb3b7a020980649e6a843751dc7ce9eb170/websockets-17.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c3241d684a76eaaef8b2dc789afde4343cd3aad55ea81e4e8ab3605b529bae51", size = 224611, upload-time = "2026-08-26T14:56:15.702Z" }, + { url = "https://files.pythonhosted.org/packages/be/de/07c6d48eb3d2069709410c851e7de10ab83d752c4bd09862899627c2729b/websockets-17.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e5f5c7a893507d0e83a80b88aefd6522f7e882cd53f9722c6f23f5a020c9557c", size = 225848, upload-time = "2026-08-26T14:56:16.962Z" }, + { url = "https://files.pythonhosted.org/packages/f3/dd/3c68572d20509648cc2fb6f50ccf3deeb4b87270f2c8966e99476e278ea3/websockets-17.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:00bf34b64501e3477e81fc281532ff3cbf4da26633c10b63979d5085d46602d3", size = 227290, upload-time = "2026-08-26T14:56:18.204Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4a/8f6651c8a22093539c9215af0c5bbf217b87b382c99d2112039b92d593c2/websockets-17.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ce0305b702b20d1e1d60a9aaace6bc89970e1753565543f310d549eab22c2435", size = 226476, upload-time = "2026-08-26T14:56:19.459Z" }, + { url = "https://files.pythonhosted.org/packages/f5/be/f6fc33cea86b1127fd1297b18c107e81580ab55a73a39f9a934441ef321f/websockets-17.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29176d8b429cfa0fa443c473878d37a5c06cfd0cb36b71ba4314accc71e05906", size = 225233, upload-time = "2026-08-26T14:56:20.939Z" }, + { url = "https://files.pythonhosted.org/packages/cb/83/65edaf05f7c9b1dea82f4d252fdc37706a84571646f06119a27b0a16fe19/websockets-17.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3709a1ab30b4b922027d22f68d2b61a0656a91680ac894a537624e6be7dd7f7c", size = 222488, upload-time = "2026-08-26T14:56:22.208Z" }, + { url = "https://files.pythonhosted.org/packages/07/42/d1169c2f7f1f0032b0d4b0c00f0711a070cd7c735de37bfeb876bc0f9606/websockets-17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:43bd0c1ceb924d67f5c1a5254d8361dd9d94246e6331a726064dfa2917880780", size = 225295, upload-time = "2026-08-26T14:56:23.445Z" }, + { url = "https://files.pythonhosted.org/packages/a6/f4/64e2a386c3899b917c2933225c9b47887874229d159797f3bf1a11c20d51/websockets-17.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:1fce0f43e0d41422e0b2cad6561e1970df22f212f4c7e884967df7cf591b031c", size = 223891, upload-time = "2026-08-26T14:56:24.647Z" }, + { url = "https://files.pythonhosted.org/packages/26/b3/dfb5c482f7e310a3432fdbb045ddfe6d34114680e89a233d4ff900a32961/websockets-17.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4031152769179ab8dcdeafc7b0e58052a49117560a28671700b47b2c7b717aad", size = 224661, upload-time = "2026-08-26T14:56:26.027Z" }, + { url = "https://files.pythonhosted.org/packages/a4/cf/94865130a336029f46412adc127c4fbe380f46172b90ce251369e35c4302/websockets-17.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a06f3b5085176763182449559e20391d7ce616a8972a9f7a33deda87ea6d4f3c", size = 225766, upload-time = "2026-08-26T14:56:27.455Z" }, + { url = "https://files.pythonhosted.org/packages/96/34/eb8c658f86dfe562ed49a887a27424bfe9e618c26ea6f865b093d075d3a6/websockets-17.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:77b37cceca17291897c3c73bd30a7c7c7909593554b5da574ec852af83c1742a", size = 223323, upload-time = "2026-08-26T14:56:28.807Z" }, + { url = "https://files.pythonhosted.org/packages/1b/7e/2629609652ece5ca0c7ac235927dd4511b08131e3a5d53439b798fddf002/websockets-17.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d8e83333385cac6030a5167fd18bf96cc6c58b914c308e683f05b0cf94bc8dd0", size = 224276, upload-time = "2026-08-26T14:56:29.991Z" }, + { url = "https://files.pythonhosted.org/packages/a1/6b/8525737fe840b38e5f40956c198fb586a4fac1e07144d41a5b949b989cf8/websockets-17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:073c5c3f7e127041fa9d34a9e29ceefee8c3cafbd267ed2927318f425144380d", size = 224558, upload-time = "2026-08-26T14:56:31.184Z" }, + { url = "https://files.pythonhosted.org/packages/74/ab/3a958c6cbcf74b118f601c20a80ac8bd5e8dfec0bcf7345116feaeefb121/websockets-17.1-cp313-cp313-win32.whl", hash = "sha256:2afb58c7ba48b329d56769f8dfd89f394efe587b65ef806bae810a484d6d3608", size = 217475, upload-time = "2026-08-26T14:56:32.431Z" }, + { url = "https://files.pythonhosted.org/packages/22/36/fb521f0f2994c25509651f169efe5582dddd8713d57a0757ba87859372ef/websockets-17.1-cp313-cp313-win_amd64.whl", hash = "sha256:0340bbef6bfbe16da888b3983d666a4db4954ac3253c38f13bc7aba0c7db5a2f", size = 217784, upload-time = "2026-08-26T14:56:33.608Z" }, + { url = "https://files.pythonhosted.org/packages/68/92/9b8419584681a12a7534b746dfb2737c466efe2455483e2fbf8b941a04ec/websockets-17.1-cp313-cp313-win_arm64.whl", hash = "sha256:7a72efa3bf4fa3a6669a54420a472ad056da3973d827f10e3a536da463f926c2", size = 217715, upload-time = "2026-08-26T14:56:34.865Z" }, + { url = "https://files.pythonhosted.org/packages/90/0d/500cf5daea09d4669dff3a7d67159094a0bd6c4ef130381404f6edd3eb5f/websockets-17.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0c9982938980e086da59f70d05f9418cd143401a601a0faac10fa48f7bb1cd3e", size = 217048, upload-time = "2026-08-26T14:56:36.03Z" }, + { url = "https://files.pythonhosted.org/packages/97/12/5b12c6168aa269cffbfd24d177cd492b130120403a418c7e89462e27b4ac/websockets-17.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:57b39dc8541cf7ed3f639da82bf7451060483967f9e733da1f8173e4095f0642", size = 214737, upload-time = "2026-08-26T14:56:37.43Z" }, + { url = "https://files.pythonhosted.org/packages/0c/36/e453e5106e4e2416f008ac222837c2f1637a063b08008afcd1088889b631/websockets-17.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:96abdecbaae746851b87c3a36cb4a661df93ca3d92f114270f79228bf1d00de6", size = 214955, upload-time = "2026-08-26T14:56:38.71Z" }, + { url = "https://files.pythonhosted.org/packages/dd/30/0204bb86176db02cdfc678ce65ed808a66fab87d250ce61a8790800a60b0/websockets-17.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d9fc873e239c5abeb150bc24dbd1a7af23a9254526383ce0a077f5e20adbeb19", size = 224331, upload-time = "2026-08-26T14:56:39.924Z" }, + { url = "https://files.pythonhosted.org/packages/46/c8/d8372256e00c4e3cab1115c45075d1eeedb642a3f2b42bd70c4deae03f06/websockets-17.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6f42912fa9eb4cb7c7ec9fde9b3332ba339eb8a8811981043d4029599f3d950b", size = 224685, upload-time = "2026-08-26T14:56:41.169Z" }, + { url = "https://files.pythonhosted.org/packages/12/7d/650355b8f67f908ff99603351d4458d1a0b787d627950a47c38db7e25308/websockets-17.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f98bf378d7a5be047a044a1a27c987a8f355e10e3b5754617dbe756248cbc5ce", size = 225927, upload-time = "2026-08-26T14:56:42.359Z" }, + { url = "https://files.pythonhosted.org/packages/34/6c/a9ffa5b903579eed76017870f055d75ecc73988d9d0c9b65a92ba0bf2a27/websockets-17.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d334d11398086bb5559606cb42d51c013ea7c061c7db701521392373d3c087f5", size = 227300, upload-time = "2026-08-26T14:56:43.538Z" }, + { url = "https://files.pythonhosted.org/packages/9b/5d/4551c2269066af7481ee44605a0813770961615b5b5da3e87a8f5cb859ea/websockets-17.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5c27336b1a0ac56569493e858497870347854372395f50483725f8cdacc5a45c", size = 226533, upload-time = "2026-08-26T14:56:44.669Z" }, + { url = "https://files.pythonhosted.org/packages/3c/43/237a99233e5c445759a613831b3a92e91905afc064dc3bd0ad33c35fd1e2/websockets-17.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:67258b00302a5aaf0b267771c7014b13429abd7ea17eebc4c55bd935ff101555", size = 225280, upload-time = "2026-08-26T14:56:45.83Z" }, + { url = "https://files.pythonhosted.org/packages/d3/b5/e9407a91613d1d1cd932414143a1012096b26674a782fc55a0bd23217ee4/websockets-17.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:455ffeea0879d313205df1e745e5883e1feb7f31ecd26be882f5f0babd3db04f", size = 222540, upload-time = "2026-08-26T14:56:47.053Z" }, + { url = "https://files.pythonhosted.org/packages/db/d2/db76628db0577b783205d9779f64d8e373416b04c62d1546be4b75dc8540/websockets-17.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f7233eaf441a345a5943a929fd4b5ea3278f11aed35a9ed0f3106b8cb3ca846a", size = 225354, upload-time = "2026-08-26T14:56:48.32Z" }, + { url = "https://files.pythonhosted.org/packages/a9/4c/2174181c067b89a74ae18e2650c2ac29959f4b796afe876ab3f4d30d642c/websockets-17.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:c65da239a5ad553619804c1f9d65c1a0b3005381c6158ee14da2c7444cbd0c78", size = 223867, upload-time = "2026-08-26T14:56:49.579Z" }, + { url = "https://files.pythonhosted.org/packages/df/75/274decb9a8253561b5be3261e02a6676fc8ecdf31e95b722e53d5bfb8fd2/websockets-17.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9fa1ffa08c81a4f809cdab6129f8e55bee4650b9d6d3461019dda73aacd146b6", size = 224652, upload-time = "2026-08-26T14:56:50.885Z" }, + { url = "https://files.pythonhosted.org/packages/9f/e6/49824f1fb4db7656d2f7492b1d8be16147b759d909490e32f4776843ee64/websockets-17.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:406b8107943a43ef4649b1e0cb0cdc052bbf08fe1c8905a623c4af9586e5cebb", size = 225822, upload-time = "2026-08-26T14:56:52.356Z" }, + { url = "https://files.pythonhosted.org/packages/b8/6a/5dc43838c0b02a95f42c47a0de33c5ddd7767a9feeb4d0d8777ac1cfefe4/websockets-17.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:4e8ffcb486c8490a34a4cef5e4409d8da5a1cb1681e5bf7d786ce5e84aa8540d", size = 223379, upload-time = "2026-08-26T14:56:53.699Z" }, + { url = "https://files.pythonhosted.org/packages/c2/62/585637cf06d6b321232f79c55dc14d65518d12cf87c94c44f5864068810e/websockets-17.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:fb88076df585b69c5761c387c0081aa87d7b9eb1b205a6535ca4777e25650d81", size = 224330, upload-time = "2026-08-26T14:56:55.184Z" }, + { url = "https://files.pythonhosted.org/packages/de/68/c3b234a6a1366b6ab5bbfaa4434a1b946e1dc4e8ddd6824bfd93a8835b7f/websockets-17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5d4724255fb8398acd9e583b97eb2279cec20e0bd0f9a94bf75f6056ef9f13da", size = 224622, upload-time = "2026-08-26T14:56:56.393Z" }, + { url = "https://files.pythonhosted.org/packages/6a/d4/84cf3d1376f5d8207f55f43c1c818babd6b89447f5dcd01f18a6d5526796/websockets-17.1-cp314-cp314-win32.whl", hash = "sha256:be3f0129c5654517b2abf07dcb75bb1d9479759a4ccfb569e8293579e9fc029a", size = 217036, upload-time = "2026-08-26T14:56:57.652Z" }, + { url = "https://files.pythonhosted.org/packages/d0/0f/9e7ac63c5d7cb642952200814f584318e65146df008b7d375d5d9c6b2c97/websockets-17.1-cp314-cp314-win_amd64.whl", hash = "sha256:2a4dc6ef83f4559e0d05f313a375cb38f63c986096a9da99fe94fdd779d313e5", size = 217382, upload-time = "2026-08-26T14:56:59.065Z" }, + { url = "https://files.pythonhosted.org/packages/54/bb/1ae6b91f7f3ac05f5c9f14a72dc2181c115ff370bcd8a7f10f02c174adfd/websockets-17.1-cp314-cp314-win_arm64.whl", hash = "sha256:46c0331c9eaaf73a559f3a9e388466be0df96eb83d40f06f1ca6ab6613b35c82", size = 217268, upload-time = "2026-08-26T14:57:00.654Z" }, + { url = "https://files.pythonhosted.org/packages/b3/f0/f65644d0e0b2b90918a8c41503841cc4072a58f2bf76c09bc36e751fc0dd/websockets-17.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d411ea5ca18ac1b12c0c94be88b60c18ca641ac43bcdfdf1c9f79d46cdbe1603", size = 217379, upload-time = "2026-08-26T14:57:02.181Z" }, + { url = "https://files.pythonhosted.org/packages/ff/35/4c46d1f620ac1a30f92b6eae78ee40a772a93f568647ca7ccdc5ea283cf8/websockets-17.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:07fa3e7c30e2c577928d359b56bf872a3e0cbcc15553eaa0907c1ee86344b56f", size = 214911, upload-time = "2026-08-26T14:57:03.478Z" }, + { url = "https://files.pythonhosted.org/packages/04/6e/4587e8406d7c1188e97b9cf466c081e93399380d447f885bfce81626cd37/websockets-17.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6de9acef07e3a78e9567fcd26c29011a4da8f050b13004bbf880a0fd82a6eea5", size = 215115, upload-time = "2026-08-26T14:57:04.692Z" }, + { url = "https://files.pythonhosted.org/packages/ec/06/1381c8fff525041025909eb80ace32489194a00ba22a0a8d428030afcc84/websockets-17.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ea0ed9373b880115911d9d39634bccc95b8ce590c9c42e8589f5cacc3ef3cee2", size = 224696, upload-time = "2026-08-26T14:57:05.899Z" }, + { url = "https://files.pythonhosted.org/packages/36/9d/9034e867dc85340be058619751742b895f722326e83100d110063461ca07/websockets-17.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:50903d335bfda026c2fa11dd9aed09d8cbee0c451e3a85122a9acb041b7dc69b", size = 224975, upload-time = "2026-08-26T14:57:07.262Z" }, + { url = "https://files.pythonhosted.org/packages/40/eb/ed03aa3cae748ebf6397e5d44028f433f746bad09dc568ff754fda3a3c9b/websockets-17.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a74531ce81af587f906ab42f194032388fcff8fc7938402e5917c9147a39441", size = 226151, upload-time = "2026-08-26T14:57:08.524Z" }, + { url = "https://files.pythonhosted.org/packages/b1/c9/cc1964a096d16f3b73cb1ee5f14f277f5a3bcac07c6e8f9a1dcded99f4c8/websockets-17.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8fbf28e639544503b7d1c96452a5e5e043e4108d89b1f3fa02910603622d19db", size = 228292, upload-time = "2026-08-26T14:57:09.846Z" }, + { url = "https://files.pythonhosted.org/packages/1a/26/46da6dd0363c2db2e4876fd59a40fd40c1943a82d7018d0a33afbce47d52/websockets-17.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f612dc57f00c07cf4aa2673f7cbceabd654ad2457b7e639f061b794d6e11f9fd", size = 226722, upload-time = "2026-08-26T14:57:11.118Z" }, + { url = "https://files.pythonhosted.org/packages/78/98/ecd8f5e1c5d0e54c08ebc5c66852271112166db68107cb0e17ca1bf25009/websockets-17.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1c7ac77401227212dc6e849182feee50d57cf456ec6329ffd6979c94bb136c5c", size = 225451, upload-time = "2026-08-26T14:57:12.601Z" }, + { url = "https://files.pythonhosted.org/packages/65/4d/da8d2760db53e17aae763738b6ba834b1fcf16813d3632f3edb6951e1ec8/websockets-17.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:32a2a68d989d6e5b74a9d5095415c51189ebae29fceb7cf2b64a1c0318a81256", size = 223003, upload-time = "2026-08-26T14:57:13.875Z" }, + { url = "https://files.pythonhosted.org/packages/a4/40/ea401c141a79c5b1d0021a0dab9d0df2051c108f1620fbb39a6e7c714c3b/websockets-17.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:aec00f018d34c67500ff0438dc314b40277be4a1b983cbacbf53ccf7db63e257", size = 225704, upload-time = "2026-08-26T14:57:15.091Z" }, + { url = "https://files.pythonhosted.org/packages/e1/8e/07ab3f44215d89840d5385fdcaaab1fed8caeffa67c6899e15062957c12c/websockets-17.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:0014eaff8ad5b3b43feda2279f9d34bf2eaae040720b9fbbb55944b10f40b14d", size = 224192, upload-time = "2026-08-26T14:57:16.3Z" }, + { url = "https://files.pythonhosted.org/packages/58/93/ccf1af0a23e5748d4e22292a377d78d15cf294d7e707bbb11a8990ae6bd5/websockets-17.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:db9d7ee47f3ba531e278be539af39e2c7c7d28fb94897b6cd1120d63b0ef5922", size = 225082, upload-time = "2026-08-26T14:57:17.531Z" }, + { url = "https://files.pythonhosted.org/packages/e2/db/e32200f99ce282e728d2929f2c429db353cf3282db7d0eba99eb32c9fec1/websockets-17.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:ff3e2ba7a9f0a110b0555452e9b5a03a34e11662544e01beea15f144b48ba7b7", size = 226101, upload-time = "2026-08-26T14:57:18.802Z" }, + { url = "https://files.pythonhosted.org/packages/28/3d/e7a6e9777b29433620167c98f3caaff0d6b08b1239a273ef7f7fd1393349/websockets-17.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:6da17fc94bd270f5987b10bee113461ac36a36a98b0481ddcc98056e5a90001a", size = 223794, upload-time = "2026-08-26T14:57:20.313Z" }, + { url = "https://files.pythonhosted.org/packages/48/05/ac569090726dedd6656f3ee28b0c02dfb1ba76e898dceaccc2987a237cef/websockets-17.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:e8dc3fa6d6b7ead3f9de57895f41b116a28787548e066365d9d90f7356bcaad2", size = 224567, upload-time = "2026-08-26T14:57:21.634Z" }, + { url = "https://files.pythonhosted.org/packages/14/50/4ef62941111db6b31193f4fabbb65f845a5177579040cb8fe0d774d25034/websockets-17.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b65d5fe48219dc2d5e158de9e6514e75600f379cc7e37108d35f31764c155566", size = 224993, upload-time = "2026-08-26T14:57:22.86Z" }, + { url = "https://files.pythonhosted.org/packages/28/42/2b95ada4ea19bf3a2072b68669ce4f4afb212690b727d31640576287fd68/websockets-17.1-cp314-cp314t-win32.whl", hash = "sha256:2cce251f3e2469b99b6802b55435bcdd07123b41870f54c87b336183af9d7e68", size = 217168, upload-time = "2026-08-26T14:57:24.466Z" }, + { url = "https://files.pythonhosted.org/packages/32/0a/67d5ee08dd8060a37d612fd40a625b5376ad19ae48fe1c8ad428c278b817/websockets-17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:8f6c38cdcaf98a911d7acc25577f2f9e710f3a2fc2bde1563556784320196b51", size = 217508, upload-time = "2026-08-26T14:57:25.983Z" }, + { url = "https://files.pythonhosted.org/packages/76/a3/822005d0c674451d2411027b878cdc128a2b7ea5a30d337d9e279da22eba/websockets-17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:d1e2f5fa2b6d01f0d85b4f223fea7ed1d504be282a02a81bd2be4817ef7a2f03", size = 217425, upload-time = "2026-08-26T14:57:27.324Z" }, + { url = "https://files.pythonhosted.org/packages/de/d5/99a6c6a1eb5d5ae9f45f59a3c97f4e3b21f310eb404a547fb3e7d2fc054c/websockets-17.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:88381602e379165b66244b2ebc29f9b23ea0851fbe63ae157f91ca324f072d6f", size = 216970, upload-time = "2026-08-26T14:57:28.575Z" }, + { url = "https://files.pythonhosted.org/packages/a6/0e/1e7f6e833728193958d3ed3d67b5d57c3c7cfa948abf94d4bc553257c954/websockets-17.1-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:88bc5138e53903a85c354e59df7ba73ce306f7b09724cef74dba121e60a88ce2", size = 214699, upload-time = "2026-08-26T14:57:29.862Z" }, + { url = "https://files.pythonhosted.org/packages/07/00/95d39549f86e34425a0412bcbe61708dd1fc46af654e2134a6c4389102ad/websockets-17.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:3546ef55b3a074494106508bc6505c73825970d2d9505f7bf53882b3e88b0d1e", size = 214927, upload-time = "2026-08-26T14:57:31.148Z" }, + { url = "https://files.pythonhosted.org/packages/4c/ff/b442415fc4f7f9943b0fc8e8eebaa13923ca73361e167c439ba634eecbd9/websockets-17.1-cp315-cp315-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9ae55d24241fc055f22aea3ac924559069848bd0ad4ea065fdd72d2194685fe8", size = 224373, upload-time = "2026-08-26T14:57:32.833Z" }, + { url = "https://files.pythonhosted.org/packages/a8/dd/b83537aae4cf61615b9d8b2dbb235c0030ba85457a6d934798273814600f/websockets-17.1-cp315-cp315-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d7b349265fad6244013eecd99df8d83c12bf3013943e431f4fadd5bffc37db42", size = 224801, upload-time = "2026-08-26T14:57:34.041Z" }, + { url = "https://files.pythonhosted.org/packages/76/83/5ab0abed58454909e8dbab45086ac68ee4556d7a8ada26735addc909b903/websockets-17.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc5789e5ea182b77a38881383ada5347202a6c66f4857d054e075290e80b604b", size = 225967, upload-time = "2026-08-26T14:57:35.292Z" }, + { url = "https://files.pythonhosted.org/packages/4b/26/e2412f2b998a8c1dfc00c0709ff6ee0c634dd0b0b4f92bdfe9667876b71c/websockets-17.1-cp315-cp315-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ce13c7d233239e739600a57d4a73c1192ad8259e655a4d55aa1a454242bc809d", size = 227664, upload-time = "2026-08-26T14:57:36.493Z" }, + { url = "https://files.pythonhosted.org/packages/ec/25/0dd4495df3c0e02f6db705312ba85ab9b2dd42257dc23eb0da10066e4844/websockets-17.1-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1036189bd34b0bc1b10a4679321e2c7968af317efe6e8e4c1c5141c4254fb5bb", size = 226447, upload-time = "2026-08-26T14:57:37.781Z" }, + { url = "https://files.pythonhosted.org/packages/be/67/6df3f63ffc48f08126ed0cd2fd2a41092967c3e364f8ec100deae90b6d77/websockets-17.1-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e78fd4b7b2c5086a38671c9c882c1e643385eccea360b5b1fda4a105e590087e", size = 225343, upload-time = "2026-08-26T14:57:39.133Z" }, + { url = "https://files.pythonhosted.org/packages/b1/8d/a8479bbb09ff054907d141123d8f52fb6ae5ac39c6dbe39e6a02a8408309/websockets-17.1-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:46e7a10bf04318c7b0c0273791925ae5e1cbe4a11e34aa934d2ef27862058a80", size = 222748, upload-time = "2026-08-26T14:57:40.478Z" }, + { url = "https://files.pythonhosted.org/packages/40/fb/4c3d2a3269cde3f3087916de9c3d9fc5d7196b46846d8c3a9ae59ad0a884/websockets-17.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:33e45c7ea38428e740a7f233555d71df0b875cef7fc080acebc9654475e35335", size = 225453, upload-time = "2026-08-26T14:57:41.859Z" }, + { url = "https://files.pythonhosted.org/packages/7f/1c/6467b401d19408f34e1c7389c222c2c7e1dfdf08c551190269b5eabc726c/websockets-17.1-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:6e63c01803be425ff062b7f7fc201a74def1d49fc94a2410dd17375df75936e9", size = 224112, upload-time = "2026-08-26T14:57:43.136Z" }, + { url = "https://files.pythonhosted.org/packages/c5/5f/744e032ac80e11039a7447657ebabb46e9b5c2dbcec83be571335212932f/websockets-17.1-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:722ec21717eec6477bce582147a28acdfe034e604239466a6a95daedb863e774", size = 224646, upload-time = "2026-08-26T14:57:44.871Z" }, + { url = "https://files.pythonhosted.org/packages/9f/47/bcb9128d9afc4d0934d9192e2a24897ca2f7a63df2654904915349c6c46d/websockets-17.1-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:e74e41f0ad12ff1e8983e349daef79d37cc8280c743ce9d134d6c74c18dab5d6", size = 225797, upload-time = "2026-08-26T14:57:46.338Z" }, + { url = "https://files.pythonhosted.org/packages/c7/e0/b058047b7cf565e1105b10ef6b6b24a6ebe3575678c7dc75a645334705a7/websockets-17.1-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:12fe8984a32dbfd084e0603f1a8d740c0180cb85b3174585c54a80d2455a8394", size = 223605, upload-time = "2026-08-26T14:57:48.175Z" }, + { url = "https://files.pythonhosted.org/packages/b9/69/fc1555bff884de363f1bf9eebf2836dbeb29fa7e4f957debb7bbcf43abba/websockets-17.1-cp315-cp315-musllinux_1_2_s390x.whl", hash = "sha256:01dcb47deebc40b38fd4a493b9b9f4d0a704b7bec6f35e4d34085b329abce71a", size = 224508, upload-time = "2026-08-26T14:57:49.407Z" }, + { url = "https://files.pythonhosted.org/packages/e7/f9/648d4e68621688b19093b06f7b497d520952e68cdea1c1b54371fe9491de/websockets-17.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:f4c45ee2512d3757b5e6c67c5a34e435143f2ecb7df3324f9fd888688c45c0f4", size = 224767, upload-time = "2026-08-26T14:57:50.799Z" }, + { url = "https://files.pythonhosted.org/packages/58/93/f8342b55864f71df13eb8e9ef7dce691b87a87f04f75bb8a1385b3336e7c/websockets-17.1-cp315-cp315-win32.whl", hash = "sha256:0f4f50dfe2cc810fc4e2de979b35e83bf8bb4bccdc6fe472d93762ea7b1d5927", size = 217003, upload-time = "2026-08-26T14:57:52.122Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f0/7b5fdb774c245e0b6217009e2a24d2105c1a64923949f33be41aa7959302/websockets-17.1-cp315-cp315-win_amd64.whl", hash = "sha256:4af784f3e436f65b355c117c6497320f2b5cf6a559295cb1c4c7338e335d45cc", size = 217300, upload-time = "2026-08-26T14:57:53.492Z" }, + { url = "https://files.pythonhosted.org/packages/76/33/1fe6ed1b5087516115ca451b2c240314b010647071f8fc3bd78a21e4dddb/websockets-17.1-cp315-cp315-win_arm64.whl", hash = "sha256:d58159af7835fde09c462394293c0d7aaf8fb4557d8f8e5699f5e722ccae013d", size = 217214, upload-time = "2026-08-26T14:57:54.88Z" }, + { url = "https://files.pythonhosted.org/packages/94/ca/ed02e75996a266d76c5fcb5dd9b930db4cf2b388ca5fa3d2a72086f81568/websockets-17.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:1a5cf4e7bbe3ca499e6a289206cb4fcb7444b09919e129bd517f57d5fa192c13", size = 217282, upload-time = "2026-08-26T14:57:56.108Z" }, + { url = "https://files.pythonhosted.org/packages/bd/7d/d536f5bc89ea5b52fd1c1727c59fabafee6bc41f5ce92c3bd2f83047908c/websockets-17.1-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:416b4bc8789a1865a3ff643ec4ee073a5f52402d0dbeafd27b1798d5dd6b6a51", size = 214863, upload-time = "2026-08-26T14:57:57.355Z" }, + { url = "https://files.pythonhosted.org/packages/37/37/944cf17bad668e9be1247e6314f88a48b9faf7c250e383410db8b38af0b9/websockets-17.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:259f45358c76d3b18489e3e80636cdbe807e05ecf1b10fdf1a779106d23d0c8e", size = 215073, upload-time = "2026-08-26T14:57:58.719Z" }, + { url = "https://files.pythonhosted.org/packages/74/bf/3267966cc1bbc2b8fa62fd329651b0af502df1f5d1c0eed027ff339d6aa8/websockets-17.1-cp315-cp315t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d9d01e8ede41fea4f5a847dad9d628355f74905f437a5b6856d67aa66d193800", size = 225229, upload-time = "2026-08-26T14:58:00.235Z" }, + { url = "https://files.pythonhosted.org/packages/7f/d8/85ea722f483510abb39fc71aafb4465d17cf9051a275ab036874ff3c300c/websockets-17.1-cp315-cp315t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a7b35181a14cbfcae163b4de545d22abfd07d06c2c41ca69cfcd99251d6888ab", size = 225500, upload-time = "2026-08-26T14:58:01.994Z" }, + { url = "https://files.pythonhosted.org/packages/50/ce/64c7d00005bd0d15ecb5c5fcb7fb2597b6b92ddd16c4fa6bbc3d2835ad63/websockets-17.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6a8e768a048c2220697477ce2e67e4345dc9f693d0ee6af53945b5e30227c6a7", size = 226829, upload-time = "2026-08-26T14:58:03.327Z" }, + { url = "https://files.pythonhosted.org/packages/b4/dc/096c67940fb957e667ca3c542818150434eb0388c6fdc90b3a502f3c3e96/websockets-17.1-cp315-cp315t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:880069d21cc33a558dcf180924a546d1ecf8ada5be3e4e70acee87019d706a24", size = 228457, upload-time = "2026-08-26T14:58:04.78Z" }, + { url = "https://files.pythonhosted.org/packages/51/fe/f2331b6b7ccc67589891da354fa46a5cb79e95f83b9fd0e734d77f1f2140/websockets-17.1-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cec1bb8f22abccc8d20f8ca63df9be41600c26c190f4b97ee86c675fd4a863a6", size = 227265, upload-time = "2026-08-26T14:58:06.102Z" }, + { url = "https://files.pythonhosted.org/packages/47/a5/fb1642302f8ec77ca922203074f155a9831a5128ad75e725059a476d1227/websockets-17.1-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f3a1d577e081667dda7f8e5b4796e6e32f9713c93e2a3d930669519840a3c623", size = 226143, upload-time = "2026-08-26T14:58:07.464Z" }, + { url = "https://files.pythonhosted.org/packages/d7/41/7133fcfb63f5562750b269d6a845c689dde6a2c6407286da395beea19ddd/websockets-17.1-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dc053f9e95a76213c5eb7ed95779f7daf0d2bf0e4e03073629ebfa43a033f151", size = 223501, upload-time = "2026-08-26T14:58:08.766Z" }, + { url = "https://files.pythonhosted.org/packages/64/b1/82b36bfabc79ff2d383a1fc043cee6a13f794ef4f6bf1b4810ad6988cf6f/websockets-17.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:bb0efe019480a1c93e168ce96479273aaebd672fc8c350d5eed1e507ababb1b8", size = 226330, upload-time = "2026-08-26T14:58:09.987Z" }, + { url = "https://files.pythonhosted.org/packages/41/7d/5b511b9bf6e9ad331e6ff902fcbcc71c3794d10ef3b5efe80ccb8f0a7861/websockets-17.1-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:615746b12b26a3fd4077bc6fbeb277a1c192a45dd57b531d07ad9ed5c52a9a7a", size = 224980, upload-time = "2026-08-26T14:58:11.303Z" }, + { url = "https://files.pythonhosted.org/packages/e0/50/aed08f25301f8eef23be903ff9319fcf35630ca2bdec9d226f7d804dd5b3/websockets-17.1-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:1a20136d61f9ca3a31493732762661fafc2c20e8861930214e21afc6a8a692a2", size = 225478, upload-time = "2026-08-26T14:58:12.543Z" }, + { url = "https://files.pythonhosted.org/packages/3e/47/0d63d4168536b4682c9d19b7399443b1176f25dbb68878374fa716670230/websockets-17.1-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:2786cbd273ab69c22612db8a41229ddf2c158060b17b5928884bf388d07887f3", size = 226588, upload-time = "2026-08-26T14:58:14.457Z" }, + { url = "https://files.pythonhosted.org/packages/b3/dd/844bd0b6386fc81ed6a55f4b6dd26f01c6987eda205afa10175ea12b2164/websockets-17.1-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:b1c323fc3be1dc3f87f6c59458cb7d9e13dcbbf971d6c3f3e2bbaf58d3bfcdfe", size = 224336, upload-time = "2026-08-26T14:58:15.778Z" }, + { url = "https://files.pythonhosted.org/packages/96/18/03709c84bc88ec4dcea68d4be4ccd07d611073dec111203a5bf45af8809d/websockets-17.1-cp315-cp315t-musllinux_1_2_s390x.whl", hash = "sha256:12c8e2b25df59755954a04dfa09c990b96691025aaf7eafd19ed6da24b09c18d", size = 225197, upload-time = "2026-08-26T14:58:17.141Z" }, + { url = "https://files.pythonhosted.org/packages/27/cf/0d1c694b6466c89e875b85b32b51312c472cf6708eee91914866f5087dde/websockets-17.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:f58f58b4b29bbea2a3635e2c56eff4a3adab011fe383802a9e542e31b97085fc", size = 225493, upload-time = "2026-08-26T14:58:18.521Z" }, + { url = "https://files.pythonhosted.org/packages/4e/f5/99857c3dd9676749f33e3668665a34ad6099505fb8d75eb084f49f7807a9/websockets-17.1-cp315-cp315t-win32.whl", hash = "sha256:f78a3ffb1994304db2c0c4588e4d1a518079b557054fa3bb985a6f5e50ff49a3", size = 217130, upload-time = "2026-08-26T14:58:20.037Z" }, + { url = "https://files.pythonhosted.org/packages/2c/84/77599922ab441bfe61508f97dab2c71f8e114d31793993ea54011db16199/websockets-17.1-cp315-cp315t-win_amd64.whl", hash = "sha256:ad68c28a27246fed109a4409393d677b7e1388345cbbd2f5aee5c182d8506110", size = 217448, upload-time = "2026-08-26T14:58:21.382Z" }, + { url = "https://files.pythonhosted.org/packages/ce/3c/8b9a225b523f06a9389be81f1b0ab07c49bec6014742e6aa359c1f920f1f/websockets-17.1-cp315-cp315t-win_arm64.whl", hash = "sha256:e552e0037230ac16e5f568de7012041344d1b18c9feed30ec2891b8eba55af81", size = 217372, upload-time = "2026-08-26T14:58:22.807Z" }, + { url = "https://files.pythonhosted.org/packages/41/63/23572870e01836a98346075b9e17a8bc24a6ddd9800a3204ceee58677f3c/websockets-17.1-py3-none-any.whl", hash = "sha256:f221081107b8c48184d99f7019604486376e7ef826037e70aad6b02540732c23", size = 211134, upload-time = "2026-08-26T17:25:31.397Z" }, +] + +[[package]] +name = "wrapt" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ba/8dc25478ed234dacc7d83c671634f347d0bdfb65bf0502f41879cf2f15a9/wrapt-2.4.0.tar.gz", hash = "sha256:7082fc1f94b020ac275870c4af71b09cff22876fe6e9c4c0ad01ea21d217b288", size = 161179, upload-time = "2026-08-30T04:41:51.424Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/86/f9de4e11582ff96ad2199eeeceaa17faa27bbdc599243f520070c4f3de07/wrapt-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5c5c4c728cd22a36e4b8bb5df4a7d3bccaa865d27725b36eeb3b6f18fb2e1bc2", size = 96041, upload-time = "2026-08-30T04:39:59.575Z" }, + { url = "https://files.pythonhosted.org/packages/c3/ab/1dbf50802bea3b46192fd0dc39bb0eb2e77a064c813b2bbd88d2888ad49f/wrapt-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7de5b8d94417e55c02be50cc226e0ae1209bbc73813bf691dff3979c94438115", size = 96269, upload-time = "2026-08-30T04:40:01.182Z" }, + { url = "https://files.pythonhosted.org/packages/cb/a3/a3b5cde1cd06e04b6e95134eb3187a0a7da607a530e7795b221d4e4fa819/wrapt-2.4.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6436e2bda993a3eb69a1b317fc831c8ebcafb5704c390859ebd49f81218c4bbb", size = 225787, upload-time = "2026-08-30T04:40:02.715Z" }, + { url = "https://files.pythonhosted.org/packages/cb/f7/d100f6c348b7669f19119cf890dcd4764623e2233af065586d110e0cd99e/wrapt-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e084558fbd112d2e1e34b0f5c71e45a3405bdad51a17150368a959bcf6697964", size = 226649, upload-time = "2026-08-30T04:40:04.647Z" }, + { url = "https://files.pythonhosted.org/packages/52/c6/3af8df515d5d7e92306957536f3468c6bdfecbe3659f99dbf09a468c2c4c/wrapt-2.4.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e78c947e18fadfd690c9420c30a96d221feeb93fc8f1cc00509b370ac16c3114", size = 206760, upload-time = "2026-08-30T04:40:06.332Z" }, + { url = "https://files.pythonhosted.org/packages/b9/c5/40d355552bd3eb6c5186e26051c19b573d24d7896de42caa7937d6b5ca9f/wrapt-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:08d8378c4514ac8dcc0ace76044cf87a873e6a52b5e6109834c8fb9037f4441b", size = 223467, upload-time = "2026-08-30T04:40:07.829Z" }, + { url = "https://files.pythonhosted.org/packages/40/ab/d198eebdb39f0d7e182e771e590a36673489cd58cebdad8aa273dcf28e04/wrapt-2.4.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:93180c2199784dd6a1075b33f9ed636bd0966821edbece6b3d5379b1c4f0bb7d", size = 205358, upload-time = "2026-08-30T04:40:09.344Z" }, + { url = "https://files.pythonhosted.org/packages/1c/0e/974a60672ad507d39a3d8a1c6351ef37fe65b07240d000ceba5d2b83e9e9/wrapt-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3d5e5eb76fb87e62752af751d2dcd9d1cd986b12037d2e1363d109ba716029e8", size = 214654, upload-time = "2026-08-30T04:40:10.923Z" }, + { url = "https://files.pythonhosted.org/packages/cc/5a/8b2db70206db0a4246758e0472ce344cb9636217113ef70640fc8d2ce874/wrapt-2.4.0-cp313-cp313-win32.whl", hash = "sha256:49bb5a572469e0e18163a8ec2aa972135a0929899ecbe627665f274506e1b5b4", size = 91171, upload-time = "2026-08-30T04:40:12.895Z" }, + { url = "https://files.pythonhosted.org/packages/fe/1e/e782b511c680dbe7369c92e7d981484aacca0cda584da1f28a84cd9a8e1a/wrapt-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:b1737f46b1e4a81eb93500a7f2854319e1c7a86e8863fb050b7b4daadd5a4178", size = 96178, upload-time = "2026-08-30T04:40:14.336Z" }, + { url = "https://files.pythonhosted.org/packages/9f/62/095ba31123fa5dd482d6183c05200b061314aabbd5442c010aba4b03ff1c/wrapt-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:f1e9e088094f4895f84ab043e7d59401df137d663efbf1e80c82144882960830", size = 92949, upload-time = "2026-08-30T04:40:15.935Z" }, + { url = "https://files.pythonhosted.org/packages/1f/dd/1f269e4daf0c992f675e1ca2de6b1683b761c6d0aeb6c7b4b412486823ea/wrapt-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:788e473d1a6786d29d577b1e2bd95e214c09cdafde84907c522c31069c9acfac", size = 96386, upload-time = "2026-08-30T04:40:17.584Z" }, + { url = "https://files.pythonhosted.org/packages/be/42/7ecef06d33c0121c68d66a8a695efe67ebaa57218c1c61c585eca2a6117a/wrapt-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:947bd4b3438167b3638bf5477cb83a068a586ffb6d331ac427f39839c2b93b3c", size = 96532, upload-time = "2026-08-30T04:40:19.116Z" }, + { url = "https://files.pythonhosted.org/packages/c2/e3/8fdc9eba0e6cbbfe8303e1e807d734691309a27970b2ea458d099f1a46b0/wrapt-2.4.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3a69161cae7f0dca44c89c1d14146b4a0508a0c3cad98b3f2db1f4e9016c94ba", size = 228775, upload-time = "2026-08-30T04:40:20.604Z" }, + { url = "https://files.pythonhosted.org/packages/f4/77/4ac5882abfb29bf9821c5fa5cf9f30241a194e0f47faa2682b9b29765278/wrapt-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0536f5d85ff6a157ebe7e0fe08c5479943742cf1ce59569075a66159efcbc495", size = 229029, upload-time = "2026-08-30T04:40:22.186Z" }, + { url = "https://files.pythonhosted.org/packages/c9/c5/8a3608311a02faf3e5c072da38d06a7c623150fc258e29f18fe377d91703/wrapt-2.4.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5f041ed6a4d571010944bd6cfad9072db463e1851877b6d3227467a44af37456", size = 210436, upload-time = "2026-08-30T04:40:23.953Z" }, + { url = "https://files.pythonhosted.org/packages/de/90/e0cbc43f435fd39df25460e9f173e7b96f3dac5c7f66be41c7227166f021/wrapt-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f7fed45dbadf5d98a52bfff9624d3cca00affeb9543d493c9632b7a53cdd35c9", size = 226586, upload-time = "2026-08-30T04:40:25.507Z" }, + { url = "https://files.pythonhosted.org/packages/81/6c/7e5f2143228635ec139ef6df733dc477049f7d96a0c49deb23944a73ed6a/wrapt-2.4.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5cc2e7c7b6032e11a2b367a9baadaf0c5241feff2d8205260d87f1aa6dbdf84b", size = 208880, upload-time = "2026-08-30T04:40:27.128Z" }, + { url = "https://files.pythonhosted.org/packages/10/16/1de84402bb7a0916e10739bf6586e031244172b299e87c8cff2a04baf9ff/wrapt-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:72826910a1cf5a081234720fd43011304b899acfee219af49148155b4d795533", size = 216689, upload-time = "2026-08-30T04:40:28.844Z" }, + { url = "https://files.pythonhosted.org/packages/20/19/cd6bd5050381a541b44be97c4e0994eed60c5f439f4314f95eb5777d6c1a/wrapt-2.4.0-cp314-cp314-win32.whl", hash = "sha256:0eca69c9e93518240abe8801fb9b2726116a6e48172e4564c2651a2e14521747", size = 91581, upload-time = "2026-08-30T04:40:30.592Z" }, + { url = "https://files.pythonhosted.org/packages/a4/f8/b642f3184619adde676ad449030bcbeae6cc78ea07a92f0b5fddeec4c4e6/wrapt-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:63b94f401d7ae3a9a3027472fd3a3ff38afd2ed293b2f0b3b84a6d133a9f99a3", size = 96510, upload-time = "2026-08-30T04:40:32.1Z" }, + { url = "https://files.pythonhosted.org/packages/4d/3b/3415a18b91221261eeac85bf8ee23dfb0e2a39d76b9703a797efca177439/wrapt-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:6b3e082d43f592fcd381aee46354a11ce887a813ce5bbcedd9766fd681723c09", size = 93648, upload-time = "2026-08-30T04:40:33.563Z" }, + { url = "https://files.pythonhosted.org/packages/ac/90/80cf6a09e9599a11249775928df9bb790b82471e4312b847a861ffb2c2ed/wrapt-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:09064c7be688c38c3ff125ce86bc26b69b5d78dd56062c3ddd9c814b2a25f1e1", size = 99615, upload-time = "2026-08-30T04:40:35.134Z" }, + { url = "https://files.pythonhosted.org/packages/b2/da/c1d3245abb911a42584f8f7e9781995bdc41345c7affba75cf7e376c85ac/wrapt-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:4f8ddff4bbb75916be36da5169b8b9d475b59a1bd24acdb7551bb2c71be9aaac", size = 100031, upload-time = "2026-08-30T04:40:36.641Z" }, + { url = "https://files.pythonhosted.org/packages/84/46/8ec4941d0abbb010df7caf0a34840ca0128177389843b0f5ef2f9ee48ac5/wrapt-2.4.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e9f8017443595870aa31f46125553a5c55ce95a26a267b96261baee6ba566d83", size = 269389, upload-time = "2026-08-30T04:40:38.212Z" }, + { url = "https://files.pythonhosted.org/packages/14/b5/a0ae1b431cc1f49a545d32b8b678a5788c50583ecf0ecb85dc0c7f95b4f6/wrapt-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:328eb2d978ca3a6ae25f8d8fe560bf8f4bc9778b5932e7b142664eef05b92e8f", size = 281081, upload-time = "2026-08-30T04:40:40.045Z" }, + { url = "https://files.pythonhosted.org/packages/c7/24/dfaf53dd3bdb0703524a9367b48e2a64ea86433fcc854b5f14be6a8e0e39/wrapt-2.4.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7a057d376d994da6bd1bbf955ecfda699aa7353826f98847f5605e1801abdfd4", size = 249637, upload-time = "2026-08-30T04:40:41.657Z" }, + { url = "https://files.pythonhosted.org/packages/3e/27/bdd82044d7503c2bfa78afcc89881f82a1b82b5d2013aabab853d339ce2a/wrapt-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3367a5212212c9393e0d3ca6ae029b3a8fa40c5896e4a985d43fe8a4b8322f0d", size = 275322, upload-time = "2026-08-30T04:40:43.408Z" }, + { url = "https://files.pythonhosted.org/packages/c4/82/04f4228eb3fb348d660dd1ea7225e53665b1809df2273ff4861d4d33b741/wrapt-2.4.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:c4fca1e63af6675af3df7cdfcd5a0c878b5e655c7e48611ced9dc8d62183a11d", size = 247292, upload-time = "2026-08-30T04:40:45.457Z" }, + { url = "https://files.pythonhosted.org/packages/a2/20/67b2968fa9200458446c51b36a435adb6906083428b70fafb4caf92d4dc2/wrapt-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:694005fdc3002ade0f21641408c588028abde03c85961f3ba7727d8bead3ed6b", size = 264586, upload-time = "2026-08-30T04:40:47.079Z" }, + { url = "https://files.pythonhosted.org/packages/d7/fd/0db9ba03e08a7663f52455e95520c723f567bc037bffc6699950fcc456c4/wrapt-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:332d9bad7e9b718974bb2a576504c4956f45b4a0fcd7b3bb7827279167550464", size = 93752, upload-time = "2026-08-30T04:40:48.81Z" }, + { url = "https://files.pythonhosted.org/packages/3f/87/ced171220935c696b157207385fa6be5675558a74655479f071d95a00f1d/wrapt-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6d57264c9dfcf37d2bf0b0fbec68d0f6184fc5617267619ada04d03e8b0231f3", size = 99890, upload-time = "2026-08-30T04:40:50.407Z" }, + { url = "https://files.pythonhosted.org/packages/a3/af/4a10c9a6d3b7ae41f830978c28d33a59ceb29537bd6875d2abfe78db4b41/wrapt-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:f43af38a642c3d6062e9740d8f5cc0feb5dbe0da516702df892147393b8cb14d", size = 96033, upload-time = "2026-08-30T04:40:51.933Z" }, + { url = "https://files.pythonhosted.org/packages/a0/df/3a0b6225ab88bd47090df70391c059a3308057638f8fc0ae32e8ac9d1886/wrapt-2.4.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:430fde1a116df3ceb5c29035de1da6609b70e680d9b8ce3ee624422f3fe0978c", size = 96389, upload-time = "2026-08-30T04:40:53.555Z" }, + { url = "https://files.pythonhosted.org/packages/ea/6f/803b0d0e14de11781f0e938e6f7d6e29e79652139fe70d7513460357ac78/wrapt-2.4.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:7d28f8f35a02d49f75f57fa4e755db4ba33f65841c0de64cd65b253916f5bf06", size = 96557, upload-time = "2026-08-30T04:40:55.033Z" }, + { url = "https://files.pythonhosted.org/packages/aa/e8/46571e1218d0494604a7aadc4c898c738c4b179052327ee1e57e278cebd6/wrapt-2.4.0-cp315-cp315-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:efd9a4be6785295e471f71efdf5682bd11d5b822b9665e6e1b4844917cf2f7ac", size = 229230, upload-time = "2026-08-30T04:40:56.703Z" }, + { url = "https://files.pythonhosted.org/packages/78/2e/0cab15fcaec56096a5734feace3620bc01edc885653be04bd756f84a6784/wrapt-2.4.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:75529a2fb569a671cf162f762c1b576f569f571b55ec7f3481258ca842ba507f", size = 229444, upload-time = "2026-08-30T04:40:58.51Z" }, + { url = "https://files.pythonhosted.org/packages/e7/9e/a92c049371a2675f98a0381ab2951f984866d1ba4de0e0771d6a31fdaa2b/wrapt-2.4.0-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:66e7512c0d324cc37bba1def2be1fc365cbb685d3aa393a8f6f4d2d00202881d", size = 212482, upload-time = "2026-08-30T04:41:00.224Z" }, + { url = "https://files.pythonhosted.org/packages/ee/3b/8b5b57d0ff24edcd3421dbaeb4e94c89be3616824e47708f4e13f25ae3d7/wrapt-2.4.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:5f3bdfc35c83b562fcaebc0f24593045e5ed9f3b633adafd35222718a0ec38fa", size = 227017, upload-time = "2026-08-30T04:41:01.918Z" }, + { url = "https://files.pythonhosted.org/packages/0e/20/124b40bfd9585848db5a5aa6741d0c8dbf378dd995c6c2d95f090d9cf540/wrapt-2.4.0-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:d5f45bead708e2c0014be5e98531ce7202916b098a208c7be83c6ceb0a2559fa", size = 210498, upload-time = "2026-08-30T04:41:03.617Z" }, + { url = "https://files.pythonhosted.org/packages/4b/bf/89db9d5a80a9f2af52b24bdfdb5392be80bc0f0fd39fc39d1aab72afd0bd/wrapt-2.4.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:d294576fddac636589e4deccfe782e8f429da10f167c1985c4d51071de3672b7", size = 217046, upload-time = "2026-08-30T04:41:05.473Z" }, + { url = "https://files.pythonhosted.org/packages/3b/0b/021c9d6ce64c639894bffdaa7a895ddd4187abfefb2873ce55e536cd9d56/wrapt-2.4.0-cp315-cp315-win32.whl", hash = "sha256:0191d717dfbb8e519e7bfd4775e5b9bd57e359b3a09ab5db1ea47f6025b4d845", size = 91591, upload-time = "2026-08-30T04:41:07.086Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d3/6ebd944041cea0ac4a108a4739510ed2dc891a3f3216e4f7bf0650f5b5a6/wrapt-2.4.0-cp315-cp315-win_amd64.whl", hash = "sha256:e8df31a126a0a247c1aa379e30873839de03912dea09ca360c680f3625d815df", size = 96517, upload-time = "2026-08-30T04:41:08.671Z" }, + { url = "https://files.pythonhosted.org/packages/96/84/7c5e52e450f80ba76fd0282dccf7c79cd004ebd8ccabd0903064d3d2c56e/wrapt-2.4.0-cp315-cp315-win_arm64.whl", hash = "sha256:e9e7e94472f0e3f1447caf27e1939eb384d0e87972a35a05f5c2e0968e9c01af", size = 93652, upload-time = "2026-08-30T04:41:10.258Z" }, + { url = "https://files.pythonhosted.org/packages/35/89/f08ff45d7646de29750932805cc3b1e86b6ac3128015b293ed45fa8efe86/wrapt-2.4.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:8828369b7d3e93c547cc8ad931b5a57b4e8d174035c82762fb1091e7d05ac9f5", size = 99610, upload-time = "2026-08-30T04:41:11.933Z" }, + { url = "https://files.pythonhosted.org/packages/4b/c2/f9a3c40901a36c6bb7ecaff8e1e54af78fa7fa0b95a0e54d13d3a24c8a0a/wrapt-2.4.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:413e757dce7a43fcda8bb8441994b1127492ffac6a5803af777d44516df8c6e2", size = 100064, upload-time = "2026-08-30T04:41:13.492Z" }, + { url = "https://files.pythonhosted.org/packages/0b/e1/e2437f17f2a1ec292056e2fcafe1248269ebc39502f2ffe79424bf86f8a6/wrapt-2.4.0-cp315-cp315t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:75944792cf6b99262d649d55710bf5901f7013fbb212c7a1d736b97a20517607", size = 269421, upload-time = "2026-08-30T04:41:15.238Z" }, + { url = "https://files.pythonhosted.org/packages/c6/d0/c98d6548dc4c7d12ab9baa192234ca1a57e141afd283252b448faddbd9ef/wrapt-2.4.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:648d1d4f94e8a0a1656675c755f40d2f0ee5fe92c449ab45326f4ecc2738cbe8", size = 281452, upload-time = "2026-08-30T04:41:16.939Z" }, + { url = "https://files.pythonhosted.org/packages/a3/57/673168e00aa03725148ce621ed201b75df4e787a57acd48fecefd2725600/wrapt-2.4.0-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a112a1bfdd2621e4344cb0a32dbaab80636b32dac1b055d03fbb2a67d806d1db", size = 250358, upload-time = "2026-08-30T04:41:18.716Z" }, + { url = "https://files.pythonhosted.org/packages/78/0b/f2e576de5bf53ef5b578470104ea93f33e273a704c825131bc1719fffc42/wrapt-2.4.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:0972cd025f4c86fa2d8abd953d9f875779935343af58b4ce019ff89573fc65bd", size = 275654, upload-time = "2026-08-30T04:41:20.418Z" }, + { url = "https://files.pythonhosted.org/packages/33/7f/9347b2e236346b1ba4cb28b82b205b8a377bb2da9417cb81bbe3d25816d7/wrapt-2.4.0-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:c246aaed719dcdb62eeb7b8d9306a6237777226ef3baad35919c4ae134c91ce7", size = 248662, upload-time = "2026-08-30T04:41:22.371Z" }, + { url = "https://files.pythonhosted.org/packages/a5/36/3b84d9e1ac8393bf2c94272760a2d361dc394ac30301e6d6dbd6583ade2d/wrapt-2.4.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:1656de3835f760781c9b974bce07d8c04edb9c9ad7ad67264aee69cd68a1db09", size = 264813, upload-time = "2026-08-30T04:41:24.116Z" }, + { url = "https://files.pythonhosted.org/packages/6e/a2/de7b1de1702667b4a048318e301e26887268c17b07c8b9797cea06b10aee/wrapt-2.4.0-cp315-cp315t-win32.whl", hash = "sha256:d8e6e1e5dc684dfce7c33fc8b67a08ba2af94f3a45cfc70d5c1d6a839d2caf97", size = 93753, upload-time = "2026-08-30T04:41:25.793Z" }, + { url = "https://files.pythonhosted.org/packages/09/50/4e7ef58c4eb058861ceddc0d1f94a6ed87f62e1cb27783c60b2897ef7e58/wrapt-2.4.0-cp315-cp315t-win_amd64.whl", hash = "sha256:85ed3c67fd39e8d9a36c224758cb6f2f4eb277d07ea677930caa0008c18ec002", size = 99888, upload-time = "2026-08-30T04:41:27.305Z" }, + { url = "https://files.pythonhosted.org/packages/68/64/d15740c763dd0ddea2338ad42e3bd4a84f8702e16083e7ff61674c504a13/wrapt-2.4.0-cp315-cp315t-win_arm64.whl", hash = "sha256:36b56a4fba13b34ed8ff307557325fff215de0a58b5dbaef2c50e4d8aa39dbd1", size = 96039, upload-time = "2026-08-30T04:41:29.062Z" }, + { url = "https://files.pythonhosted.org/packages/79/c8/fafe0002f572ced999c792cfe8b05d39269c63d8193d15d25bd828bcad7a/wrapt-2.4.0-py3-none-any.whl", hash = "sha256:18aabd9301d06026f5900538051773d6f87f65ae02cdc60de482df978513dc0a", size = 73713, upload-time = "2026-08-30T04:41:49.805Z" }, +] + +[[package]] +name = "yarl" +version = "1.24.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/31/33/ebe9e3d1f86c7a0b51094c0a146392045ca1631d2664889539dec8088a33/yarl-1.24.5.tar.gz", hash = "sha256:e81b83143bee16329c23db3c1b2d82b29892fcbcb849186d2f6e98a5abe9a57f", size = 228679, upload-time = "2026-07-20T02:07:45.435Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/63/64ef361967cc983573149dc1515d531db5da8a4c92d22bb833d59e01b313/yarl-1.24.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:79af890482fc94648e8cde4c68620378f7fef60932710fa17a66abc039244da2", size = 135075, upload-time = "2026-07-20T02:05:59.671Z" }, + { url = "https://files.pythonhosted.org/packages/bb/89/55920fd853ce43e608adbc3962456f0d649d6bb15250dc2988321da0fe1c/yarl-1.24.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:46c2f213e23a04b93a392942d782eb9e413e6ef6bf7c8c53884e599a5c174dcb", size = 97225, upload-time = "2026-07-20T02:06:01.769Z" }, + { url = "https://files.pythonhosted.org/packages/15/f0/7688d3f2cfff7590df2af38ec46d969f4281a4dddb08a9ad2eafbcdddf98/yarl-1.24.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92ab3e11448f2ff7bf53c5a26eff0edc086898ec8b21fb154b85839ce1d88075", size = 96751, upload-time = "2026-07-20T02:06:03.676Z" }, + { url = "https://files.pythonhosted.org/packages/05/1a/a851a0f94aaaf379dd4f901bfc80f634280bec51eb260b47363e2a4cd62e/yarl-1.24.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ebb0ec7f17803063d5aeb982f3b1bd2b2f4e4fae6751226cbd6ba1fcfe9e63ff", size = 107960, upload-time = "2026-07-20T02:06:05.699Z" }, + { url = "https://files.pythonhosted.org/packages/6c/a8/faea066c12f9c77ca0de90641f1655f9dd7b412477bf28c76d692f3aecff/yarl-1.24.5-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:82632daed195dcc8ea664e8556dc9bdbd671960fb3776bd92806ce05792c2448", size = 103500, upload-time = "2026-07-20T02:06:07.556Z" }, + { url = "https://files.pythonhosted.org/packages/fb/9c/1e67084c2a6e2f2db0e3be798328cb3be42c0119b621d25461479a224d21/yarl-1.24.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:53e549287ef628fecba270045c9701b0c564563a9b0577d24a4ec75b8ab8040f", size = 115780, upload-time = "2026-07-20T02:06:09.599Z" }, + { url = "https://files.pythonhosted.org/packages/58/86/1f94664e147474337e3359f52012cf3d02f825f694317b178bfba1078c62/yarl-1.24.5-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fcd3b77e2f17bbe4ca56ec7bcb07992647d19d0b9c05d84886dcd6f9eb810afd", size = 115308, upload-time = "2026-07-20T02:06:11.352Z" }, + { url = "https://files.pythonhosted.org/packages/0a/43/8e55ae7538ba5f28ccb3c845c6dd4549cf7016d5992e5326512519107cdd/yarl-1.24.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d46b86567dd4e248c6c159fcbcdcce01e0a5c8a7cd2334a0fff759d0fa075b16", size = 110574, upload-time = "2026-07-20T02:06:13.129Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ba/a889ec8765cedcf2ac44dcb02d6a21e4861399b243b263c5f2dde27ee740/yarl-1.24.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7f72c74aa99359e27a2ee8d6613fefa28b5f76a983c083074dfc2aaa4ab46213", size = 109914, upload-time = "2026-07-20T02:06:15.243Z" }, + { url = "https://files.pythonhosted.org/packages/9c/c3/e45f821af67b791c2dbbe4a9f4137a1d33f8d386654a05a0c3f47bdfa25d/yarl-1.24.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3f45789ce415a7ec0820dc4f82925f9b5f7732070be1dec1f5f23ec381435a24", size = 107712, upload-time = "2026-07-20T02:06:17.443Z" }, + { url = "https://files.pythonhosted.org/packages/02/00/2ab0f42c9857fcb490bfaa6647b14540b53d241ab209f23220b958cc5832/yarl-1.24.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:6e73e7fe93f17a7b191f52ec9da9dd8c06a8fe735a1ecbd13b97d1c723bff385", size = 104251, upload-time = "2026-07-20T02:06:19.259Z" }, + { url = "https://files.pythonhosted.org/packages/7a/70/709d9a286e98af2c7fd8e4e6cada658b5c0e30d87dd7e2a63c2fb5767217/yarl-1.24.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4a36f9becdd4c5c52a20c3e9484128b070b1dcfc8944c006f3a528295a359a9c", size = 115319, upload-time = "2026-07-20T02:06:21.207Z" }, + { url = "https://files.pythonhosted.org/packages/5c/6c/3eaa515142991fe84cfc483ff986492211f1978f90161ccefdbec919d09b/yarl-1.24.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:7bcbe0fcf850eae67b6b01749815a4f7161c560a844c769ad7b48fcd99f791c4", size = 109163, upload-time = "2026-07-20T02:06:23.006Z" }, + { url = "https://files.pythonhosted.org/packages/bb/64/711dafce66c323a3144d470547a71c5384c57623308ac8bb5e4b903ac148/yarl-1.24.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:24e861e9630e0daddcb9191fb187f60f034e17a4426f8101279f0c475cd74144", size = 115435, upload-time = "2026-07-20T02:06:24.923Z" }, + { url = "https://files.pythonhosted.org/packages/cf/f3/9b9d0e6d84bea851eb1ba99e4bdc755b86fd813e49ec86dfe42f26befdef/yarl-1.24.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9335a099ad87287c37fe5d1a982ff392fa5efe5d14b40a730b1ec1d6a41382b4", size = 110691, upload-time = "2026-07-20T02:06:26.973Z" }, + { url = "https://files.pythonhosted.org/packages/86/e4/62a06b7e87c4246ac76b7c2da136f972eb4a3a1fc94abb07e7022d6fdb0a/yarl-1.24.5-cp313-cp313-win_amd64.whl", hash = "sha256:2dbe06fc16bc91502bca713704022182e5729861ae00277c3a23354b40929740", size = 97454, upload-time = "2026-07-20T02:06:29.163Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c9/5fc8025b318ab10db413b61056bd0d95c557a70e8df4210c7511f866329c/yarl-1.24.5-cp313-cp313-win_arm64.whl", hash = "sha256:6b8536851f9f65e7f00c7a1d49ba7f2be0ffe2c11555367fc9f50d9f842410a1", size = 92813, upload-time = "2026-07-20T02:06:31.113Z" }, + { url = "https://files.pythonhosted.org/packages/a9/08/5f3085fef9564217074db9dd8573de1795bc82cde61a7ad10b6a7234a569/yarl-1.24.5-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:2729fcfc4f6a596fb0c50f32090400aa9367774ac296a00387e65098c0befa76", size = 135680, upload-time = "2026-07-20T02:06:33.273Z" }, + { url = "https://files.pythonhosted.org/packages/98/35/ba9436e579bd48a8801f2021d842d9ab4994c26e4c7dd3a4c1f1bcb57a9e/yarl-1.24.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ff330d3c30db4eb6b01d79e29d2d0b407a7ecad39cfd9ec993ece57396a2ec0d", size = 97395, upload-time = "2026-07-20T02:06:35.259Z" }, + { url = "https://files.pythonhosted.org/packages/18/a9/a07f76f3c44e02b25cc743af5ef93eef27f7013eadca770451b6a6ccb5db/yarl-1.24.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e42d75862735da90e7fc5a7b23db0c976f737113a54b3c9777a9b665e9cbff75", size = 97223, upload-time = "2026-07-20T02:06:37.216Z" }, + { url = "https://files.pythonhosted.org/packages/77/f7/a9a1d6fa7dd9e388f95b30f6ad3ec4e285f6c8f61f44ce16070c3fcfe414/yarl-1.24.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a3732e66413163e72508da9eff9ce9d2846fde51fae45d3605393d3e6cd303e9", size = 108777, upload-time = "2026-07-20T02:06:39.292Z" }, + { url = "https://files.pythonhosted.org/packages/2f/44/e0b86c302471fabd6f02808ecf2ac52b8412b624787849d4bf2cdb466f6f/yarl-1.24.5-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5b8ee53be440a0cffc991a27be3057e0530122548dbe7c0892df08822fce5ede", size = 103119, upload-time = "2026-07-20T02:06:41.456Z" }, + { url = "https://files.pythonhosted.org/packages/d1/16/9c16d180bf8faaf223225eb50e1245870ff1ae0e302a27153988e65c51fd/yarl-1.24.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:af3aefa655adb5869491fa907e652290386800ae99cc50095cba71e2c6aefdca", size = 116471, upload-time = "2026-07-20T02:06:43.696Z" }, + { url = "https://files.pythonhosted.org/packages/d2/8d/b219b9df28a02ce95cfbdd41d2f7caa5669d0ff979c1c9975697145e33c5/yarl-1.24.5-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2120b96872df4a117cde97d270bac96aea7cc52205d305cf4611df694a487027", size = 115974, upload-time = "2026-07-20T02:06:45.874Z" }, + { url = "https://files.pythonhosted.org/packages/9b/e8/f20557aca240d88e69850ad1ee91756821d094bb1310565c04d25c6682a2/yarl-1.24.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:66410eb6345d467151934b49bfa70fb32f5b35a6140baa40ad97d6436abea2e9", size = 110830, upload-time = "2026-07-20T02:06:47.852Z" }, + { url = "https://files.pythonhosted.org/packages/db/18/199b85109a53eeca64ee19c9cca228287e8e4ab0cc1a09b28f530e65cce0/yarl-1.24.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4af7b7e1be0a69bee8210735fe6dcfc38879adfac6d62e789d53ba432d1ffa41", size = 110054, upload-time = "2026-07-20T02:06:49.84Z" }, + { url = "https://files.pythonhosted.org/packages/aa/2f/ed28147f8cd7f48c49367c90713b30a555284b6105a6a56f3a05568da795/yarl-1.24.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa139875ff98ab97da323cfadfaff08900d1ad42f1b5087b0b812a55c5a06373", size = 108312, upload-time = "2026-07-20T02:06:51.835Z" }, + { url = "https://files.pythonhosted.org/packages/c5/c5/55e16ae0a5c227cea8df1c6871ba57d614a34243146c05729caf2a1bd9c5/yarl-1.24.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:0055afc45e864b92729ac7600e2d102c17bef060647e74bca75fa84d66b9ff36", size = 103662, upload-time = "2026-07-20T02:06:54.061Z" }, + { url = "https://files.pythonhosted.org/packages/8d/ea/dbd7c2caec459c9a426f18b02688ecbfb58620d0f6a3422d24769fbaf8ab/yarl-1.24.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f0e466ed7511fe9d459a819edbc6c2585c0b6eabde9fa8a8947552468a7a6ef0", size = 116090, upload-time = "2026-07-20T02:06:56.015Z" }, + { url = "https://files.pythonhosted.org/packages/06/84/39ce4ce3059e07fece5fbdbee8c4053406af9aca911ce9fa5f8548aab6af/yarl-1.24.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f141474e85b7e54998ec5180530a7cda99ab29e282fa50e0756d89981a9b43c5", size = 109523, upload-time = "2026-07-20T02:06:57.926Z" }, + { url = "https://files.pythonhosted.org/packages/a9/8b/71ff44137b405c64a7788075669c24010019f57a7464b78c3a6cbee539d9/yarl-1.24.5-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:e2935f8c39e3b03e83519292d78f075189978f3f4adc15a78144c7c8e2a1cba5", size = 116084, upload-time = "2026-07-20T02:06:59.868Z" }, + { url = "https://files.pythonhosted.org/packages/62/c0/423078fdd4042e1862c11f0ffd977a0ffa393783c12bee94685923bc189e/yarl-1.24.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9d1216a7f6f77836617dba35687c5b78a4170afc3c3f18fc788f785ba26565c4", size = 111006, upload-time = "2026-07-20T02:07:01.907Z" }, + { url = "https://files.pythonhosted.org/packages/cf/52/6daa2ee9d95e5c98b8128f8df91eb692eb423ab274b8cf08db52152fad26/yarl-1.24.5-cp314-cp314-win_amd64.whl", hash = "sha256:5ba4f78df2bcc19f764a4b26a8a4f5049c110090ad5825993aacb052bf8003ad", size = 99215, upload-time = "2026-07-20T02:07:03.852Z" }, + { url = "https://files.pythonhosted.org/packages/ec/0e/464a847d7359e0da75dd9fc5c1d1aa35d0159ea31e5f8e66a3c1c29ff3d0/yarl-1.24.5-cp314-cp314-win_arm64.whl", hash = "sha256:9e4e16c73d717c5cf27626c524d0a2e261ad20e46932b2670f64ad5dde23e26f", size = 94566, upload-time = "2026-07-20T02:07:06.074Z" }, + { url = "https://files.pythonhosted.org/packages/e2/55/e03acc4446772660bc335e86e41ef31e4d0d838fd641531a11a5ee33b493/yarl-1.24.5-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:e1ae548a9d901adca07899a4147a7c826bbcc06239d3ce9a59f57886a28a4c88", size = 142533, upload-time = "2026-07-20T02:07:08.284Z" }, + { url = "https://files.pythonhosted.org/packages/ae/71/4acd3a1fc7cf14345cdb302665ecd2097f62c365b4f14ca17d4f37775cf9/yarl-1.24.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:ff405d91509d88e8d44129cd87b18d70acd1f0c1aeabd7bc3c46792b1fe2acba", size = 100776, upload-time = "2026-07-20T02:07:10.197Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0b/cfb76b7fe99686db264bff829779a539d923e7564ffd7ef18da6c54c3774/yarl-1.24.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:47e98aab9d8d82ff682e7b0b5dded33bf138a32b817fcf7fa3b27b2d7c412928", size = 100913, upload-time = "2026-07-20T02:07:12.357Z" }, + { url = "https://files.pythonhosted.org/packages/8b/3f/7116e782992abbd4fb6948488aec72078895e929a23078290739e8396fce/yarl-1.24.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f0a658a6d3fafee5c6f63c58f3e785c8c43c93fbc02bf9f2b6663f8185e0971f", size = 106507, upload-time = "2026-07-20T02:07:14.173Z" }, + { url = "https://files.pythonhosted.org/packages/33/90/d4d2d73ee78229cc889872eb8e085d8f5c6f51abdb178409fd9b23cf74fd/yarl-1.24.5-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4377407001ca3c057773f44d8ddd6358fa5f691407c1ba92210bd3cf8d9e4c95", size = 99219, upload-time = "2026-07-20T02:07:16.019Z" }, + { url = "https://files.pythonhosted.org/packages/3e/fa/a6df1a9bccd644eec00abee0dff4277416222cec435330fd1f2858523ec1/yarl-1.24.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7c0494a31a1ac5461a226e7947a9c9b78c44e1dc7185164fa7e9651557a5d9bc", size = 111804, upload-time = "2026-07-20T02:07:18.141Z" }, + { url = "https://files.pythonhosted.org/packages/8a/9e/7b2a1f4bcc20e9447156dd2b1c4d01f70d9df0759025ee7d09a84ffae134/yarl-1.24.5-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a7cff474ab7cd149765bb784cf6d78b32e18e20473fb7bda860bce98ab58e9da", size = 110943, upload-time = "2026-07-20T02:07:20.06Z" }, + { url = "https://files.pythonhosted.org/packages/08/ff/22c92affb0f9b623ca753d27d968b5625b868f12c6378d049d55ae247643/yarl-1.24.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cbb833ccacdb5519eff9b8b71ee618cc2801c878e77e288775d77c3a2ced858a", size = 108251, upload-time = "2026-07-20T02:07:22.217Z" }, + { url = "https://files.pythonhosted.org/packages/45/44/5769b96298c1e195fb412997b6090af2a84105cf59c17613558a2d011d1f/yarl-1.24.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:82f75e05912e84b7a0fe57075d9c59de3cb352b928330f2eb69b2e1f54c3e1f0", size = 106025, upload-time = "2026-07-20T02:07:24.083Z" }, + { url = "https://files.pythonhosted.org/packages/4c/40/009e8e791fd9762c0e1567e69248acb4f49064597e1680874c16dd8bb798/yarl-1.24.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:16a2f5010280020e90f5330257e6944bc33e73593b136cc5a241e6c1dc292498", size = 106573, upload-time = "2026-07-20T02:07:26.248Z" }, + { url = "https://files.pythonhosted.org/packages/20/c6/b7480578f8a0a80946f36ad6df547ecec704f9ba69d2de60f8aa6f1c1cbf/yarl-1.24.5-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:ffcd54362564dc1a30fb74d8b8a6e5a6b11ebd5e27266adc3b7427a21a6c9104", size = 100751, upload-time = "2026-07-20T02:07:28.098Z" }, + { url = "https://files.pythonhosted.org/packages/d4/27/4476f3360b91a48c5cf125e91f59a3bd35299d84a431a258d57f5977bb11/yarl-1.24.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0465ec8cedc2349b97a6b595ace64084a50c6e839eca40aa0626f38b8350e331", size = 111643, upload-time = "2026-07-20T02:07:30.88Z" }, + { url = "https://files.pythonhosted.org/packages/4c/4b/5cdd3e5ee944e8af31e52f6cd3d3af5fd7b937e036ccbbba2c9ffebede95/yarl-1.24.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4db9aecb141cb7a5447171b57aa1ed3a8fee06af40b992ffc31206c0b0121550", size = 106312, upload-time = "2026-07-20T02:07:33.06Z" }, + { url = "https://files.pythonhosted.org/packages/18/86/f406b0c2a6f99575de2da671ef47aa06f89a5be83a27a46971c3b86cecdb/yarl-1.24.5-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:f540c013589084679a6c7fac07096b10159737918174f5dfc5e11bf5bca4dfe6", size = 110379, upload-time = "2026-07-20T02:07:35.155Z" }, + { url = "https://files.pythonhosted.org/packages/f0/6c/9f3adfbd3b30b4fa0f7ccb3a83eba2c1152d3fff554d535e640ba0f7ba2b/yarl-1.24.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a61834fb15d81322d872eaafd333838ae7c9cea84067f232656f75965933d047", size = 108497, upload-time = "2026-07-20T02:07:37.35Z" }, + { url = "https://files.pythonhosted.org/packages/dd/37/91eb2e5ca883a529c1b390348a74cd9fc0512171727f547ce70bfe02be5c/yarl-1.24.5-cp314-cp314t-win_amd64.whl", hash = "sha256:5c88e5815a49d289e599f3513aa7fde0bc2092ff188f99c940f007f90f53d104", size = 102450, upload-time = "2026-07-20T02:07:39.578Z" }, + { url = "https://files.pythonhosted.org/packages/bf/f4/ed5c402ac8fde4403ed3366c2716bfddc8a6677ebd59f3d62772cc7fe468/yarl-1.24.5-cp314-cp314t-win_arm64.whl", hash = "sha256:cf139c02f5f23ef6532040a30ff662c00a318c952334f211046b8e60b7f17688", size = 97222, upload-time = "2026-07-20T02:07:41.55Z" }, + { url = "https://files.pythonhosted.org/packages/61/02/962c1cbfc401a30c1d034dc67ff395f64b52302c6d62de556c1fca99acc0/yarl-1.24.5-py3-none-any.whl", hash = "sha256:a33700d13d9b7d84fd10947b09ff69fb9a792e519c8cb9764a3ca70baa6c23a7", size = 58612, upload-time = "2026-07-20T02:07:43.461Z" }, +] diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/apphost.cs b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/apphost.cs new file mode 100644 index 000000000..1505bbb54 --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/apphost.cs @@ -0,0 +1,55 @@ +#:sdk Aspire.AppHost.Sdk@13.5.3 +#:package Aspire.Hosting.JavaScript@13.5.3 +#:package Aspire.Hosting.Python@13.5.3 + +var builder = DistributedApplication.CreateBuilder(args); + + + +#pragma warning disable ASPIRECSHARPAPPS001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + +var app = builder.AddUvicornApp("app", "./app", "main:app") + .WithUv() + //.WithEnvironment("REPAIR_PLANNER_AGENT_URL", dotnetapp.GetEndpoint("https")) + .WithArgs("--host", "0.0.0.0", "--port", "8000") + .WithHttpsEndpoint(port: 8000, name: "api", isProxied: false) + .WithExternalHttpEndpoints() + .WithHttpHealthCheck("/health", endpointName: "api"); + +var appApiEndpoint = app.GetEndpoint("api"); + +var dotnetworkflow = builder.AddCSharpApp("dotnetworkflow", "./dotnetworkflow/FactoryWorkflow.csproj") + .WithExternalHttpEndpoints() + // .WithEnvironment("SSL_CERT_DIR", "/etc/ssl/certs") + // .WithHttpEndpoint(port:8001, env: "DOTNETWORKFLOW_PORT", name: "api") + .WithEnvironment("SSL_CERT_FILE", "/etc/ssl/certs/ca-certificates.crt") + .WithReference(app) + .WithEnvironment("MAINTENANCE_SCHEDULER_AGENT_URL", ReferenceExpression.Create($"{appApiEndpoint}/maintenance-scheduler")) + .WithEnvironment("PARTS_ORDERING_AGENT_URL", ReferenceExpression.Create($"{appApiEndpoint}/parts-ordering")) + .WithHttpHealthCheck("/health"); +#pragma warning restore ASPIRECSHARPAPPS001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. +// Use the externally reachable HTTP endpoint for the frontend (Aspire also creates an internal/proxied endpoint). +var apiEndpoint = dotnetworkflow.GetEndpoint("http") + ?? throw new InvalidOperationException("Expected 'http' endpoint for 'dotnetworkflow' was not created."); +var frontend = builder.AddViteApp("frontend", "./frontend") + .WithReference(dotnetworkflow) + .WithReference(app); + +var codespaceName = Environment.GetEnvironmentVariable("CODESPACE_NAME"); +var codespacesPortForwardingDomain = Environment.GetEnvironmentVariable("GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN"); + +if (!string.IsNullOrWhiteSpace(codespaceName) && !string.IsNullOrWhiteSpace(codespacesPortForwardingDomain)) +{ + frontend.WithEnvironment("VITE_API_URL", () => $"https://{codespaceName}-{apiEndpoint.Port}.{codespacesPortForwardingDomain}/"); +} +else +{ + frontend.WithEnvironment("VITE_API_URL", ReferenceExpression.Create($"{apiEndpoint}/")); +} + +frontend.WaitFor(app) + .WaitFor(dotnetworkflow); + +app.PublishWithContainerFiles(frontend, "./static"); + +builder.Build().Run(); diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/apphost.run.json b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/apphost.run.json new file mode 100644 index 000000000..44e6a9772 --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/apphost.run.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:17072;http://localhost:15161", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21114", + "ASPIRE_DASHBOARD_MCP_ENDPOINT_URL": "https://localhost:23144", + "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22282" + } + }, + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:15161", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19006", + "ASPIRE_DASHBOARD_MCP_ENDPOINT_URL": "http://localhost:18251", + "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20270" + } + } + } +} diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/aspire.config.json b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/aspire.config.json new file mode 100644 index 000000000..e9e2065d6 --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/aspire.config.json @@ -0,0 +1,27 @@ +{ + "appHost": { + "path": "apphost.cs" + }, + "profiles": { + "https": { + "applicationUrl": "https://localhost:17072;http://localhost:15161", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21114", + "ASPIRE_DASHBOARD_MCP_ENDPOINT_URL": "https://localhost:23144", + "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22282" + } + }, + "http": { + "applicationUrl": "http://localhost:15161", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19006", + "ASPIRE_DASHBOARD_MCP_ENDPOINT_URL": "http://localhost:18251", + "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20270" + } + } + } +} \ No newline at end of file diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/dotnetworkflow/AgentProviders.cs b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/dotnetworkflow/AgentProviders.cs new file mode 100644 index 000000000..d0d7bdaa6 --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/dotnetworkflow/AgentProviders.cs @@ -0,0 +1,161 @@ +using Azure.AI.Projects; +using Microsoft.Agents.AI; +using Microsoft.Agents.AI.A2A; +using Microsoft.Extensions.Logging; +using FactoryWorkflow.RepairPlanner; +using FactoryWorkflow.RepairPlanner.Services; +using A2A; + +namespace FactoryWorkflow; + +/// +/// Provides agents hosted in Azure AI Foundry (Agent Service). +/// These are v2 Prompt Agents created and managed in the Azure AI Foundry portal. +/// +public static class AgentServiceProvider +{ + /// + /// Retrieves agents from Azure AI Foundry Agent Service. + /// - AnomalyClassificationAgent: Classifies telemetry anomalies by severity + /// - FaultDiagnosisAgent: Diagnoses root causes of detected anomalies + /// + public static List GetAgents(AIProjectClient projectClient, ILogger logger) + { + var agents = new List(); + + // AnomalyClassificationAgent - classifies severity of telemetry anomalies + var anomalyAgent = projectClient + .AsAIAgent("AnomalyClassificationAgent") + .AsBuilder() + .UseOpenTelemetry(sourceName: "AnomalyClassificationAgent") + .Build(); + agents.Add(anomalyAgent); + logger.LogInformation("Retrieved Agent Service agent: {AgentName}", anomalyAgent.Name); + + // FaultDiagnosisAgent - diagnoses root causes using knowledge base + var faultAgent = projectClient + .AsAIAgent("FaultDiagnosisAgent") + .AsBuilder() + .UseOpenTelemetry(sourceName: "FaultDiagnosisAgent") + .Build(); + agents.Add(faultAgent); + logger.LogInformation("Retrieved Agent Service agent: {AgentName}", faultAgent.Name); + + return agents; + } +} + +/// +/// Provides locally-hosted agents that use Azure OpenAI directly. +/// These agents have custom tools (e.g., Cosmos DB) that aren't available in Agent Service. +/// +public static class LocalAgentProvider +{ + /// + /// Creates the RepairPlannerAgent which uses Cosmos DB tools for work order management. + /// This agent runs locally using Azure OpenAI and has access to: + /// - GetAvailableTechnicians: Query technicians from Cosmos DB + /// - GetAvailableParts: Query parts inventory from Cosmos DB + /// - CreateWorkOrder: Create work orders in Cosmos DB + /// + public static AIAgent? GetRepairPlannerAgent( + IConfiguration config, + ILoggerFactory loggerFactory, + ILogger logger) + { + var aoaiEndpoint = config["AZURE_OPENAI_ENDPOINT"]; + var aoaiDeployment = config["AZURE_OPENAI_DEPLOYMENT_NAME"] ?? "gpt-5.4"; + var cosmosEndpoint = config["COSMOS_ENDPOINT"]; + var cosmosKey = config["COSMOS_KEY"]; + var cosmosDatabase = config["COSMOS_DATABASE"] ?? "FactoryOpsDB"; + + if (string.IsNullOrEmpty(aoaiEndpoint)) + { + logger.LogWarning("AZURE_OPENAI_ENDPOINT not configured - RepairPlannerAgent will be skipped"); + return null; + } + + try + { + // Create Cosmos DB service for the tools (if configured) + CosmosDbService? cosmosService = null; + if (!string.IsNullOrEmpty(cosmosEndpoint) && !string.IsNullOrEmpty(cosmosKey)) + { + cosmosService = new CosmosDbService( + cosmosEndpoint, cosmosKey, cosmosDatabase, + loggerFactory.CreateLogger()); + logger.LogInformation("CosmosDbService created for RepairPlannerAgent tools"); + } + + var agent = RepairPlannerAgentFactory.Create( + aoaiEndpoint, aoaiDeployment, cosmosService, loggerFactory); + + logger.LogInformation("Created local agent: {AgentName} at {Endpoint}", agent.Name, aoaiEndpoint); + return agent; + } + catch (Exception ex) + { + logger.LogWarning(ex, "Could not create RepairPlannerAgent - skipping"); + return null; + } + } +} + +/// +/// Provides agents via the Agent-to-Agent (A2A) protocol. +/// These agents are hosted in a separate Python service and communicate via HTTP. +/// +public static class A2AAgentProvider +{ + /// + /// Retrieves agents from A2A-compatible endpoints (Python services from Challenge 3). + /// - MaintenanceSchedulerAgent: Schedules maintenance windows based on technician availability + /// - PartsOrderingAgent: Orders required parts for repairs + /// + public static async Task> GetAgentsAsync(IConfiguration config, ILogger logger) + { + var agents = new List(); + + // MaintenanceSchedulerAgent - schedules maintenance windows + var maintenanceSchedulerUrl = config["MAINTENANCE_SCHEDULER_AGENT_URL"]; + if (!string.IsNullOrEmpty(maintenanceSchedulerUrl)) + { + var agent = await ResolveA2AAgentAsync(maintenanceSchedulerUrl, "MaintenanceSchedulerAgent", logger); + if (agent != null) agents.Add(agent); + } + else + { + logger.LogWarning("MAINTENANCE_SCHEDULER_AGENT_URL not configured - MaintenanceSchedulerAgent will be skipped"); + } + + // PartsOrderingAgent - orders required parts + var partsOrderingUrl = config["PARTS_ORDERING_AGENT_URL"]; + if (!string.IsNullOrEmpty(partsOrderingUrl)) + { + var agent = await ResolveA2AAgentAsync(partsOrderingUrl, "PartsOrderingAgent", logger); + if (agent != null) agents.Add(agent); + } + else + { + logger.LogWarning("PARTS_ORDERING_AGENT_URL not configured - PartsOrderingAgent will be skipped"); + } + + return agents; + } + + private static async Task ResolveA2AAgentAsync(string url, string agentName, ILogger logger) + { + try + { + var cardResolver = new A2ACardResolver(new Uri(url.TrimEnd('/') + "/")); + var agent = await cardResolver.GetAIAgentAsync(); + logger.LogInformation("Retrieved A2A agent: {AgentName} at {Url}", agent.Name, url); + return agent; + } + catch (Exception ex) + { + logger.LogError(ex, "Failed to resolve A2A agent {AgentName} at {Url}", agentName, url); + return null; + } + } +} diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/dotnetworkflow/FactoryWorkflow.csproj b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/dotnetworkflow/FactoryWorkflow.csproj new file mode 100644 index 000000000..ab9b2deaa --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/dotnetworkflow/FactoryWorkflow.csproj @@ -0,0 +1,38 @@ +ο»Ώ + + + net10.0 + enable + enable + $(NoWarn);CA2252 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/dotnetworkflow/Models.cs b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/dotnetworkflow/Models.cs new file mode 100644 index 000000000..445345703 --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/dotnetworkflow/Models.cs @@ -0,0 +1,141 @@ +namespace FactoryWorkflow; + +/// +/// Request payload for the /api/analyze_machine endpoint. +/// +/// The machine identifier to analyze +/// Raw telemetry data from the machine sensors +public record AnalyzeRequest(string machine_id, System.Text.Json.JsonElement telemetry); + +/// +/// Complete workflow response containing results from all agents in the pipeline. +/// +public class WorkflowResponse +{ + /// + /// Results from each agent in execution order. + /// + public List AgentSteps { get; set; } = new(); + + /// + /// Final output message from the last agent in the workflow. + /// + public string? FinalMessage { get; set; } +} + +/// +/// Execution details for a single agent in the workflow pipeline. +/// +public class AgentStepResult +{ + /// + /// Name of the agent that executed this step. + /// + public string AgentName { get; set; } = string.Empty; + + /// + /// Tool/function calls made by this agent during execution. + /// + public List ToolCalls { get; set; } = new(); + + /// + /// Accumulated text output from the agent. + /// + public string TextOutput { get; set; } = string.Empty; + + /// + /// Indicates that the agent execution itself failed. + /// + public bool IsError { get; set; } + + /// + /// Final message from this agent (passed to the next agent in the workflow). + /// + public string? FinalMessage { get; set; } +} + +/// +/// Information about a tool/function call made by an agent. +/// +public class ToolCallInfo +{ + /// + /// Name of the tool that was called. + /// + public string ToolName { get; set; } = string.Empty; + + /// + /// Unique identifier for this tool call. + /// + public string? CallId { get; set; } + + /// + /// Arguments passed to the tool (serialized). + /// + public string? Arguments { get; set; } + + /// + /// Result returned by the tool (truncated for response size). + /// + public string? Result { get; set; } +} + +// ============================================================================ +// Server-Sent Events (SSE) Types +// ============================================================================ +// Prefixed with "Sse" to avoid conflicts with Microsoft.Agents.AI.Workflows types + +/// +/// Base class for all SSE events sent during workflow execution. +/// +public abstract class SseEvent +{ + public string Type { get; init; } = string.Empty; + public DateTimeOffset Timestamp { get; init; } = DateTimeOffset.UtcNow; +} + +/// +/// Emitted when the workflow starts executing. +/// +public class SseWorkflowStarted : SseEvent +{ + public SseWorkflowStarted() => Type = "workflow_started"; + public List AgentPipeline { get; set; } = new(); +} + +/// +/// Emitted when an agent begins processing. +/// +public class SseAgentStarted : SseEvent +{ + public SseAgentStarted() => Type = "agent_started"; + public string AgentName { get; set; } = string.Empty; + public int AgentIndex { get; set; } +} + +/// +/// Emitted when an agent completes processing. +/// +public class SseAgentCompleted : SseEvent +{ + public SseAgentCompleted() => Type = "agent_completed"; + public AgentStepResult Step { get; set; } = new(); +} + +/// +/// Emitted when the entire workflow completes. +/// +public class SseWorkflowCompleted : SseEvent +{ + public SseWorkflowCompleted() => Type = "workflow_completed"; + public WorkflowResponse Result { get; set; } = new(); +} + +/// +/// Emitted when an error occurs during workflow execution. +/// +public class SseWorkflowError : SseEvent +{ + public SseWorkflowError() => Type = "workflow_error"; + public string Error { get; set; } = string.Empty; +} diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/dotnetworkflow/Program.cs b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/dotnetworkflow/Program.cs new file mode 100644 index 000000000..1b57dab05 --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/dotnetworkflow/Program.cs @@ -0,0 +1,311 @@ +using Azure.AI.Projects; +using Azure.Identity; +using Azure.Monitor.OpenTelemetry.Exporter; +using FactoryWorkflow; +using Microsoft.Agents.AI; +using Microsoft.Agents.AI.Workflows; +using OpenTelemetry; +using OpenTelemetry.Metrics; +using OpenTelemetry.Resources; +using OpenTelemetry.Trace; +using System.Diagnostics; +using System.Text.Json; + +// ============================================================================ +// Application Startup +// ============================================================================ + +DotNetEnv.Env.TraversePath().Load(); + +var builder = WebApplication.CreateBuilder(args); + +// Configure services +builder.Services.AddHttpClient(); +builder.Services.AddCors(options => +{ + options.AddDefaultPolicy(policy => + policy.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod()); +}); +builder.Configuration.AddEnvironmentVariables(); + +// Register Azure AI Project client for Agent Service access +builder.Services.AddSingleton(sp => +{ + var endpoint = sp.GetRequiredService()["AZURE_AI_PROJECT_ENDPOINT"] + ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is required"); + return new AIProjectClient(new Uri(endpoint), new AzureCliCredential()); +}); + +builder.Services.AddSingleton(sp => LoggerFactory.Create(b => b.AddConsole())); + +// Configure OpenTelemetry tracing +ConfigureTracing(builder); + +var app = builder.Build(); +app.UseCors(); + +// ============================================================================ +// API Endpoints +// ============================================================================ + +app.MapGet("/health", () => Results.Ok(new { Status = "Healthy", Timestamp = DateTimeOffset.UtcNow })); + +app.MapPost("/api/analyze_machine", async ( + AnalyzeRequest request, + AIProjectClient projectClient, + IConfiguration config, + ILoggerFactory loggerFactory, + ILogger logger) => +{ + logger.LogInformation("Starting analysis for machine {MachineId}", request.machine_id); + + try + { + // ================================================================ + // Step 1: Collect all agents for the workflow pipeline + // ================================================================ + // The workflow executes agents sequentially, passing text output + // from each agent to the next. Agent order matters! + // + // Pipeline: AnomalyClassification β†’ FaultDiagnosis β†’ RepairPlanner + // β†’ MaintenanceScheduler β†’ PartsOrdering + // ================================================================ + + var agents = new List(); + + // Agent Service agents (Azure AI Foundry hosted) + agents.AddRange(AgentServiceProvider.GetAgents(projectClient, logger)); + + // Local agent with Cosmos DB tools + var repairPlanner = LocalAgentProvider.GetRepairPlannerAgent(config, loggerFactory, logger); + if (repairPlanner != null) agents.Add(repairPlanner); + + // A2A agents (Python services from Challenge 3) + agents.AddRange(await A2AAgentProvider.GetAgentsAsync(config, logger)); + + logger.LogInformation("Workflow pipeline: [{Agents}]", string.Join(" β†’ ", agents.Select(a => a.Name))); + + // ================================================================ + // Step 2: Build and execute the workflow + // ================================================================ + // Uses WorkflowBuilder to create a sequential pipeline. + // TextOnlyAgentExecutor strips MCP tool history between agents + // to work around SDK deserialization issues. + // ================================================================ + + var telemetryJson = JsonSerializer.Serialize(request); + var workflowResult = await ExecuteWorkflowAsync(agents, telemetryJson, logger); + + return Results.Ok(workflowResult); + } + catch (Exception ex) + { + logger.LogError(ex, "Workflow failed for machine {MachineId}", request.machine_id); + return Results.Problem(ex.Message); + } +}); + +// ============================================================================ +// Streaming Endpoint (Server-Sent Events) +// ============================================================================ + +app.MapPost("/api/analyze_machine/stream", async ( + HttpContext httpContext, + AIProjectClient projectClient, + IConfiguration config, + ILoggerFactory loggerFactory, + ILogger logger) => +{ + // Read request body manually since we're using HttpContext directly + var request = await httpContext.Request.ReadFromJsonAsync(); + if (request == null) + { + httpContext.Response.StatusCode = 400; + await httpContext.Response.WriteAsync("Invalid request body"); + return; + } + + logger.LogInformation("Starting SSE stream for machine {MachineId}", request.machine_id); + + // Set up SSE response headers + httpContext.Response.ContentType = "text/event-stream"; + httpContext.Response.Headers.CacheControl = "no-cache"; + httpContext.Response.Headers.Connection = "keep-alive"; + + var jsonOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + + // Helper to send SSE events + async Task SendEventAsync(T evt) where T : SseEvent + { + var json = JsonSerializer.Serialize(evt, jsonOptions); + await httpContext.Response.WriteAsync($"data: {json}\n\n"); + await httpContext.Response.Body.FlushAsync(); + } + + try + { + // Collect agents (same as non-streaming endpoint) + var agents = new List(); + agents.AddRange(AgentServiceProvider.GetAgents(projectClient, logger)); + + var repairPlanner = LocalAgentProvider.GetRepairPlannerAgent(config, loggerFactory, logger); + if (repairPlanner != null) agents.Add(repairPlanner); + + agents.AddRange(await A2AAgentProvider.GetAgentsAsync(config, logger)); + + // Send workflow_started event + await SendEventAsync(new SseWorkflowStarted + { + AgentPipeline = agents.Select(a => a.Name).ToList() + }); + + // Track agent index for progress updates + int agentIndex = 0; + + // Set up callback to stream agent completions + TextOnlyAgentExecutor.SetEventCallback(async (step) => + { + await SendEventAsync(new SseAgentCompleted { Step = step }); + agentIndex++; + + // Send agent_started for next agent if there is one + if (agentIndex < agents.Count) + { + await SendEventAsync(new SseAgentStarted + { + AgentName = agents[agentIndex].Name, + AgentIndex = agentIndex + }); + } + }); + + // Send first agent_started event + if (agents.Count > 0) + { + await SendEventAsync(new SseAgentStarted + { + AgentName = agents[0].Name, + AgentIndex = 0 + }); + } + + // Execute workflow + var telemetryJson = JsonSerializer.Serialize(request); + var workflowResult = await ExecuteWorkflowAsync(agents, telemetryJson, logger); + + // Send final workflow_completed event + await SendEventAsync(new SseWorkflowCompleted { Result = workflowResult }); + } + catch (Exception ex) + { + logger.LogError(ex, "SSE workflow failed for machine {MachineId}", request.machine_id); + await SendEventAsync(new SseWorkflowError { Error = ex.Message }); + } + finally + { + // Clear the callback to avoid memory leaks + TextOnlyAgentExecutor.SetEventCallback(null); + } +}); + +app.Run(); + +// ============================================================================ +// Workflow Execution +// ============================================================================ + +static async Task ExecuteWorkflowAsync( + List agents, + string input, + ILogger logger) +{ + // Create executors that pass only text between agents + var executors = agents.Select(a => new TextOnlyAgentExecutor(a)).ToList(); + //var executors = agents; + // Clear results from any previous run + TextOnlyAgentExecutor.ClearResults(); + + // Build sequential workflow: agent1 β†’ agent2 β†’ agent3 β†’ ... + var workflowBuilder = new WorkflowBuilder(executors[0]); + for (int i = 1; i < executors.Count; i++) + { + //workflowBuilder.BindExecutor(executors[i]); + workflowBuilder.AddEdge(executors[i - 1], executors[i]); + } + workflowBuilder.WithOpenTelemetry( + // Set `EnableSensitiveData` to true to include message content in traces + configure: cfg => cfg.EnableSensitiveData = true); + workflowBuilder.WithOutputFrom(executors[^1]); + + var workflow = workflowBuilder.Build(); + logger.LogInformation("Workflow built with {Count} agents", executors.Count); + + // Execute the workflow + var run = await InProcessExecution.RunStreamingAsync(workflow, input); + + + // Extract final output from workflow events + string? finalOutput = null; + await foreach (var evt in run.WatchStreamAsync()) + { + if (evt is WorkflowOutputEvent outputEvent && outputEvent.Is(out var text)) + { + finalOutput = text; + } + } + + // Build response from collected step results + return new WorkflowResponse + { + AgentSteps = TextOnlyAgentExecutor.StepResults.ToList(), + FinalMessage = finalOutput ?? TextOnlyAgentExecutor.StepResults.LastOrDefault()?.FinalMessage + }; +} + +// ============================================================================ +// Telemetry Configuration +// ============================================================================ + +static void ConfigureTracing(WebApplicationBuilder builder) +{ + const string SourceName = "FactoryWorkflow"; + + var resourceBuilder = ResourceBuilder.CreateDefault() + .AddService( + serviceName: builder.Environment.ApplicationName, + serviceVersion: typeof(Program).Assembly.GetName().Version?.ToString()) + .AddAttributes([ + new KeyValuePair("deployment.environment", builder.Environment.EnvironmentName), + ]); + + builder.Services.AddOpenTelemetry() + .WithTracing(tracingBuilder => + { + tracingBuilder + .SetResourceBuilder(resourceBuilder) + .AddSource(SourceName, "ChatClient") + .AddSource("*Microsoft.Agents.AI*") // Agent Framework telemetry + .AddSource("AnomalyClassificationAgent", "FaultDiagnosisAgent", "RepairPlannerAgent") + .AddSource("MaintenanceSchedulerAgent", "PartsOrderingAgent") + .AddHttpClientInstrumentation() + .AddAspNetCoreInstrumentation(); + + if (!string.IsNullOrEmpty(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"])) + { + tracingBuilder.AddOtlpExporter(); + } + + var appInsightsConnectionString = builder.Configuration["ApplicationInsights:ConnectionString"]; + if (!string.IsNullOrWhiteSpace(appInsightsConnectionString)) + { + tracingBuilder.AddAzureMonitorTraceExporter(options => + options.ConnectionString = appInsightsConnectionString); + } + }) + .WithMetrics(metricsBuilder => + { + metricsBuilder + .AddMeter("Microsoft.Agents.AI*") // Agent Framework metrics + .AddHttpClientInstrumentation(); + }); +} diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/dotnetworkflow/Properties/launchSettings.json b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/dotnetworkflow/Properties/launchSettings.json new file mode 100644 index 000000000..6f1dee9a3 --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/dotnetworkflow/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5231", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:7006;http://localhost:5231", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/dotnetworkflow/RepairPlanner/Models/DiagnosedFault.cs b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/dotnetworkflow/RepairPlanner/Models/DiagnosedFault.cs new file mode 100644 index 000000000..00860e65f --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/dotnetworkflow/RepairPlanner/Models/DiagnosedFault.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace FactoryWorkflow.RepairPlanner.Models; + +/// +/// Represents the diagnosed fault produced by the Fault Diagnosis Agent (Challenge 1). +/// This is the primary input to the Repair Planner Agent. +/// +public sealed class DiagnosedFault +{ + /// + /// The machine identifier (e.g., "machine-001"). + /// + [JsonProperty("machineId")] + public string MachineId { get; set; } = string.Empty; + + /// + /// The fault key/type (snake_case in the sample dataset, e.g., "curing_temperature_excessive"). + /// + [JsonProperty("faultType")] + public string FaultType { get; set; } = string.Empty; + + /// + /// Human-readable root cause hypothesis. + /// + [JsonProperty("rootCause")] + public string? RootCause { get; set; } + + /// + /// Severity label (e.g., "critical", "high", "medium", "low"). + /// + [JsonProperty("severity")] + public string? Severity { get; set; } + + /// + /// When the fault was detected. + /// + [JsonProperty("detectedAt")] + public DateTimeOffset DetectedAt { get; set; } + + /// + /// Additional context from upstream agents/sensors. + /// Values typically deserialize as . + /// + [JsonProperty("metadata")] + public Dictionary Metadata { get; set; } = new(StringComparer.OrdinalIgnoreCase); +} diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/dotnetworkflow/RepairPlanner/Models/Part.cs b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/dotnetworkflow/RepairPlanner/Models/Part.cs new file mode 100644 index 000000000..195a83c69 --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/dotnetworkflow/RepairPlanner/Models/Part.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace FactoryWorkflow.RepairPlanner.Models; + +/// +/// Represents a part inventory item stored in Cosmos DB (container: PartsInventory). +/// +public sealed class Part +{ + /// + /// Cosmos document id (e.g., "part-curing-bladder"). + /// + [JsonProperty("id")] + public string Id { get; set; } = string.Empty; + + [JsonProperty("partNumber")] + public string PartNumber { get; set; } = string.Empty; + + [JsonProperty("name")] + public string Name { get; set; } = string.Empty; + + [JsonProperty("category")] + public string? Category { get; set; } + + [JsonProperty("compatibleMachines")] + public List CompatibleMachines { get; set; } = new(); + + [JsonProperty("manufacturer")] + public string? Manufacturer { get; set; } + + [JsonProperty("quantityInStock")] + public int QuantityInStock { get; set; } + + [JsonProperty("unit")] + public string? Unit { get; set; } + + [JsonProperty("reorderLevel")] + public int? ReorderLevel { get; set; } + + [JsonProperty("unitCost")] + public decimal? UnitCost { get; set; } + + [JsonProperty("leadTimeDays")] + public int? LeadTimeDays { get; set; } + + [JsonProperty("location")] + public string? Location { get; set; } +} diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/dotnetworkflow/RepairPlanner/Models/RepairTask.cs b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/dotnetworkflow/RepairPlanner/Models/RepairTask.cs new file mode 100644 index 000000000..d1c7e36fd --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/dotnetworkflow/RepairPlanner/Models/RepairTask.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace FactoryWorkflow.RepairPlanner.Models; + +/// +/// A single step in a repair plan / work order. +/// +public sealed class RepairTask +{ + [JsonProperty("description")] + public string Description { get; set; } = string.Empty; + + /// + /// Estimated task duration in minutes. + /// + [JsonProperty("estimatedMinutes")] + public int EstimatedMinutes { get; set; } + + [JsonProperty("requiredTools")] + public List RequiredTools { get; set; } = new(); +} diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/dotnetworkflow/RepairPlanner/Models/Technician.cs b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/dotnetworkflow/RepairPlanner/Models/Technician.cs new file mode 100644 index 000000000..b55ce40f8 --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/dotnetworkflow/RepairPlanner/Models/Technician.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace FactoryWorkflow.RepairPlanner.Models; + +/// +/// Represents a maintenance technician stored in Cosmos DB (container: Technicians). +/// +public sealed class Technician +{ + /// + /// Cosmos document id (e.g., "tech-001"). + /// + [JsonProperty("id")] + public string Id { get; set; } = string.Empty; + + [JsonProperty("employeeId")] + public string? EmployeeId { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } = string.Empty; + + [JsonProperty("role")] + public string? Role { get; set; } + + [JsonProperty("department")] + public string? Department { get; set; } + + [JsonProperty("email")] + public string? Email { get; set; } + + [JsonProperty("phone")] + public string? Phone { get; set; } + + /// + /// Skill tags (snake_case) used for matching to fault requirements. + /// + [JsonProperty("skills")] + public List Skills { get; set; } = new(); + + [JsonProperty("certifications")] + public List Certifications { get; set; } = new(); + + [JsonProperty("available")] + public bool Available { get; set; } + + [JsonProperty("shiftSchedule")] + public string? ShiftSchedule { get; set; } + + /// + /// Work order numbers currently assigned (if any). + /// + [JsonProperty("assignedWorkOrders")] + public List AssignedWorkOrders { get; set; } = new(); +} diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/dotnetworkflow/RepairPlanner/Models/WorkOrder.cs b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/dotnetworkflow/RepairPlanner/Models/WorkOrder.cs new file mode 100644 index 000000000..76875e46a --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/dotnetworkflow/RepairPlanner/Models/WorkOrder.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace FactoryWorkflow.RepairPlanner.Models; + +/// +/// A work order produced by the Repair Planner Agent and stored in Cosmos DB (container: WorkOrders). +/// +public sealed class WorkOrder +{ + /// + /// Cosmos document id (e.g., "wo-2024-445"). + /// + [JsonProperty("id")] + public string Id { get; set; } = string.Empty; + + /// + /// Human-friendly work order number (e.g., "WO-2026-001"). + /// + [JsonProperty("workOrderNumber")] + public string? WorkOrderNumber { get; set; } + + [JsonProperty("machineId")] + public string MachineId { get; set; } = string.Empty; + + [JsonProperty("faultType")] + public string? FaultType { get; set; } + + [JsonProperty("title")] + public string? Title { get; set; } + + [JsonProperty("description")] + public string? Description { get; set; } + + /// + /// Work order type (e.g., "preventive", "corrective", "emergency"). + /// + [JsonProperty("type")] + public string? Type { get; set; } + + /// + /// Priority label (e.g., "critical", "high", "medium", "low"). + /// + [JsonProperty("priority")] + public string? Priority { get; set; } + + /// + /// Status label (e.g., "scheduled", "in_progress", "completed"). + /// + [JsonProperty("status")] + public string? Status { get; set; } + + /// + /// Technician id assigned to the work order (e.g., "tech-001"). + /// + [JsonProperty("assignedTo")] + public string? AssignedTo { get; set; } + + /// + /// Optional: embed the selected technician details. + /// + [JsonProperty("assignedTechnician")] + public Technician? AssignedTechnician { get; set; } + + [JsonProperty("createdDate")] + public DateTimeOffset? CreatedDate { get; set; } + + [JsonProperty("scheduledDate")] + public DateTimeOffset? ScheduledDate { get; set; } + + [JsonProperty("completedDate")] + public DateTimeOffset? CompletedDate { get; set; } + + /// + /// Estimated total duration in minutes. + /// + [JsonProperty("estimatedDuration")] + public int? EstimatedDurationMinutes { get; set; } + + /// + /// Actual duration in minutes (when completed). + /// + [JsonProperty("actualDuration")] + public int? ActualDurationMinutes { get; set; } + + /// + /// Optional: an AI-generated breakdown of tasks. + /// + [JsonProperty("tasks")] + public List Tasks { get; set; } = new(); + + /// + /// Optional: the parts required to execute the plan (by part number). + /// + [JsonProperty("requiredParts")] + public List RequiredParts { get; set; } = new(); + + /// + /// Parts actually consumed by the work order. + /// + [JsonProperty("partsUsed")] + public List PartsUsed { get; set; } = new(); + + [JsonProperty("notes")] + public string? Notes { get; set; } + + [JsonProperty("cost")] + public decimal? Cost { get; set; } + + /// + /// Downtime impact in minutes. + /// + [JsonProperty("downtime")] + public int? DowntimeMinutes { get; set; } +} + +/// +/// A part requirement expressed as a part number and quantity. +/// +public sealed class PartRequirement +{ + [JsonProperty("partNumber")] + public string PartNumber { get; set; } = string.Empty; + + [JsonProperty("quantity")] + public int Quantity { get; set; } +} + +/// +/// Represents a part usage entry in an existing work order. +/// +public sealed class WorkOrderPartUsage +{ + [JsonProperty("partId")] + public string PartId { get; set; } = string.Empty; + + [JsonProperty("quantity")] + public int Quantity { get; set; } +} diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/dotnetworkflow/RepairPlanner/RepairPlannerAgentFactory.cs b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/dotnetworkflow/RepairPlanner/RepairPlannerAgentFactory.cs new file mode 100644 index 000000000..1163980dc --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/dotnetworkflow/RepairPlanner/RepairPlannerAgentFactory.cs @@ -0,0 +1,278 @@ +using Azure.AI.OpenAI; +using Azure.Identity; +using Microsoft.Agents.AI; +using Microsoft.Extensions.AI; +using Microsoft.Extensions.Logging; +using System.Text.Json; +using FactoryWorkflow.RepairPlanner.Models; +using FactoryWorkflow.RepairPlanner.Services; + +namespace FactoryWorkflow.RepairPlanner; + +/// +/// Factory for creating the RepairPlanner AIAgent with Cosmos DB tools. +/// +public static class RepairPlannerAgentFactory +{ + // Mock data mappings for fault types to required skills + private static readonly IReadOnlyDictionary> FaultToSkills = + new Dictionary>(StringComparer.OrdinalIgnoreCase) + { + ["curing_temperature_excessive"] = new[] + { + "tire_curing_press", + "temperature_control", + "instrumentation", + "electrical_systems", + "plc_troubleshooting", + "mold_maintenance" + }, + ["curing_cycle_time_deviation"] = new[] + { + "tire_curing_press", + "plc_troubleshooting", + "mold_maintenance", + "bladder_replacement", + "hydraulic_systems", + "instrumentation" + }, + ["building_drum_vibration"] = new[] + { + "tire_building_machine", + "vibration_analysis", + "bearing_replacement", + "alignment", + "precision_alignment", + "drum_balancing", + "mechanical_systems" + }, + ["ply_tension_excessive"] = new[] + { + "tire_building_machine", + "tension_control", + "servo_systems", + "precision_alignment", + "sensor_alignment", + "plc_programming" + }, + ["extruder_barrel_overheating"] = new[] + { + "tire_extruder", + "temperature_control", + "rubber_processing", + "screw_maintenance", + "instrumentation", + "electrical_systems", + "motor_drives" + }, + ["low_material_throughput"] = new[] + { + "tire_extruder", + "rubber_processing", + "screw_maintenance", + "motor_drives", + "temperature_control" + }, + ["high_radial_force_variation"] = new[] + { + "tire_uniformity_machine", + "data_analysis", + "measurement_systems", + "tire_building_machine", + "tire_curing_press" + }, + ["load_cell_drift"] = new[] + { + "tire_uniformity_machine", + "load_cell_calibration", + "measurement_systems", + "sensor_alignment", + "instrumentation" + }, + ["mixing_temperature_excessive"] = new[] + { + "banbury_mixer", + "temperature_control", + "rubber_processing", + "instrumentation", + "electrical_systems", + "mechanical_systems" + }, + ["excessive_mixer_vibration"] = new[] + { + "banbury_mixer", + "vibration_analysis", + "bearing_replacement", + "alignment", + "mechanical_systems", + "preventive_maintenance" + }, + }; + + // Mock data mappings for fault types to required parts + private static readonly IReadOnlyDictionary> FaultToParts = + new Dictionary>(StringComparer.OrdinalIgnoreCase) + { + ["curing_temperature_excessive"] = new[] { "TCP-HTR-4KW", "GEN-TS-K400" }, + ["curing_cycle_time_deviation"] = new[] { "TCP-BLD-800", "TCP-SEAL-200" }, + ["building_drum_vibration"] = new[] { "TBM-BRG-6220" }, + ["ply_tension_excessive"] = new[] { "TBM-LS-500N", "TBM-SRV-5KW" }, + ["extruder_barrel_overheating"] = new[] { "EXT-HTR-BAND", "GEN-TS-K400" }, + ["low_material_throughput"] = new[] { "EXT-SCR-250", "EXT-DIE-TR" }, + ["high_radial_force_variation"] = Array.Empty(), + ["load_cell_drift"] = new[] { "TUM-LC-2KN", "TUM-ENC-5000" }, + ["mixing_temperature_excessive"] = new[] { "BMX-TIP-500", "GEN-TS-K400" }, + ["excessive_mixer_vibration"] = new[] { "BMX-BRG-22320", "BMX-SEAL-DP" }, + }; + private const string DefaultInstructions = """ + You are a Repair Planner Agent for factory maintenance operations. + + Your role is to analyze diagnosed faults and create detailed repair work orders. + + When you receive a fault diagnosis, you should: + 1. Use the GetAvailableTechnicians tool to find technicians with the required skills + 2. Use the GetAvailableParts tool to check parts inventory + 3. Create a detailed repair plan based on the fault and available resources + 4. Use the CreateWorkOrder tool to save the work order to the database + + Output your repair plan in a structured format with: + - Work Order ID (from CreateWorkOrder result) + - Machine ID (from the input) + - Fault Type (from the diagnosis) + - Priority (critical/high/medium/low based on severity) + - Assigned Technician (from GetAvailableTechnicians) + - Estimated Duration (in minutes) + - Repair Tasks (numbered list of steps) + - Required Parts (from GetAvailableParts) + - Safety Notes (any precautions) + + Always call the tools to get real data from the database. + + List the work order id prominently in your final response. + + """; + + /// + /// Creates a RepairPlanner AIAgent with optional Cosmos DB tools. + /// + public static AIAgent Create( + string azureOpenAIEndpoint, + string deployment, + CosmosDbService? cosmosService = null, + ILoggerFactory? loggerFactory = null) + { + var tools = new List(); + + if (cosmosService != null) + { + tools.AddRange(CreateCosmosTools(cosmosService)); + } + + return new AzureOpenAIClient(new Uri(azureOpenAIEndpoint), new AzureCliCredential()) + .GetChatClient(deployment) + .AsIChatClient() + .AsBuilder() + .UseOpenTelemetry(sourceName: "RepairPlannerAgent") + .Build() + .AsAIAgent( + instructions: DefaultInstructions, + name: "RepairPlannerAgent", + tools: tools.Count > 0 ? tools : null); + } + + /// + /// Creates the Cosmos DB tools for the RepairPlanner agent. + /// + private static IEnumerable CreateCosmosTools(CosmosDbService cosmosService) + { + // Tool to get required skills for a fault type (mock implementation) + yield return AIFunctionFactory.Create( + (string faultType) => + { + var skills = GetSkillsForFault(faultType); + // Return mock technicians with matching skills + var mockTechnicians = new[] + { + new { id = "tech-001", name = "John Smith", skills = skills.Take(3).ToArray(), available = true, department = "Maintenance" }, + new { id = "tech-002", name = "Jane Doe", skills = skills.Skip(1).Take(3).ToArray(), available = true, department = "Maintenance" }, + new { id = "tech-003", name = "Bob Wilson", skills = skills.Take(2).ToArray(), available = true, department = "Maintenance" } + }; + return JsonSerializer.Serialize(mockTechnicians); + }, + "GetAvailableTechnicians", + "Gets available technicians with the required skills for a given fault type. " + + "Parameters: faultType (the diagnosed fault type like 'curing_temperature_excessive', 'building_drum_vibration')"); + + // Tool to get required parts for a fault type (mock implementation) + yield return AIFunctionFactory.Create( + (string faultType) => + { + var partNumbers = GetPartsForFault(faultType); + // Return mock parts with inventory info + var mockParts = partNumbers.Select((pn, i) => new + { + partNumber = pn, + name = $"Part {pn}", + quantityInStock = 5 + i, + available = true, + location = $"Warehouse-{(char)('A' + i)}" + }).ToArray(); + return JsonSerializer.Serialize(mockParts); + }, + "GetAvailableParts", + "Gets the required parts for a given fault type from inventory. " + + "Parameters: faultType (the diagnosed fault type like 'curing_temperature_excessive', 'building_drum_vibration')"); + + // Tool to create a work order + yield return AIFunctionFactory.Create( + async (string machineId, string faultType, string priority, string assignedTo, + int estimatedMinutes, string description, string[] partNumbers) => + { + var workOrder = new WorkOrder + { + Id = $"wo-{DateTime.UtcNow:yyyy}-{Guid.NewGuid().ToString()[..8]}", + MachineId = machineId, + FaultType = faultType, + Priority = priority, + AssignedTo = assignedTo, + EstimatedDurationMinutes = estimatedMinutes, + Description = description, + Status = "scheduled", + CreatedDate = DateTimeOffset.UtcNow, + RequiredParts = partNumbers.Select(p => new PartRequirement { PartNumber = p, Quantity = 1 }).ToList() + }; + var id = await cosmosService.CreateWorkOrderAsync(workOrder); + return JsonSerializer.Serialize(new { workOrderId = id, status = "created" }); + }, + "CreateWorkOrder", + "Creates a new work order in the database. " + + "Parameters: machineId, faultType, priority (critical/high/medium/low), assignedTo (technician id), " + + "estimatedMinutes, description, partNumbers (array of required part numbers)"); + } + + /// + /// Gets the required skills for a given fault type from the mock mapping. + /// + public static IReadOnlyList GetSkillsForFault(string faultType) + { + if (string.IsNullOrWhiteSpace(faultType)) + return Array.Empty(); + + return FaultToSkills.TryGetValue(faultType, out var skills) + ? skills + : Array.Empty(); + } + + /// + /// Gets the required parts for a given fault type from the mock mapping. + /// + public static IReadOnlyList GetPartsForFault(string faultType) + { + if (string.IsNullOrWhiteSpace(faultType)) + return Array.Empty(); + + return FaultToParts.TryGetValue(faultType, out var parts) + ? parts + : Array.Empty(); + } +} diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/dotnetworkflow/RepairPlanner/Services/CosmosDbService.cs b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/dotnetworkflow/RepairPlanner/Services/CosmosDbService.cs new file mode 100644 index 000000000..b389499b1 --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/dotnetworkflow/RepairPlanner/Services/CosmosDbService.cs @@ -0,0 +1,287 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Azure.Cosmos; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using FactoryWorkflow.RepairPlanner.Models; + +namespace FactoryWorkflow.RepairPlanner.Services; + +/// +/// Cosmos DB data access for the Repair Planner Agent. +/// +public sealed class CosmosDbService : IDisposable +{ + private readonly CosmosClient _client; + private readonly bool _ownsClient; + private readonly ILogger _logger; + + private readonly Container _techniciansContainer; + private readonly Container _partsContainer; + private readonly Container _workOrdersContainer; + + /// + /// Creates a CosmosDbService using an endpoint/key pair. + /// + public CosmosDbService( + string endpoint, + string key, + string databaseName, + ILogger? logger = null, + string techniciansContainerName = "Technicians", + string partsContainerName = "PartsInventory", + string workOrdersContainerName = "WorkOrders") + { + _client = new CosmosClient(endpoint, key, new CosmosClientOptions + { + ApplicationName = "RepairPlannerAgent", + }); + _ownsClient = true; + + _logger = logger ?? NullLogger.Instance; + + if (string.IsNullOrWhiteSpace(databaseName)) + { + throw new ArgumentException("Database name is required.", nameof(databaseName)); + } + + var database = _client.GetDatabase(databaseName); + _techniciansContainer = database.GetContainer(techniciansContainerName); + _partsContainer = database.GetContainer(partsContainerName); + _workOrdersContainer = database.GetContainer(workOrdersContainerName); + } + + /// + /// Creates a CosmosDbService using an existing CosmosClient (recommended for DI). + /// + public CosmosDbService( + CosmosClient cosmosClient, + string databaseName, + ILogger? logger = null, + string techniciansContainerName = "Technicians", + string partsContainerName = "PartsInventory", + string workOrdersContainerName = "WorkOrders") + { + _client = cosmosClient ?? throw new ArgumentNullException(nameof(cosmosClient)); + _ownsClient = false; + + _logger = logger ?? NullLogger.Instance; + + if (string.IsNullOrWhiteSpace(databaseName)) + { + throw new ArgumentException("Database name is required.", nameof(databaseName)); + } + + var database = _client.GetDatabase(databaseName); + _techniciansContainer = database.GetContainer(techniciansContainerName); + _partsContainer = database.GetContainer(partsContainerName); + _workOrdersContainer = database.GetContainer(workOrdersContainerName); + } + + public void Dispose() + { + if (_ownsClient) + { + _client.Dispose(); + } + } + + /// + /// Queries technicians who are available and match the given required skills. + /// + public async Task> GetAvailableTechniciansWithSkillsAsync( + IReadOnlyList requiredSkills, + string? department = null, + bool requireAllSkills = true, + CancellationToken cancellationToken = default) + { + requiredSkills ??= Array.Empty(); + + var normalizedSkills = requiredSkills + .Where(s => !string.IsNullOrWhiteSpace(s)) + .Select(s => s.Trim()) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToArray(); + + var queryText = "SELECT * FROM c WHERE c.available = true"; + var parameters = new List<(string Name, object Value)>(); + + if (!string.IsNullOrWhiteSpace(department)) + { + queryText += " AND c.department = @department"; + parameters.Add(("@department", department)); + } + + if (normalizedSkills.Length > 0) + { + var clauses = new List(capacity: normalizedSkills.Length); + for (var i = 0; i < normalizedSkills.Length; i++) + { + var paramName = $"@skill{i}"; + clauses.Add($"ARRAY_CONTAINS(c.skills, {paramName})"); + parameters.Add((paramName, normalizedSkills[i])); + } + + var joiner = requireAllSkills ? " AND " : " OR "; + queryText += $" AND ({string.Join(joiner, clauses)})"; + } + + var queryDefinition = new QueryDefinition(queryText); + foreach (var (name, value) in parameters) + { + queryDefinition = queryDefinition.WithParameter(name, value); + } + + var results = new List(); + + try + { + var requestOptions = new QueryRequestOptions + { + PartitionKey = string.IsNullOrWhiteSpace(department) ? null : new PartitionKey(department), + }; + + using var iterator = _techniciansContainer.GetItemQueryIterator( + queryDefinition, + requestOptions: requestOptions); + + while (iterator.HasMoreResults) + { + var response = await iterator.ReadNextAsync(cancellationToken); + results.AddRange(response); + } + + return results; + } + catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound) + { + _logger.LogWarning(ex, "Technicians container or database not found."); + return Array.Empty(); + } + catch (CosmosException ex) + { + _logger.LogError(ex, "Cosmos query failed in {Method}.", nameof(GetAvailableTechniciansWithSkillsAsync)); + throw; + } + } + + /// + /// Fetches parts inventory items by part numbers. + /// + public async Task> GetPartsByPartNumbersAsync( + IReadOnlyList partNumbers, + string? category = null, + CancellationToken cancellationToken = default) + { + partNumbers ??= Array.Empty(); + + var normalizedPartNumbers = partNumbers + .Where(p => !string.IsNullOrWhiteSpace(p)) + .Select(p => p.Trim()) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToArray(); + + if (normalizedPartNumbers.Length == 0) + { + return Array.Empty(); + } + + var queryDefinition = new QueryDefinition( + "SELECT * FROM c WHERE ARRAY_CONTAINS(@partNumbers, c.partNumber)") + .WithParameter("@partNumbers", normalizedPartNumbers); + + if (!string.IsNullOrWhiteSpace(category)) + { + queryDefinition = new QueryDefinition( + "SELECT * FROM c WHERE c.category = @category AND ARRAY_CONTAINS(@partNumbers, c.partNumber)") + .WithParameter("@category", category) + .WithParameter("@partNumbers", normalizedPartNumbers); + } + + var results = new List(); + + try + { + var requestOptions = new QueryRequestOptions + { + PartitionKey = string.IsNullOrWhiteSpace(category) ? null : new PartitionKey(category), + }; + + using var iterator = _partsContainer.GetItemQueryIterator( + queryDefinition, + requestOptions: requestOptions); + + while (iterator.HasMoreResults) + { + var response = await iterator.ReadNextAsync(cancellationToken); + results.AddRange(response); + } + + return results; + } + catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound) + { + _logger.LogWarning(ex, "Parts container or database not found."); + return Array.Empty(); + } + catch (CosmosException ex) + { + _logger.LogError(ex, "Cosmos query failed in {Method}.", nameof(GetPartsByPartNumbersAsync)); + throw; + } + } + + /// + /// Creates a new work order document in Cosmos DB. + /// + public async Task CreateWorkOrderAsync(WorkOrder workOrder, CancellationToken cancellationToken = default) + { + if (workOrder is null) + { + throw new ArgumentNullException(nameof(workOrder)); + } + + if (string.IsNullOrWhiteSpace(workOrder.Id)) + { + workOrder.Id = $"wo-{Guid.NewGuid():N}"; + } + + workOrder.CreatedDate ??= DateTimeOffset.UtcNow; + workOrder.Status = string.IsNullOrWhiteSpace(workOrder.Status) ? "new" : workOrder.Status; + + if (string.IsNullOrWhiteSpace(workOrder.WorkOrderNumber)) + { + workOrder.WorkOrderNumber = $"WO-{DateTimeOffset.UtcNow:yyyyMMdd-HHmmss}"; + } + + try + { + var response = await _workOrdersContainer.CreateItemAsync( + workOrder, + new PartitionKey(workOrder.Status), + cancellationToken: cancellationToken); + + _logger.LogInformation( + "Created work order {WorkOrderNumber} (id={Id}, status={Status}).", + workOrder.WorkOrderNumber, + response.Resource.Id, + response.Resource.Status); + + return response.Resource.Id; + } + catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.Conflict) + { + _logger.LogWarning(ex, "Work order id conflict for id={Id}.", workOrder.Id); + throw; + } + catch (CosmosException ex) + { + _logger.LogError(ex, "Cosmos create failed in {Method}.", nameof(CreateWorkOrderAsync)); + throw; + } + } +} diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/dotnetworkflow/TextOnlyAgentExecutor.cs b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/dotnetworkflow/TextOnlyAgentExecutor.cs new file mode 100644 index 000000000..228c3d2a5 --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/dotnetworkflow/TextOnlyAgentExecutor.cs @@ -0,0 +1,212 @@ +using Microsoft.Agents.AI; +using Microsoft.Agents.AI.Workflows; +using Microsoft.Extensions.AI; +using System.Collections.Concurrent; +using System.Text; +using System.Text.Json; + +namespace FactoryWorkflow; + +/// +/// Custom agent executor that only passes TEXT output between agents in a sequential workflow. +/// This works around the Azure.AI.Projects SDK bug where MCP tool call history in +/// conversation messages causes deserialization errors when passed to subsequent agents. +/// +/// The executor is Executor{string, string} so outputs can directly chain to next agent's input. +/// Agent step details (tool calls, etc.) are collected via the static StepResults property. +/// +public sealed class TextOnlyAgentExecutor : Executor +{ + private readonly AIAgent _agent; + + // Static collector for step results - ConcurrentQueue preserves FIFO order + private static readonly ConcurrentQueue _stepResults = new(); + + // Callback for streaming events - set via SetEventCallback before workflow execution + private static Func? _onStepCompleted; + + public static IEnumerable StepResults => _stepResults.ToArray(); + + public static void ClearResults() + { + while (_stepResults.TryDequeue(out _)) { } + } + + /// + /// Sets a callback that fires when each agent step completes (for SSE streaming). + /// + public static void SetEventCallback(Func? callback) + { + _onStepCompleted = callback; + } + + public TextOnlyAgentExecutor(AIAgent agent) : base($"{agent.Name}") + { + _agent = agent ?? throw new ArgumentNullException(nameof(agent)); + } + + public string AgentName => _agent.Name; + + public override async ValueTask HandleAsync(string input, IWorkflowContext context, CancellationToken cancellationToken = default) + { + var agentStep = new AgentStepResult { AgentName = _agent.Name }; + + try + { + // Create a clean message with only text input (no MCP tool history) + var messages = new List { new ChatMessage(ChatRole.User, input) }; + + // Run the agent + var response = await _agent.RunAsync(messages, null); + + // Extract text output and tool calls from response + string? outputText = null; + if (response.Messages != null) + { + foreach (var msg in response.Messages) + { + if (msg.Role == ChatRole.Assistant) + { + foreach (var content in msg.Contents) + { +#pragma warning disable MEAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + if (content is TextContent tc && !string.IsNullOrWhiteSpace(tc.Text)) + { + outputText = tc.Text; + agentStep.TextOutput += tc.Text; + } + else if (content is McpServerToolCallContent mcp) + { + agentStep.ToolCalls.Add(new ToolCallInfo + { + ToolName = mcp.Name, + CallId = mcp.CallId, + Arguments = SerializeArguments(mcp.Arguments) + }); + } + else if (content is McpServerToolResultContent mcpr) + { + Console.WriteLine("Received MCPServerToolResultContent "); + + var matchingCall = agentStep.ToolCalls.LastOrDefault(t => t.CallId == mcpr.CallId); + if (matchingCall != null) + { + // Extract readable content from the Output (which is List) + matchingCall.Result = ExtractOutputText(mcpr.Outputs); + } + } + else if (content is FunctionCallContent fcc) + { + agentStep.ToolCalls.Add(new ToolCallInfo + { + ToolName = fcc.Name, + CallId = fcc.CallId, + Arguments = SerializeArguments(fcc.Arguments) + }); + } +#pragma warning restore MEAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + } + } + else if (msg.Role == ChatRole.Tool) + { + foreach (var content in msg.Contents) + { +#pragma warning disable MEAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + if (content is FunctionResultContent frc) + { + var matchingCall = agentStep.ToolCalls.LastOrDefault(t => t.CallId == frc.CallId); + if (matchingCall != null) + { + matchingCall.Result = frc.Result?.ToString()?.Substring(0, Math.Min(500, frc.Result?.ToString()?.Length ?? 0)); + } + } + +#pragma warning restore MEAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + } + } + } + } + + agentStep.FinalMessage = outputText ?? ""; + Console.WriteLine($"Agent {_agent.Name} completed with {agentStep.ToolCalls.Count} tool calls."); + } + catch (Exception ex) + { + Console.WriteLine($"Agent {_agent.Name} failed: {ex.Message}"); + agentStep.IsError = true; + agentStep.FinalMessage = $"Error: {ex.Message}"; + agentStep.TextOutput = $"Error: {ex.Message}"; + } + + // Store step result for later collection (ConcurrentQueue preserves order) + _stepResults.Enqueue(agentStep); + + // Notify SSE callback if registered (for streaming progress to frontend) + if (_onStepCompleted != null) + { + await _onStepCompleted(agentStep); + } + + // Return just the text for the next agent + return agentStep.FinalMessage ?? ""; + } + + /// + /// Serializes tool arguments to JSON string. Handles Dictionary and other types. + /// + private static string? SerializeArguments(object? arguments) + { + if (arguments == null) return null; + + // If it's already a string (pre-serialized JSON), return as-is + if (arguments is string s) return s; + + // Serialize dictionaries and other objects to JSON + try + { + return JsonSerializer.Serialize(arguments); + } + catch + { + return arguments.ToString(); + } + } + + /// + /// Extracts readable text from MCP tool output + /// + private static string? ExtractOutputText(object? output) + { + if (output == null) return null; + + StringBuilder result = new(); + + // Handle IEnumerable (the actual type of mcpr.Output) + if (output is IEnumerable contentList) + { + var textParts = new List(); + foreach (var item in contentList) + { + if (item is TextContent textContent && !string.IsNullOrWhiteSpace(textContent.Text)) + { + textParts.Add(textContent.Text); + } + else + { + // For other content types, try to serialize to JSON + try + { + var json = JsonSerializer.Serialize(item, new JsonSerializerOptions { WriteIndented = false }); + textParts.Add(json); + } + catch + { + textParts.Add(item?.ToString() ?? ""); + } + } + } + result.Append(string.Join("\n", textParts)); + } + return result.ToString(); + } +} diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/dotnetworkflow/appsettings.Development.json b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/dotnetworkflow/appsettings.Development.json new file mode 100644 index 000000000..0c208ae91 --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/dotnetworkflow/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/frontend/.dockerignore b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/frontend/.dockerignore new file mode 100644 index 000000000..b94707787 --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/frontend/.dockerignore @@ -0,0 +1,2 @@ +node_modules/ +dist/ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/frontend/.gitignore b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/frontend/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/frontend/eslint.config.js b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/frontend/eslint.config.js new file mode 100644 index 000000000..092408a9f --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/frontend/eslint.config.js @@ -0,0 +1,28 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' + +export default tseslint.config( + { ignores: ['dist'] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ['**/*.{ts,tsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, + }, +) diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/frontend/index.html b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/frontend/index.html new file mode 100644 index 000000000..cfa1f458a --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Factory Agent Workflow + + +
+ + + diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/frontend/package-lock.json b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/frontend/package-lock.json new file mode 100644 index 000000000..5fc5ea8dd --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/frontend/package-lock.json @@ -0,0 +1,3308 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "dependencies": { + "react": "^19.2.1", + "react-dom": "^19.2.1" + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@types/node": "^24.10.1", + "@types/react": "^19.2.7", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.1", + "eslint": "^9.39.1", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.24", + "globals": "^16.5.0", + "typescript": "~5.9.3", + "typescript-eslint": "^8.48.1", + "vite": "^7.2.6" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz", + "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.47.tgz", + "integrity": "sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.1.tgz", + "integrity": "sha512-UZ8sUxPTiHWYX9QNdJedb1kDZSpS1t/VPWBWGSgqHNi9w3Cu6IXvu2mzbhiTiPvtrqgTQJ+zqiAq2iPIPilpaQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.1.tgz", + "integrity": "sha512-cQ4nFQABN5cDvDpbvJ7bMStCpnaVxynZrRMfUJYgxcIk9Sh54FIO1vtfkg0B69REjER77ioZ/ov+eAApx/KmLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.1.tgz", + "integrity": "sha512-FQNqd1lRy/0QhDk3xeRIkSBiCpXCiDnZO3YLVdcDKN1UBiKToNftCzcXYNLshmPDUMlu2TdeS8tGcsU6f3YF1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.1.tgz", + "integrity": "sha512-pvD16V939D3CloK0+qikpGaxiPrDUXTe7Y5cWOMkMSy7m1cawa8EGy/kXYi/G/cKAC4HDAbSnzCIk1WmsoOKXg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.1.tgz", + "integrity": "sha512-pcFGeL2345VwdTnJhA6zLbew+YgWB0qBG2+dMtXjCicf6+rm6kO6cOoh5VnTe0ZMrMRgRyuHmCJxZWrIdzYuOw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.1.tgz", + "integrity": "sha512-mRJlqSRulVzcKq/LKA6ICSIc3K/l4fzlVn/gePn2nXIHy8seRi5z/eeRE0d/XMBxcMldiXtQTSpRj0tkkC3g8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.1.tgz", + "integrity": "sha512-YDUNvVM85TI3g/1OpnqKP1h4NeW/j64DfWMf+G3M809xNk1bJSnpFp4sh83NpmVE5DXnkh8ULor4LTVZKoYLHw==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.1.tgz", + "integrity": "sha512-7Mcn71p9ZuQFAj+h+dhQXy/yeLePRS2yKRnmW1DijA9thKO5qap0GNOIQK4yQ6iP3SU0Mrb/yWo8h8vgRba8lw==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.1.tgz", + "integrity": "sha512-4YiLQTX6U4CSl0L9cluep9A9W6UmTfqBDc2/CH6wlu54pl4E7Jn3cOD8oxzvBDEGk/JMKgJ47C8g+radF7mwvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.1.tgz", + "integrity": "sha512-2ra8F7w8OquwZN9z2/fKFnli69wa8PLwaVzRMIPGb13ByMJwC28Fbp8YcVGoUhlYMTt7j5j9bNgpysrN2UM+vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.1.tgz", + "integrity": "sha512-Sy20ncyhjmBP0Ml+UvQbimjlk6VFgjW5uNP+qqwHB00mTE8Bl2C1TuHTlRwK2YoXeZbee5lP2XevBWVkAQAtSQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.1.tgz", + "integrity": "sha512-noITLp8oNjYliPnGWmLyelIHwULGqbHloQHGw1rtxbWhTuWooRpnZarZQJ1y9EUC4szuCusCc+HEpUtxpIwYvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.1.tgz", + "integrity": "sha512-hlxxXd+F1mWiAcaFR7Sv9ZQT6m6UfI8+Vy/kFJzztq2pDMU/0wZ9sish0iszNZvsQDo8Gc0i5yuFEOz5dDf6fA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.1.tgz", + "integrity": "sha512-EF7OpqQTQ/BvGqLzUi4rEHuagCV9MugAUXSHemwPW5vxZ75RR+jxO/2j95Ph2dalMpFHSVECjRoioHZgA9zOYA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.1.tgz", + "integrity": "sha512-wQO3JesW9PRkwlabQ27y7sPfVOOTLRG73I4F2UYHG5PXun3J9U3y+b7ezVKSYbsvSKGQ1k1cq8Qlun4C9kLt3w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.1.tgz", + "integrity": "sha512-ouAGwhO6wHRXdnOVCOsB0tRFkA7nhNB2Nwax6oECXN0YiN8EYUTBAOudADOB1PI+yDL61TeNx/u7MVCzksNbkQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.1.tgz", + "integrity": "sha512-q2R38Sn+1J8RxhfJ+T54wSWmyKXWec+9jgDfqO2AtArEqHO5R2aeayp5H5OYLr5UYDVGsVaZPEFUooMhYCdz5A==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.1.tgz", + "integrity": "sha512-gfI5T24WLLuFfSKw7Go/zDXjAAV0fny0swTaDv+WjK7vqcw4cRhFfdsyKL1n+ukI+ooBxn3bVQnyrn06WpI50w==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.1.tgz", + "integrity": "sha512-4h6XqthmB4Hspji84wvgk+ElodTsGj+dbZqHJHHtKxj4mYq0ANSEEPX9ys3moJueqsRjwpaJYH7874Itwnj2ow==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.1.tgz", + "integrity": "sha512-dlfCOa87o1VAYegLQ9EKilx2JCeRofiyPGhTCmqnuXZ6bMPiycO1rq1+sKoulAp7pGLIsTIw+1x5R+zgh5LhhA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.1.tgz", + "integrity": "sha512-cjkLbOlfcm3QGhMM1J5zaZjsw1GggbN6rw9UTSSRrPrR1KkcXnN7Uq9rPw34xImQ9VOY9GN+6u2Zj80B9ptkcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.1.tgz", + "integrity": "sha512-Li1KdUnWGE4N3e1F/B4RTB1ms+nG4WBgjByO46pkeBVX/2UBsY53xf5vK9WygVmnH3RwncIST7lkSdLSY6P9lg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.1.tgz", + "integrity": "sha512-t4ZYOSoLTgwhuFMrmTMLx/+i1DQVK7HYqMc6kY46EApwi8X0nIVphzdNoThU3xt6n+N5urG1/gxBdCaKDLavfg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.1.tgz", + "integrity": "sha512-RgroPfMmKlD1RzSDxvwgcPiy2HNQKoYV7OmwIXDsk73uKW5t6B/V8KIy27SMv/FNXFo/oSBtWc9J0X7t91ezZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.1.tgz", + "integrity": "sha512-at8QVep6S3h5Y6gSbdGU06bRY5WJkf6WUduM9YtvYMbYhB1MOFfUgc6kehitQXzOtMSaT70q7f9ydPhpqu821w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", + "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.48.1.tgz", + "integrity": "sha512-X63hI1bxl5ohelzr0LY5coufyl0LJNthld+abwxpCoo6Gq+hSqhKwci7MUWkXo67mzgUK6YFByhmaHmUcuBJmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.48.1", + "@typescript-eslint/type-utils": "8.48.1", + "@typescript-eslint/utils": "8.48.1", + "@typescript-eslint/visitor-keys": "8.48.1", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.48.1", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.48.1.tgz", + "integrity": "sha512-PC0PDZfJg8sP7cmKe6L3QIL8GZwU5aRvUFedqSIpw3B+QjRSUZeeITC2M5XKeMXEzL6wccN196iy3JLwKNvDVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.48.1", + "@typescript-eslint/types": "8.48.1", + "@typescript-eslint/typescript-estree": "8.48.1", + "@typescript-eslint/visitor-keys": "8.48.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.48.1.tgz", + "integrity": "sha512-HQWSicah4s9z2/HifRPQ6b6R7G+SBx64JlFQpgSSHWPKdvCZX57XCbszg/bapbRsOEv42q5tayTYcEFpACcX1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.48.1", + "@typescript-eslint/types": "^8.48.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.48.1.tgz", + "integrity": "sha512-rj4vWQsytQbLxC5Bf4XwZ0/CKd362DkWMUkviT7DCS057SK64D5lH74sSGzhI6PDD2HCEq02xAP9cX68dYyg1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.48.1", + "@typescript-eslint/visitor-keys": "8.48.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.48.1.tgz", + "integrity": "sha512-k0Jhs4CpEffIBm6wPaCXBAD7jxBtrHjrSgtfCjUvPp9AZ78lXKdTR8fxyZO5y4vWNlOvYXRtngSZNSn+H53Jkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.48.1.tgz", + "integrity": "sha512-1jEop81a3LrJQLTf/1VfPQdhIY4PlGDBc/i67EVWObrtvcziysbLN3oReexHOM6N3jyXgCrkBsZpqwH0hiDOQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.48.1", + "@typescript-eslint/typescript-estree": "8.48.1", + "@typescript-eslint/utils": "8.48.1", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.48.1.tgz", + "integrity": "sha512-+fZ3LZNeiELGmimrujsDCT4CRIbq5oXdHe7chLiW8qzqyPMnn1puNstCrMNVAqwcl2FdIxkuJ4tOs/RFDBVc/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.48.1.tgz", + "integrity": "sha512-/9wQ4PqaefTK6POVTjJaYS0bynCgzh6ClJHGSBj06XEHjkfylzB+A3qvyaXnErEZSaxhIo4YdyBgq6j4RysxDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.48.1", + "@typescript-eslint/tsconfig-utils": "8.48.1", + "@typescript-eslint/types": "8.48.1", + "@typescript-eslint/visitor-keys": "8.48.1", + "debug": "^4.3.4", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.48.1.tgz", + "integrity": "sha512-fAnhLrDjiVfey5wwFRwrweyRlCmdz5ZxXz2G/4cLn0YDLjTapmN4gcCsTBR1N2rWnZSDeWpYtgLDsJt+FpmcwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.48.1", + "@typescript-eslint/types": "8.48.1", + "@typescript-eslint/typescript-estree": "8.48.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.48.1.tgz", + "integrity": "sha512-BmxxndzEWhE4TIEEMBs8lP3MBWN3jFPs/p6gPm/wkv02o41hI6cq9AuSmGAaTTHPtA1FTi2jBre4A9rm5ZmX+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.48.1", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.1.tgz", + "integrity": "sha512-WQfkSw0QbQ5aJ2CHYw23ZGkqnRwqKHD/KYsMeTkZzPT4Jcf0DcBxBtwMJxnu6E7oxw5+JC6ZAiePgh28uJ1HBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.5", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.47", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.18.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.21", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.21.tgz", + "integrity": "sha512-uh8vpY/1/YyFkunIDFH/12p7/7VdPKA1hejMVEbdkEaWnUz0Hesvx5EbiU6XxjyHZIOju+ZMbQJkRh+es3/spQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/browserslist": { + "version": "4.28.9", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.9.tgz", + "integrity": "sha512-EWazOblFYUvlGZcfGhPUPmYh3nikUxBVb+y9MJun5f3hBi812X+8MSQTujLBtgK3cf51fJWbWfOjyeO954d+Eg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.11.20", + "caniuse-lite": "^1.0.30001810", + "electron-to-chromium": "^1.5.420", + "node-releases": "^2.0.54", + "update-browserslist-db": "^1.3.2" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001810", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", + "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.425", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.425.tgz", + "integrity": "sha512-QvPtl41EUOnuT1HBvMKgxXRIaHNcagBPs50u7VULzhZXaGfqTbZyE16LQsctZ/RQHlGu+FOWeDTR4mY6YbeF1g==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz", + "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.1", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.24.tgz", + "integrity": "sha512-nLHIW7TEq3aLrEYWpVaJ1dRgFR+wLDPN8e8FpYAql/bMV2oBEfC37K0gLEGgv9fy66juNShSMV8OkTqzltcG/w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", + "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.55", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.55.tgz", + "integrity": "sha512-mIrE/Cw9y+9Au6dS5vDKDhQza9YvG6w+ZrS6X+ZzA7yFW/soAeaups4Qzn1bL6g5FVy8WtP79+0j82oPIbqRjQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.18", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react": { + "version": "19.2.1", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.1.tgz", + "integrity": "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.1.tgz", + "integrity": "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.1" + } + }, + "node_modules/react-refresh": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/rollup": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.63.1.tgz", + "integrity": "sha512-3Df9jsstwhccuEfmAMi9l8XUh/GOkVObmFTU7CCVBysEbcOZLl84jCtaAZMcPiMz2EGKsATzQcU+Xr3n/wU6cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.63.1", + "@rollup/rollup-android-arm64": "4.63.1", + "@rollup/rollup-darwin-arm64": "4.63.1", + "@rollup/rollup-darwin-x64": "4.63.1", + "@rollup/rollup-freebsd-arm64": "4.63.1", + "@rollup/rollup-freebsd-x64": "4.63.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.63.1", + "@rollup/rollup-linux-arm-musleabihf": "4.63.1", + "@rollup/rollup-linux-arm64-gnu": "4.63.1", + "@rollup/rollup-linux-arm64-musl": "4.63.1", + "@rollup/rollup-linux-loong64-gnu": "4.63.1", + "@rollup/rollup-linux-loong64-musl": "4.63.1", + "@rollup/rollup-linux-ppc64-gnu": "4.63.1", + "@rollup/rollup-linux-ppc64-musl": "4.63.1", + "@rollup/rollup-linux-riscv64-gnu": "4.63.1", + "@rollup/rollup-linux-riscv64-musl": "4.63.1", + "@rollup/rollup-linux-s390x-gnu": "4.63.1", + "@rollup/rollup-linux-x64-gnu": "4.63.1", + "@rollup/rollup-linux-x64-musl": "4.63.1", + "@rollup/rollup-openbsd-x64": "4.63.1", + "@rollup/rollup-openharmony-arm64": "4.63.1", + "@rollup/rollup-win32-arm64-msvc": "4.63.1", + "@rollup/rollup-win32-ia32-msvc": "4.63.1", + "@rollup/rollup-win32-x64-gnu": "4.63.1", + "@rollup/rollup-win32-x64-msvc": "4.63.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.48.1.tgz", + "integrity": "sha512-FbOKN1fqNoXp1hIl5KYpObVrp0mCn+CLgn479nmu2IsRMrx2vyv74MmsBLVlhg8qVwNFGbXSp8fh1zp8pEoC2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.48.1", + "@typescript-eslint/parser": "8.48.1", + "@typescript-eslint/typescript-estree": "8.48.1", + "@typescript-eslint/utils": "8.48.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.2.tgz", + "integrity": "sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "version": "7.3.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz", + "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0 || ^0.28.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/frontend/package.json b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/frontend/package.json new file mode 100644 index 000000000..b2d952de2 --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/frontend/package.json @@ -0,0 +1,30 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.2.1", + "react-dom": "^19.2.1" + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@types/node": "^24.10.1", + "@types/react": "^19.2.7", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.1", + "eslint": "^9.39.1", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.24", + "globals": "^16.5.0", + "typescript": "~5.9.3", + "typescript-eslint": "^8.48.1", + "vite": "^7.2.6" + } +} diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/frontend/public/Aspire.png b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/frontend/public/Aspire.png new file mode 100644 index 000000000..d4be662a6 Binary files /dev/null and b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/frontend/public/Aspire.png differ diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/frontend/public/github.svg b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/frontend/public/github.svg new file mode 100644 index 000000000..162a4a43a --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/frontend/public/github.svg @@ -0,0 +1,3 @@ + + + diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/frontend/src/App.css b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/frontend/src/App.css new file mode 100644 index 000000000..168bd6e80 --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/frontend/src/App.css @@ -0,0 +1,1408 @@ +/* CSS Variables for theming */ +:root { + --bg-gradient-start: #1a1a2e; + --bg-gradient-end: #16213e; + --card-bg: rgba(30, 30, 46, 0.95); + --card-hover-shadow: rgba(0, 0, 0, 0.3); + --text-primary: #ffffff; + --text-secondary: #e2e8f0; + --text-tertiary: #cbd5e0; + --accent-gradient-start: #7c92f5; + --accent-gradient-end: #8b5ecf; + --weather-card-bg: rgba(45, 45, 60, 0.8); + --weather-card-border: rgba(255, 255, 255, 0.1); + --section-title-color: #f7fafc; + --date-color: #cbd5e0; + --summary-color: #f7fafc; + --temp-unit-color: #e2e8f0; + --divider-color: #4a5568; + --error-bg: rgba(220, 38, 38, 0.1); + --error-border: #ef4444; + --error-text: #fca5a5; + --skeleton-bg-1: rgba(255, 255, 255, 0.05); + --skeleton-bg-2: rgba(255, 255, 255, 0.1); + --tile-min-height: 120px; + --tile-gap: 0.75rem; + --cta-height: 3rem; + --card-inner-gap: 1rem; + --focus-color: #a78bfa; +} + +@media (prefers-color-scheme: light) { + :root { + --bg-gradient-start: #f0f4ff; + --bg-gradient-end: #e0e7ff; + --card-bg: rgba(255, 255, 255, 0.98); + --card-hover-shadow: rgba(0, 0, 0, 0.15); + --text-primary: #1a202c; + --text-secondary: #2d3748; + --text-tertiary: #4a5568; + --accent-gradient-start: #5b6fd8; + --accent-gradient-end: #6b46a3; + --weather-card-bg: rgba(255, 255, 255, 0.9); + --weather-card-border: rgba(102, 126, 234, 0.15); + --section-title-color: #1a202c; + --date-color: #2d3748; + --summary-color: #1a202c; + --temp-unit-color: #4a5568; + --divider-color: #cbd5e0; + --error-bg: #fee; + --error-border: #dc2626; + --error-text: #991b1b; + --skeleton-bg-1: #f0f0f0; + --skeleton-bg-2: #e0e0e0; + --tile-min-height: 120px; + --tile-gap: 0.75rem; + --cta-height: 3rem; + --card-inner-gap: 1rem; + --focus-color: #5b21b6; + } +} + +/* Root container */ +#root { + width: 100%; + min-height: 100vh; + display: flex; + flex-direction: column; + overflow-x: hidden; +} + +/* Accessibility utilities */ +.visually-hidden { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; +} + +/* App container */ +.app-container { + width: 100%; + min-height: 100vh; + display: flex; + flex-direction: column; + background: linear-gradient(135deg, var(--bg-gradient-start) 0%, var(--bg-gradient-end) 100%); + color: var(--text-primary); +} + +/* Header */ +.app-header { + padding: 2.5rem 2rem 1.5rem; + text-align: center; + animation: fadeInDown 0.6s ease-out; +} + +.logo-link { + display: inline-block; + border-radius: 0.5rem; +} + +.logo-link:focus-visible { + outline: 3px solid var(--accent-gradient-end); + outline-offset: 8px; +} + +.logo { + height: 5rem; + width: auto; + transition: transform 300ms ease, filter 300ms ease; + filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.2)); +} + +.logo:hover { + transform: scale(1.1) rotate(5deg); + filter: drop-shadow(0 8px 16px rgba(0, 0, 0, 0.3)); +} + +.app-title { + font-size: 2.75rem; + font-weight: 700; + margin: 1.25rem 0 0.5rem; + background: linear-gradient(135deg, var(--text-primary) 0%, var(--text-secondary) 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + letter-spacing: -0.02em; +} + +.app-subtitle { + font-size: 1.05rem; + color: var(--text-tertiary); + margin: 0; + font-weight: 300; +} + +/* Main content */ +.main-content { + flex: 1; + max-width: 1400px; + width: 100%; + margin: 0 auto; + padding: 0rem 2rem 2rem; + display: flex; + justify-content: center; + align-items: center; +} + +/* Card styles */ +.card { + background: var(--card-bg); + backdrop-filter: blur(10px); + border-radius: 1rem; + padding: 1.25rem 1.5rem; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); + color: var(--text-primary); + animation: fadeInUp 0.6s ease-out; + border: 1px solid var(--weather-card-border); + display: flex; + flex-direction: column; + gap: var(--card-inner-gap); +} + +/* Section styles */ +.demo-section { + animation: fadeInUp 0.6s ease-out; +} + +.weather-section { + animation: fadeInUp 0.6s ease-out; + animation-delay: 0.1s; + flex: 1; + max-width: 1200px; + width: 100%; +} + +.section-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 0; + flex-wrap: wrap; + gap: 1rem; +} + +.header-actions { + display: flex; + gap: 0.75rem; + align-items: center; +} + +.section-title { + font-size: 1.25rem; + font-weight: 600; + margin: 0; + color: var(--section-title-color); +} + +/* Counter area */ +.counter-card .section-header { + margin-bottom: 0; +} + +.counter-panel { + background: var(--weather-card-bg); + border-radius: 0.75rem; + border: 1px solid var(--weather-card-border); + padding: 1.25rem; + display: flex; + flex-direction: column; + gap: var(--tile-gap); + min-height: var(--tile-min-height); + backdrop-filter: blur(10px); + flex: 1; +} + +.counter-value-group { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 0.25rem; + flex: 1; + margin-bottom: var(--tile-gap); + text-align: center; +} + +.counter-label { + font-size: 0.75rem; + text-transform: uppercase; + letter-spacing: 0.1em; + color: var(--text-tertiary); + font-weight: 600; +} + +.counter-value { + font-size: 2.25rem; + font-weight: 700; + line-height: 1; + background: linear-gradient(135deg, var(--accent-gradient-start) 0%, var(--accent-gradient-end) 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.increment-button { + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + background: linear-gradient(135deg, var(--accent-gradient-start) 0%, var(--accent-gradient-end) 100%); + color: white; + border: none; + border-radius: 0.5rem; + padding: 0 1.5rem; + height: var(--cta-height); + font-size: 0.875rem; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; + box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3); + width: 100%; + margin-top: auto; +} + +.increment-button:hover { + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4); +} + +.increment-button:active { + transform: translateY(0); +} + +.increment-button:focus-visible { + outline: 3px solid var(--accent-gradient-end); + outline-offset: 2px; +} + +.increment-icon { + transition: transform 0.3s ease; +} + +.increment-button:hover .increment-icon { + transform: scale(1.1); +} + +/* Toggle switch */ +.toggle-switch { + display: flex; + background: rgba(255, 255, 255, 0.1); + border-radius: 0.5rem; + padding: 0.25rem; + gap: 0.25rem; + border: 1px solid var(--weather-card-border); + margin: 0; + padding: 0.25rem; + min-width: 0; +} + +.toggle-switch legend { + padding: 0; +} + +@media (prefers-color-scheme: light) { + .toggle-switch { + background: rgba(102, 126, 234, 0.08); + } +} + +.toggle-option { + display: flex; + align-items: center; + justify-content: center; + background: transparent; + color: var(--text-secondary); + border: none; + border-radius: 0.375rem; + padding: 0 1rem; + height: 2.5rem; + font-size: 0.875rem; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; + min-width: 3rem; + position: relative; +} + +/* --- Factory workflow UI additions --- */ + +.workflow-layout { + width: 100%; + display: grid; + grid-template-columns: 1.05fr 0.95fr; + gap: 1rem; + align-items: start; +} + +@media (max-width: 980px) { + .workflow-layout { + grid-template-columns: 1fr; + } +} + +.muted { + margin: 0.25rem 0 0; + color: var(--text-tertiary); + font-size: 0.9rem; +} + +.inline-code { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', + monospace; + font-size: 0.9em; + padding: 0.1rem 0.35rem; + border-radius: 0.4rem; + background: rgba(0, 0, 0, 0.22); + border: 1px solid rgba(255, 255, 255, 0.10); +} + +.card-footer { + display: flex; + justify-content: space-between; + align-items: center; + gap: 1rem; + padding-top: 0.25rem; +} + +.primary-button, +.secondary-button { + height: var(--cta-height); + border-radius: 0.6rem; + padding: 0 1.1rem; + border: 1px solid var(--weather-card-border); + font-weight: 650; +} + +.primary-button { + background: linear-gradient(135deg, var(--accent-gradient-start) 0%, var(--accent-gradient-end) 100%); + color: #fff; + border: none; + box-shadow: 0 2px 10px rgba(102, 126, 234, 0.35); +} + +.primary-button:disabled { + opacity: 0.75; + cursor: not-allowed; +} + +.secondary-button { + background: rgba(255, 255, 255, 0.06); + color: var(--text-primary); +} + +.secondary-button:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +.alarm-form { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 0.75rem; +} + +@media (max-width: 680px) { + .form-grid { + grid-template-columns: 1fr; + } +} + +.field { + display: flex; + flex-direction: column; + gap: 0.35rem; +} + +.field.span-2 { + grid-column: span 2; +} + +@media (max-width: 680px) { + .field.span-2 { + grid-column: auto; + } +} + +.field label { + font-size: 0.85rem; + color: var(--text-secondary); +} + +.field input, +.field select, +.field textarea { + background: rgba(255, 255, 255, 0.06); + border: 1px solid var(--weather-card-border); + border-radius: 0.6rem; + padding: 0.75rem 0.85rem; + color: var(--text-primary); + outline: none; +} + +.field textarea { + resize: vertical; +} + +.field input:focus-visible, +.field select:focus-visible, +.field textarea:focus-visible, +.primary-button:focus-visible, +.secondary-button:focus-visible { + outline: 3px solid var(--focus-color); + outline-offset: 2px; +} + +.agent-illustration { + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.run-pill { + display: inline-flex; + align-items: center; + gap: 0.5rem; + padding: 0.4rem 0.7rem; + border-radius: 999px; + border: 1px solid var(--weather-card-border); + background: rgba(255, 255, 255, 0.06); + color: var(--text-secondary); + font-size: 0.85rem; + text-transform: capitalize; +} + +.run-pill__dot { + width: 10px; + height: 10px; + border-radius: 999px; + background: rgba(255, 255, 255, 0.35); +} + +.run-pill--running .run-pill__dot { + background: #a78bfa; + box-shadow: 0 0 0 6px rgba(167, 139, 250, 0.15); + animation: pulseDot 1.1s ease-in-out infinite; +} + +.run-pill--completed .run-pill__dot { + background: #34d399; +} + +.agent-list { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.agent-node { + display: grid; + grid-template-columns: 26px 1fr; + gap: 0.75rem; + align-items: start; +} + +.agent-node__rail { + position: relative; + display: flex; + flex-direction: column; + align-items: center; +} + +.agent-node__dot { + width: 14px; + height: 14px; + border-radius: 999px; + border: 2px solid rgba(255, 255, 255, 0.35); + background: rgba(255, 255, 255, 0.08); +} + +.agent-node__line { + width: 2px; + flex: 1; + min-height: 52px; + background: rgba(255, 255, 255, 0.12); + margin-top: 6px; +} + +.agent-node__card { + border: 1px solid var(--weather-card-border); + background: var(--weather-card-bg); + border-radius: 0.75rem; + padding: 0.9rem 1rem; +} + +.agent-node__top { + display: flex; + justify-content: space-between; + align-items: center; + gap: 0.75rem; +} + +.agent-node__title { + font-weight: 700; + color: var(--text-primary); +} + +.agent-node__desc { + margin-top: 0.35rem; + color: var(--text-tertiary); + font-size: 0.9rem; +} + +.badge { + padding: 0.2rem 0.55rem; + border-radius: 999px; + font-size: 0.78rem; + border: 1px solid var(--weather-card-border); + color: var(--text-secondary); +} + +.badge--running { + color: #e9d5ff; + border-color: rgba(167, 139, 250, 0.45); + background: rgba(167, 139, 250, 0.15); +} + +.badge--done { + color: #bbf7d0; + border-color: rgba(52, 211, 153, 0.45); + background: rgba(52, 211, 153, 0.12); +} + +.badge--pending { + color: var(--text-secondary); + background: rgba(255, 255, 255, 0.06); +} + +.badge--idle { + color: var(--text-tertiary); + background: rgba(255, 255, 255, 0.04); +} + +.agent-node--running .agent-node__dot { + border-color: rgba(167, 139, 250, 0.75); + background: #a78bfa; + box-shadow: 0 0 0 6px rgba(167, 139, 250, 0.16); +} + +.agent-node--done .agent-node__dot { + border-color: rgba(52, 211, 153, 0.75); + background: #34d399; +} + +.agent-legend { + display: flex; + gap: 0.75rem; + flex-wrap: wrap; + color: var(--text-tertiary); + font-size: 0.85rem; +} + +.legend-item { + display: inline-flex; + align-items: center; + gap: 0.4rem; +} + +.legend-swatch { + width: 10px; + height: 10px; + border-radius: 999px; + border: 1px solid rgba(255, 255, 255, 0.22); +} + +.legend-swatch--pending { + background: rgba(255, 255, 255, 0.16); +} + +.legend-swatch--running { + background: #a78bfa; +} + +.legend-swatch--done { + background: #34d399; +} + +.submitted-preview { + margin-top: 0.5rem; + border-top: 1px solid rgba(255, 255, 255, 0.10); + padding-top: 1rem; +} + +.request-response-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 0.75rem; + margin-top: 0.5rem; +} + +@media (max-width: 980px) { + .request-response-grid { + grid-template-columns: 1fr; + } +} + +.code-block { + margin: 0; + background: rgba(0, 0, 0, 0.25); + border: 1px solid rgba(255, 255, 255, 0.10); + border-radius: 0.75rem; + padding: 0.85rem; + overflow: auto; + max-height: 280px; + font-size: 0.85rem; +} + +@keyframes pulseDot { + 0% { + transform: scale(1); + } + 50% { + transform: scale(1.08); + } + 100% { + transform: scale(1); + } +} + +.toggle-option[aria-pressed="true"] { + background: linear-gradient(135deg, var(--accent-gradient-start) 0%, var(--accent-gradient-end) 100%); + color: white; + box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3); +} + +.toggle-option[aria-pressed="false"]:hover { + background: rgba(255, 255, 255, 0.05); + color: var(--text-primary); +} + +@media (prefers-color-scheme: light) { + .toggle-option[aria-pressed="false"]:hover { + background: rgba(102, 126, 234, 0.1); + } +} + +.toggle-option:focus-visible { + outline: 3px solid var(--focus-color); + outline-offset: 2px; + z-index: 1; +} + +/* Refresh button */ +.refresh-button { + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + background: linear-gradient(135deg, var(--accent-gradient-start) 0%, var(--accent-gradient-end) 100%); + color: white; + border: none; + border-radius: 0.5rem; + padding: 0 1.5rem; + height: var(--cta-height); + font-size: 0.875rem; + font-weight: 600; + cursor: pointer; + transition: transform 0.3s ease, box-shadow 0.3s ease, opacity 0.3s ease; + box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3); + min-width: 140px; + white-space: nowrap; +} + +.refresh-button:hover:not(:disabled) { + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4); +} + +.refresh-button:disabled { + opacity: 0.6; + cursor: not-allowed; + transform: none; +} + +.refresh-button:focus-visible { + outline: 3px solid var(--focus-color); + outline-offset: 3px; +} + +.refresh-icon { + transition: transform 0.3s ease; +} + +.refresh-icon.spinning { + animation: spin 1s linear infinite; +} + +/* Error message */ +.error-message { + display: flex; + align-items: center; + gap: 0.75rem; + background-color: var(--error-bg); + border-left: 4px solid var(--error-border); + color: var(--error-text); + padding: 1rem; + border-radius: 0.5rem; + margin: 1rem 0; + animation: slideIn 0.3s ease-out; +} + +/* Loading skeleton */ +.loading-skeleton { + display: flex; + flex-direction: column; + gap: 1rem; + margin-top: 1rem; +} + +.skeleton-row { + height: 80px; + background: linear-gradient(90deg, var(--skeleton-bg-1) 25%, var(--skeleton-bg-2) 50%, var(--skeleton-bg-1) 75%); + background-size: 200% 100%; + animation: shimmer 1.5s infinite; + border-radius: 0.5rem; +} + +/* Weather grid */ +.weather-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 1rem; + margin-top: 0.75rem; +} + +.weather-card { + background: var(--weather-card-bg); + border-radius: 0.75rem; + padding: 1.25rem; + display: flex; + flex-direction: column; + gap: 0.75rem; + transition: all 0.3s ease; + border: 1px solid var(--weather-card-border); + backdrop-filter: blur(10px); + min-height: var(--tile-min-height); +} + +.weather-card:hover { + transform: translateY(-4px); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2); +} + +.weather-card:focus-within { + outline: 2px solid var(--focus-color); + outline-offset: 2px; +} + +.weather-date { + font-weight: 600; + font-size: 0.875rem; + color: var(--date-color); + text-transform: uppercase; + letter-spacing: 0.05em; + margin: 0; +} + +.weather-summary { + font-size: 1.125rem; + font-weight: 500; + color: var(--summary-color); + min-height: 1.5rem; + margin: 0; +} + +.weather-temps { + display: flex; + align-items: center; + justify-content: center; + gap: 0.75rem; + margin-top: 0.5rem; + padding-top: 0.75rem; + border-top: 1px solid var(--weather-card-border); +} + +.temp-group { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; +} + +.temp-value { + font-size: 1.5rem; + font-weight: 700; + background: linear-gradient(135deg, var(--accent-gradient-start) 0%, var(--accent-gradient-end) 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.temp-unit { + font-size: 0.75rem; + color: var(--temp-unit-color); + margin-top: 0.125rem; +} + +/* Responsive design */ +@media (max-width: 1024px) { + .main-content { + padding: 1rem; + } +} +.app-footer { + padding: 1.5rem; + text-align: center; + background: rgba(0, 0, 0, 0.2); + backdrop-filter: blur(10px); +} + +.app-footer nav { + display: flex; + justify-content: center; + align-items: center; + gap: 1.5rem; +} + +.app-footer a { + color: var(--text-secondary); + text-decoration: none; + font-weight: 500; + transition: color 0.3s ease, border-color 0.3s ease; + border-bottom: 2px solid transparent; + font-size: 0.875rem; + padding-bottom: 0.125rem; +} + +.app-footer a:hover { + color: var(--text-primary); + border-bottom-color: var(--text-primary); +} + +.app-footer a:focus-visible { + outline: 3px solid var(--focus-color); + outline-offset: 4px; + border-radius: 4px; +} + +/* Animations */ +@keyframes fadeInDown { + from { + opacity: 0; + transform: translateY(-20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes slideIn { + from { + opacity: 0; + transform: translateX(-10px); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@keyframes shimmer { + 0% { + background-position: -200% 0; + } + 100% { + background-position: 200% 0; + } +} + +/* Responsive design */ +@media (max-width: 1024px) { + .main-content { + grid-template-columns: 1fr; + padding: 1rem; + gap: 1rem; + } +} + +/* Footer */ +.app-footer { + padding: 1.5rem 0; + text-align: center; + background: rgba(0, 0, 0, 0.2); + backdrop-filter: blur(10px); + display: flex; + justify-content: center; + align-items: center; +} + +.app-footer > * { + max-width: 1400px; + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 2rem; + gap: 1rem; +} + +.app-footer a { + color: var(--text-secondary); + text-decoration: none; + font-weight: 500; + transition: color 0.3s ease, transform 0.3s ease; + border-bottom: 2px solid transparent; + font-size: 0.875rem; +} + +.app-footer a:hover { + color: var(--text-primary); + border-bottom-color: var(--text-primary); +} + +.app-footer a:focus-visible { + outline: 2px solid var(--text-primary); + outline-offset: 4px; + border-radius: 2px; +} + +.github-link { + display: inline-flex; + align-items: center; + gap: 0.5rem; + border-bottom: none !important; +} + +.github-link:focus-visible { + outline: 3px solid var(--focus-color); + outline-offset: 4px; + border-radius: 4px; +} + +.github-link img { + transition: transform 0.3s ease, opacity 0.3s ease; + filter: brightness(0) invert(1); +} + +@media (prefers-color-scheme: light) { + .github-link img { + filter: brightness(0) invert(0); + opacity: 0.7; + } + + .github-link:hover img { + opacity: 1; + } +} + +.github-link:hover img { + transform: scale(1.1); +} + +@media (max-width: 768px) { + :root { + --cta-height: 2.75rem; + } + + .app-header { + padding: 1.5rem 1rem 1rem; + } + + .logo { + height: 3rem; + } + + .app-title { + font-size: 1.5rem; + } + + .app-subtitle { + font-size: 0.875rem; + } + + .main-content { + padding: 0.75rem; + } + + .card { + padding: 1rem; + } + + .section-title { + font-size: 1.125rem; + } + + .section-header { + flex-direction: column; + align-items: stretch; + gap: 0.75rem; + } + + .header-actions { + width: 100%; + } + + .toggle-switch { + flex: 1; + } + + .toggle-option { + flex: 1; + } + + .refresh-button { + flex: 1; + justify-content: center; + padding: 0 1.25rem; + } + + .weather-grid { + grid-template-columns: 1fr; + gap: 0.75rem; + } + + .weather-card { + padding: 1.25rem; + } + + .app-footer { + padding: 1rem 0; + } + + .app-footer > * { + flex-direction: column; + padding: 0 1.5rem; + } + + .github-link { + order: -1; + } +} + +/* Reduced motion support */ +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } +} + +/* High contrast mode support */ +@media (prefers-contrast: high) { + .card { + border: 2px solid currentColor; + } + + .weather-card { + border: 1px solid currentColor; + } +} + +/* Focus visible support for better keyboard navigation */ +*:focus-visible { + outline: 3px solid var(--accent-gradient-end); + outline-offset: 2px; +} + +/* Agent Events Visualization */ +.agent-events { + margin-top: 0.75rem; + padding-top: 0.75rem; + border-top: 1px solid rgba(255, 255, 255, 0.08); +} + +.agent-events--error { + border-top-color: rgba(239, 68, 68, 0.3); +} + +.agent-events__tools { + display: flex; + flex-direction: column; + gap: 0.5rem; + margin-bottom: 0.75rem; +} + +.agent-events__tools-label { + font-size: 0.75rem; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--text-tertiary); + font-weight: 600; +} + +/* Tool Call Display */ +.tool-call { + background: rgba(255, 255, 255, 0.04); + border: 1px solid var(--weather-card-border); + border-radius: 0.5rem; + overflow: hidden; +} + +.tool-call__header { + display: flex; + align-items: center; + gap: 0.5rem; + width: 100%; + padding: 0.5rem 0.75rem; + background: transparent; + border: none; + color: var(--text-secondary); + font-size: 0.85rem; + cursor: pointer; + text-align: left; + transition: background 0.2s ease; +} + +.tool-call__header:hover { + background: rgba(255, 255, 255, 0.06); +} + +.tool-call__icon { + font-size: 0.9rem; +} + +.tool-call__name { + flex: 1; + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; + font-weight: 600; + color: var(--accent-gradient-start); +} + +.tool-call__toggle { + font-size: 0.7rem; + color: var(--text-tertiary); + transition: transform 0.2s ease; +} + +.tool-call__details { + padding: 0.5rem 0.75rem; + background: rgba(0, 0, 0, 0.15); + border-top: 1px solid var(--weather-card-border); +} + +.tool-call__section { + margin-bottom: 0.5rem; +} + +.tool-call__section:last-child { + margin-bottom: 0; +} + +.tool-call__label { + display: block; + font-size: 0.7rem; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--text-tertiary); + margin-bottom: 0.25rem; +} + +.tool-call__code { + margin: 0; + padding: 0.5rem; + background: rgba(0, 0, 0, 0.25); + border-radius: 0.375rem; + font-size: 0.75rem; + overflow-x: auto; + max-height: 150px; + color: var(--text-secondary); +} + +/* Agent Output Display */ +.agent-events__output { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.agent-events__output-header { + display: flex; + align-items: center; + gap: 0.5rem; + width: 100%; + padding: 0.5rem 0.75rem; + background: rgba(255, 255, 255, 0.04); + border: 1px solid var(--weather-card-border); + border-radius: 0.5rem; + color: var(--text-secondary); + font-size: 0.85rem; + cursor: pointer; + text-align: left; + transition: background 0.2s ease; +} + +.agent-events__output-header:hover { + background: rgba(255, 255, 255, 0.08); +} + +.agent-events--error .agent-events__output-header { + border-color: rgba(239, 68, 68, 0.4); + background: rgba(239, 68, 68, 0.1); +} + +.agent-events__output-icon { + font-size: 0.9rem; +} + +.agent-events__output-label { + flex: 1; + font-weight: 600; +} + +.agent-events__toggle { + font-size: 0.7rem; + color: var(--text-tertiary); +} + +.agent-events__summary { + margin: 0; + padding: 0 0.75rem; + font-size: 0.85rem; + color: var(--text-tertiary); + line-height: 1.5; + font-style: italic; +} + +.agent-events__full-output { + margin: 0; + padding: 0.75rem; + background: rgba(0, 0, 0, 0.25); + border: 1px solid var(--weather-card-border); + border-radius: 0.5rem; + font-size: 0.8rem; + overflow: auto; + max-height: 300px; + white-space: pre-wrap; + word-break: break-word; + color: var(--text-secondary); + line-height: 1.5; +} + +.agent-events--error .agent-events__full-output { + border-color: rgba(239, 68, 68, 0.3); + color: var(--error-text); +} + +/* Error status styling for agent nodes */ +.agent-node--error .agent-node__dot { + border-color: rgba(239, 68, 68, 0.75); + background: #ef4444; +} + +.badge--error { + color: #fca5a5; + border-color: rgba(239, 68, 68, 0.45); + background: rgba(239, 68, 68, 0.15); +} + +.legend-swatch--error { + background: #ef4444; +} + +/* Adjust agent card for better event display */ +.agent-node__card { + transition: all 0.3s ease; +} + +.agent-node--done .agent-node__card, +.agent-node--error .agent-node__card { + border-color: rgba(255, 255, 255, 0.15); +} + +/* Light mode adjustments */ +@media (prefers-color-scheme: light) { + .agent-events { + border-top-color: rgba(0, 0, 0, 0.08); + } + + .agent-events--error { + border-top-color: rgba(220, 38, 38, 0.3); + } + + .tool-call { + background: rgba(0, 0, 0, 0.02); + } + + .tool-call__header:hover { + background: rgba(0, 0, 0, 0.04); + } + + .tool-call__name { + color: var(--accent-gradient-end); + } + + .tool-call__details { + background: rgba(0, 0, 0, 0.03); + } + + .tool-call__code { + background: rgba(0, 0, 0, 0.05); + } + + .agent-events__output-header { + background: rgba(0, 0, 0, 0.02); + } + + .agent-events__output-header:hover { + background: rgba(0, 0, 0, 0.05); + } + + .agent-events--error .agent-events__output-header { + background: rgba(220, 38, 38, 0.08); + border-color: rgba(220, 38, 38, 0.3); + } + + .agent-events__full-output { + background: rgba(0, 0, 0, 0.03); + } + + .agent-events--error .agent-events__full-output { + border-color: rgba(220, 38, 38, 0.3); + color: #991b1b; + } + + .badge--error { + color: #991b1b; + border-color: rgba(220, 38, 38, 0.45); + background: rgba(220, 38, 38, 0.1); + } +} + diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/frontend/src/App.tsx b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/frontend/src/App.tsx new file mode 100644 index 000000000..cebcd62a7 --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/frontend/src/App.tsx @@ -0,0 +1,351 @@ +import { useMemo, useState, useCallback, useRef } from 'react' +import aspireLogo from '/Aspire.png' +import './App.css' + +import { AlarmForm, type AnalyzeMachinePayload } from './components/AlarmForm' +import { AgentIllustration, type AgentNode } from './components/AgentIllustration' +import type { WorkflowResponse, AgentStepResult } from './types/workflow' +import { streamAnalyzeMachine } from './api/streamAnalyze' + +// Sample workflow response for demo purposes +const DEMO_WORKFLOW_RESPONSE: WorkflowResponse = { + agentSteps: [ + { + agentName: "AnomalyClassificationAgent_AnomalyClassificationAgent_5", + toolCalls: [], + textOutput: "", + finalMessage: null + }, + { + agentName: "AnomalyClassificationAgent_AnomalyClassificationAgent_5", + toolCalls: [ + { + toolName: "getMachine", + arguments: "{\"id\":\"machine-001\"}", + result: null + }, + { + toolName: "getThreshold", + arguments: "{\"machineType\":\"tire_curing_press\"}", + result: null + } + ], + textOutput: "{\n \"status\": \"medium\",\n \"alerts\": [\n {\n \"name\": \"curing_temperature\",\n \"severity\": \"warning\",\n \"description\": \"Curing temperature recorded at 179.2Β°C, which exceeds the warning threshold of 178Β°C but not the critical threshold (182Β°C).\"\n },\n {\n \"name\": \"cycle_time\",\n \"severity\": \"warning\",\n \"description\": \"Cycle time measured at 14.5 minutes, which exceeds the warning threshold of 14 minutes but is below the critical threshold (16 minutes).\"\n }\n ],\n \"summary\": {\n \"totalRecordsProcessed\": 2,\n \"violations\": { \"critical\": 0, \"warning\": 2 }\n }\n}\n\nSummary: Both curing temperature (179.2Β°C) and cycle time (14.5 min) for Tire Curing Press A1 are above their respective warning thresholds but below critical limits. Immediate attention is suggested to prevent escalation into critical failure. No critical violations were observed in this telemetry sample.", + finalMessage: null + }, + { + agentName: "FaultDiagnosisAgent_FaultDiagnosisAgent_2", + toolCalls: [], + textOutput: "", + finalMessage: null + }, + { + agentName: "FaultDiagnosisAgent_FaultDiagnosisAgent_2", + toolCalls: [ + { + toolName: "getMachine", + arguments: "{\"id\":\"machine-001\"}", + result: null + } + ], + textOutput: "{\n \"MachineId\": \"machine-001\",\n \"FaultType\": \"curing_temperature_excessive\",\n \"RootCause\": \"Heating element malfunction\",\n \"Severity\": \"High\",\n \"DetectedAt\": \"2024-06-17T00:21:13Z\"\n}", + finalMessage: null + }, + { + agentName: "RepairPlannerAgent_3fb5335ca1f54fdd969e4e840d74169c", + toolCalls: [], + textOutput: "", + finalMessage: null + }, + { + agentName: "RepairPlannerAgent_3fb5335ca1f54fdd969e4e840d74169c", + toolCalls: [ + { + toolName: "GetAvailableTechnicians", + arguments: "{\"requiredSkills\":[\"electrical\",\"heating systems\"],\"department\":\"Maintenance\"}", + result: "[]" + }, + { + toolName: "CreateWorkOrder", + arguments: "{\"machineId\":\"machine-001\",\"faultType\":\"curing_temperature_excessive\",\"priority\":\"high\"}", + result: "{\"workOrderId\":\"wo-2026-26e49738\",\"status\":\"created\"}" + } + ], + textOutput: "Work Order ID: WO-2026-26E49738\n\nMachine ID: machine-001\n\nFault Type: curing_temperature_excessive (Heating element malfunction)\n\nPriority: High\n\nAssigned Technician: None currently available with required skills.\n\nEstimated Duration: 120 minutes\n\nRepair Tasks:\n1. Lock out and tag out (LOTO) the machine\n2. Test heating element resistance\n3. Replace component if faulty\n4. Recalibrate temperature sensor", + finalMessage: null + }, + { + agentName: "MaintenanceSchedulerAgent_b8a9a9a43b91474689180c7957021038", + toolCalls: [], + textOutput: "", + finalMessage: null + }, + { + agentName: "MaintenanceSchedulerAgent_b8a9a9a43b91474689180c7957021038", + toolCalls: [], + textOutput: "Error processing maintenance schedule request: Work order not found", + finalMessage: null + }, + { + agentName: "PartsOrderingAgent_83df9000225a48c999e49d058cb54c6d", + toolCalls: [], + textOutput: "", + finalMessage: null + }, + { + agentName: "PartsOrderingAgent_83df9000225a48c999e49d058cb54c6d", + toolCalls: [], + textOutput: "Error processing parts order request: Work order not found", + finalMessage: null + } + ], + finalMessage: "Workflow completed with errors in maintenance scheduling and parts ordering." +} + +function App() { + const apiBaseUrl = import.meta.env.VITE_API_URL as string | undefined + // Use streaming endpoint for real-time updates + const analyzeStreamUrl = apiBaseUrl + ? new URL('/api/analyze_machine/stream', apiBaseUrl).toString() + : '/api/analyze_machine/stream' + + const agents = useMemo( + () => [ + { + id: 'anomaly', + name: 'Anomaly Classification Agent', + description: 'Determines whether the alarm indicates an anomaly and classifies severity.', + }, + { + id: 'diagnosis', + name: 'Fault Diagnosis Agent', + description: 'Analyzes symptoms and proposes likely root causes and next checks.', + }, + { + id: 'planner', + name: 'Repair Planner Agent', + description: 'Drafts a repair plan, parts list, and recommended technician actions.', + }, + { + id: 'scheduler', + name: 'Maintenance Scheduler Agent', + description: 'Schedules maintenance windows and assigns technicians based on availability.', + }, + { + id: 'parts', + name: 'Parts Ordering Agent', + description: 'Orders required parts from inventory or external suppliers.', + }, + ], + [], + ) + + const [submittedPayload, setSubmittedPayload] = useState(null) + const [runState, setRunState] = useState<'idle' | 'running' | 'completed'>('idle') + const [activeIndex, setActiveIndex] = useState(null) + const [apiResponse, setApiResponse] = useState(null) + const [apiError, setApiError] = useState(null) + + // Accumulate steps as they stream in + const [streamingSteps, setStreamingSteps] = useState([]) + + // AbortController for canceling in-flight requests + const abortControllerRef = useRef(null) + + const callAnalyzeMachine = useCallback(async (payload: AnalyzeMachinePayload) => { + // Cancel any in-flight request + abortControllerRef.current?.abort() + abortControllerRef.current = new AbortController() + + setSubmittedPayload(payload) + setRunState('running') + setApiResponse(null) + setApiError(null) + setStreamingSteps([]) + setActiveIndex(0) + + try { + await streamAnalyzeMachine( + analyzeStreamUrl, + payload, + { + onWorkflowStarted: (evt) => { + console.log('Workflow started:', evt.agentPipeline) + }, + onAgentStarted: (evt) => { + console.log('Agent started:', evt.agentName, 'index:', evt.agentIndex) + setActiveIndex(evt.agentIndex) + }, + onAgentCompleted: (evt) => { + console.log('Agent completed:', evt.step.agentName) + setStreamingSteps((prev) => [...prev, evt.step]) + }, + onWorkflowCompleted: (evt) => { + console.log('Workflow completed') + setApiResponse(evt.result) + setRunState('completed') + setActiveIndex(null) + }, + onError: (error) => { + console.error('Workflow error:', error) + setApiError(error) + setRunState('idle') + setActiveIndex(null) + }, + }, + abortControllerRef.current.signal + ) + } catch (err) { + if ((err as Error).name === 'AbortError') { + console.log('Request aborted') + return + } + setApiError(err instanceof Error ? err.message : 'Request failed') + setRunState('idle') + setActiveIndex(null) + } + }, [analyzeStreamUrl]) + + const loadDemoData = () => { + setSubmittedPayload({ + machine_id: 'machine-001', + telemetry: [ + { metric: 'curing_temperature', value: 179.2 }, + { metric: 'cycle_time', value: 14.5 }, + ], + }) + setApiResponse(DEMO_WORKFLOW_RESPONSE) + setRunState('completed') + setApiError(null) + } + + const reset = () => { + abortControllerRef.current?.abort() + setRunState('idle') + setSubmittedPayload(null) + setApiResponse(null) + setApiError(null) + setStreamingSteps([]) + setActiveIndex(null) + } + + return ( +
+
+ + Aspire logo + +

Factory Agent Workflow

+

+ Define an alert, then watch agents process it. +

+
+ +
+
+
+ + + {apiError && ( +
+ {apiError} +
+ )} + +
+ + +
+ {runState === 'running' + ? 'Calling API…' + : apiBaseUrl + ? `Using VITE_API_URL: ${apiBaseUrl}` + : 'Using relative /api (Vite proxy).'} +
+
+
+ +
+ + +
+
+

Request/response

+
+
+
+
Request
+ {submittedPayload ? ( +
+                      {JSON.stringify(submittedPayload, null, 2)}
+                    
+ ) : ( +
Submit to see request payload.
+ )} +
+
+
Response
+ {apiResponse != null ? ( +
+                      {typeof apiResponse === 'string'
+                        ? apiResponse
+                        : JSON.stringify(apiResponse, null, 2)}
+                    
+ ) : ( +
Awaiting response.
+ )} +
+
+
+
+
+
+ + +
+ ) +} + +export default App diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/frontend/src/api/streamAnalyze.ts b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/frontend/src/api/streamAnalyze.ts new file mode 100644 index 000000000..251a0d1de --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/frontend/src/api/streamAnalyze.ts @@ -0,0 +1,132 @@ +import type { WorkflowResponse, AgentStepResult } from '../types/workflow' + +/** + * SSE event types matching the backend WorkflowEvent classes + */ +export interface WorkflowStartedEvent { + type: 'workflow_started' + timestamp: string + agentPipeline: string[] +} + +export interface AgentStartedEvent { + type: 'agent_started' + timestamp: string + agentName: string + agentIndex: number +} + +export interface AgentCompletedEvent { + type: 'agent_completed' + timestamp: string + step: AgentStepResult +} + +export interface WorkflowCompletedEvent { + type: 'workflow_completed' + timestamp: string + result: WorkflowResponse +} + +export interface WorkflowErrorEvent { + type: 'workflow_error' + timestamp: string + error: string +} + +export type WorkflowEvent = + | WorkflowStartedEvent + | AgentStartedEvent + | AgentCompletedEvent + | WorkflowCompletedEvent + | WorkflowErrorEvent + +/** + * Callbacks for handling SSE events during workflow execution + */ +export interface StreamCallbacks { + onWorkflowStarted?: (event: WorkflowStartedEvent) => void + onAgentStarted?: (event: AgentStartedEvent) => void + onAgentCompleted?: (event: AgentCompletedEvent) => void + onWorkflowCompleted?: (event: WorkflowCompletedEvent) => void + onError?: (error: string) => void +} + +/** + * Stream workflow analysis via Server-Sent Events. + * + * Uses fetch + ReadableStream to handle SSE from a POST endpoint + * (native EventSource only supports GET requests). + */ +export async function streamAnalyzeMachine( + url: string, + payload: { machine_id: string; telemetry: unknown }, + callbacks: StreamCallbacks, + signal?: AbortSignal +): Promise { + const response = await fetch(url, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload), + signal, + }) + + if (!response.ok) { + const text = await response.text() + throw new Error(text || `Request failed with ${response.status}`) + } + + if (!response.body) { + throw new Error('Response body is null') + } + + const reader = response.body.getReader() + const decoder = new TextDecoder() + let buffer = '' + + try { + while (true) { + const { done, value } = await reader.read() + if (done) break + + buffer += decoder.decode(value, { stream: true }) + + // Parse SSE events from buffer (format: "data: {...}\n\n") + const lines = buffer.split('\n\n') + buffer = lines.pop() || '' // Keep incomplete chunk in buffer + + for (const line of lines) { + if (!line.startsWith('data: ')) continue + + const json = line.slice(6) // Remove "data: " prefix + if (!json.trim()) continue + + try { + const event = JSON.parse(json) as WorkflowEvent + + switch (event.type) { + case 'workflow_started': + callbacks.onWorkflowStarted?.(event as WorkflowStartedEvent) + break + case 'agent_started': + callbacks.onAgentStarted?.(event as AgentStartedEvent) + break + case 'agent_completed': + callbacks.onAgentCompleted?.(event as AgentCompletedEvent) + break + case 'workflow_completed': + callbacks.onWorkflowCompleted?.(event as WorkflowCompletedEvent) + break + case 'workflow_error': + callbacks.onError?.((event as WorkflowErrorEvent).error) + break + } + } catch (parseError) { + console.warn('Failed to parse SSE event:', json, parseError) + } + } + } + } finally { + reader.releaseLock() + } +} diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/frontend/src/components/AgentIllustration.tsx b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/frontend/src/components/AgentIllustration.tsx new file mode 100644 index 000000000..74b7a99fb --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/frontend/src/components/AgentIllustration.tsx @@ -0,0 +1,293 @@ +import { useState } from 'react' +import type { WorkflowResponse, AgentStepResult, ToolCallInfo } from '../types/workflow' +import { normalizeAgentName } from '../types/workflow' + +export type AgentStatus = 'pending' | 'running' | 'done' | 'idle' | 'error' + +export interface AgentNode { + id: string + name: string + description: string +} + +function statusLabel(status: AgentStatus) { + switch (status) { + case 'running': + return 'Working' + case 'done': + return 'Done' + case 'pending': + return 'Queued' + case 'error': + return 'Error' + default: + return 'Idle' + } +} + +/** Extracts a short summary from text output, respecting word boundaries */ +function getSummary(text: string, maxLength = 150): string { + if (!text) return '' + const trimmed = text.trim() + if (trimmed.length <= maxLength) return trimmed + + // Find the last space before maxLength to avoid cutting words + const lastSpace = trimmed.lastIndexOf(' ', maxLength) + const cutPoint = lastSpace > maxLength * 0.5 ? lastSpace : maxLength + return trimmed.substring(0, cutPoint) + '…' +} + +/** Checks if the step has any meaningful content to display */ +function hasContent(step: AgentStepResult): boolean { + return step.toolCalls.length > 0 || !!step.textOutput?.trim() +} + +/** Checks if text output indicates an error */ +function isErrorOutput(text: string): boolean { + const lowerText = text.toLowerCase() + // Check for various error patterns + return ( + lowerText.includes('error') || + lowerText.includes('failed') || + lowerText.includes('exception') || + lowerText.includes('unable to') || + lowerText.includes('could not') + ) +} + +/** Tool call display component */ +function ToolCallDisplay({ toolCall }: { toolCall: ToolCallInfo }) { + const [expanded, setExpanded] = useState(false) + + return ( +
+ + {expanded && ( +
+ {toolCall.arguments && ( +
+ Arguments: +
{toolCall.arguments}
+
+ )} + {toolCall.result && ( +
+ Result: +
{toolCall.result}
+
+ )} +
+ )} +
+ ) +} + +/** Agent step events display component */ +function AgentStepEvents({ steps }: { steps: AgentStepResult[] }) { + const [expanded, setExpanded] = useState(false) + + // Merge all steps for this agent + const allToolCalls: ToolCallInfo[] = [] + let combinedOutput = '' + + for (const step of steps) { + allToolCalls.push(...step.toolCalls) + if (step.textOutput?.trim()) { + combinedOutput += step.textOutput + '\n' + } + } + + const hasEvents = allToolCalls.length > 0 || combinedOutput.trim() + if (!hasEvents) return null + + // Prefer the backend's explicit execution status. Fall back to text matching + // only for older or demo payloads that do not include isError. + const isError = steps.some((step) => + step.isError === undefined ? isErrorOutput(step.textOutput) : step.isError, + ) + const summary = getSummary(combinedOutput) + + return ( +
+ {allToolCalls.length > 0 && ( +
+ + Tool calls ({allToolCalls.length}): + + {allToolCalls.map((tc, i) => ( + + ))} +
+ )} + + {combinedOutput.trim() && ( +
+ + + {!expanded && summary && ( +

{summary}

+ )} + + {expanded && ( +
{combinedOutput.trim()}
+ )} +
+ )} +
+ ) +} + +export function AgentIllustration(props: { + agents: AgentNode[] + activeIndex: number | null + runState: 'idle' | 'running' | 'completed' + workflowResponse?: WorkflowResponse | null + streamingSteps?: AgentStepResult[] +}) { + const { agents, activeIndex, runState, workflowResponse, streamingSteps = [] } = props + + // Build a map of agent events from the workflow response OR streaming steps + const agentEventsMap = new Map() + + // Use final response steps if completed, otherwise use streaming steps + const stepsToDisplay = runState === 'completed' && workflowResponse?.agentSteps + ? workflowResponse.agentSteps + : streamingSteps + + for (const step of stepsToDisplay) { + const normalizedId = normalizeAgentName(step.agentName) + const existingSteps = agentEventsMap.get(normalizedId) + if (existingSteps) { + existingSteps.push(step) + } else { + agentEventsMap.set(normalizedId, [step]) + } + } + + const getStatus = (agentId: string, index: number): AgentStatus => { + if (runState === 'idle') return 'idle' + + // If we have workflow response, determine status from actual data + if (runState === 'completed' && workflowResponse) { + const steps = agentEventsMap.get(agentId) + if (steps && steps.some(s => hasContent(s))) { + const hasExecutionError = steps.some((step) => + step.isError === undefined ? isErrorOutput(step.textOutput) : step.isError, + ) + if (hasExecutionError) { + return 'error' + } + return 'done' + } + // If no steps for this agent but workflow completed, it might have been skipped + return steps && steps.length > 0 ? 'done' : 'pending' + } + + if (runState === 'completed') return 'done' + if (activeIndex == null) return 'pending' + if (index < activeIndex) return 'done' + if (index === activeIndex) return 'running' + return 'pending' + } + + const activeAgent = + activeIndex == null ? null : agents[Math.min(activeIndex, agents.length - 1)] + + // Count agents with events + const agentsWithEvents = agents.filter(a => { + const steps = agentEventsMap.get(a.id) + return steps && steps.some(s => hasContent(s)) + }).length + + return ( +
+
+
+

Agent Workflow

+

+ {runState === 'running' && activeAgent + ? `Currently working: ${activeAgent.name}` + : runState === 'completed' + ? `Workflow completed β€’ ${agentsWithEvents} agents processed` + : 'Waiting for an alarm'} +

+
+
+
+
+ +
    + {agents.map((agent, index) => { + const status = getStatus(agent.id, index) + const steps = agentEventsMap.get(agent.id) + + return ( +
  1. +
  2. + ) + })} +
+ +
+
+
+
+
+
+
+
+
+
+
+ ) +} diff --git a/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/frontend/src/components/AlarmForm.tsx b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/frontend/src/components/AlarmForm.tsx new file mode 100644 index 000000000..90162b637 --- /dev/null +++ b/03-Azure/01-04-AI/08_Predictive_Maintenance/src/agent-workflow/frontend/src/components/AlarmForm.tsx @@ -0,0 +1,122 @@ +import { useId, useMemo, useState } from 'react' + +export interface AnalyzeMachinePayload { + machine_id: string + telemetry: unknown +} + +export function AlarmForm(props: { + disabled?: boolean + onSubmit: (payload: AnalyzeMachinePayload) => void +}) { + const machineIdInputId = useId() + const telemetryInputId = useId() + + const [machineId, setMachineId] = useState('machine-001') + const [telemetryText, setTelemetryText] = useState( + JSON.stringify( + [ + { metric: 'curing_temperature', value: 179.2 }, + { metric: 'cycle_time', value: 14.5 }, + ], + null, + 2, + ), + ) + + const [touched, setTouched] = useState(false) + + const validation = useMemo(() => { + const errors: string[] = [] + if (!machineId.trim()) errors.push('Machine ID is required.') + + let parsedTelemetry: unknown = null + try { + parsedTelemetry = telemetryText.trim() ? JSON.parse(telemetryText) : null + } catch { + errors.push('Telemetry must be valid JSON.') + } + + if (parsedTelemetry == null) errors.push('Telemetry is required.') + + return { + ok: errors.length === 0, + errors, + parsedTelemetry, + } + }, [machineId, telemetryText]) + + const submit = () => { + setTouched(true) + if (!validation.ok) return + + props.onSubmit({ + machine_id: machineId.trim(), + telemetry: validation.parsedTelemetry, + }) + } + + return ( +
{ + e.preventDefault() + submit() + }} + > +
+
+

Define an anomaly

+

+ Posts JSON to /api/analyze_machine. +

+
+ +
+ + {touched && !validation.ok && ( +
+ + {validation.errors.length === 1 + ? validation.errors[0] + : 'Please fix the highlighted fields.'} + +
+ )} + +
+
+ + setMachineId(e.target.value)} + placeholder="e.g., machine-001" + disabled={props.disabled} + required + /> +
+ +
+ +